使用 git 和 meld 进行交互式变基的 3 路合并中的三个文件是啥?

Posted

技术标签:

【中文标题】使用 git 和 meld 进行交互式变基的 3 路合并中的三个文件是啥?【英文标题】:What are the three files in a 3-way merge for interactive rebasing using git and meld?使用 git 和 meld 进行交互式变基的 3 路合并中的三个文件是什么? 【发布时间】:2016-08-27 21:09:51 【问题描述】:

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

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

在交互式变基期间,3 路合并中的这些文件各自代表什么?而在编辑这些文件时,我的目标是什么?

更新:基于我看到的 cmets 和实验:

左 (LOCAL):提交重播序列中此时文件的本地版本。 右 (REMOTE):在最初应用当前提交之后的文件状态。 中间:原始提交序列中右的父级。

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

请注意,这种配置似乎是特定于融合的,至少在某种程度上是这样。 Git 的 3 路合并行为可能因其他编辑器而异。

【问题讨论】:

【参考方案1】:

中间版本是合并基础,就像git merge一样。

(名称“other”可能比“remote”更合适,因为没有要求合并的另一端是远程的,并且由于 Mercurial 始终使用名称“other”,而不是 Git 需要匹配 Mercurial,但有些一致性可能会很好。请注意,Git 在这里也使用名称“我们的”和“他们的”,所以我们永远不会从 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之间找到一个合并基,它是...commit *。这实际上是一个相当不错的合并基础,尽管最好 Git 可以按原样应用补丁,而不必回退到三向合并代码。

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

现在我们将挑选B。这将B 与其父级A 进行比较,并尝试将差异应用于A'

不过,提交 A' 与提交 B 有所不同。事实上,我们可以在BA' 之间找到一个合并基,那就是……再次提交*。不幸的是,这是一个糟糕的合并基础。幸运的是,只有当补丁不能按原样应用时,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。这区分了BC,就像我们上次区分AB 一样。如果我们可以按原样应用补丁,那很好;如果不是,我们退回 再次使用提交 * 作为合并基础。它又是一个非常糟糕的合并基础。 和以前一样,假装B 中的版本是公共基础。

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

【讨论】:

我现在正在做一个变基,中间不是左右的共同祖先 - 它只是右边(远程)的祖先,并且似乎是左边的继承者(当地的)。如果它是一个合并基地,这怎么可能?请注意,在我的 rebase 中,我正在从历史记录中删除一个提交,中间表示应用删除的提交时文件的状态。 嗯,究竟如何?我将在此处注意您正在查看一个文件,这不是一个提交(因此我们不能使用文件的 ID 来派生提交的 ID,除非该文件的特定版本对于一个特定的提交是唯一的)。某些合并工具也可能会做一些不同的事情以提供帮助。例如,Git 中有一些代码可以提取两个提交提示文件的公共部分,一些工具可能会在此过程中使用这些部分。特别是 p4merge 使用来自 git-sh-setup 的create_virtual_base。我对meld 一无所知。 啊哈,我对此很好奇,所以我翻遍了git am 源代码,发现我对这里的合并基数有误。除了 git 作弊之外,这将是答案! @CodeWizard,杰克:更新了答案。它实际上是来自父提交的版本(在这种特殊情况下,但不适用于只有 blob ID 而没有实际父提交 ID 的电子邮件补丁)。 有趣!感谢您写出如此详尽的答案,并在此基础上进行了额外的研究。这确实帮助我理解了合并/变基过程:)【参考方案2】:

在这方面,合并和变基是相同的。合并和变基之间的唯一区别是,使用变基的历史看起来更好(更线性)。但是对于即将出现的和你必须解决的冲突,它们是相同的。

【讨论】:

你能澄清一下吗?合并涉及两个分支。交互式变基涉及重新排序或修改一个分支中的提交。所以他们不一样。因此,不清楚本地、远程和共同祖先是什么。

以上是关于使用 git 和 meld 进行交互式变基的 3 路合并中的三个文件是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在没有变基的情况下重做 GIT 中的提交历史

Git中分支变基的原理

Git从青铜到王者第四篇:Git的分支与合并

关于Git分支变基操作的一些笔记

关于Git分支变基操作的一些笔记

Git之深入解析Git的杀手级特性·分支管理与分支变基的开发工作流以及远程分支的跟踪