git 合并之间的变基导致完全不相关的文件发生冲突

git rebase between merges results in conflicts in completely unrelated files

我有一个很大的 Git 存储库,其中几个月前引入了一个错误 我想 bisect 它,首先在过去引入一个提交 存储库,然后重放合并(对新的执行 rebase 提交),如下图所示。

我知道由于合并,Git 似乎没有那么好用 预期的,但我想更好地了解为什么会发生这种情况(如果有 可以帮助它更好地工作的参数)。

注意:最后,我的 git apply; git bisect 组合确实奏效了,但我 仍然对 Git 行为的替代解决方案或解释感兴趣。

基本问题说起来简单,解决起来却很难。

您正处于 master 的顶端。这些提交图很好,但很难输入 :-) 所以让我们在这里使用一个非常简化的 ASCII 码。 (这只是问题的重述,经过简化并带有不同的标签。)

                D
              /   \
A - B ----- C       F - G   <-- master
              \   /
                E

此处,在提交 B 时引入了一个错误,因此存在于每个子提交(CG)中。为了进行修复,我们想要创建一个新的提交序列(我将使用小写字母而不是 C'D' 等),并将修复 x 作为新安装提交:

                d
              /   \
A - B - x - c       f - g   <-- [anonymous; will become master]
              \   /
                e

为了做到这一点,git(git rebase -p 在这种情况下——注意 typical rebase removes 合并提交!)必须根据现有提交进行新提交。对于像新提交 c 这样的非合并,这并不太棘手:rebase 得到 CB 的差异,然后将该补丁应用于附加到提交 [=20] 的源代码树=].也就是说,rebase 在新的匿名分支上执行 git cherry-pick master~3。到目前为止没问题!现在 rebase 需要创建提交 d,这也不是问题,它只需要做 git cherry-pick master~2,它确实做到了,给出:

                d
              /
A - B - x - c

制作 e 有点棘手,因为它的父提交应该是 c。原则上,rebase 离开 d 挂起,倒回 c,并 git cherry-pick master~1^2 复制 E。 (我认为 rebase 脚本实际上执行了它自己的 git commit-tree 操作,因此它不必倒回匿名分支,但这同样有效,它只是涉及一些内部毛羽。)这给了我们:

                d
              /
A - B - x - c
              \
                e

这是我们遇到问题的地方:变基将如何创建合并提交f?它无法将提交 F 的树与 D 的树进行比较:这只是合并的一半。它也无法将 FE 进行比较:这只是合并的另一半。所以它不能使用 git cherry-pick 因为这需要选择原始合并的一侧或另一侧。

变基使用的解决方案是 运行 git merge(或者说 git merge 运行 的内部位)。这样,由 x 引入并继续通过新的 de 的任何更改也会得到正确处理。

(这确实引入了一个完全不同的问题:如果 F 是一个 "evil merge",即,一个合并改变了父代中没有改变的东西,合并的复制树 f 不会与 F 的树匹配,因为 git 永远不会看到 "evil merge" 的变化。需要的是 cherry-pick 的变体,它可以比较合并提交的将树与其所有父树进行比较——作为某种组合差异,除了比 git 的标准组合差异更完整——并将该差异应用于所有输入树。但是没有这样的八足动物樱桃选择 [git calamari-pick?], 所以它只是不可用。)


无论如何,这种 rebase 使用合并,在这里您看到的合并冲突可能与原始合并期间发生的相同。如果您简单地以相同的方式再次解决它——例如使用 git rerere——那将允许您继续。不幸的是,没有办法追溯打开git rerere(尽管我认为这样的东西很容易)。