樱桃挑选存在问题:来自先前提交的更改也被应用。

16

在我的项目中,几个月前我发布了一个版本。在那之后,我对主分支进行了许多更改。

如果我遇到了一些上一个版本中存在的错误,我会在主分支上修复它们,然后挑选它们到我在上一个版本创建的分支上。然后我可以提供一个只包含错误修复的新版本,而不发布主分支上未完成的工作。

当我尝试将某个错误修复挑选到发布分支时,我遇到了合并冲突。

据我所知,挑选某个提交会将一个新的提交引入到目标分支中,并带有在挑选提交中进行的更改。

但是,当我尝试解决冲突时,似乎git已经将未被挑选提交引入到我的发布分支中的主分支上的更改应用了。挑选的提交仅向冲突文件引入了几行文本。但是,当我尝试解决冲突时,我发现文件中还引入了其他几行文本,这些文本是通过不同的提交添加到主分支上的。

有人能解释一下为什么会将除了挑选提交以外的提交的更改引入到我的发布分支吗?


我也遇到了同样的问题,在多年使用 Windows 上的 git 后第一次出现这种情况。尝试了最新的 Windows 64 位 2.13.0 版本和旧版本 2.10.x,git cherry-pick 没有冲突合并,但修改后的文件却包含不应该存在的更改,同样的问题出现在使用补丁和 git apply 时。 - angularsen
1
我创建了一个仓库,可能有助于澄清:https://github.com/ianmiell/cherry-pick-mystery - ianmiell
2个回答

11
我理解为,挑选某个提交点进行精选会在目标分支中引入一个新的提交记录,其中包含了这个提交点的更改内容。
是的。但是,这些更改可能无法干净地应用,如果出现这种情况:
...... 我遇到了合并冲突。
此时,git cherry-pick正在执行三方合并,需要选择一个合并基准
你的 Git 版本会影响合并基础使用的文件或提交,据我所知。现代的 Git,如果你运行 git cherry-pick 命令,将使用被拾取的提交的 parent 作为 合并基础,但至少有一种形式(使用 git amgit apply 并带有 --3way 选项,这是 git rebase 仍然可以做到的),可以使用 git diff 输出中的 index 行来挑选出一个更早版本的文件。最终,这可能不太重要。
无论如何,合并将实际上从基础提交运行git diff到两个“提示”(挑选的提交和您试图应用挑选的提交的HEAD提交)。为了直观地看到正在发生的事情,您应该像往常一样开始绘制提交图。 (我没有您的存储库,因此我将绘制一个不同的图形,并希望它足够接近。但是您应该绘制自己的图表-或者让Git做到这一点,或使用gitk或类似工具。)
          o--@         <-- branch (HEAD)
         /
...--o--o
         \
          I--P--C--o   <-- otherbranch

我在这里为不同的提交分配了单个字母的名称:C是我们即将挑选的提交,P是其父提交。我将当前(HEAD)提交标记为@,尽管所有真正的工作都将在索引和工作树中进行。(幸运的是,git cherry-pick要求索引和工作树“干净”,除非您使用-n将多个挑选组合成一个大的挑选,因此索引和工作树将与提交@匹配。)我将提交I标记为“重要”。

现在,考虑如果我们以P为基础,以C作为其中一个提交,以@作为另一个提交进行三方合并会发生什么。当我们计算将P更改为C的指令时,我们得到了要应用的差异:这很简单直接。但是当我们计算将P更改为@的指令时,嗯,有点麻烦。

我们对 I 进行了一些重要的更改。这些更改是 P 的一部分,也就是说,它们在我们的合并基础中。但是它们不在 @ 中。对于合并,Git 将认为这些重要更改是我们试图撤消的事情。实际上,它们确实是!我们没有挑选 I 本身,因此我们必须撤消这些 I 更改,以应用来自 C 的更改。

无论那些我们正在“撤消”的I更改是否起始于@,并且不影响来自C的任何更改,我们都没问题:它们已经被撤消了。如果偶然或有意地,其中一个I更改位于@中(可能是通过@的父级),那么这些甚至不在我们首先尝试撤消的集合中,因此我们仍然没问题。当这些更改与PC的更改发生冲突,甚至仅仅是接触到(上下文)时,我们就会遇到问题。

在这种情况下,Git将会在两个合并冲突区域之一中显示一些更改。这些更改位于我们试图挑选的部分中,它们不一定被应用,只是我们必须解决的冲突的一部分。如果您将merge.conflictStyle设置为diff3(我通常建议这样做),那么更改将显示为合并基础的一部分,因为合并基础是提交P,而P本身就是基于I的(即P的快照包括来自I的代码,除非我们在制作P时对其进行了更改)。

所以,我不太清楚您所询问的内容,但在合并冲突区域中看到与您正在挑选的位无关的更改是正常的。


我看到了相同类型的冲突。你的解释非常有教育意义,谢谢。然而,它并没有提供解决方案。一个为 P-C 构建的 git 补丁应用到 @ 上失败了。因此,后续问题变成了:我们如何让 git 只应用 P-C 的更改而不是 o-I-P 的更改? - Suncat2000
@Suncat2000:没有任何保证这些都可以自动化。但是,如果你使用 git show 显示提交 C,让 Git 将 P-vs-C 显示为差异,并使用 git apply 将该差异应用于提交 @,那么在某些情况下,这可能比 git cherry-pick C 更好。如果补丁一开始无法干净地应用,则可以选择使用 --reject--3way 进行应用,尽管 --3way 使其更像樱桃拣选:您可能最终会遇到相同的冲突(减去对重命名文件的任何识别)。 - torek
如果您使用了这些方法之一,重要的是您仔细检查和/或测试结果。对于使用 { ... } 分组结构的语言,git diff ... | git apply 可能会盲目地将例如闭合括号放在错误的位置。 - torek

2

我看到文件中还引入了其他几行代码,这些代码是在主分支上不同的提交中添加的。

确保在该提交中没有不必要的更改。

$ git show <commit-sha>

挑选该提交。

$ git checkout <release-branch>
$ git cherry-pick <commit-sha>

最好手动解决冲突。

或者,如果你想保留提交的更改,则接受theirs,否则接受ours(保留发行分支的更改)。

$ git checkout --theirs -- .
Or,
$ git checkout --ours -- .

$ git status                     # see the changes
$ git add .
$ git commit -m 'Fix conflicts'
$ git push origin HEAD

替代方法: 如果你想从目标提交中选择少量文件更改并将它们应用到发布分支上,那么这个解决方案是有效的。

挑选的提交只对冲突文件引入了几行代码。

$ git checkout <release-branch>
$ git checkout <commit-sha> <file> .      # change the <file> to commit's version

$ git add .
$ git commit -m 'Fix the bug'
$ git push origin HEAD

2
我完全按照你所说的做了。我挑选的提交本身没有任何不良变化,但是该提交更改的文件确实有一些不良变化(由主分支上之前的其他提交引入)。因此,我不明白为什么这些更改会在发布分支上引入。另外,你提出的替代方案对我来说不合适,因为从主分支中获取整个文件对我来说行不通,因为存在上述不良变化。 - Lahiru Chandima
“(由主分支上之前的其他提交引入)”这不符合Git的本质!当您挑选某个提交时,只应该选择该提交的更改。---另一种解决方案不会考虑主分支上的更改。git checkout <commit-sha> <file> = 提交的文件更改!=主分支的HEAD文件更改。它应该与对“文件”的cherry-pick产生相同的结果。 - Sajib Khan
1
实际上,我对这种行为也感到很惊讶,因为我经常进行选择性提取操作,但从未见过这样的情况。我尝试了你提供的替代解决方案,它所做的是在给定的提交哈希值上检出文件。它不仅仅是检出给定提交引入的更改,而是检出给定提交哈希值上的完整文件。由于主分支上的其他先前提交对文件进行了不需要的更改,这并不是我想要的结果。 - Lahiru Chandima

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