Git rebase --preserve-merges失败

12
我有一个(庞大的)提交树,其中包含多个合并提交,我想将它们变基到另一个提交。普通的变基会导致git要求我解决合并冲突。我不想审核每个合并,因为这将是很多工作。在发现--preserve-merges选项后,这个选项被巧妙地解释了here,我认为我找到了这个任务的完美工具。然而,我似乎无法让它正常工作。我创建了一个演示问题的玩具示例。
从一个空文件夹开始,我们首先创建一个带有合并的分支和另一个我们将要变基的分支。
A---B--
\      \
 ---C---D
 \
  ---E

master 指代 Bbranch 指代 D,而 goodbye-branch 指代 E 时。

git init
echo Hello > Hello.txt
git add Hello.txt
git commit -m "Create Hello.txt (commit A)"
git tag start

echo World! >> Hello.txt
git commit -am "Change to Hello World (commit B)"

git checkout start
git checkout -b branch
echo Dave >> Hello.txt
git commit -am "Change to Hello Dave (commit C)"

git merge master
echo Hello World, Dave! > Hello.txt
git add Hello.txt
git commit -m "Merge branch master into branch (commit D)"

git checkout start
git checkout -b goodbye-branch
echo Goodbye > Goodbye.txt
git add Goodbye.txt
git commit -m "Add Goodbye.txt (commit E)"

到目前为止,一切都顺利进行。虽然出现了合并冲突,但我们已经解决了它。现在我们尝试将分支重新基于 E 进行变基,以便最终得到以下提交树:

A---E----B'
     \    \
      C'---D'

git checkout branch
git rebase -p goodbye-branch

这以以下错误结束:

Auto-merging Hello.txt
CONFLICT (content): Merge conflict in Hello.txt
Automatic merge failed; fix conflicts and then commit the result.
Error redoing merge f567809e2cc91244cc7fdac210e1771dc75e4d86

该文件包含以下内容:

Hello
<<<<<<< HEAD
Dave
=======
World!
>>>>>>> 0437403c97f33f229e41ec9584ce891a50052e48

我做错了什么?我原以为Git可以使用提交来解决在rebase过程中遇到的合并冲突。
我正在使用Git 1.9.4.msysgit.1,这是目前最新的版本。

1
你帮了很大的忙,Jubobs,我已经给你的答案点了赞。但严格来说,这个问题还没有得到解答。这个问题的解决方案将是 rerere-teach.sh 脚本在 Windows 版本的实现。 - DieterDP
1个回答

31

简述

--preserve-merges参数告诉git-rebase尝试重新创建合并提交,而不是忽略它们。它并不会记录冲突解决的方式,也就是说它不能记住未来使用的冲突解决方法。你需要用到的是rerere

在你的示例中,在rebase期间出现的冲突与前面合并时解决的冲突完全相同。如果在合并之前激活了rerere,则在rebase期间无需再次解决该冲突。

如果你预计将合并分支,然后对其进行rebase,您应该激活rerere以便在未来只需要解决一次给定的合并冲突,而不是多次。

详细解释

让我们分解一下你的示例。

git init
echo Hello > Hello.txt
git add Hello.txt
git commit -m "Create Hello.txt (commit A)"
git tag start

echo World! >> Hello.txt
git commit -am "Change to Hello World (commit B)"

git checkout start
git checkout -b branch
echo Dave >> Hello.txt
git commit -am "Change to Hello Dave (commit C)"

目前为止还算顺利。就在你的第一次git merge命令之前,你的代码库看起来是这样的:

enter image description here

在提交A中,Hello.text包含内容:

Hello
在提交B中,Hello.text包含的内容。
Hello
World!

在提交 C 中,Hello.text 包含了:

Hello
Dave
现在,如果您运行以下命令尝试将master合并到branch
git merge master

Git 报告合并冲突是因为它无法独自判断在合并后,Hello.txt 文件的内容应该是什么。

Hello
World!
Dave
Hello
Dave
World!

通过覆盖Hello.txt的内容为Hello World, Dave!,暂存你的更改,并完成合并提交,您可以解决这个冲突。

echo "Hello World, Dave!" > Hello.txt
git add Hello.txt
git commit -m "Merge branch master into branch (commit D)"
你的代码库现在看起来像这样:

enter image description here

然后你运行

git checkout start
git checkout -b goodbye-branch
echo Goodbye > Goodbye.txt
git add Goodbye.txt
git commit -m "Add Goodbye.txt (commit E)"

此时,您的代码库看起来如下所示:

enter image description here

现在运行

git checkout branch
git rebase -p goodbye-branch

但是会遇到冲突。在解释为什么出现这种冲突之前,让我们看一下如果git-rebase操作成功(即无冲突),你的仓库会是什么样子:

enter image description here

现在让我们看看为什么在第一次合并时Hello.txt遇到了相同的冲突问题,而Goodbye.txt没有出现任何问题。实际上,一个rebase可以被分解成一系列更基本的操作(checkoutcherry-pick);关于这个可以在http://think-like-a-git.net/sections/rebase-from-the-ground-up.html上了解更多。简而言之,在进行git rebase操作期间,你的仓库将如下所示:

enter image description here

情况非常类似于第一次合并之前的情况:在提交B'中,Hello.text包含的内容是:

Hello
World!

在提交 C' 中,Hello.text 包含了

Hello
Dave

然后Git尝试创建合并B'和C',但由于与第一个合并冲突的原因完全相同,出现了合并冲突:Git无法确定Dave行应该在World!行之前还是之后。因此,变基操作停滞不前,Git要求您在完成变基之前解决该合并冲突。

解决方法:使用rerere

Git的rerere可以帮助你解决这个问题。

它的名字代表“重用已记录的解决方案”,正如其名称所示,它允许你让Git记住你如何解决一个块级冲突,这样下次遇到相同的冲突时,Git就可以自动地为你解决它。

如果你想对之前合并并解决过大量冲突的分支进行变基,那么启用rerere之后,你可能就不需要再次解决所有的冲突。

如果启用了rerere

git config --global rerere.enabled true

合并之前,Git会记录你在创建提交D时解决合并冲突的方式,并在后续的变基操作中遇到相同的冲突时应用相同的解决方法。冲突仍将中断变基操作,但会自动解决。你只需要执行git rebase --continue

然而,看起来在合并之前没有启用rerere,这意味着Git没有保留你第一次如何解决冲突的记录。此时,你可以现在激活rerere并再次手动解决所有相同的冲突,或者使用rerere-train.sh脚本(也可参见这篇博客文章)利用现有历史记录预填充rerere缓存。


3
我理解在合并代码时出现了冲突,但我不明白为什么在变基时会再次出现冲突。因为 Git 知道如何解决冲突,毕竟合并提交记录也正在进行重新基础处理...假设这只是重新基础的工作原理,还有其他方法可以避免在移动提交记录时需要重新解决所有冲突吗? - DieterDP
根据你提供的信息,我只能告诉你冲突是在合并过程中发生的(而不是变基),Git 在你解决冲突之前不会允许你继续进行。话虽如此,在变基、挑选、还原操作等过程中也可能出现冲突。也许你应该编辑你的问题,添加一个玩具示例,其中冲突仅由于变基而引起,我会尽力解释冲突的原因。 - jub0bs
4
我所知道的唯一解决方法是使用rerere,但我同意@DieterDP的观点,即使没有启用rerere,git也能够在rebase期间解决冲突,因为当git将D挑选出来生成D'时,就会产生冲突。 如果您检查提交D,它实际上包含了冲突的解决方案,并且将解决方案应用于冲突不会“重新发生冲突”,即由于rebase而引起的冲突甚至没有稍微变化,因此可以应用相同的解决方案。 - André Sassi
1
我认为@AndréSassi的意思是,如果git更加智能,它可以“向前看”,以查看当前正在cherry-pick的提交之后是否有另一个提交,因此与该提交相关联的树定义(在某种程度上)正确的冲突解决方案。然而,Git并不那么聪明。 - torek
1
这是一个非常清晰有趣的解释,配有图形等。简洁明了,可以成为一篇不错的博客文章。 - Juh_
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接