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 扫一眼文件列表,心里有个底。
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 会撤销所有已经应用的提交。
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-picked from <原始哈希>。
11.5 知识体系总览
下面这张图帮你理清 cherry-pick 的核心逻辑:
从图上可以看得很清楚:cherry-pick 不是把整个分支拉过来,而是像「复制粘贴」一样,只把那个提交的改动拿过来。源分支的历史不受影响,目标分支多了一个新提交。
11.6 总结一下
cherry-pick 是个好工具,但别滥用。我的原则是:能用合并解决的问题,就别用 cherry-pick。只有在确实只需要个别提交、或者分支已经无法合并的时候,才亮出这把手术刀。
记住三个关键点:
- 基本用法:
git cherry-pick <hash> - 冲突处理:
--continue/--abort - 批量操作:按顺序列出哈希,或用范围语法
嗯,下次遇到「这个提交那个分支也需要」的情况,你应该知道怎么做了。