简介 (2018年5月更新)
整个事情基本上至少有点困惑,因为Git让其内部工作直接展现给你。
请注意,在这里我们关注的情况是当您运行以下命令:
git checkout somebranch; git rebase origin/their-branch
暂停变基是为了让你解决合并冲突,然后使用
git add
添加已解决的冲突,并运行
git rebase --continue
。如果你使用带有
git mergetool
或者高级图形化界面的某些合并工具,那么该界面可能会以其他方式自动完成这些步骤,但在底层,它会通过
git add
和运行
git rebase --continue
来完成操作。
一开始,
HEAD
提交是
他们的分支,所以如果你使用
git checkout --ours
或者
git checkout --theirs
,
--ours
表示
theirs——
origin/their-branch
的最终提交,而
--theirs
表示
yours,即你正在变基的第一个提交。这是日常Git混淆的普遍情况(参见
What is the precise meaning of "ours" and "theirs" in git?),并不是导致最初问题的原因。
然而,随后
HEAD
提交实际上是
某些你的提交复制到了他们的最新提交之上的
一种混合体。现在,你的新提交系列和原始提交发生了冲突。这个冲突通常是由"他们"做的某些事情(在
origin/their-branch
中改变了的东西)引起的。你仍然需要解决这个冲突。当你这样做时,你可能会在后续提交中看到完全相同的冲突。
再次说明,
HEAD
或者
local
或
--ours
是
变基将你的更改和他们的更改合并而成的一次提交,而另一个提交(
remote
或
>>>>>>>
或
--theirs
)是你自己的提交,变基正在尝试将其复制到
HEAD
之上。
更长的内容
当合并(包括内部重复的“合并”操作,即变基)时,涉及到两个“头”(两个特定分支的末端)。我们称这些为
your-branch
和
origin/their-branch
:
G - H -------- <-- HEAD=your-branch
/ \
... - E - F M <-- desired merge commit [requires manual merge]
\ /
I - J - K - L <-- origin/their-branch
这一点通常是(并不令人惊讶地)令人困惑的,尽管标记得很清楚。
更糟糕的是,git在合并时使用--ours
和--theirs
来表示两个头提交,其中“ours”是您运行 git merge
时所在的一个头提交(提交H
),而“theirs”则是他们的提交(提交L
)。但是当您进行变基时,这两个头会相反,因此“ours”是您正在将其变基到的头提交,即他们的更新代码,而“theirs”则是您当前正在变基的提交,即您自己的代码。
这是因为变基实际上使用一系列的 cherry-pick 操作。您开始的图片与合并时大致相同:
G - H <-- HEAD=your-branch
/
... - E - F
\
I - J - K - L <-- origin/their-branch
Git 需要做的是 复制 提交记录 G
和 H
的效果,也就是说,使用 git cherry-pick
命令复制提交记录 G
,然后再复制一次提交记录 H
。但为了做到这点,Git 必须先在内部(使用 "detached HEAD" 模式)切换 到提交记录 L
:
G - H <-- your-branch
/
... - E - F
\
I - J - K - L <-- HEAD, origin/their-branch
现在它可以通过比较提交记录 F
和 G
的树(以查看您进行了哪些更改),然后比较 F
与 L
(以查看您的部分工作是否已经在 L
中)并将任何尚未在 L
中的更改添加进来。这是一个内部的“合并”操作。
G - H <-- your-branch
/
... - E - F G' <-- HEAD
\ /
I - J - K - L <-- origin/their-branch
如果合并不顺利,
HEAD
仍然停留在提交
L
(因为提交
G'
尚不存在)。 因此,是的,
HEAD
是他们开发分支的头 - 至少现在是这样。
一旦存在
G
的副本,
HEAD
就会移动到
G'
,并且Git会尝试以相同的方式复制更改(差异
G
vs
H
,然后差异
F
vs
G'
,并合并结果):
G - H <-- your-branch
/
... - E - F G' - H' <-- HEAD
\ /
I - J - K - L <-- origin/their-branch
如果合并失败并需要帮助,你将会发现 HEAD
指向 G'
而不是 H'
,因为 H'
还不存在。
一旦所有的合并都成功并提交了 G'
和 H'
,git 将从提交 H
中删除标签 your-branch
,并将其指向提交 H'
:
G - H
/
... - E - F G' - H' <-- HEAD=your-branch
\ /
I - J - K - L <-- origin/their-branch
现在您已经重新定位,并且HEAD
再次符合您的预期。 但是在变基过程中,HEAD
可能是其分支末端(提交L
),也可能是复制并附加到其分支末端之后的新提交之一;而--ours
表示在 L
的末尾增长的分支,而--theirs
则表示被复制的提交(上述的G
或H
)。
(这基本上是git揭示了它执行操作的原始机制,这在git中经常发生。)
<<<<<<< HEAD
时,我仍然不确定这一部分是我的还是他们的?答案的复杂性是否意味着它可以被改述为“这取决于具体情况”? - dumbledadgit merge
还是git rebase
(或者git cherry-pick
或git revert
)。在所有情况下,当实际命令运行时,HEAD
是任何时候的HEAD
,因此最令人困惑的是,对于交互式rebase,git rebase
从一个正在增加新提交项的目标分离的HEAD中运行git cherry-pick
(用于非交互式)或git am
。 - torekHEAD
是您第三个提交的副本。合并冲突是复制您的第四个提交的结果。但显然,您的第四个提交在原始的四个提交序列中也很好地应用了。冲突从哪里来?它必须来自某个早期(HEAD
的祖先)提交:不是您已经复制的三个提交之一,而是在那个点之前的某个提交。但它可能出现在任何地方。 - torek