git cherry-pick 和 git format-patch 有什么区别? git 上午?

What is the difference between git cherry-pick and git format-patch | git am?

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

这样做
git cherry-pick tags/myfix

这行得通,但是挑选樱桃需要越来越长的时间"inexact rename detection"。

我的直觉是

这可能会更快
git format-patch -k -1 --stdout tags/myfix | git am -3 -k

事实上,结果是立即应用修复,使我的分支处于与 cherry-picking 完全相同的状态。

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

cherry-pick 作为合并实现,合并基础是您要引入的提交的父级。在没有合并冲突的情况下,这应该与生成完全相同的效果并按照您的方式应用补丁(但请参阅 以获得一些警告,其中 am 理论上可能会做错事)。

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

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

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

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

cherry-pickC 提交到 master。假设您在 o 中创建了 file.txt,并且在 A 中您对 file.txt 进行了修改。但是提交 Bfile.txt 移动到 my-old-file.txt,并且提交 C 修改了 my-old-file.txt.

C 中对 my-old-file.txt 的更改可能会与 A 中对 file.txt 的更改发生冲突;但是要看到这种可能性,git 必须进行重命名检测,以便它可以确定 file.txtmy-old-file.txt 是“同一件事”。

您可能知道自己没有这种情况,但 git 在尝试检测重命名之前不知道。我不确定为什么在这种情况下会很耗时;根据我的经验,它通常不是,但是在添加和删除了很多路径的回购中(在我们的示例中 BCA 之间)它可能是。

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


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

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

根据 torek 的评论,am 似乎重命名检测应该不是问题。也就是说,我可以确认它能够正确处理合并仅适用于重命名检测的情况。我打算 return 尝试在不是星期五下午的某个时候了解这件事的来龙​​去脉。

是正确的(并且已投票,也许您应该接受)。但这里有一个历史上的怪事需要注意。

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

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

删除行周围的上下文只是大括号(或者更糟,白色-space)——换句话说,一些无用的匹配。在同一文件的你的 版本中,由于某些其他事件, 行 123-through-127 现在在同一文件中更早或更晚文件。例如,假设它们现在是第 153-158 行。同时,您的 文件中的第 123-127 行读取:

    }
  }
  thing();
  {
    {

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

三向合并会将合并基础与 您的 版本进行比较,并且可能-可能,取决于运气和差异- able context——发现你插入了不同的行,所以应该删除的错误调用现在在第 155 行,而不是第 125 行。然后它将执行正确的删除,因为它知道 base 的第 125 行是 你的第155行

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


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

请注意,Git 文档将 -X 参数称为 "strategy-option options" 或 "strategy-option arguments",无论哪种方式,这都是一个非常笨拙的短语。我喜欢这里的 "extended" 这个词,因为它解释了为什么它是 -X,即扩展。扩展选项只是一个选项传递 你选择的策略 -s: Git 不知道每个 -s 理解什么附加选项,所以无论你给 -X 什么,Git 都会给所选策略,然后策略本身要么接受 -X 选项并做某事,要么抱怨它未知。