git cherry-pick 详解
初识 git cherry-pick(拣选)
拣选会提取某次提交的补丁,之后尝试将其重新应用到当前分支上。 这种方式在你只想引入特性分支中的某个提交时很有用。
假设你的项目提交历史如下:
如果你希望将提交 e43a6 拉取到 master 分支,你可以运行:
# 当前处于 master 分支
$ git cherry-pick e43a6
Finished one cherry-pick.
[master]: created a0a41a9: "More friendly message when locking the index
fails."
3 files changed, 17 insertions(+), 3 deletions(-)
这样会拉取和 e43a6 相同的更改,但是因为应用的日期不同,你会得到一个新的提交 SHA-1 值。 现在你的历史会变成这样:
现在你可以删除这个特性分支(ruby_client),并丢弃不想拉入的提交(5ddae)。
需要说明的是,提取某次提交的“补丁”,这个补丁是基于其父提交的。
下图可以说明:
我们要拣选提交 C4 到 maint 分支(maint 指向 C7),Git 会生成一个补丁( Δ = C 4 − C 3 \Delta = C4-C3 Δ=C4−C3),然后把 Δ \Delta Δ应用到C7上,也就是说把 C4 对 C3 的变化在 C7 上重放一遍。
为何会产生冲突
同 merge 操作一样,拣选操作也可能产生冲突。有人会问:不会吧,打个补丁也能冲突?
当然能。
用 diff 工具生成 patch 时,我们所做的每一处修改都会连同它的“定位信息”(原始文件中的行号、修改处前三行和后三行的原始文本)一并保存到 patch 文件中。patch 被应用时,会在目标文件中寻找“定位信息”,找到后再实施修改。可是,当我们把补丁应用到 C7 上时,有可能找不到那些定位信息了:在master分支上,C2变成了C3,在maint分支上,C2变成了C6,又变成了C7,也许C3和C7相差越来越远,C3中的上下文在C7中早已面目全非,不见踪迹。于是应用patch失败,即发生冲突。
冲突了怎么办
当拣选发生冲突的时候,GIT 会采用三路合并算法。还是以上面的图为例子,
当你运行命令 git cherry-pick C4
的时候,Local是C7,Remote是C4,Base是C3(即C4的父提交)。总结:
当你运行命令
git cherry-pick <commit C>
如果冲突了,那么Git会尝试三方合并
- LOCAL: the commit you’re merging on top of (. the HEAD of your branch)
- REMOTE: the commit you’re cherry picking (. commit C)
- BASE: the parent of the commit you’re cherry-picking (. C^, ie the parent of C)
如果三方合并的时候又冲突了怎么办?那只能靠我们人工解决了。
参考资料
【0】《Pro Git》(Scott Chacon, Ben Straub Version 2.1.14, 2018-05-19)
【1】 /jiangyouxin/blog/108717
【2】《Git 高手之路》,人民邮电出版社
【3】 *, /questions/10058068/in-a-git-cherry-pick-or-rebase-merge-conflict-how-are-base-aka-the-ancestor