我有时需要在我的分支中挑选一个带有特定修复的标签,并且过去常常这样做
git cherry-pick tags/myfix
这种方法很有效,但樱桃采摘需要花费越来越长的时间来进行“不精确的重命名检测”。
我的预感是,这可能会更快
git format-patch -k -1 --stdout tags/myfix | git am -3 -k
事实上,这最终立即应用了修复,使我的分支处于与挑选樱桃完全相同的状态。
现在我的问题是,樱桃采摘到底有什么不同?我认为樱桃采摘基本上就是这样实现的,但我一定是弄错了。
cherry-pick
实现为合并,合并基础是您引入的cmomit的父级。如果没有合并冲突,这应该与生成和应用补丁完全相同(但是请参阅torek's answer有点警告,其中am
理论上可以做错事)。
但是通过合并,cherry-pick
可以尝试更优雅地处理变更会发生冲突的情况。 (事实上,你给-3
的am
选项告诉它,如果需要的话,它应该做同样的事情,如果补丁中有足够的上下文就可以这样做。我会回到那一点。结束...)
应用修补程序时,默认情况下,如果它更改了应用它的提交中的不同代码块,就像在生成它的父提交中那样,则apply将失败。但是cherry-pick
/ merge方法将研究这些差异是什么,并从它们产生合并冲突 - 所以你有机会解决冲突并继续。
作为冲突检测的一部分,cherry-pick
确实重命名检测。例如,假设你有
o -- x -- x -- A <--(master)
\
B -- C -- D <--(feature)
而你cherry-pick
将C
交给master
。假设在o
你创建了file.txt
,而在A
你有file.txt
的修改。但是承诺B
将file.txt
移动到my-old-file.txt
,并且提交C
修改my-old-file.txt
。
在my-old-file.txt
中对C
的改变可能与file.txt
中A
的变化相冲突;但是为了看到这种可能性,git必须进行重命名检测,这样才能发现file.txt
和my-old-file.txt
是“同一件事”。
您可能知道您没有这种情况,但是在尝试检测重命名之前,git不知道。我不确定为什么在这种情况下这会很费时间;根据我的经验,它通常不是,但在一个回购中添加和删除了很多路径(在我们的例子中B
和C
或A
之间)可能是。
当您生成并应用补丁时,它会尝试在假设没有冲突的情况下应用补丁。只有当这遇到问题时(然后,只是因为你给了-3
选项)它才会回退到合并,并且有冲突检测。它可以跳过所有这些 - 以及任何可能的重命名检测 - 只要它的第一次尝试干净利落。
更新 - 正如对该问题的评论中所述,如果它没有帮助并且运行缓慢,您也可以关闭重命名检测。如果在实际上将“重要”重命名为合并时使用它,则可能会导致重命名检测解决它们的冲突。虽然我认为不应该这样做,但我不能排除它可能只是计算一个不正确的合并结果并悄悄地应用它 - 这就是为什么我很少使用这个选项。
对于默认合并策略,-X no-renames
选项将关闭重命名检测。您可以将此选项传递给cherry-pick
。
根据torek的评论,似乎重命名检测应该是am
的非问题。也就是说,我可以确认它能够正确处理合并仅适用于重命名检测的情况。当我不是星期五下午的某个时候,我会回去试图理解这个问题的来龙去脉。
Mark Adelsberger's answer是正确的(并且赞成,也许你应该接受它)。但这里有一个历史奇怪的地方。
事实上,cherry-pick曾经被实现为git format-patch | git am -3
,而git rebase
仍然使用这种特殊的方法来复制提交某些类型的rebase.1。这里的问题是,这无法检测到重命名的文件,有时 - 在(罕见)条件下很难描述,但我会尝试错误地应用更改,适当的三向合并将正确应用它们。例如考虑这种情况:
@@ -123,5 ... @@
}
}
- thing();
{
{
换句话说,删除线周围的周围环境只是大括号(或更糟糕的是,空白区域) - 无用的匹配。在您的同一文件的版本中,由于某些其他事件,123到127行的内容现在在同一文件中更早或更晚。比方说,现在它们现在是153-158行。同时,文件中的第123-127行为:
}
}
thing();
{
{
但是这些行是正确的:应该删除的thing()
调用(因为它是错误的)已经向下移动了,但是在同一个地方调用了不应删除的thing()
。
三向合并将比较合并基础与您的版本,也许 - 可能,取决于运气和差异上下文 - 发现您插入了各种行,以便应删除的错误调用现在在第155行,而不是然后它将执行正确的删除,因为它知道基线的第125行是你的第155行。
重命名检测是format-patch-then-apply与true三向合并之间的这两个差异中更重要的,但在某些情况下两者都很重要。运行git cherry-pick
可以做得更彻底,更慢,更经常是正确的事情。
1特别是,只有非交互式git rebase
才使用格式补丁,即使这样,只有当你不使用-m
选项时,使用-s
指定合并策略,或者使用-X
指定扩展选项。这三者中的任何一个强制非交互式rebase使用cherry-pick方法。
请注意,Git文档调用-X
参数“strategy-option options”或“strategy-option arguments”,这两种方式都是非常笨拙的短语。我喜欢这里的“扩展”这个词,因为它解释了为什么它是-X
,即扩展。扩展选项只是一个选项传递给你用-s
选择的策略:Git不知道每个-s
理解的附加选项,所以无论你给-X
,Git给出所选策略,然后策略本身接受-X
选择并做某事,或抱怨它是未知的。