Git 高级工作流:rebase、cherry-pick、reflog
会 add/commit/push 不算会用 Git。本文讲清进阶工作流:rebase(变基,让历史线性)vs merge、interactive rebase(改写历史)、cherry-pick(挑提交)、reflog(救命稻草)、stash、以及怎么从"搞砸了"里救回来,让你不再怕 Git。
本文要点
- 主题聚焦:Vibe Coding、DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
Git 高级工作流:rebase、cherry-pick、reflog
如果你对 Git 的认知停在 add / commit / push / pull,那你对 Git 的恐惧大概率来自两次体验:一是历史乱成一团、合并冲突看不懂;二是误操作之后以为代码"没了"连夜重写。
会基础命令 ≠ 会用 Git。真正区分新手和老手的,是 rebase、cherry-pick、reflog 这套进阶工作流。它们做两件事:让协作历史更顺,以及在你搞砸时把你捞回来。这篇文章按"先讲清楚是什么、再讲什么时候用、最后讲怎么救"的顺序展开,读完你应该能在生产仓库上放心地用它们。
一、merge vs rebase:两条哲学
把分支合并回主干,有两条路。
merge:保留双方原始历史,生成一个新的"合并提交"(merge commit)。
git switch main
git merge feature/login
历史长这样:
* Merge branch 'feature/login' (merge commit)
|\
| * add login API (feature)
| * add login UI
* | fix typo in readme (main)
|/
* initial commit
优点是真实——你能看出哪条分支做了什么、何时合的。缺点是历史是一张网,人多了会变脏。
rebase:把 feature 分支的提交"摘下来",逐个重新放到 main 最新提交之上,历史被线性化。
git switch feature/login
git rebase main
结果:
* add login API (重放后)
* add login UI
* fix typo in readme
* initial commit
干净、线性、像一条时间线。代价是:这些提交被"重写"了,它们的 commit hash 已经不是原来的。
什么时候用哪个?
- 团队公共分支(main / release):用 merge 或 squash merge,保留"何时合入"的可追溯性。
- 个人 feature 分支同步主干:
git rebase main拉平最新代码,PR 里历史清爽。 - 发版前的整理:interactive rebase 把十几个 WIP 提交压成两三个有意义的。
rebase 的黄金规则:永远不要 rebase 已经推送到公共分支的提交。因为 rebase 重写了 hash,别人 pull 下来会出现重复提交和地狱级冲突。一旦共享,就当作"已固化"的历史。
二、interactive rebase:改写历史的瑞士军刀
git rebase -i 是 Git 里最被低估的命令。它打开一个编辑器,列出指定范围内的每个提交,让你逐个决定命运:
git rebase -i HEAD~5 # 整理最近 5 个提交
git rebase -i main # 整理当前分支相对 main 的所有提交
你会看到:
pick a1b2c3 wip
pick d4e5f6 add login form
pick g7h8i9 fix typo
pick j0k1l2 wip
pick m3n4o5 finalize login
把 pick 改成动作词就行:
- squash / s:把这个提交合并进上一个,提交信息也合在一起。WIP 提交通常这么处理。
- fixup / f:同 squash,但丢弃这个提交的信息(纯垃圾提交用)。
- reword / r:保留提交,但改提交信息。把 "fix bug" 改成有意义的描述。
- reorder:直接调整行顺序,提交会被重排。
- drop / d:删除这个提交。
- edit / e:到这个提交时暂停,允许你当场修改文件内容(用来拆分提交、补改动)。
整理完一个 feature 分支,把 12 个杂乱提交压成 3 个清晰提交,PR review 体验直接拉满。记住:interactive rebase 也是重写历史,只在自己的分支上做。
三、cherry-pick:精确投递一个提交
有时候你不想合并整条分支,只想把某个提交"搬"过来。典型场景:
- 线上出 bug,你在 main 上修了,但要把这个修复单独同步到 release 分支。
- 同事在一个分支里顺手修了一个跟你相关的问题,你只要那一行。
git switch release-1.2
git cherry-pick a1b2c3d # 把这个提交应用到当前分支
git cherry-pick a1b2c3d d4e5f6g # 挑多个
git cherry-pick a1b2c3d..e5f6g7 # 挑一个范围(左开右闭)
cherry-pick 会生成一个新的提交(因为 hash 变了,内容相同)。如果冲突,Git 会停下让你解决,然后 git cherry-pick --continue(或 --abort 放弃)。
注意:cherry-pick 后如果将来再 merge 那条源分支,Git 通常能识别重复提交不会重复应用,但跨上下文的 cherry-pick 有时还是会制造冲突,别滥用。
四、reflog:Git 里的"后悔药"
这是本文最值钱的一节。
很多人以为 git reset --hard 或 git branch -D 之后,东西就"没了"。几乎从来不是。 Git 在背后维护一份 reflog——记录 HEAD 和所有分支指针的每一次移动,默认保留 90 天。
git reflog
输出大概这样:
a1b2c3d HEAD@{0}: reset: moving to HEAD~3
e5f6g7h HEAD@{1}: commit: finalize login
d4e5f6g HEAD@{2}: commit: add tests
c3d4e5f HEAD@{3}: commit: wip
...
HEAD@{1} 就是"上一次 HEAD 在的地方"。这意味着——
误 reset --hard 了? 找到 reset 之前那个 hash(或 HEAD@{1}):
git reset --hard HEAD@{1}
误删分支了?
git switch -c recovered-branch HEAD@{2}
# 或直接用 hash
git switch -c recovered-branch a1b2c3d
误 amend 覆盖了原提交? amend 也是一次 HEAD 移动,原提交还在 reflog 里,直接捡回来。
rebase 把提交弄丢了? reflog 里 rebase 开始前的位置还在,git reset --hard <那个点> 一键穿越回去。
记住一句话:只要你 commit 过(哪怕没 push),reflog 几乎一定能帮你找回来。 真正丢的只有两种:从未提交过的改动(stash 和工作区被 git clean -fd 干掉),以及超过 reflog 保留期的对象。
五、stash:临时存档,切场救急
写到一半被叫去修线上紧急 bug,代码还没法提交,怎么办?git stash 把工作区和暂存区的改动临时存起来,工作区还原成干净状态,你就能切分支了。
git stash push -m "wip: 登录表单校验" # 存档并备注
git switch main
# ...修 hotfix、提 PR、合并...
git switch feature/login
git stash pop # 取回最近的存档并删除
进阶用法:
git stash list # 看所有存档
git stash show -p stash@{0} # 看某个存档的 diff
git stash apply stash@{1} # 取回但不删除(可反复 apply)
git stash drop stash@{0} # 删掉某个存档
git stash clear # 清空所有(谨慎)
pop 冲突时会保留存档,解决完冲突手动 drop。实战建议:每次 stash 都加 -m "备注",不然 stash list 里几个无名条目会让你完全想不起哪个是哪个,三天后只能 clear。
六、救援手册:从"搞砸了"里自救
把前面零散的救援场景集中成一份速查表。
场景 1:刚 commit 完发现漏改了一个文件 / 提交信息写错了
只要还没 push:
# 改文件后
git add .
git commit --amend # 修改提交,会打开编辑器改信息
git commit --amend --no-edit # 保持原信息,只把新改动并进来
amend 本质是"用新提交替换旧提交",所以旧的那个在 reflog 里依然可找回。
场景 2:已经 push 了,但想改提交信息/合并提交
如果只有你一个人用这个分支,可以 amend 或 rebase 后 git push --force-with-lease。--force-with-lease 而不是 --force——前者会在远程有别人新提交时拒绝推送,避免覆盖同事的工作。
场景 3:误删了本地分支
git reflog # 找到分支最后一次指向的 hash
git switch -c feature/login <hash>
场景 4:git reset --hard 把工作干没了
git reflog
git reset --hard HEAD@{1} # 回到 reset 之前
场景 5:rebase 中途改乱了,想全部放弃
git rebase --abort
任何 rebase 进行中状态都能用 --abort 干净退回起点。
场景 6:merge 冲突搞不定了
git merge --abort # 放弃这次合并,回到合并前
场景 7:想撤销某个已经 push 的提交(不删历史,而是反向提交)
git revert a1b2c3d # 生成一个"反向"提交,抵消改动
git push
公共分支上永远用 revert 而不是 reset——revert 不改写历史,安全可推送。
把这套救援动作内化之后,你对 Git 的恐惧会大幅下降,因为你心里清楚:只要提交过,几乎都能救。
小结
- merge 保留历史,rebase 线性化,公共分支用 merge / squash merge,个人分支用 rebase 同步主干。
- rebase 的黄金规则:不 rebase 已推送的公共提交;私人分支随便改。
- interactive rebase(
rebase -i) 是整理历史的瑞士军刀:squash 合并、reword 改信息、reorder 调序、drop 删除。 - cherry-pick 用来精确搬运单个提交,做 hotfix 同步、跨分支移植改动。
- reflog 是 Git 的后悔药:
reset --hard、删分支、误 amend 都能找回——只要你 commit 过。 - stash 用来临时存档切场救急,养成
stash push -m备注的习惯。 - 公共分支用
revert,私有分支用amend/rebase+push --force-with-lease。
金句一句:Git 之所以可怕,是因为你以为它"不可逆";学会 reflog 和 rebase 之后你会发现——Git 几乎没有真正的不可逆,只有你还不知道怎么逆转。
Related