本节目标
这一节学习 Git 中非常实用的命令:cherry-pick。
你要理解这些问题:
什么是 cherry-pick?
什么时候需要 cherry-pick?
cherry-pick 和 merge 有什么区别?
如何把某个提交复制到另一个分支?
cherry-pick 冲突怎么解决?
Gerrit 中如何挑选修复提交到 release 分支?
本节重点命令:
git log --oneline
git switch
git cherry-pick
git cherry-pick --continue
git cherry-pick --abort
git cherry-pick --skip
git status
1. cherry-pick 是什么
cherry-pick 的意思是:
从一串提交中,挑选某一个提交,复制到当前分支。
假设有两个分支:
A --- B --- C --- D main
\
E --- F release
现在你发现 main 分支上的 D 是一个重要 bug 修复。
你不想把整个 main 都合并到 release,只想把 D 这个提交拿过来。
这时可以在 release 分支执行:
git cherry-pick D
结果:
A --- B --- C --- D main
\
E --- F --- D' release
注意:
D' 是 D 的复制版本。
它的代码改动和 D 类似,但 commit id 不一样。
2. cherry-pick 的直观理解
可以这样理解:
merge 是把一个分支整体合进来。
cherry-pick 是只挑某一个提交拿过来。
例如:
main 分支有 10 个新提交。
release 分支只需要其中 1 个 bug 修复提交。
这时不适合直接 merge main。
因为 merge 会带来很多不需要的修改。
更合适的是:
git cherry-pick 修复提交ID
3. cherry-pick 和 merge 的区别
| 对比项 | merge | cherry-pick |
|---|---|---|
| 操作对象 | 一个分支 | 一个或多个提交 |
| 结果 | 合并整个分支历史 | 复制指定提交 |
| 是否带入其他提交 | 通常会 | 不会,只拿指定提交 |
| 常见用途 | 整合功能分支 | 把修复挑到其他分支 |
| commit id | 保留原历史关系 | 生成新的 commit id |
一句话:
merge 是合并分支,cherry-pick 是复制提交。
4. 什么时候使用 cherry-pick
常见场景:
-
把
master/main上的 bug 修复复制到release分支 -
把
release上的紧急修复复制回develop -
从别人分支挑一个有用提交到自己分支
-
只想要某个提交,不想合并整个分支
-
多版本维护时,把同一个修复同步到多个版本分支
典型公司场景:
产品已经发布 v1.0,对应 release/v1.0 分支。
开发主线 main 已经继续开发 v2.0。
现在 main 上修了一个严重 bug。
这个 bug 也影响 v1.0。
你需要把这个 bug 修复提交 cherry-pick 到 release/v1.0。
5. 什么时候不适合 cherry-pick
不适合场景:
-
你其实需要整个功能分支
-
提交之间依赖很强,只挑一个会编译失败
-
想保持完整分支历史
-
不确定这个提交是否依赖前面的提交
-
大量提交都要同步,这时 merge 或 rebase 可能更合适
使用前要思考:
这个提交能不能单独工作?
它有没有依赖其他提交?
挑过来会不会缺文件、缺接口、缺配置?
6. cherry-pick 基本流程
假设你要把 main 上的某个提交挑到 release/v1.0。
第一步,找到提交 ID:
git log --oneline
示例:
def5678 修复登录空指针问题
abc1234 实现用户登录功能
第二步,切到目标分支:
git switch release/v1.0
第三步,执行 cherry-pick:
git cherry-pick def5678
第四步,检查历史:
git log --oneline
你会看到当前分支多了一个新提交。
7. cherry-pick 会生成新的 commit id
假设原提交是:
def5678 修复登录空指针问题
你在 release/v1.0 上执行:
git cherry-pick def5678
新提交可能变成:
9999999 修复登录空指针问题
标题一样,代码改动类似,但 commit id 不一样。
原因是:
新提交的父提交不同,所以 commit id 一定不同。
这很正常。
8. cherry-pick 多个提交
你可以一次挑多个提交:
git cherry-pick abc1234 def5678
Git 会按顺序应用这些提交。
也可以挑一个连续范围。
例如:
git cherry-pick A^..C
意思是:
从 A 到 C,包括 A 和 C。
新手阶段建议先一个一个挑:
git cherry-pick 提交ID
这样更容易定位问题。
9. cherry-pick 不自动提交
默认情况下:
git cherry-pick 提交ID
会应用修改并自动创建提交。
如果你想只应用修改,不自动提交,可以使用:
git cherry-pick -n 提交ID
或者:
git cherry-pick --no-commit 提交ID
作用是:
把指定提交的修改放到当前工作区和暂存区,但不自动 commit。
适合场景:
你想挑多个提交后合成一个提交。
你想先检查或调整代码再提交。
10. cherry-pick 可能冲突
如果目标分支和原提交改到了同一个位置,就可能冲突。
例如:
git cherry-pick def5678
可能看到:
CONFLICT (content): Merge conflict in config.txt
error: could not apply def5678... 修复登录空指针问题
意思是:
Git 在复制这个提交时遇到了冲突。
你需要手动解决。
11. cherry-pick 冲突解决流程
冲突后先看状态:
git status
打开冲突文件,处理冲突标记:
<<<<<<< HEAD
当前分支内容
=======
被 cherry-pick 的提交内容
>>>>>>> def5678
整理成最终正确内容后:
git add 冲突文件
git cherry-pick --continue
注意:
cherry-pick 冲突解决后,用 git cherry-pick --continue。
不是直接 git commit。
12. 放弃 cherry-pick
如果你发现挑错了提交,或者冲突太复杂,可以放弃:
git cherry-pick --abort
作用是:
取消当前 cherry-pick,回到 cherry-pick 前的状态。
这和前面学过的:
git merge --abort
git rebase --abort
git revert --abort
思想类似。
13. 跳过当前提交
如果一次 cherry-pick 多个提交,中途某个提交不想要了,可以:
git cherry-pick --skip
作用是:
跳过当前这个提交,继续后面的提交。
新手不要随便使用 --skip。
因为它可能导致:
后面的提交缺少依赖。
最终代码不完整。
14. Gerrit 中的 cherry-pick 场景
Gerrit 中常见场景:
一个 bug 修复已经合入 master。
现在 release 分支也需要这个修复。
流程通常是:
git fetch origin
git switch release/v1.0
git cherry-pick 修复提交ID
git push origin HEAD:refs/for/release/v1.0
这样 Gerrit 会在 release/v1.0 目标分支上创建一个新的 Change。
注意:
这通常是一个新的 Change。
因为目标分支不同,commit id 也不同。
15. Gerrit 中 Change-Id 的注意点
如果 cherry-pick 的提交来自 Gerrit,它的 commit message 里可能已经有:
Change-Id: Iabc1234567890abcdef1234567890abcdef1234
当你 cherry-pick 到另一个目标分支时,很多 Gerrit 项目允许同一个 Change-Id 出现在不同目标分支上。
但不同公司规范可能不同。
有些团队希望:
同一个修复在不同分支上保留同一个 Change-Id,方便追踪。
也有团队希望:
不同目标分支生成不同 Change。
实际工作中要遵守项目规范。
新手先记住:
不要随便删除或修改 Change-Id。
如果 Gerrit 提示 Change-Id 问题,再按团队规范处理。
16. cherry-pick 后如何检查
执行 cherry-pick 后,不要马上推送。
建议检查:
git status
git log --oneline -5
git show --stat HEAD
如果想看最新提交具体内容:
git show HEAD
重点确认:
是不是挑了正确的提交?
有没有引入不该有的文件?
代码是否能在当前分支独立工作?
提交信息是否符合规范?
Change-Id 是否符合 Gerrit 要求?
17. cherry-pick 和 amend 的配合
有时 cherry-pick 后,你需要稍微调整代码才能适配目标分支。
例如:
master 上方法叫 ValidateUser
release 分支旧代码里方法叫 CheckUser
你 cherry-pick 后修改适配代码。
然后可以:
git add .
git commit --amend --no-edit
这样最终还是一个清晰的提交。
再推送到 Gerrit:
git push origin HEAD:refs/for/release/v1.0
18. 实战练习一:基础 cherry-pick
创建练习仓库:
mkdir git-cherry-pick-demo
cd git-cherry-pick-demo
git init
创建基础提交:
echo "base" > readme.txt
git add readme.txt
git commit -m "添加基础文件"
创建 release 分支:
git switch -c release/v1.0
echo "release version" >> readme.txt
git add readme.txt
git commit -m "添加 release 版本说明"
切回主分支:
git switch main
如果主分支叫 master:
git switch master
在主分支创建 bug 修复提交:
echo "bug fix" > fix.txt
git add fix.txt
git commit -m "修复示例 bug"
查看提交 ID:
git log --oneline
假设最新提交是:
def5678 修复示例 bug
切到 release 分支:
git switch release/v1.0
挑选这个提交:
git cherry-pick def5678
查看历史:
git log --oneline --graph --all
19. 实战练习二:cherry-pick 冲突
创建练习仓库:
mkdir git-cherry-pick-conflict-demo
cd git-cherry-pick-conflict-demo
git init
创建基础文件:
echo "version: base" > config.txt
git add config.txt
git commit -m "添加基础配置"
创建 release 分支:
git switch -c release/v1.0
echo "version: release" > config.txt
git add config.txt
git commit -m "修改 release 配置"
切回主分支:
git switch main
如果主分支叫 master:
git switch master
主分支也修改同一行:
echo "version: main fix" > config.txt
git add config.txt
git commit -m "修复主分支配置"
记录最新提交 ID:
git log --oneline
切回 release:
git switch release/v1.0
执行 cherry-pick:
git cherry-pick 提交ID
这时应该会出现冲突。
20. 实战练习三:解决 cherry-pick 冲突
查看状态:
git status
打开 config.txt,你可能看到:
<<<<<<< HEAD
version: release
=======
version: main fix
>>>>>>> 修复主分支配置
改成最终内容:
version: release with main fix
然后:
git add config.txt
git cherry-pick --continue
查看历史:
git log --oneline --graph --all
21. 实战练习四:不自动提交
假设你只想应用某个提交的修改,但暂时不提交:
git cherry-pick -n 提交ID
查看状态:
git status
git diff --cached
你可以继续调整代码。
调整完成后再提交:
git add .
git commit -m "挑选并适配某个修复"
22. 常见错误
错误一:挑错分支
执行 cherry-pick 前先确认当前分支:
git branch
git status
你必须在目标分支上执行 cherry-pick。
错误二:不检查提交依赖
有些提交依赖前面的提交。
只挑一个可能导致:
编译失败
方法不存在
配置缺失
测试失败
挑选前要看:
git show 提交ID
错误三:冲突后直接 git commit
cherry-pick 冲突解决后应该:
git cherry-pick --continue
不是直接:
git commit
错误四:以为 commit id 会一样
cherry-pick 后 commit id 通常会变化。
这是正常的。
错误五:随便修改 Change-Id
Gerrit 项目中不要随便删改 Change-Id。
如果推送失败或生成了不符合预期的 Change,先确认团队规范。
23. 本节必须记住的命令
git cherry-pick 提交ID
git cherry-pick -n 提交ID
git cherry-pick --continue
git cherry-pick --abort
git cherry-pick --skip
git show 提交ID
git log --oneline
对应含义:
git cherry-pick 提交ID 把指定提交复制到当前分支
git cherry-pick -n 提交ID 只应用修改,不自动提交
git cherry-pick --continue 解决冲突后继续
git cherry-pick --abort 放弃当前 cherry-pick
git cherry-pick --skip 跳过当前提交
git show 提交ID 查看某个提交内容
git log --oneline 查看提交历史
24. 本节总结
这一节你学习了 cherry-pick:
-
cherry-pick用来把某个提交复制到当前分支 -
它适合把 bug 修复同步到 release 分支
-
它和
merge不同,merge是合并分支,cherry-pick是挑选提交 -
cherry-pick 后通常会生成新的 commit id
-
cherry-pick 可能产生冲突
-
冲突解决后要用
git cherry-pick --continue -
Gerrit 中常用 cherry-pick 把修复提交推到不同目标分支评审
最重要的一句话:
cherry-pick 是复制一个提交,不是合并整个分支。
下一节建议学习:
Git tag 与 release:如何给版本打标签,以及发布版本在 Git/Gerrit 中如何管理。