准备执行 rebase 之前,最值得先输入的往往不是 rebase,而是下面四条命令:
bash
git status --short
git fetch origin
git branch rescue/before-history-edit
git tag history-edit-start
它们分别确认工作区状态、更新远端引用,并给当前提交建立两个容易核对的恢复点。分支和标签仍然只是本地引用,不等于远端备份;重要仓库还要确认关键对象已经推送到受控远端,或者另有 bundle、镜像仓库等备份。
rebase、cherry-pick 和 bisect 经常被放在一起讲,但它们不是三种写法相似的"高级 Git 命令"。它们解决的是三类不同问题:重放一组提交、搬运一个指定提交、二分定位引入缺陷的提交。共同点是都会移动 HEAD、改写提交或切换工作区,所以安全边界比参数记忆更重要。
一、需要把一组私有提交换到新基底:rebase
假设功能分支从较早的 main 分出,现在需要把自己的提交重放到最新的远端主分支之后:
bash
git switch feature/reporting
git rebase origin/main
重放后,即便文件内容没有变化,提交的父节点和哈希也可能改变。rebase 并不天然优于 merge。提交仍由当前作者独占、团队约定保持线性历史时,它比较容易控制;如果多人已经基于原历史开发,或者真实的分支拓扑本身就有保留价值,merge 通常更合适。
遇到冲突后,不要只把冲突标记删掉就继续。至少检查工作区差异和暂存区内容:
bash
git status
git diff
git add resolved-file
git diff --cached
git rebase --continue
发现方向不对,可以使用 git rebase --abort 回到本次 rebase 之前。交互式 rebase 适合整理尚未共享的提交:
bash
git rebase -i origin/main
其中 reword、edit、squash、fixup 和 drop 都会改变历史。特别是 drop,执行前必须确认对应内容已经被其他提交覆盖,或者确实应该删除。
原分支含有需要保留的 merge 结构时,可以评估:
bash
git rebase --rebase-merges origin/main
这里的边界很重要:Git 的行为是尝试保留分支结构并重新创建 merge commit,不是保证得到与原历史完全相同的 merge 结果。操作完成后仍要跑测试,并用 git range-diff 或等价方式审阅改写前后的提交差异。
如果团队允许改写已经推送的私有功能分支,通常会使用:
bash
git push --force-with-lease origin feature/reporting
--force-with-lease 会依据预期的远端引用值拒绝一部分意外覆盖,但它不是万能的并发锁。后台 fetch、跟踪引用是否新鲜,以及是否显式给出预期值,都会影响保护边界。共享分支没有协调就不要改写。
二、需要把一个修复带到另一分支:cherry-pick
先切换到接收变化的分支,再选择要搬运的提交:
bash
git switch release/1.x
git cherry-pick fix-commit
cherry-pick 应用的是指定提交带来的变化,并通常生成一个新提交。跨公开维护分支回补修复时,可以在无冲突操作中用 -x 记录来源哈希:
bash
git cherry-pick -x fix-commit
Git 官方文档限定了这个细节:自动附加来源说明只适用于没有冲突的 cherry-pick。如果中途发生冲突,继续前要人工确认提交信息是否仍保留所需的来源记录。
想先看变化而不立即提交,可以执行:
bash
git cherry-pick --no-commit fix-commit
git diff --cached
解决冲突后使用 git cherry-pick --continue,放弃整个序列则使用 git cherry-pick --abort。不要把 cherry-pick 当作长期同步分支的办法:相同逻辑以不同哈希进入多个分支后,后续合并仍可能产生语义冲突。
三、不知道哪个提交引入了问题:bisect
bisect 需要一个已知坏的提交和一个可达的已知好提交:
bash
git bisect start
git bisect bad HEAD
git bisect good known-good-ref
Git 会切换到区间中的候选提交。每轮运行同一种验证,有问题标 bad,没有问题标 good。定位完成后务必退出二分状态:
bash
git bisect reset
能稳定脚本化的回归可以交给 git bisect run:
bash
git bisect start HEAD known-good-ref
git bisect run ./check-regression.sh
git bisect reset
判定脚本中,退出码 0 表示 good;1 到 127(125 除外)表示 bad;125 表示当前提交无法测试,应跳过。编译失败不一定就是目标缺陷,因此不能不加区分地把所有失败都返回 1。脚本还应自包含依赖安装、构建、服务启动和状态清理;无法保证可重复时,手动判断反而更可靠。
四、操作错了:先用 reflog 找位置,再建立新引用
reflog 可以帮助查看本地引用之前指向过的位置。先从 git reflog 的实际输出中选择目标条目;下面的 {5} 只是展示语法,不是固定位置:
bash
git reflog
git show HEAD@{5}
git branch rescue/recovered HEAD@{5}
先创建恢复分支并检查内容,比直接执行 reset --hard 更稳。确认恢复点正确后,再决定是否需要移动当前分支。
不过 reflog 不是永久备份。它只存在于本地,会按配置过期,不可达对象之后还可能被垃圾回收;换一台机器也未必拥有相同记录。需要长期恢复能力,仍应保存受控远端引用或独立备份。
最后可以把决策压缩成四句话:一组私有提交换基底,用 rebase;独立修复跨分支回补,用 cherry-pick;未知回归来源,用 bisect;历史操作失误,先从 reflog 建恢复分支。每次结束都要审阅差异、运行测试、核对远端,再推送。
真正降低风险的不是记住更多参数,而是让每次历史变更都有恢复引用、明确的共享边界和可重复的验证。
官方参考
作者:Julian Cooper
创作说明:本文由 Julian Cooper 基于历史技术底稿、Git 官方文档及隔离临时仓库验证结果修订,使用 AI 辅助整理;作者对最终内容负责。命令中的分支名与引用名均为教学示例,不对应真实公司、客户或用户项目。