Skip to content
Charles
Go back

Git 一次 Cherry-pick 一段连续提交

Edit page

需要把一个分支上的几次提交移植到当前分支时,最直接的做法是逐个执行:

git cherry-pick abc123
git cherry-pick b81e4d6
git cherry-pick d4e8b73

如果这些提交在历史上是连续的,可以使用一个范围一次完成:

git cherry-pick abc123^..d4e8b73

这会按提交顺序应用从 abc123d4e8b73 的所有提交,并在当前分支上为每个提交创建一个新的提交。

为什么是 abc123^..d4e8b73

Git 的双点范围是“左边不包含,右边包含”:

abc123..d4e8b73

表示包含 d4e8b73 及其历史,但排除 abc123 及其祖先。因此它不会包含起点 abc123

abc123^ 表示 abc123 的父提交。把它放在左边以后:

abc123^..d4e8b73

就会排除 abc123 的父提交及更早历史,同时包含 abc123 本身,最终得到一个两端都包含的范围:

abc123, 之后的提交, ..., d4e8b73

这里的 ^ 不是 Cherry-pick 专用语法,而是 Git revision 中用于表示父提交的写法。

执行前先确认范围

范围写错时,Cherry-pick 可能会应用多余的提交。执行前可以先查看 Git 将要处理的提交:

git log --oneline --reverse abc123^..d4e8b73

--reverse 会让提交按照从旧到新的顺序显示,便于检查起点和终点是否正确。

如果想同时查看分支关系,可以使用:

git log --graph --oneline abc123^..d4e8b73

确认无误后,切换到接收提交的目标分支:

git switch release
git status
git cherry-pick abc123^..d4e8b73

执行前最好保证工作区干净。Cherry-pick 会把每个提交的改动重新应用到当前 HEAD,所以它不是把原提交直接搬过来,而是在目标分支上生成新的提交。

不包含起点提交

如果只想应用 abc123 之后的提交,不包含 abc123,直接使用普通的双点范围:

git cherry-pick abc123..d4e8b73

可以简单记成:

A..B      不包含 A,包含 B
A^..B     包含 A,也包含 B

只挑选不连续的提交

提交不连续时,可以直接在同一条命令中列出多个提交:

git cherry-pick 7f3a9c2 b81e4d6 d4e8b73

Git 会按照命令中从左到右的顺序应用它们。这适合只移植几个独立修复,也可以用来明确调整应用顺序。不过,如果后一个提交依赖前一个提交,最好保持原本的依赖顺序,否则更容易产生冲突。

想合并成一个提交

默认情况下,范围中的每个提交都会在目标分支上生成一个新提交。如果只想把整段改动合并成一个提交,可以加上 --no-commit

git cherry-pick --no-commit abc123^..d4e8b73
git commit -m "Backport selected changes"

--no-commit 会应用改动,但不会自动提交。确认工作区内容后,再手动创建一个提交即可。

发生冲突时怎么办?

范围 Cherry-pick 本质上是按顺序处理多个提交,其中某一个提交发生冲突时,Git 会暂停在这个提交上。解决冲突并加入暂存区后,继续执行:

git add <冲突文>
git cherry-pick --continue

如果当前提交不需要,可以跳过它:

git cherry-pick --skip

如果想取消整个这次范围操作,回到开始前的状态:

git cherry-pick --abort

一个容易忽略的边界

A^..B 表示的是 Git 提交图上的可达范围,不一定等于界面上看到的“从 A 到 B 的一条直线”。如果历史中有分支合并,范围可能包含额外的提交。执行前用 git log --graph 检查,通常比凭记忆输入 SHA 更可靠。

如果范围中包含合并提交,Git 通常需要知道要以哪个父提交为主线:

git cherry-pick -m 1 <merge-commit>

普通的线性提交不需要 -m。合并提交的选择取决于具体历史,不应该随意套用同一个父编号。

总结

移植一段连续提交时,最常用的写法是:

git cherry-pick A^..B

记住三件事就够了:

  1. A..B 不包含 A
  2. A^..BA 也包含进来。
  3. 执行前先用 git log --oneline --reverse A^..B 确认范围。

参考:Cherry-picking a range of commits in gitgit-cherry-pick 官方文档Git revisions 官方文档


Edit page
Share this post:

Previous Post
用 jq 格式化剪贴板中的 JSON