11、Git cherry-pick:挑选提交、解决冲突、批量cherry-pick、应用场景

说实话,刚用 Git 那会儿,我对 cherry-pick 是有点抵触的。总觉得「挑提交」这事儿听起来就不太正经——好好的分支历史,干嘛要东拼西凑?直到有一次线上出了个紧急 Bug,修复代码在开发分支上,但发布分支已经切走了……嗯,那次 cherry-pick 救了我一命。

说白了,cherry-pick 就是让你从任意分支里,挑一个或几个提交,复制到当前分支来。它不关心你俩是不是同一个祖先,也不管历史干不干净。它只做一件事:把那个提交的改动,拿过来。

核心命令:
git cherry-pick <commit-hash>

11.1 基本用法:挑一个提交

假设你在 feature/login 分支上修复了一个登录 Bug,提交哈希是 a1b2c3d。现在 main 分支也需要这个修复,但你不想把整个 feature 分支合进来。

# 切换到 main 分支
git checkout main

# 挑那个修复提交
git cherry-pick a1b2c3d

Git 会把这个提交的改动应用到当前分支,并生成一个新的提交。新提交的哈希会变,但改动内容是一样的。我在项目中遇到过这种情况:同事在开发分支上修了一个性能问题,但发布分支已经冻结了,只允许 cherry-pick 特定提交。那次我连续 cherry-pick 了三个提交,才把问题彻底解决。

小技巧: 如果你只想看改动内容,不想自动提交,可以加 -n 参数(--no-commit)。改动会暂存在工作区,你可以自己调整后再提交。

11.2 解决冲突:cherry-pick 的必修课

cherry-pick 不是总能一次成功的。如果挑过来的提交改动了某个文件,而当前分支的这个文件也被改过,Git 就会停下来,让你解决冲突。

为什么会这样?你想想看,cherry-pick 本质上是在「重放」一个提交的 diff。如果目标分支的上下文变了,diff 就可能打不上。

# 冲突发生后,Git 会提示:
# error: could not apply a1b2c3d... fix login bug
# hint: after resolving the conflicts, mark them with
# hint: "git add/rm <path>", then run "git cherry-pick --continue"

# 手动解决冲突后:
git add .
git cherry-pick --continue

如果你觉得这个提交不该挑,或者冲突太麻烦,可以随时放弃:

git cherry-pick --abort

我曾经在一个大项目里 cherry-pick 一个跨了 20 多个文件的提交,结果冲突了七八个文件。那次我硬着头皮一个一个手动合并,花了快一个小时。后来我学乖了——挑提交之前,先看看改动范围,用 git show <hash> --stat 扫一眼文件列表,心里有个底。

注意: cherry-pick 解决冲突时,不要用 git merge --continue。那是合并用的。cherry-pick 有自己的 --continue--skip--abort 三件套。

11.3 批量 cherry-pick:一次挑多个提交

有时候你需要的不只是一个提交,而是一连串提交。比如从 feature/payment 分支里挑出最近三个修复提交到 hotfix 分支。

批量 cherry-pick 有两种常见写法:

# 写法一:按顺序列出哈希(从左到右依次应用)
git cherry-pick a1b2c3d e4f5g6h i7j8k9l

# 写法二:使用范围(注意:左开右闭)
git cherry-pick a1b2c3d..i7j8k9l

这里有个坑,我刚开始用的时候踩过:范围写法是左开右闭的。也就是说 a1b2c3d..i7j8k9l 会挑从 a1b2c3d 之后到 i7j8k9l 之间的所有提交,但不包括 a1b2c3d 本身。如果你想把 a1b2c3d 也包含进来,得用三个点:

# 包含 a1b2c3d 本身
git cherry-pick a1b2c3d^..i7j8k9l

批量 cherry-pick 时,如果中间某个提交冲突了,Git 会停下来。解决完冲突后,用 git cherry-pick --continue 继续。如果你不想继续了,--abort 会撤销所有已经应用的提交。

我的习惯: 批量 cherry-pick 之前,先用 git log --oneline 确认一下提交顺序。Git 是按你给的顺序依次应用的,顺序错了可能引发不必要的冲突。

11.4 应用场景:什么时候该用 cherry-pick?

cherry-pick 不是银弹,但它有几个非常合适的场景:

场景 说明 示例
紧急修复移植 修复提交在开发分支,需要移植到发布分支 hotfix 分支 cherry-pick 开发分支的 Bug 修复
选择性合并 不想合并整个分支,只想要其中几个提交 从实验分支挑出有用的功能提交到主分支
撤销误操作 不小心删了某个提交,从 reflog 里找回 git cherry-pick <reflog中的哈希>
跨仓库移植 两个仓库共享部分代码,但不想建立远程关联 将仓库 A 的某个提交 cherry-pick 到仓库 B

我个人用得最多的是第一种场景。项目上线后,测试环境发现一个 Bug,开发同学在 dev 分支修好了,但 release 分支已经切出去了。这时候 cherry-pick 就是最优雅的解法——不用合并整个 dev 分支,也不用重新切分支,一个命令搞定。

避坑指南: 我曾经在 cherry-pick 之后,发现两个分支上有「双胞胎提交」——内容一样但哈希不同。后来合并时 Git 傻眼了,冲突一堆。所以如果你打算以后还要合并这两个分支,最好在 cherry-pick 的提交消息里注明来源,比如加上 cherry-picked from <原始哈希>

11.5 知识体系总览

下面这张图帮你理清 cherry-pick 的核心逻辑:

Git cherry-pick 核心逻辑 源分支(feature) C1 C2 C3 目标分支(main) M1 M2 cherry-pick C3 → M2 之后 C3' 新提交(哈希不同,内容相同) 只挑需要的提交,不合并整个分支

从图上可以看得很清楚:cherry-pick 不是把整个分支拉过来,而是像「复制粘贴」一样,只把那个提交的改动拿过来。源分支的历史不受影响,目标分支多了一个新提交。

11.6 总结一下

cherry-pick 是个好工具,但别滥用。我的原则是:能用合并解决的问题,就别用 cherry-pick。只有在确实只需要个别提交、或者分支已经无法合并的时候,才亮出这把手术刀。

记住三个关键点:

  • 基本用法:git cherry-pick <hash>
  • 冲突处理:--continue / --abort
  • 批量操作:按顺序列出哈希,或用范围语法

嗯,下次遇到「这个提交那个分支也需要」的情况,你应该知道怎么做了。


公众号:蓝海资料掘金营,微信deep3321