git cherry-pick:复制一份,绝不搬家
一个提交、把起点含进去的范围、序列中途的冲突——以及终结争论的 pick、merge、rebase 分界线。
■ 空间 · 散文道
git cherry-pick <sha> 复制一个提交——同一个补丁,新 SHA,什么都不动.
范围会排除起点: A^..B 才把 A 含进去, A..B 会悄悄把它跳过。
卡在冲突上?解完之后 git add, git cherry-pick --continue ——在那之前你就停在序列半路。
复制这件事,画出来
一个修复住在 feature 分支上;main 现在就要它。pick 重放补丁、铸一枚新 SHA,原件分毫不动:
图——复制而非搬家:feature 分支想删就删;pick 出来的副本有自己的 SHA,不依赖旧的那颗
三个步骤
找到 SHA,切到目标分支,pick——落地处就是 HEAD 指着的地方:
# 1. locate the commit on the source branch
git log feature/payment-fix --oneline -5
# 2. switch to the branch that should receive it
git switch main
# 3. copy it over
git cherry-pick 1a2b3c4
verify: git log -1 --stat → pick 进来的改动、新 SHA、在目标分支上
两个值得一知的便利: git cherry-pick <branch> 会 pick 那个分支的 tip 提交——好用,但如果对「tip 是什么」的认知过期了,很容易误触。而每一次 pick 之后, git log -1 --stat 会确认落了什么:读一秒钟,省一次 revert。
多个提交
三种形态,按你想要的频次排序——都能跨分支用,因为 SHA 是对着整个仓库解析的:
# 1. explicit list — picked in the order you list them
git cherry-pick 1a2b3c4 5d6e7f8
# 2. range — everything after A up to and including B
git cherry-pick A..B
# 3. range including A
git cherry-pick A^..B
verify: git log --oneline -4 pick 之后 → 两个提交按顺序重放,最老的在前
「A..B」与「A^..B」之别是经典的意外: A..B 排除 A。把范围从 git log 出发,pick oldest..newest,就这样无声地跳过了最老的提交—— oldest^..newest 让差一错误消失。想多次 pick 折叠成一个提交而不是三个,用 -n / --no-commit 打包不提交,然后一次提交:
git cherry-pick -n 1a2b3c4 5d6e7f8
git commit -m "Backport: payment retry fixes"
「怎么不生效」——四种原因
- 冲突停住了 pick. git 打补丁,撞上一条两边都改过的行,在序列半路暂停。把文件解开,然后
git add <resolved-files>和git cherry-pick --continue——或者--abort退回 pick 之前的状态。--continue不是可选的,也不会被暗示:没跑它之前,git status会一直念叨 You are currently cherry-picking. - pick 是空的(「The previous cherry-pick is now empty」)——改动已经在这条分支上,多半来自更早的一次 pick 或压扁的 merge。用
git cherry-pick --skip跳过,或用--allow-empty. - 这个提交是个 merge 提交。 merge 有两个父提交,「套用这个 diff」因此有歧义——git 宁可拒绝也不猜。说出你 diff 的基准是哪个父提交:
git cherry-pick -m 1 <merge-sha>。父提交按 git 记录顺序编号:parent 1 是你做 merge 时所在的分支,parent 2 是进来的那一支——-m 1几乎总是 backport 想要的那个。 - 工作树不对,或 HEAD 分离。 pick 的落点就是 HEAD 指的位置。
git branch --show-current先看一眼;如果什么都不打印,你在 detached HEAD 里,一换分支提交就成了孤儿。
PICK、MERGE、REBASE
| 命令 | 落在目标上的是什么 | 历史 | 适用场景 |
|---|---|---|---|
git cherry-pick <sha> | 仅指名提交 | 复制,新 SHA | 某一处修复现在就得搬 |
git merge <branch> | 整条分支 | merge 提交或 fast-forward | 要整条分支,分歧可见 |
git rebase <base> | 分支上的全部提交,重放 | 线性,SHA 重写 | 要分支变成一条干净线性道 |
git revert <sha> | 提交的反演 | 加一个 undo 提交 | 已落地的提交要在共享历史上撤销 |
一句话规则: cherry-pick 搬的是一份选择;merge 和 rebase 搬的是全部。 想跟一条分支「同步」却伸手去 cherry-pick,其实说明你要的是 merge。一个要戒掉的习惯:长期把同一个提交 pick 进好几条分支——源分支以后每个修复都得多 pick 一次,最后各分支越走越散。往 release 分支做 backport 是正常操作;但永久的平行宇宙不是。
流程压缩版
git log <source-branch> --oneline -5 # find the SHA(s)
git switch <target-branch> # land in the right place
git cherry-pick A^..B # range, list, or single SHA
# on conflict: resolve → git add → git cherry-pick --continue
git log -1 --stat # confirm what landed
找到、切换、pick、验证。这条命令担着它配不上的危险名声——diff 要么套上,要么停下告诉你原因。唯一真正破坏性的错误是 pick 错了分支,而 git log -1 在 push 之前跑一遍,这个错误就很难漏过去。
A..B 排除 A)、空 pick 行为与 -m 父提交编号,都是 git 的文档化行为,出自真正落地过的 pick 亲敲。pick 页看完 · 未拉取任何东西 · 一切本地