9

我有时需要在我的分支中挑选一个带有特定修复的标签,并且过去常常通过

git cherry-pick tags/myfix

这行得通,但是挑选樱桃需要越来越长的时间来进行“不精确的重命名检测”。

我的预感是,这可能会更快

git format-patch -k -1 --stdout tags/myfix | git am -3 -k

事实上,事实证明,这立即应用了修复,使我的分支处于与樱桃采摘完全相同的状态。

现在我的问题是,樱桃采摘到底有什么不同?我以为樱桃采摘基本上就是这样实现的,但我一定是弄错了。

4

2 回答 2

11

cherry-pick被实现为合并,合并基础是您引入的提交的父级。在没有合并冲突的情况下,这应该与生成和应用补丁具有完全相同的效果(但请参阅torek 的回答有点警告,am理论上,哪里可能做错事)。

但是通过合并,cherry-pick可以尝试更优雅地处理更改发生冲突的情况。(事实上​​,-3你给它的选项am告诉它,如果需要的话,如果补丁中有足够的上下文可以这样做,它应该做同样的事情。我会在最后回到这一点。 ..)

当您应用补丁时,默认情况下,如果它更改了应用它的提交中与生成它的父提交中不同的代码块,则应用将失败。但是cherry-pick/merge 方法将查看这些差异是什么,并从中产生合并冲突 - 因此您有机会解决冲突并继续。

作为冲突检测的一部分,cherry-pick会进行重命名检测。例如,假设你有

o -- x -- x -- A <--(master)
      \
       B -- C -- D <--(feature)

然后你cherry-pick承诺. 假设您已创建,并且您已对. 但是 commit移动到,并且 commit修改了。Cmasterofile.txtAfile.txtBfile.txtmy-old-file.txtCmy-old-file.txt

更改为my-old-file.txtinC可能与更改为file.txtin冲突A;但要看到这种可能性,git 必须进行重命名检测,以便它可以找出它file.txt并且my-old-file.txt是“同一件事”。

您可能知道您没有这种情况,但 git 直到它尝试检测重命名时才知道。我不确定为什么在这种情况下会很耗时。以我的经验,它通常不是,但在一个添加和删除了很多路径的仓库中(在我们的示例之间 或B在我们的示例中)它可能是。CA

当您生成并应用补丁时,它会在假设没有冲突的情况下尝试应用补丁。只有当这遇到问题时(然后,只是因为您提供了-3选项),它才会回退到进行合并,并进行冲突检测。只要它的第一次尝试干净地应用,它就可以跳过所有这些 - 以及任何潜在的重命名检测。


更新- 如对问题的评论中所述,如果重命名检测没有帮助并且运行缓慢,您也可以关闭它。如果您在实际上存在对合并“重要”的重命名时使用它,则可能会导致重命名检测可以解决它们的冲突。尽管我认为不应该,但我不能排除它也可能只是计算出不正确的合并结果并悄悄地应用它——这就是我很少使用此选项的原因。

对于默认的合并策略,该-X no-renames选项将关闭重命名检测。您可以将此选项传递给cherry-pick.

根据 torek 的评论,似乎重命名检测应该不是am. 也就是说,我可以确认它能够正确处理合并仅适用于重命名检测的情况。在不是周五下午的时候,我会回到试图了解这件事的来龙​​去脉。

于 2018-08-31T17:40:16.157 回答
10

Mark Adelsberger 的回答是正确的(并且赞成,也许你应该接受它)。但这里有一个历史上的奇怪之处。

事实上,cherry-pick 曾经被实现 git format-patch | git am -3,并且git rebase仍然使用这种复制提交的特殊方法来进行某些类型的变基。1 这里的问题是它无法检测重命名的文件,并且有时——在难以描述但我会尝试的(罕见)条件下——错误应用更改,而适当的三向合并将正确应用它们。例如考虑这种情况:

@@ -123,5 ... @@
     }
   }
-  thing();
   {
     {

删除行周围的上下文只是大括号(或更糟糕的是,空格)——换句话说,这是无用的匹配。在同一个文件版本中,由于某些其他事件,第 123 行到第 127 行现在在同一个文件中更早或更晚例如,假设它们现在是第 153-158 行。同时,文件中的第 123-127 行内容如下:

    }
  }
  thing();
  {
    {

这些行是正确的:thing()应该删除的调用(因为它是错误的)已经下移,但是在同一个地方有一个调用thing()应该被删除。

三路合并会将合并基础与您的版本进行比较,并且可能 - <em>可能,取决于运气和可区分的上下文 - 发现您插入了不同的行,因此应该删除的错误调用现在在线155,而不是第 125 行。然后它将执行正确的删除,因为它知道基础的第 125 行是您的第 155 行。

重命名检测是 format-patch-then-apply 与真正的三向合并之间的这两个差异中更重要的一个,但在某些情况下,两者都很重要。跑步git cherry-pick做更彻底、更慢、更经常正确的事情。


1特别是,只有非交互式git rebase使用格式补丁,即使那样,只有当您使用该-m选项时,才使用 指定合并策略-s,或使用 指定扩展选项-X。这三个中的任何一个都会强制非交互式变基使用cherry-pick方法。

请注意,Git 文档将-X参数称为“策略选项选项”或“策略选项参数”,无论哪种方式,这都是一个非常笨拙的短语。我喜欢这里的“扩展”一词,因为它解释了为什么它是-X,即扩展。扩展选项只是传递您选择的策略的选项-s:Git 不知道每个人都-s理解哪些附加选项,因此无论您给予什么-X,Git 都会给予所选策略,然后策略本身要么接受该-X选项并执行某些操作,或抱怨它是未知的。

于 2018-08-31T18:36:21.607 回答