git의“rebase --preserve-merges”는 정확히 무엇을하며 왜 그런가?


355

명령에 대한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합니까? 플래그가없는 기본 동작과 어떻게 다릅니 까? 병합 등을 "재 작성"한다는 것은 무엇을 의미합니까?


20
경고 : Git 2.18 (2018 년 2 분기, 5 년 후) git --rebase-merges부터는 이전을 대체 할 것 git --preserve-merges입니다. 아래 내 답변
VonC

답변:


464

일반적인 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-mergesC 위에서 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-mergesH (주제)를 D (마스터)에 대해 시도 하면 다음 커밋이 재생을 위해 선택됩니다.

  • m1을 선택
  • H를 선택

m1에 통합 된 변경 사항 (B, F)은 이미 D에 통합되어야합니다 (m2는 B와 F의 하위 요소를 병합하기 때문에 이러한 변경 사항은 이미 m2에 통합되어야합니다). 따라서 개념적으로 m1을 D는 아마도 no-op이거나 비어있는 커밋을 만들어야합니다 (즉, 연속적인 개정판의 차이가 비어있는 커밋).

그러나 git은 D 위에서 m1을 재생하려는 시도를 거부 할 수 있습니다. 다음과 같은 오류가 발생할 수 있습니다.

error: Commit 90caf85 is a merge but no -m option was given.
fatal: cherry-pick failed

git에 플래그를 전달하는 것을 잊어 버린 것처럼 보이지만 근본적인 문제는 git이 빈 커밋을 만드는 것을 싫어한다는 것입니다.


6
나는 것으로 나타났습니다 git rebase --preserve-merges입니다 훨씬 느리게 이상 rebase없이 --preserve-merges. 이것이 올바른 커밋을 찾는 부작용입니까? 속도를 높이기 위해 할 수있는 일이 있습니까? (그런데… 매우 자세한 답변을 주셔서 감사합니다!)
David Alan Hjelle

7
항상 --preserve-merges를 사용해야하는 것처럼 들립니다. 그렇지 않으면 기록을 잃을 가능성이 있습니다 (예 : 병합 커밋).
DarVar

19
@DarVar 리베이스에서 항상 히스토리를 느슨하게합니다. 왜냐하면 실제 위치와 다른 코드베이스로 변경 한 부분이 있다고 주장하기 때문입니다.
Chronial

5
여전히 "잠정 답변"입니까?
Andrew Grimm

5
@Chronial 물론 rebasing은 항상 잃어버린 역사를 포함하지만 DarVar는 아마도 역사가 느슨 할뿐만 아니라 코드베이스로 변경되었다는 사실을 암시했을 것입니다. 충돌 해결에는 리베이스가 수행 될 수있는 모든 가능한 방법으로 손실되는 정보가 포함됩니다. 당신은 항상 그것을 다시해야합니다. git이 충돌 해결을 다시 할 수있는 방법이 실제로 있습니까? 왜 자식 커밋을 병합 커밋 할 수 없습니까?
Nils_M

94

Git 2.18 (2018 년 2 분기)은 --preserve-merge새로운 옵션을 추가 하여 옵션을 상당히 개선 할 것 입니다.

커밋 그래프의 전체 토폴로지를 다른 곳에 이식하기위한 " git rebase"학습 된 " --rebase-merges" .

(참고 : Git 2.22, Q2 2019는 실제로 더 이상 사용되지 않으며 --preserve-merge Git 2.25, Q1 2020 은 " git rebase --help"출력 에서 광고를 중단 합니다. )

참조 25cff9f 커밋 , 7543f6f 커밋 , 1131ec9 커밋 , 7ccdf65 커밋 , 537e7d6 커밋 , a9be29c 커밋 , 8f6aed7 커밋 , 1644c73 커밋 , d1e8b01 커밋 , 4c68e7d 커밋 , 9055e40 커밋 , cb5206e 커밋 , a01c2a5 커밋 , 2f6b1d1 커밋 , bf5c057 커밋 (2018 4월 25일를) Johannes Schindelin (에dscho 의해 ) . Stefan Beller의 커밋 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 garden shears " 가 탄생했습니다 : 스크립트, 대화 형 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 gitsterHamano 에 의해 합병 -- 커밋 750eb11 , 2018 년 8 월 20 일)

rebase --exec: 작동하도록 --rebase-merges

아이디어는 각각 후에 전화 --exec를 추가하는 것 입니다.execpick

의 도입 이후 fixup!/ s의 quash!커밋,이 아이디어는 간부가 사이에 삽입되지 않을 것 즉, "아마도 픽스 업 / 스쿼시 체인 다음, 선택"에 적용하도록 확장되었다 pick및 해당의 fixup또는 squash라인.

현재 구현에서는 더티 트릭을 사용하여이를 달성합니다. pick / fixup / squash 명령 만 있다고 가정 한 다음 첫 번째 명령 앞에 행 을 삽입 하고 마지막 명령을 추가합니다.execpick

에 의해 생성 된 할 일 목록과 함께 git rebase --rebase-merges,이 간단한 구현 프로그램의 문제 : 그것은 정확한 잘못이있는 것은 생산 label, resetmerge명령.

우리가 원하는 것을 정확하게 수행하도록 구현을 변경해 봅시다 : 라인을 찾고 pick, 수정 / 스쿼시 체인을 건너 뛰고, exec 라인 을 삽입하십시오 . 오히려 헹구고 반복하십시오.

참고 : 우리가 삽입 고통을 하기 전에 주석 행 가능한 빈 커밋에 의해 표현으로 주석 처리 된 선 선택 (우리는 앞의 선택의 간부 라인을 삽입 할 전에 하지 이후, 같은 라인).

그 동안 명령과 유사하기 때문에 명령 exec뒤에 줄 을 추가하십시오. 새로운 커밋을 추가합니다.mergepick


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 (시퀀서 : labelworktree-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
요하네스 Schindelin (dscho ) .
(의해 병합 Junio C 하마노 - gitster-917a319 커밋 2,019 9월 18)


Git 2.25 (Q1 2020)에서는 보존 트리 병합을 용이하게하기 위해 worktree 로컬 및 저장소 전역 참조를 구별하는 데 사용되는 논리가 고정되어 있습니다.

참조 f45f88b 커밋 , c72fc40 커밋 , 8a64881 커밋 , 7cb8c92 커밋 , e536b1f 커밋 에 의해 (2019년 10월 21일)를 SZEDER 가보 ( szeder) .
( Junoio C gitsterHamano 에 의해 병합 - 커밋 db806d7 , 2019 년 11 월 10 일)

path.c: match값이없는 함수를 호출하지 마십시오 .trie_find()

서명자 : SZEDER Gábor

'logs / refs'는 작동하는 트리 별 경로가 아니지만 커밋 b9317d55a3 이후로 (refs / rewritten /가 작업 트리 당인지 확인하십시오, 2019-03-07, v2.22.0-rc0) ' git rev-parse --git-path'는 가짜 경로를 반환했습니다 후미 ' /'가있는 경우 :

$ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
/home/szeder/src/git/.git/logs/refs
/home/szeder/src/git/.git/worktrees/WT/logs/refs/

trie데이터 구조를 사용하여 경로가 공통 디렉토리에 속하는지 또는 작동중인 트리에 속하는지 여부를 효율적으로 결정합니다.

공교롭게도 b9317d55a3이 는 AS 오래된 같다 버그 트리거 trie에 추가 구현 자체 4e09cf2acf을 ( " path: 최적화 공통의 디렉토리 검사", 2015년 8월 31일은, 힘내 v2.7.0-RC0 - 병합 에 나열된 배치 # 2 ).

  • 설명하는 주석에 따르면 trie_find(), "trie가 값을 포함하는 키의 // 또는 \ 0- 종료 된 접두사"에 대해 지정된 일치 함수 'fn'만 호출해야합니다.
    이것은 사실이 아닙니다. trie_find ()가 match 함수를 호출하는 세 곳이 있지만 그중 하나에 값이 있는지 확인하지 않습니다.

  • b9317d55a3trie:

    • ' logs/refs/rewritten'및
    • ' logs/refs/worktree', 기존 ' logs/refs/bisect' 옆에 있습니다.
      그 결과 trie경로가 ' logs/refs/'인 노드가 생겼으며 이전에는 존재하지 않았으며 값이 첨부되지 않았습니다.
      ' logs/refs/'에 대한 쿼리 는이 노드를 찾은 다음 match값의 존재를 확인하지 않는 함수 의 호출 사이트 하나를 히트 하여 값으로 match함수를 호출합니다 NULL.
  • match함수 check_common()NULL값을 사용하여 호출 되면 0을 반환합니다. 이는 쿼리 된 경로가 공통 디렉토리에 속하지 않음을 나타내며 결국 위의 가짜 경로를 나타냅니다.

trie_find()존재하지 않는 값으로 일치 함수를 호출하지 않도록 누락 된 조건을 추가하십시오 .

check_common() 그런 다음 더 이상 NULL이 아닌 값을 가지고 있는지 확인할 필요가 없으므로 해당 조건을 제거하십시오.

비슷한 가짜 출력을 일으킬 수있는 다른 경로가 없다고 생각합니다.

AFAICT NULL값 으로 호출되는 일치 함수를 초래하는 다른 키 는 ' co'( ' common'및 ' config' 키로 인해 )입니다.

그러나 이들이 공통 디렉토리에 속하는 디렉토리에 없으므로 결과적인 작업 트리 특정 경로가 예상됩니다.


3
나는 이것이 가장 좋은 대답이어야한다고 생각합니다. --preserve-merges실제로 원하는대로 병합을 "보존"하지는 않습니다. 매우 순진합니다. 이를 통해 병합 커밋 및 상위 커밋 관계를 유지하면서 대화 형 리베이스의 유연성을 제공 할 수 있습니다. 이 새로운 기능은 굉장하며 잘 작성된 SO 답변이 아니라면 알 수 없었습니다!
egucciar

@egucciar 감사합니다. 또한 Git 2.18 ( stackoverflow.com/search?q=user%3A6309+%22git+2.18%22 ) 및 Git 2.19 ( stackoverflow.com/search?q=user%3A6309+%22git+2.19% ) 의 유일한 기능은 아닙니다. 22 )
VonC

1
당신이 주변이 Q / A,처럼 커밋 심술쟁이을 이동하려는 경우 매우 도움이 stackoverflow.com/questions/45059039/...
okovko

1
아, 정말 오랜만에 찾고 있었어요! 모든 병합에 참여하는 가상 커밋을 만들어야하는 경우 수동 해결 방법 이있었습니다 .
carnicer

전형적인 힘내. 감히 간단한 질문을하면 Git의 역사, 내부 알고리즘, 모든 지저분한 구현 세부 사항을 배우고 아마도 무슨 일이 일어나고 있는지 이해하기 위해 그래프 이론 전공이 필요합니다.
Dimitris
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.