Command Reference

git rebase 教程

解释 git rebase 的核心模型、推荐流程、风险边界和恢复办法。

适合谁看
  • 已经会 merge / pull 的开发者
  • 想整理本地提交历史的人
前置知识
  • 知道共享历史和本地历史的区别
  • 愿意先看 reflog 再做改写
常见风险
  • 对已共享提交做 rebase
  • 冲突时不先保住旧位置就继续重试

一句话理解

git rebase 会把一组提交重新应用到新的基底之上,所以你通常会得到“内容相近但提交 ID 变化过”的历史。

先建立一个脑内模型

把 rebase 想成“把你这条分支上的提交重新排队,再接到新的基底后面”。它不是简单合并,而是重写提交之间的祖先关系。

rebase 的历史变化feature 上原来的 E、F 会以新的提交身份重新接到最新 main 后面,所以常见现象是内容还在,但提交 ID 已经变了。
rebase 前
main
ABCD
feature
BEF
rebase 后
main
ABCD
feature
DE'F'

把它想成“把自己的提交重新排队”。图里最重要的不是字母本身,而是 E/F 变成了 E'/F' 这件事,这正是为什么 rebase 会被视为改写历史。

什么时候应该用

  • 让特性分支追上最新的 main
  • 合并前整理提交历史
  • 使用交互式 rebase 调整提交顺序、压缩提交、修改提交信息

什么时候不要轻易用

  • 公共分支已经被其他人基于其继续开发时
  • 你不清楚团队对共享历史是否允许改写时
  • 你还没有准备好恢复方案时

实践上,rebase 最适合“自己的特性分支”。

高风险边界

只要这段历史已经进入 review、CI 或同事的工作流,rebase 的问题就不再只是“会不会冲突”,而是“会不会让别人依赖的提交位置整体变化”。

基本流程

场景:让特性分支同步最新主分支

git checkout feature/login
git fetch origin
git rebase origin/main

建议按这个顺序做的原因:

  1. 先切到目标分支
  2. 先 fetch,确保你看到的是远端最新状态
  3. 再明确指定新的基底

冲突时怎么处理

当 rebase 过程中发生冲突时,一般按下面的节奏处理:

git status
# 手动解决冲突
git add <resolved-files>
git rebase --continue

如果你决定放弃这次 rebase:

git rebase --abort

一个更稳的实际工作流

如果你准备在本地整理自己的特性分支,比较稳的顺序通常是:

  1. git status,确认工作区干净
  2. git fetch origin
  3. 如担心风险,先建一个备份分支
  4. 再执行 git rebase origin/main
  5. 最后用 git log --oneline --graph 快速复核历史

备份分支很简单:

git branch backup/feature-login-before-rebase

这一步在高风险场景下非常值,因为它把“能回去”先做实了。

一个很值的小习惯

在高风险 rebase 前先建 backup/... 分支,成本很低,但会显著降低你后面做判断时的心理压力。

交互式 rebase 最常见的用途

git rebase -i HEAD~4

它常用于:

  • 压缩零碎提交
  • 让提交顺序更符合阅读逻辑
  • 修改提交信息
  • 删除不该保留的实验性提交

交互式 rebase 里最常见的动作

虽然 rebase -i 可以做很多事,但最常见的其实就这几类:

  • pick:保留提交
  • reword:修改提交信息
  • squash:把小提交压进前一个提交
  • fixup:类似 squash,但丢弃当前提交说明
  • drop:删除不想保留的提交

一个安全的团队建议

如果你的分支已经推送到远端,但仍想整理历史,优先遵守这条规则:

  • 只 rebase 自己负责的分支
  • 推送时使用 git push --force-with-lease

--force-with-lease 比裸 --force 更安全,因为它会先检查远端引用是否已经被别人更新。

pull --rebase 和手动 rebase 的区别

很多人会问:既然可以 git pull --rebase,为什么还推荐先 fetch 再手动 rebase?

原因是手动流程更清楚:

  1. 你先看到远端状态
  2. 再自己决定要不要改写本地历史
  3. 出问题时也更容易判断是哪一步出了问题

看图判断什么时候适合 rebase

如果你看到的关系更像“我自己的分支落后了主分支,但这些提交还没共享出去”,那就很适合用上面的 rebase 模式。

如果你脑中更接近“这条分支已经被 review、CI 或同事一起使用”,那就要先把注意力从“历史能不能变漂亮”切回“历史能不能安全改写”。

常见误区

误区 1:rebase 和 merge 只是长得不一样

不完全对。它们都会整合代码,但对历史的表达方式不同。rebase 会改写提交基底,因此历史更线性,但也更容易在共享分支上制造混乱。

误区 2:rebase 出错就完了

通常没有。只要对象还在,reflog 往往可以帮你回到 rebase 前的状态。

误区 3:pull 默认就是我想要的 rebase

不是。git pull 是否使用 rebase,取决于配置或命令行参数。

误区 4:只要是自己的分支就一定可以放心 rebase

也不绝对。如果这个分支已经被 CI、review 流程或同事依赖,改写历史仍然会带来协调成本。

推荐的恢复思路

如果你在 rebase 后发现历史不对,第一反应不是慌,而是:

git reflog

找到 rebase 前的 HEAD,再决定是否:

git reset --hard <safe-commit>

如果你不确定该不该直接 reset,更保守的做法是:

git checkout -b rescue/rebase HEAD@{1}

先把 rebase 前位置接住,再决定后续怎么处理。

哪些场景最适合 rebase

  • 本地特性分支落后于主分支
  • 发起 PR 前整理提交
  • 你希望把“修修补补的小提交”压成更清晰的逻辑块

哪些场景更适合 merge

  • 这段历史已经共享给多人
  • 团队更重视保留真实分叉过程
  • 你不想承担 force push 带来的沟通成本

教学补充建议

后续可以继续补充以下分支专题:

  1. --onto 的可视化解释
  2. 交互式 rebase 的指令清单
  3. rebase 与 merge 的对照案例
  4. 共享分支历史改写的团队规范

这条命令在流程里解决什么问题

git rebase 直接影响历史表达、引用位置或提交之间的关系。读这类命令时,要先判断这次操作是在整理本地未共享历史,还是在处理已经公开给团队的历史。

典型用例

  • 在合并、回滚、挑提交或整理提交序列时,用 git rebase 重塑历史表达。
  • git rebase 放进“先备份、再变更、最后复核”的高风险操作流程,降低误操作损失。
  • 在复盘冲突、回滚事故或判断分支关系时,用 git rebase 理清提交之间的因果链。

图例理解

历史命令的作用面历史类命令通常围绕提交、当前分支和祖先关系展开,差别在于它是新增历史、移动引用,还是重写已有序列。
输入
提交序列当前分支祖先关系
结果
新的提交关系移动后的引用可恢复路径
这类命令执行前最值得确认的一件事,是“这段历史是不是已经共享给别人”。

特殊情况与边界

  • 历史类命令最常见的风险,是对已经共享出去的提交继续做重写或移动。
  • 如果这次操作可能影响团队协作,先跑 git statusgit log --oneline --graphgit reflog,再决定是否继续。
  • 一旦不确定,就先建一个备份分支,让自己保留“能回去”的选项。
  • 如果分支已经进入评审、CI 或被他人基于其继续开发,就要先判断这次改写历史是否还安全。

跟着做一遍

练习:在实验分支上安全演练一次 rebase

先在临时仓库或实验分支里做,目标不是记命令,而是观察“提交 ID 变化了,但改动内容还在”这件事。

准备环境
git switch -c lab/rebase-demo
# 做两次提交
# 然后切回 main 再做一次提交
跟着做
  1. 切回 feature 分支并执行 `git fetch`。
  2. 运行 `git rebase origin/main` 或对本地 main 做 rebase。
  3. 再执行 `git log --oneline --graph --decorate -6` 对照图形。
结果会怎样
  • 你的 feature 提交会重新接到新的 main 后面。
  • 旧提交 ID 会变成新的提交 ID。
  • 如果没有冲突,工作内容通常还在,但历史表达变了。
常见错误判断
  • 如果这条分支已经被同事基于其继续开发,就不该直接 rebase 后强推。
  • 如果 rebase 一半发现不对,不要继续乱试,先用 `git rebase --abort` 或 `git reflog` 找回原位置。
  • 如果你只是想把别人改动合进来而不改写自己的共享历史,merge 往往更合适。