git의“rebase --preserve-merges”는 정확히 무엇을하며 왜 그런가?
명령에 대한rebase
Git의 문서 는 매우 간단합니다.
--preserve-merges
Instead of ignoring merges, try to recreate them.
This uses the --interactive machinery internally, but combining it
with the --interactive option explicitly is generally not a good idea
unless you know what you are doing (see BUGS below).
따라서 실제로 사용할 때 어떤 일이 발생 --preserve-merges
합니까? 플래그가없는 기본 동작과 어떻게 다릅니 까? 병합 등을 "재 작성"한다는 것은 무엇을 의미합니까?
일반적인 git rebase와 마찬가지로 git with는 --preserve-merges
먼저 commit 그래프의 한 부분에서 이루어진 커밋 목록을 식별 한 다음 다른 부분 위에서 커밋을 재생합니다. --preserve-merges
어떤 커밋이 재생을 위해 선택되는지와 해당 재생이 병합 커밋에 대해 어떻게 작동하는지에 대한 차이점 .
일반 및 병합 보존 리베이스의 주요 차이점에 대해 더 명확하게 설명하려면 다음을 수행하십시오.
- 병합 보존 리베이스는 (일부) 병합 커밋을 재생할 의향이 있지만 일반 리베이스는 병합 커밋을 완전히 무시합니다.
- 병합 커밋을 재생할 의향이 있기 때문에 병합 보존 rebase는 병합 커밋을 재생하는 의미 를 정의하고 추가 주름을 처리해야합니다.
- 개념 상 가장 흥미로운 부분은 아마도 새로운 커밋의 병합 부모가 무엇인지 선택하는 것입니다.
- 병합 커밋을 재생하려면 특정 커밋 (
git checkout <desired first parent>
)을 명시 적으로 체크 아웃해야 하지만 일반 리베이스는 걱정할 필요가 없습니다.
- 병합 보존 리베이스는 재생에 대한 더 얕은 커밋 세트를 고려합니다.
- 특히, 가장 최근의 병합 기반 (즉 , 두 분기가 분기 된 가장 최근의 시간 ) 이후에 이루어진 커밋 재생 만 고려 하는 반면, 일반 리베이스 는 두 분기 가 처음 분기 된 시점으로 돌아가는 커밋을 다시 재생할 수 있습니다 .
- 잠정적이고 불분명하기 때문에, 이것이 궁극적으로 병합 커밋에 "포함 된" "오래된 커밋"재생을 차단하는 수단이라고 생각합니다.
먼저 rebase의 기능을 "충분히 정확하게"설명 --preserve-merges
하고 몇 가지 예가 있습니다. 물론 더 유용한 것이라면 예제부터 시작할 수 있습니다.
"간단한"알고리즘
실제로 잡초에 들어가려면 git 소스를 다운로드하고 파일을 탐색하십시오 git-rebase--interactive.sh
. (Rebase는 Git의 C 코어의 일부가 아니라 bash로 작성됩니다. 그리고 배후에서 "interactive rebase"와 코드를 공유합니다.)
그러나 여기에 내가 생각하는 것의 본질을 스케치하겠습니다. 생각해야 할 것들의 수를 줄이기 위해 몇 가지 자유를 얻었습니다. (예를 들어, 계산이 수행되는 정확한 순서를 100 % 정확하게 캡처하려고 시도하지 않으며 덜 중심적인 주제를 무시합니다 (예 : 분기간에 이미 선택된 커밋에 대해 수행 할 작업).
먼저, 병합 보존 이외의 리베이스는 다소 간단합니다. 다소간 :
Find all commits on B but not on A ("git log A..B")
Reset B to A ("git reset --hard A")
Replay all those commits onto B one at a time in order.
Rebase --preserve-merges
는 비교적 복잡합니다. 매우 중요한 것처럼 보이는 것을 잃지 않고 만들 수 있었던 것처럼 간단합니다.
Find the commits to replay:
First find the merge-base(s) of A and B (i.e. the most recent common ancestor(s))
This (these) merge base(s) will serve as a root/boundary for the rebase.
In particular, we'll take its (their) descendants and replay them on top of new parents
Now we can define C, the set of commits to replay. In particular, it's those commits:
1) reachable from B but not A (as in a normal rebase), and ALSO
2) descendants of the merge base(s)
If we ignore cherry-picks and other cleverness preserve-merges does, it's more or less:
git log A..B --not $(git merge-base --all A B)
Replay the commits:
Create a branch B_new, on which to replay our commits.
Switch to B_new (i.e. "git checkout B_new")
Proceeding parents-before-children (--topo-order), replay each commit c in C on top of B_new:
If it's a non-merge commit, cherry-pick as usual (i.e. "git cherry-pick c")
Otherwise it's a merge commit, and we'll construct an "equivalent" merge commit c':
To create a merge commit, its parents must exist and we must know what they are.
So first, figure out which parents to use for c', by reference to the parents of c:
For each parent p_i in parents_of(c):
If p_i is one of the merge bases mentioned above:
# p_i is one of the "boundary commits" that we no longer want to use as parents
For the new commit's ith parent (p_i'), use the HEAD of B_new.
Else if p_i is one of the commits being rewritten (i.e. if p_i is in R):
# Note: Because we're moving parents-before-children, a rewritten version
# of p_i must already exist. So reuse it:
For the new commit's ith parent (p_i'), use the rewritten version of p_i.
Otherwise:
# p_i is one of the commits that's *not* slated for rewrite. So don't rewrite it
For the new commit's ith parent (p_i'), use p_i, i.e. the old commit's ith parent.
Second, actually create the new commit c':
Go to p_1'. (i.e. "git checkout p_1'", p_1' being the "first parent" we want for our new commit)
Merge in the other parent(s):
For a typical two-parent merge, it's just "git merge p_2'".
For an octopus merge, it's "git merge p_2' p_3' p_4' ...".
Switch (i.e. "git reset") B_new to the current commit (i.e. HEAD), if it's not already there
Change the label B to apply to this new branch, rather than the old one. (i.e. "git reset --hard B")
--onto C
인수가 있는 리베이스는 매우 유사해야합니다. B의 HEAD에서 커밋 재생을 시작하는 대신 C의 HEAD에서 커밋 재생을 시작합니다. B_new 대신 C_new를 사용하십시오.
실시 예 1
예를 들어 커밋 그래프를
B---C <-- master
/
A-------D------E----m----H <-- topic
\ /
F-------G
m은 부모 E 및 G와의 병합 커밋입니다.
병합되지 않은 일반적인 리베이스를 사용하여 마스터 (C) 위에 주제 (H)를 리베이스한다고 가정합니다. (예를 들어, checkout topic; rebase master )이 경우 git은 다음 커밋을 선택하여 재생합니다.
- D를 선택
- E를 선택
- F를 선택
- G를 선택
- H를 선택
그런 다음 커밋 그래프를 다음과 같이 업데이트하십시오.
B---C <-- master
/ \
A D'---E'---F'---G'---H' <-- topic
(D '는 D 등의 재생 된 등가물입니다.)
병합 커밋 m은 재생을 위해 선택되지 않습니다.
--preserve-merges
C 대신 H를 리베이스 했다면 (예를 들어, checkout topic; rebase --preserve-merges master )이 새로운 경우 git은 다음 커밋을 선택하여 재생합니다.
- D를 선택
- E를 선택
- F를 선택하십시오 ( '하위 주제'분기에서 D로).
- G를 선택하십시오 ( '하위 주제'분기에서 F로).
- 분기 '하위 주제'를 주제로 병합 선택
- H를 선택
이제 m 은 재생을 위해 선택되었습니다. 또한 병합 커밋 m 전에 병합 부모 E와 G가 포함되도록 선택되었습니다.
결과 커밋 그래프는 다음과 같습니다.
B---C <-- master
/ \
A D'-----E'----m'----H' <-- topic
\ /
F'-------G'
다시, D '는 D'의 체리-픽 (즉, 재생성 된) 버전입니다. E '등과 동일합니다. 마스터에없는 모든 커밋이 재생되었습니다. E와 G (m의 병합 부모)는 m '의 부모 역할을하기 위해 E'와 G '로 재생성되었습니다 (리베이스 이후에도 트리 히스토리는 여전히 동일 함).
실시 예 2
일반 리베이스와 달리 병합 보존 리베이스는 업스트림 헤드의 여러 하위를 만들 수 있습니다.
예를 들어, 다음을 고려하십시오.
B---C <-- master
/
A-------D------E---m----H <-- topic
\ |
------- F-----G--/
C (마스터) 위에 H (주제)를 리베이스하면 리베이스를 위해 선택된 커밋은 다음과 같습니다.
- D를 선택
- E를 선택
- F를 선택
- G를 선택
- m을 뽑다
- H를 선택
결과는 다음과 같습니다.
B---C <-- master
/ | \
A | D'----E'---m'----H' <-- topic
\ |
F'----G'---/
실시 예 3
위의 예에서, 병합 커밋과 그 두 부모 모두 원래 병합 커밋이 가진 원래 부모가 아닌 재생 커밋입니다. 그러나 다른 리베이스에서는 재생 된 병합 커밋이 병합 전에 커밋 그래프에 이미있는 부모로 끝날 수 있습니다.
예를 들어, 다음을 고려하십시오.
B--C---D <-- master
/ \
A---E--m------F <-- topic
주제를 마스터에 리베이스하면 (병합 유지) 재생 커밋은 다음과 같습니다.
- 병합 커밋 m 선택
- F를 선택
다시 작성된 커밋 그래프는 다음과 같습니다.
B--C--D <-- master
/ \
A-----E---m'--F'; <-- topic
여기서 재생 병합 커밋 m '은 커밋 그래프에 이미 존재하는 부모, 즉 D (마스터의 HEAD)와 E (원래 병합 커밋 m의 부모 중 하나)를 가져옵니다.
실시 예 4
병합 보존 리베이스는 특정 "빈 커밋"사례에서 혼동 될 수 있습니다. 적어도 이것은 이전 버전의 git에만 해당됩니다 (예 : 1.7.8).
이 커밋 그래프를 보자.
A--------B-----C-----m2---D <-- master
\ \ /
E--- F--\--G----/
\ \
---m1--H <--topic
커밋 m1과 m2는 모두 B와 F의 모든 변경 사항을 통합해야합니다.
git rebase --preserve-merges
H (주제)를 D (마스터)에 대해 시도 하면 다음 커밋이 재생을 위해 선택됩니다.
- m1을 선택
- H를 선택
m1에 통합 된 변경 사항 (B, F)은 이미 D에 통합되어야합니다 (m2는 B와 F의 하위 항목을 병합하기 때문에 이러한 변경 사항은 이미 m2에 통합되어야 함). D는 아마도 no-op이거나 비어있는 커밋을 만들어야합니다 (즉, 연속적인 개정판의 차이가 비어있는 커밋).
그러나 git은 D 위에서 m1을 재생하려는 시도를 거부 할 수 있습니다. 다음과 같은 오류가 발생할 수 있습니다.
error: Commit 90caf85 is a merge but no -m option was given.
fatal: cherry-pick failed
git에 플래그를 전달하는 것을 잊어 버린 것처럼 보이지만 근본적인 문제는 git이 빈 커밋을 만드는 것을 싫어한다는 것입니다.
Git 2.18 (2018 년 2 분기)은 --preserve-merge
새로운 옵션을 추가 하여 옵션을 상당히 개선 할 것 입니다.
커밋 그래프의 전체 토폴로지를 다른 곳에 이식하기 위해 " git rebase
"배운 " --rebase-merges
" .
(참고 : 2019 년 2 분기 예정인 Git 2.22는 실제로 더 이상 사용되지 않습니다. --preserve-merge
)
참조 25cff9f 커밋 , 7543f6f 커밋 , 1131ec9 커밋 , 7ccdf65 커밋 , 537e7d6 커밋 , a9be29c 커밋 , 8f6aed7 커밋 , 1644c73 커밋 , d1e8b01 커밋 , 4c68e7d 커밋 , 9055e40 커밋 , cb5206e 커밋 , a01c2a5 커밋 , 2f6b1d1 커밋 , bf5c057 커밋 (2018 4월 25일를) Johannes Schindelin (에dscho
의해 ) . Stefan Beller ( )의 commit f431d73 (2018 년 4 월 25 일)을
참조하십시오 .stefanbeller
Phillip Wood ( )의 commit 2429335 (2018 년 4 월 25 일)를 참조하십시오 . (가 합병 Junio C 하마노 - - 에 2c18e6a을 투입 , 2018 (23) 월)phillipwood
gitster
pull
:--rebase-merges
분기 토폴로지를 다시 작성하도록 승인
preserve
단순히--preserve-merges
옵션을rebase
명령에 전달 하는 모드 와 마찬가지로 모드는 단순히 옵션을merges
전달합니다--rebase-merges
.이를 통해 사용자는 새 커밋을 풀지 않고 간단하게 커밋 할 수 있습니다.
git rebase
매뉴얼 페이지는 이제 병합을 통해 히스토리를 리베이스하기 위한 전체 섹션을 가지고 있습니다.
추출물:
개발자가 병합 커밋을 다시 만들려는 합법적 인 이유가 있습니다. 여러 관련 분기에 대해 작업 할 때 분기 구조 (또는 "커밋 토폴로지")를 유지하는 것입니다.
다음 예제에서 개발자는 단추가 정의 된 방식을 리팩토링하는 토픽 브랜치 및 해당 리팩토링을 사용하여 "버그보고"버튼을 구현하는 다른 토픽 브랜치에서 작업합니다.
출력git log --graph --format=%s -5
은 다음과 같습니다.* Merge branch 'report-a-bug' |\ | * Add the feedback button * | Merge branch 'refactor-button' |\ \ | |/ | * Use the Button class for all buttons | * Extract a generic Button class from the DownloadButton one
개발자는에 그 커밋을 리베이스 할 수도 있습니다 새로운
master
첫 번째 주제 분기가 통합 될 것으로 예상되는 경우, 예를 들어, 분기 토폴로지를 유지하면서master
훨씬 이전에 두 번째, 말보다 변경으로 해결 병합 충돌,DownloadButton
만든 클래스 로master
.이 rebase는
--rebase-merges
옵션을 사용하여 수행 할 수 있습니다 .
작은 예제는 commit 1644c73 을 참조하십시오 .
rebase-helper
--make-script
: 병합을 리베이스하는 플래그 소개시퀀서는 단지 (새 명령을 재 작성 가지 구조로 구성 배운 정신에는 변함이
--preserve-merges
있지만 실질적으로 덜 깨진 디자인, ).
rebase--helper
새로운--rebase-merges
옵션 에 의해 트리거되는 이러한 명령을 사용 하여 할 일 목록을 생성 할 수 있도록하겠습니다 .
이와 같은 커밋 토폴로지의 경우 (HEAD가 C를 가리키는 경우) :- A - B - C (HEAD) \ / D
생성 된 할 일 목록은 다음과 같습니다.
# branch D pick 0123 A label branch-point pick 1234 D label D reset branch-point pick 2345 B merge -C 3456 D # C
차이점은 무엇입니까 --preserve-merge
?
커밋 8f6aed7 는 다음과 같이 설명합니다.
옛날 옛적에, 여기에 개발자는 이렇게 생각했습니다 : 예를 들어, 핵심 Git 위에있는 Windows 용 Git 패치는 굵은 가지로 표현 될 수 있고 핵심 Git 위에 기반을두고 Cherry-pick'able 패치 시리즈 세트를 유지합니까?
이에 대한 최초의 시도는 다음과 같습니다
git rebase --preserve-merges
.그러나이 실험은 대화식 옵션으로 의도 된 것이 아니며
git rebase --interactive
명령 구현은 이미 매우 친숙해 보였기 때문에 피기 백 되었습니다--preserve-merges
. 실제로 설계 한 사람이 설계했습니다 .
그리고 "당신의 진실"이라고 저자는 자신을 말합니다 : Johannes Schindelin ( dscho
) , 우리가 Git For Windows를 가지고있는 주된 이유 (Hannes, Steffen, Sebastian 등 ...) 2009 년 당시에는 쉽지 않았습니다 .)
그는 2015 년 9 월부터 Microsoft에서 근무하고 있으며 , 현재 Microsoft가 Git을 많이 사용하고 있으며 서비스가 필요하다는 점을 고려하십시오.
이러한 추세는 실제로 2013 년 TFS와 함께 시작되었습니다 . 그 이후로 Microsoft는 지구상에서 가장 큰 Git 저장소를 관리 합니다 ! 그리고 2018 년 10 월부터 Microsoft는 GitHub를 인수했습니다 .
2018 년 4 월에 Git Merge 2018의 비디오에서 Johannes가 말하는 것을 볼 수 있습니다 .
얼마 후, 다른 개발자 (Andreas! ;-)를보고 있음 )와 Git 관리자 (Git 관리자) (Gim 관리자)를
--preserve-merges
함께 사용하는 것이 좋은 생각이라고 생각했습니다--interactive
. Junio가 부재하는 동안, 즉 동의), 그리고--preserve-merges
디자인 의 매력이 오히려 빠르고 매끄럽게 떨어지기 시작했습니다.
여기 Jonathan은 Suse의 Andreas Schwab 에 대해 이야기하고 있습니다. 2012 년에 토론 내용을 다시
볼 수 있습니다 .
이유? 에서
--preserve-merges
(의, 또는 그 문제에 대한 모드, 병합의 부모가 커밋 어떤 커밋) 명시 적으로 언급되지 않은,하지만 한 암시 받는 전달 된 커밋 이름으로pick
명령 .예를 들어 커밋을 재정렬하는 것이 불가능했습니다 .
브랜치 사이에서 커밋을 이동하거나 토픽 브랜치를 두 개로 나누는 것을 금지하는 것은 말할 것도 없습니다.아아, 이러한 단점은 또한 모드 (Windows의 요구에 Git을 제공하는 원래 목적이 다른 사람들에게도 유용 할 것이라는 추가 희망)가 Windows의 요구에 Git을 제공하는 것을 막았습니다.
5 년 후, 때때로 Git for Windows에서 핵심적인 Git의 태그에 기반을 둔 부분적으로 관련이없고 부분적으로 관련이없는 하나의 다루기 힘들고 큰 hodge-podge 패치 시리즈를 가질 수 없게되었을 때
git-remote-hg
처음에는 쓸모없는 Git for Windows의 경쟁 접근 방식이 나중에 관리자 가 아닌 경우에만 버려지 는 잘못된 시리즈 중 하나는 실제로는 불가능했습니다. " Git 정원 가위 " 가 탄생했습니다 : 스크립트, 대화 형 rebase 위에 피기 백, 먼저 리베이스 할 패치의 브랜치 토폴로지를 결정하고 추가 편집을위한 의사 작업 목록을 생성하고 결과를 실제 작업 목록으로 변환합니다 (exec
명령을 누락 된 할 일 목록 명령을 "구현"하고 마지막으로 새 기본 커밋 위에 패치 시리즈를 다시 만듭니다.
(Git garden shears 스크립트는 커밋 9055e40 의이 패치에서 참조됩니다 )
2013 년에 시작되었습니다.
그리고 디자인을 완성하고 트리 외부 스크립트로 구현하는 데 약 3 주가 걸렸습니다. 말할 필요도없이, 구현 자체가 안정화되는 데 몇 년이 걸렸으며, 디자인 자체는 그 자체가 건전한 것으로 입증되었습니다.이 패치로, 힘내 정원 가위의 선 (善)은 오는
git rebase -i
자체 . 옵션을
전달하면--rebase-merges
쉽게 이해할 수있는 할 일 목록과 커밋을 재정렬하는 방법이 분명한 곳이 생성됩니다 . 명령
을 삽입label
하고을 호출 하여 새 분기를 도입 할 수 있습니다merge <label>
.
그리고이 모드가 안정적이고 보편적으로 받아 들여지면, 설계 실수를 더 이상 사용하지 않을 수 있습니다--preserve-merges
.
Git 2.19 (Q3 2018)는와 --rebase-merges
함께 작동 하여 새로운 옵션을 개선합니다 --exec
.
" --exec
"옵션 " git rebase --rebase-merges
"은 (는) 수정 된 잘못된 위치에 exec 명령을 배치했습니다.
참조 1ace63b 커밋 (2018년 8월 9일)를, 그리고 f0880f7 커밋 으로 (06 년 8 월 2018) 요하네스 Schindelin ( dscho
) .
( Junio C gitster
Hamano 에 의해 합병 -- 커밋 750eb11 , 2018 년 8 월 20 일)
rebase --exec
: 작동하도록--rebase-merges
아이디어는 각각 후에 전화
--exec
를 추가하는 것 입니다.exec
pick
의 도입 이후
fixup!
/ s의quash!
커밋,이 아이디어는 간부가 사이에 삽입되지 않을 것 즉, "아마도 픽스 업 / 스쿼시 체인 다음, 선택"에 적용하도록 확장되었다pick
및 해당의fixup
또는squash
라인.현재 구현에서는 더티 트릭을 사용하여이를 수행합니다. pick / fixup / squash 명령 만 있다고 가정 한 다음 첫 번째 명령 앞에 행 을 삽입 하고 마지막 명령을 추가합니다.
exec
pick
에 의해 생성 된 할 일 목록과 함께
git rebase --rebase-merges
,이 간단한 구현 프로그램의 문제 : 그것은 정확한 잘못이있는 것은 생산label
,reset
및merge
명령.우리가 원하는 것을 정확하게 수행하도록 구현을 변경해 봅시다 : 라인을 찾고
pick
, 수정 / 스쿼시 체인을 건너 뛰고,exec
라인 을 삽입하십시오 . 오히려 헹구고 반복하십시오.참고 : 우리가 삽입 고통을 하기 전에 주석 행 가능한 빈 커밋에 의해 표현으로 주석 처리 된 선 선택 (우리는 앞의 선택의 간부 라인을 삽입 할 전에 하지 이후, 같은 라인).
그 동안 명령과 유사하기 때문에 명령
exec
뒤에 줄 을 추가하십시오. 새로운 커밋을 추가합니다.merge
pick
Git 2.22 (Q2 2019)는 기본적으로 작업 트리 당 계층 구조를 만드는 rebase 중간 상태를 저장하기 위해 refs / rewritten / 계층 구조의 사용법을 수정합니다.
참조 b9317d5 커밋 , 90d31ff 커밋 , 09e6564 커밋 에 의해 (07 3 월 2019) 응웬 타이 응옥 두이 ( pclouds
) .
(의해 병합 - Junio C 하마노 gitster
- 에 917f2cd 커밋 2,019 09 사월)
refs / rewritten /가 작업 트리 당인지 확인하십시오
a9be29c (시퀀서 : label
worktree-local, 2018-04-25, Git 2.19 명령으로 생성 된 심판 refs/rewritten/
참조 )는 워크 트리 별 참조 공간으로 추가 합니다.
불행히도 (나쁜) 워크 트리마다 실제로 업데이트되도록 업데이트 해야하는 두 곳이 있습니다.
- add_per_worktree_entries_to_dir()
참조 목록이 리포지토리refs/rewritten/
대신 워크 트리 를 확인하도록 업데이트되었습니다 .
common_list[]
git_path()
올바른 위치 를 반환 하도록 업데이트됩니다 . 여기에는 "rev-parse --git-path
"가 포함됩니다 .이 혼란은 나에 의해 만들어졌습니다.
나는refs/worktree,
모든 처리가 특별한 처리없이 작업 트리 당 될 위치를 소개하여 문제를 해결하기 시작했습니다 .
불행한 refs / rewritten는 refs / worktree 앞에 왔으므로 이것이 우리가 할 수있는 전부입니다.
Git 2.24 (2019 년 4 분기)에서 " git rebase --rebase-merges
"는 다양한 병합 전략을 추진하고 전략 별 옵션을 전달하는 방법을 배웠습니다.
Elijah Newren ( )의 commit 476998d (2019 년 9 월 04 일)를 참조하십시오 . 참조 e1fac53 커밋 , a63f990 커밋 , 5dcdd74 커밋 , e145d99 커밋 , 4e6023b 커밋 , f67336d 커밋 , a9c7107 커밋 , b8c6f24 커밋 , d51b771 커밋 , 커밋 c248d32 , 8c1e240 커밋 , 커밋 5efed0e , 68b54f6 커밋 , 2e7bbac 커밋 , 6180b20 커밋 , d5b581f 커밋 (31 2019 년 7 월)newren
Johannes Schindelin ( dscho
) .
( Junio C gitster
Hamano 에 의해 병합 - 커밋 917a319 , 2019 년 9 월 18 일)
'Programming' 카테고리의 다른 글
Linux 커널에서 가능성이 높거나 가능성이 낮은 매크로는 어떻게 작동하며 그 이점은 무엇입니까? (0) | 2020.03.06 |
---|---|
Java 패키지 이름에서 단어 구분 기호에 대한 규칙은 무엇입니까? (0) | 2020.03.06 |
R에서 문자열의 길이를 찾는 방법 (0) | 2020.03.06 |
git에서 merge --squash와 rebase의 차이점은 무엇입니까? (0) | 2020.03.06 |
런타임에 메소드가 정의 된 위치를 찾는 방법은 무엇입니까? (0) | 2020.03.06 |