使用 git 和 meld 进行交互式变基的三向合并中的三个文件是什么?

What are the three files in a 3-way merge for interactive rebasing using git and meld?

假设我使用 git rebase -i 进行了交互式变基。如果出现一些冲突,我可能会遇到合并冲突并被要求进行 3 向合并。使用 meld,我得到三个 windows:LOCAL(左)、???(中)和 REMOTE(右)。这里的 ??? 我的意思只是 meld 没有提供一些特殊的名称来附加到文件中。

在正常合并期间,这是有道理的,因为中间是共同的祖先,而您正在将本地和远程更改合并到该祖先。然而,在交互式变基期间似乎并非如此 - 不清楚每个文件代表什么。

3 向合并中的这些文件在交互式变基过程中分别代表什么?在编辑这些文件时,我的目标是什么?

更新: 根据我看到的评论和实验:

因此,我的任务是确定从中间到右边的增量,然后将这个增量应用到左边。 Middle 应该反映当前提交增量应用于新提交序列后文件的状态。

请注意,此配置似乎是特定于 meld 的,至少在某种程度上是这样。 Git 的 3 向合并行为可能与其他编辑器不同。

合并和变基在这方面是相同的。合并和变基之间的唯一区别是,变基后历史看起来更好(更线性)。但是对于会出现的和你要解决的矛盾,都是一样的。

中间版本是合并基础,就像 git merge.

(名称 "other" 可能比 "remote" 更合适,因为不要求合并的另一端是远程的,并且由于 Mercurial 一贯使用名称 "other" 对于它,并不是说 Git 需要匹配 Mercurial,但一些一致性可能会很好。请注意 Git 在这里也使用名称 "ours" 和 "theirs",所以我们永远不会从 Git 获得 100% 的一致性。:-) )

但是等等,怎么会有合并基地?

总是一个合并基地。

通常我们甚至不必找到它,因为每个补丁在被视为补丁时都会干净地应用(无需尝试三向合并)。但有时补丁不会完全应用,我们确实不得不退回到三向合并。

(顺便说一句,您可以禁用此回退。请参阅 the git-am documentation 中的 --3way--no-3wayam.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

我们将挑选提交 ABCA = aaaaaaa 等)以便我们得到这个结果,最后:

              A - B - C   [abandoned]
            /
... - o - *           A' - B' - C'   <-- branch
            \       /
              G - H       <-- origin/branch

让我们仔细看看 A 的第一个精选。

这会将 A 与其父项(即提交 *)进行比较(差异),并尝试将结果差异应用于提交 H

然而,提交 H 与提交 * 有所不同。其实我们可以在AH之间找到一个merge base,就是...commit *。这实际上是一个相当不错的合并基础,尽管最好 Git 可以按原样应用补丁,而不必退回到三向合并代码。

因此,提交 * 是将 A 挑选到 H 时的合并基础。合并完成后,我们得到新的提交 A'。 (例如,它的新 SHA-1 ID 可能是 aaaaaa1。可能不是;我们就称它为 A'。)

现在我们将挑选 B。此差异 B 与其父级 A 的差异,并尝试将差异应用于 A'.

然而,

提交 A' 与提交 B 有所不同。其实我们可以在BA'之间找到一个merge base,那就是... commit * again。不幸的是,这是一个可怜的合并基础。幸运的是,Git 只有在补丁不能按原样应用时才会回退到它,通常它可以。但如果不能,Git 将区分 *B*A' 并尝试合并这两个差异。请注意,* vs B 包含我们在 A 中所做的所有更改,但 * vs A' 也包含所有相同的 A更改,所以如果我们幸运的话,Git 会注意到已经合并的更改并且不会复制它们。 edit Git 秘籍。 (此代码最近在 2.6 版本中有所更改,但总体策略保持不变。)

考虑 git diff 的实际输出,用于仅显示从提交 A 到提交 B 的更改。这包括 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。这与 BC 的区别,就像我们上次 AB 的区别一样。如果我们可以按原样应用补丁,那很好;如果不是,我们回退 再次使用提交 * 作为合并基础。又是一个很猥琐的merge base和之前一样,假装B里的版本是common base

顺便说一句,这也解释了为什么您倾向于一次又一次地看到相同的合并冲突对于这些变基:我们每次都使用相同的合并基。 (启用 git rerere 会有帮助。)