Git Rebase 使用指南:把提交历史"拉直"的魔法,附一次真实变基实录
前言:同样是合并分支,
merge是"原地打结",rebase是"拆了重排"。本文先用一张图、几句话讲清 rebase 的本质,然后用我最近一次真实的生产实操(把功能分支变基到最新的develop/machine_S115)带你走一遍完整流程——包括为什么最后要用--force-with-lease强推。
目录
- 一、一句话理解 Rebase(变基)
- 二、一张图看懂:merge vs rebase
- 三、实战案例:一次完整的变基实录
- 四、终端输出逐行解读
- 五、进阶:交互式 rebase 整理提交
- 六、黄金法则:别动公共历史
- 七、总结
一、一句话理解 Rebase(变基)
你的功能分支是从main某个旧点上"长"出来的。rebase 就是把你的提交一个个摘下来,接到目标分支的最新顶端重新提交一遍——基底变了,所以叫"变基"。
它和 merge 的本质区别:
| merge | rebase | |
|---|---|---|
| 做法 | 两个分支原地汇合,产生一个"合并提交" | 摘下你的提交,重放到目标分支最新处 |
| 历史 | 保留分叉的真实形状 | 重写成一条直线 |
| 提交 hash | 不变 | 全部改变(重放生成的都是新提交) |
| 适用 | 公共分支合并 | 私有分支同步主干 |
一句话:rebase 后你的分支,看起来就像你从最新代码上一次开发出来的。
二、一张图看懂:merge vs rebase
【merge:原地汇合】 main C1 ── C2 ────────── C3 ──── M \ ╱ feature F1 ── F2 ──────── M = 合并提交(有两个父节点) 历史保留分叉的真实形状 【rebase:摘下重放】 main C1 ── C2 ── C3 ── F1' ── F2' ╲ F1 ── F2 (虚线:旧提交被重写丢弃) 你的提交被搬到 main 顶端,历史变成一条直线merge 像把两条绳子打了个结;rebase 像把你的绳子拆下来,接在主绳末端——事后看全程只有一条绳。
三、实战案例:一次完整的变基实录
3.1 想要干嘛?(真实场景)
我手上两个分支:
develop/machine_S115:同事维护的主开发分支,一直有新更新feature/emerge_stop_secon_recover:我的功能分支,但基于旧代码
需求:继续在 feature 分支上开发,但又不想丢掉 S115 的新更新。与其等最后合并时面对一堆冲突,不如现在就把 feature 分支变基到云端最新的 S115 上。
3.2 动手前的自查
- 基准选云端的
origin/develop/machine_S115——不用先切到它,fetch 之后远程镜像就是最新,少两次分支切换 git status确认工作区干净——有未提交改动先 commit 或 stash- 心里有数:完成后推送要
--force-with-lease——因为 rebase 会重写历史
3.3 完整操作(终端实录)
# 0) 自查:工作区必须干净$gitstatus On branch feature/emerge_stop_secon_recover Your branch is up todatewith'origin/feature/emerge_stop_secon_recover'.nothing to commit, working tree clean# 1) 一步同步云端所有分支(不用切分支、不动本地代码)$gitfetch origin# 2) 变基:把我的提交重放到云端 S115 最新处$gitrebase origin/develop/machine_S115 Successfully rebased and updated refs/heads/feature/emerge_stop_secon_recover.# 3) 历史被重写,普通 push 会被拒——用安全强推$gitpush --force-with-lease origin feature/emerge_stop_secon_recover + 2f36c33...adcf74c feature/emerge_stop_secon_recover ->feature/emerge_stop_secon_recover(forced update)就这三条命令,一次成功、零冲突,收工。
四、终端输出逐行解读
| 终端输出 | 怎么读它 |
|---|---|
nothing to commit, working tree clean | 没有未提交改动,变基前提满足,可以动手 |
git fetch origin无任何输出 | 正常!fetch 静默完成,所有origin/xxx镜像已更新到云端最新 |
Successfully rebased and updated ... | 一次成功、零冲突——历史已重写成"一条直线" |
+ 2f36c33...adcf74c ... (forced update) | 强推成功,+和forced update表明云端指针被移到新历史上 |
remote: Create pull request ... | Bitbucket 主动给出 PR 链接——合并回 S115 时点它走正常审查 |
4.1 为什么用 --force-with-lease 而不是 --force?
--force是无脑覆盖:云端有别人的新提交也会被抹掉,同事的成果直接消失;--force-with-lease会先检查"云端还是我上次看到的那个版本吗",不是就拒绝推送。
本次变基后云端 feature 分支只有我自己在用,所以它能安全通过。养成习惯:能用--force-with-lease就永远别用--force。
4.2 收尾必做:编译验证
变基后的代码是"最新基底 + 我的改动"的混合体,接口变更、宏被删这类问题只有编译能暴露。所以我最后一步是立刻编译一遍固件确认通过,再点 remote 给出的 PR 链接发起合并请求。
这一步经常被忽略:rebase "成功"只代表 git 层面无冲突,不代表代码逻辑层面兼容。尤其是嵌入式/MCU 项目,基底更新后外设驱动接口变了是常事。
五、黄金法则:别动公共历史
rebase 会重写提交历史。如果对已经 push 到公共分支(如main、共享分支)的提交做 rebase,别人 pull 下来会撞出大量重复冲突,团队历史直接乱套。
历史一旦公开,就是团队契约——公共分支用 merge,私有分支随便 rebase。
- ✅ 自己的 feature 分支:变基同步主干、整理提交,随意
- ✅ 提 PR 前把自己的分支 rebase 到最新:审阅者看到的是干净的 diff
- ❌ 别人的分支、已发布的 release 分支:碰都不要碰
六、总结
回到开头的案例,整个流程浓缩成三句话:
- 同步:
git fetch origin一步拿到云端所有分支最新状态,不用切分支 - 变基:
git rebase origin/develop/machine_S115把自己的提交重放到最新主干上 - 强推:历史重写后用
git push --force-with-lease安全更新云端,最后编译验证
选型口诀:“公共分支 merge,私有分支 rebase;强推只认 --force-with-lease;rebase 完必须编译。”
本文案例来自笔者在 MCU 固件项目中的真实操作记录,终端输出均为原文。如果对你有帮助,欢迎点赞收藏~