中间版本是合并基础,就像git merge一样。
(名称“other”可能比“remote”更合适,因为没有要求合并的另一端是远程的,并且由于 Mercurial 始终使用名称“other”,而不是 Git 需要匹配 Mercurial,但有些一致性可能会很好。请注意,Git 在这里也使用名称“我们的”和“他们的”,所以我们永远不会从 Git 获得 100% 的一致性。:-))
但是等等,怎么有一个合并基础?
总是有一个合并基础。
通常我们甚至不必找到它,因为每个补丁在被视为补丁时都会干净地应用(无需尝试三向合并)。但有时补丁无法完全应用,我们确实不得不退回到三向合并。
(顺便说一句,您可以禁用此回退。请参阅 the git-am documentation 中的 --3way、--no-3way 和 am.threeWay,尽管此处链接的页面已经过时,因为这些控件最近发生了变化。)
$ git rebase -i
pick aaaaaaa first commit
pick bbbbbbb second commit
pick ccccccc third commit
让我们也画出提交图,这样我们就可以看到我们从什么变基和变基:
A - B - C <-- branch
/
... - o - *
\
G - H <-- origin/branch
我们将挑选提交 A、B 和 C(A = aaaaaaa 等),以便我们最终得到这个结果:
A - B - C [abandoned]
/
... - o - * A' - B' - C' <-- branch
\ /
G - H <-- origin/branch
让我们仔细看看A 的第一个樱桃。
这会将A 与其父级提交* 进行比较(差异),并尝试将生成的差异应用于提交H。
不过,提交 H 与提交 * 有所不同。事实上,我们可以在A和H之间找到一个合并基,它是...commit *。这实际上是一个相当不错的合并基础,尽管最好 Git 可以按原样应用补丁,而不必回退到三向合并代码。
因此,提交* 是在将A 樱桃采摘到H 时的合并基础。合并完成后,我们得到新的提交A'。 (例如,它的新 SHA-1 ID 可能是 aaaaaa1。可能不是;我们就叫它 A'。)
现在我们将挑选B。这将B 与其父级A 进行比较,并尝试将差异应用于A'。
不过,提交 A' 与提交 B 有所不同。事实上,我们可以在B 和A' 之间找到一个合并基,那就是……再次提交*。不幸的是,这是一个糟糕的合并基础。幸运的是,只有当补丁不能按原样应用时,Git 才会依赖它,通常它可以。但如果不能,Git 将区分 * 与 B 和 * 与 A' 并尝试合并这两个差异。请注意,* 与 B 合并了我们在 A 中所做的所有更改,但 * 与 A' 也合并了所有相同的 A 更改,所以如果我们幸运的话,Git 会注意到已经- 合并更改并且不复制它们。 编辑 Git 作弊。 (此代码最近在 2.6 版本中有所更改,但总体策略保持不变。)
当用于显示从提交A 到提交B 的更改时,请考虑git diff 的实际输出。这包括index 行:
diff --git a/foo b/foo
index f0b98f8..0ea3286 100644
左边的值是提交A 中文件foo 版本的(缩写)哈希。右边的值是提交B中文件版本的哈希值。
Git 只是从左侧哈希中伪造了一个合并基础。换句话说,提交A 中的文件版本成为伪造的合并基础。 (Git 将 --build-fake-ancestor 传递给 git apply。这要求特定的文件 blob 对象在存储库中,但它们是因为它们在提交 A 中。对于通过电子邮件发送的补丁,Git 使用相同的代码,但 blob 可能或可能不存在。)
请注意,Git 实际上在选择提交 A 时也会这样做,但这次合并基础文件是来自提交 * 的版本,这实际上是 合并基础。
最后,我们挑选C。这区分了B 与C,就像我们上次区分A 与B 一样。如果我们可以按原样应用补丁,那很好;如果不是,我们退回 再次使用提交 * 作为合并基础。它又是一个非常糟糕的合并基础。 和以前一样,假装B 中的版本是公共基础。
顺便说一句,这也解释了为什么你会一遍又一遍地看到相同的合并冲突:我们每次都使用相同的合并基础。 (启用git rerere 会有所帮助。)