写在前面:这一章要解决什么
如果你已经会了分支和合并,心里可能还有一个更具体的困惑:每次 merge 都会多出一个合并提交,分支线缠成一团。 有没有办法把代码改动「理成一条直线」?
学完后,你应该能:
读者设定: 大一同学,已经学过分支和合并,知道 commit、branch、merge 是什么,但看到分叉的历史线会头疼。
1. 定位:为什么要讲 rebase
1.1 一句话先记住
rebase = 把你的改动搬家到别人最新代码的上面,让历史变成一条直线。
不是把两份代码「和稀泥」混在一起(那是 merge),而是把你的提交一个一个「搬到」新基线的楼上。
1.2 没有 rebase 会怎样(痛点场景表)
| 功能分支开发三天,主分支已更新 | merge 产生合并提交,历史线分叉又汇合 | rebase 让你的提交变成一条直线 |
| 提交了 5 次小修改,全是「修 typo」 | 历史全是「修 typo」「又修 typo」,丢人 | 交互式 rebase 把 5 个 squash 成 1 个 |
| 想调整提交顺序 | 只能删了重来 | git rebase -i 拖一拖顺序就行 |
| 写错提交信息 | 改不了 | git rebase -i 选 reword,只改文字 |
| 代码审查时历史太乱 | 审查的人看得眼花 | rebase 之后一条线,审查更轻松 |
1.3 和你已经会的事对比
你已经会了 git merge:把两个分支的代码合到一起,会产生一个合并提交,历史会分叉再汇合。
| 历史线形状 | 分叉 + 汇合,有合并提交 | 一条直线,没有合并提交 |
| 本质 | 两个分支「和稀泥」 | 你的提交「搬家」到新基线上面 |
| 结果代码 | 一样 | 一样 |
| 安全性 | 对已推送分支安全 | 对已推送分支危险 |
| 适用场景 | 合并公共分支 | 整理自己的功能分支 |
小提示:最终代码内容是一样的,只是历史的书写方式不同。merge 保留了「分叉过」这个事实,rebase 改写了历史让你假装「一切都是在最新代码上顺滑开发的」。
2. 本质:rebase 到底是什么(白话 + 比喻 + 图解)
2.1 搬家到新楼层
- 主分支 main 是一楼,最近有人翻修了,地面换成了新地板。
- 你的功能分支 是二楼,你在旧地板上装修了自己的房间。
现在你想让你的房间建在新地板上面。怎么办?
- merge:在一楼和二楼之间搭一个空中走廊,两个楼层都保留,走廊就是合并提交。
- rebase:把你的整个房间搬到新地板上面。你的房间不变,但下面的地基变成了最新的。
用 Git 的话说:
2.2 看图说话

图:merge 保留分叉,rebase 变成直线。
merge 之后的历史:
* 5a3b1c2 Merge branch 'feature'
|\\
| * 45b9122 feat B
| * f3b398d feat A
* | 15c7f3e main fix
|/
* 812a70d base
rebase 之后的历史:
* 214be71 feat B(新哈希!)
* a937ce2 feat A(新哈希!)
* 15c7f3e main fix
* 812a70d base
白话翻译:
- merge 后有个菱形分叉,5a3b1c2 是合并提交
- rebase 后一条直线,但 45b9122 变成了 214be71,f3b398d 变成了 a937ce2——同样的代码改动,但哈希值不同了
2.3 关键认知:rebase 会产生新提交
rebase 不是「移动」提交,而是「拆掉重建」。
每个新提交的哈希值都和原来不同,因为:
- 父提交变了(从旧基线变成新基线)→ 整个提交的哈希就变了
- 哈希变了 → 这就是一个全新的提交
这也是为什么已推送的提交不能 rebase 的根本原因:别人已经基于旧哈希在干活了,你偷偷换成新哈希,别人的世界就崩了。
2.4 新手最常踩的坑
| 对已推送的分支执行 rebase | 别人拉代码时冲突地狱 | 铁律:只对自己的本地分支 rebase |
| rebase 过程中慌了不知道怎么退 | 卡在中间状态 | git rebase –abort 一键回到 rebase 之前 |
| rebase 冲突时没搞清要保留什么 | 改错了更麻烦 | 冲突标记和 merge 一样,先看懂再改 |
| 以为 rebase 能代替一切 | 不该 rebase 的地方乱用 | 公共分支用 merge,私人的功能分支才用 rebase |
3. 建议学习顺序
4. 动手准备(建可丢弃目录)
请找一个可以随便删的练习目录,不要用正在交的作业仓库练手。
mkdir lab-rebase
cd lab-rebase
git init -b main
git config user.name "Ada Example"
git config user.email "ada@example.com"
验证版本:
git –version
git version 2.43.0
5. 跟着做:一次完整的 rebase 实验
5.1 先准备两条分叉的分支
# 在 main 上建基线
echo "base content" > file.txt
git add file.txt && git commit -m "base: 初始文件"
# main 继续前进:修一个 bug
echo "bug fix from main" >> file.txt
git add file.txt && git commit -m "main: 修复一个 bug"
# 回到分叉点,创建功能分支
git checkout -b feature 812a70d
白话翻译: -b feature 创建并切换到 feature 分支,812a70d 是分叉点。
# 在 feature 分支上做两个功能提交
echo "feature A" > feature-a.txt
git add feature-a.txt && git commit -m "feature: 功能 A"
echo "feature B" > feature-b.txt
git add feature-b.txt && git commit -m "feature: 功能 B"
现在看看历史线:
git log –graph –all –oneline
* 45b9122 feature: 功能 B
* f3b398d feature: 功能 A
| * 15c7f3e main: 修复一个 bug
|/
* 812a70d base: 初始文件
白话翻译: |/ 就是分叉点,两条线从 812a70d 分开。
5.2 用 merge 合并(先看看老办法)
git checkout main && git merge feature
git log –graph –all –oneline
* 5a3b1c2 Merge branch 'feature'
|\\
| * 45b9122 feature: 功能 B
| * f3b398d feature: 功能 A
* | 15c7f3e main: 修复一个 bug
|/
* 812a70d base: 初始文件
白话翻译: merge 产生了一个合并提交,历史线变成了菱形。
5.3 撤销 merge,改用 rebase 试试
# 退回去
git reset –hard 15c7f3e
# 切到功能分支并 rebase 到 main
git checkout feature && git rebase main
Successfully rebased and updated refs/heads/feature.
看看现在的历史线:
git log –graph –all –oneline
* 7d4e8a3 feature: 功能 B
* a937ce2 feature: 功能 A
* 15c7f3e main: 修复一个 bug
* 812a70d base: 初始文件
白话翻译: 一条直线!功能 A 和 B 的哈希都变了,是「新楼层上重建的新提交」。
5.4 最后把 feature 合并进 main(快进合并)
git checkout main && git merge feature
Updating 15c7f3e..7d4e8a3
Fast-forward
白话翻译: Fast-forward 表示快进合并,没有合并提交,历史线完全是一条直线。
6. 命令分组(按场景分组)
6.1 基本 rebase(日常最常用)
| git rebase main | 把当前分支的提交搬到 main 的最新代码上面 |
| git rebase main feature | 不管当前在哪个分支,把 feature 搬到 main 上面 |
| git rebase –abort | 放弃整个 rebase,回到 rebase 之前 |
| git rebase –continue | 解决冲突后,继续执行剩余的 rebase 步骤 |
6.2 交互式 rebase(改写提交历史)
| git rebase -i HEAD~3 | 交互式重做最近 3 个提交 |
| git rebase -i main | 交互式把当前分支搬到 main 上面,同时可以改写每个提交 |
交互式 rebase 里可以用的动作:
| pick | p | 保留这个提交,原样应用 |
| reword | r | 保留代码改动,但修改提交信息 |
| squash | s | 把这个提交和前一个合并成一条 |
| fixup | f | 和 squash 一样,但丢弃这个提交的信息 |
| drop | d | 直接扔掉这个提交 |
| edit | e | 在这个提交处暂停,你可以改代码后再继续 |
6.3 取消和挽救
| git rebase –abort | 放弃本次 rebase,回到 rebase 之前 |
| git rebase –skip | 跳过当前正在处理的提交(慎用) |
| git reflog | 查看 HEAD 的移动历史,找回 rebase 之前的旧提交 |
| git reset –hard 旧哈希 | 用 reflog 找到旧哈希后,回退到 rebase 之前 |
7. 对照表(前后对比 / 选项对比)
7.1 rebase vs merge:什么时候用哪个
| 自己的功能分支要同步主分支的最新代码 | 推荐——历史更干净 | 也行,但会有合并提交 |
| 把功能分支合进 main | 先 rebase 再快进合并 | 直接 merge 也完全可以 |
| 多人同时在用的公共分支 | 绝对不要 | 用 merge |
| 想保留「同时开发了两个功能」这一事实 | rebase 会丢掉这个信息 | merge 保留了分叉 |
| 开源项目提交 PR | 很多项目要求先 rebase | 看项目规定 |
7.2 交互式 rebase 的动作对照
| 这个提交没问题,原样保留 | pick | 默认就是 pick |
| 提交信息写错了想改 | reword | 「fix bug」→「修复登录页面验证码不显示的 bug」 |
| 3 个小提交想合成 1 个 | squash | 「修 typo」「又修」「再修」→「修复文档中的拼写错误」 |
| 和前一个合并,但不要这个提交的信息 | fixup | 中间步骤的提交信息没价值 |
| 这个提交不要了 | drop | 试试看的代码,后来没用上 |
| 想修改这个提交的代码 | edit | 提交里少改了一个文件,补上 |
7.3 冲突标记对照

图:冲突标记长这样。
| <<<<<<< HEAD | 当前分支(rebase 时是你正在重放的提交) | 「你」的版本 |
| ======= | 分隔线 | 上面是你的,下面是别人的 |
| >>>>>>> feature | 被合并进来的分支 | 「别人」的版本 |
8. 安全习惯(硬规矩 + 踩坑提醒)
8.1 建议这样做
| 只对自己的、还没推送的分支做 rebase | 没人基于你的旧提交在干活,改写历史不会影响别人 |
| rebase 之前先 git status 确认工作区干净 | 脏工作区会导致 rebase 出错或中途卡住 |
| rebase 之前先看一下分支线 git log –graph | 确认你知道要 rebase 的范围 |
| 冲突时冷静看标记,想清楚要保留什么 | 别急着删,先理解 |
| rebase 失败了先 –abort | 回到原点重新来,比硬着头皮走下去安全 |
8.2 绝对不要这样做
| 对已推送到远程的公共分支执行 rebase | 别人的本地历史和你不一样了,拉代码时冲突地狱 |
| 在 rebase 中途用 –skip 随意跳过提交 | 你跳过的那个提交的代码改动就丢了 |
| rebase 时遇到冲突直接 git add . + git rebase –continue | 不看冲突内容就全盘接受,可能覆盖掉别人的代码 |
| 同时开两个终端对同一个仓库做 rebase | Git 会打架,状态乱成一锅粥 |
铁律:永远不要 rebase 已经推送到共享分支上的提交。 违反这条规则的后果是:团队成员拉代码时会发现历史对不上,要么合不出来,要么重复提交满天飞。如果一定要改已推送的历史,必须和所有相关的人协调好,而且要用 –force-with-lease 而不是 –force。
8.3 推荐的最小流程(背下来)
# 1. 确认在功能分支上
git branch
# 2. 确认工作区干净
git status
# 3. 看一眼当前历史
git log –oneline -5
# 4. 执行 rebase
git rebase main
# 5. 如果有冲突:
# – 打开冲突文件,看 <<<<<<< 和 >>>>>>> 之间的内容
# – 手动改成你想要的结果
# – git add 冲突文件
# – git rebase –continue
# 6. 如果搞砸了:
# – git rebase –abort(回到第 3 步的状态)
# 7. 确认结果
git log –graph –oneline -10
9. 真实场景速览
9.1 课程大作业:功能分支要同步主分支
你和组员协作写大作业,你的 feature-login 分支写了两天,Meanwhile 组员已经把注册功能合并到 main 了。
git checkout feature-login && git rebase main
你的登录功能就「搬」到包含注册功能的最新代码上面了。如果冲突,按第 8 节的流程解决。
9.2 提交太多太碎,提交前想整理
你写代码时习惯每改一点就 commit 一次,现在 git log 一看有 8 个提交,信息全是「改了点东西」「再改改」「应该好了」。
git rebase -i HEAD~8
在编辑器里把 8 个提交的 pick 改成:
pick a1b2c3d 实现登录验证
squash e4f5g6h 改了点东西
squash i7j8k9l 再改改
squash m0n1o2p 应该好了
pick q3r4s5t 实现注册页面
squash t5u6v7w 修了注册的 typo
squash w8x9y0z 又修
drop a1b2c3e 这次改没用
保存退出后,Git 会依次处理:前 4 个合成 1 个,第 5-7 个合成 1 个,第 8 个直接删掉。最终 8 个提交变成 2 个,干干净净。
9.3 代码审查前整理历史
提 PR 之前,用 git rebase -i 把零碎的提交压缩成几个有意义的提交。改完之后 force push 到你自己的 fork。
git rebase -i main
# 整理好之后
git push –force-with-lease origin feature-branch
–force-with-lease 比 –force 安全——如果别人在你之后也推了代码,它会拒绝推送,不会覆盖别人的工作。
10. 进阶补充(选读)
11. 小实验(动手练习 + 通过标准)
实验甲:基础 rebase——把分叉变成直线
通过标准: rebase 后历史线是一条直线,没有合并提交,功能提交的哈希值变了。
实验乙:交互式 rebase——压缩提交
通过标准: 3 个提交压缩成 1 个,代码内容不变。
实验丙:rebase 冲突解决
通过标准: 能独立从冲突状态走到 rebase 完成,最终代码是你期望的内容。
实验丁:rebase abort——随时能跑
通过标准: abort 后仓库完整回到 rebase 之前的状态,没有任何残留。
12. 常见问题 FAQ
问 1:rebase 和 merge 到底哪个更好? 没有绝对的好坏。merge 保留真实历史,rebase 让历史更整洁。自己的功能分支用 rebase 整理,合进公共分支用 merge 记录。两条路最终的代码是一样的。
问 2:为什么 rebase 后提交的哈希值变了? 因为提交的哈希是根据「内容 + 父提交 + 作者信息 + 时间」算出来的。rebase 改变了父提交,所以哈希必然不同。代码改动不变,但提交对象是全新的。
问 3:rebase 到一半不想做了怎么办? git rebase –abort,一秒回到 rebase 之前。这是 rebase 的安全网,大胆用。
问 4:我在 rebase 时冲突了,怎么知道该保留谁的? <<<<<<< HEAD 和 ======= 之间是你当前正在重放的提交的代码,======= 和 >>>>>>> 之间是新基线上的代码。根据实际情况决定保留哪部分。
问 5:交互式 rebase 弹出的编辑器我不会用怎么办? 默认编辑器可能是 vim。进去后按 i 进入编辑模式,改完按 Esc,输入 :wq 保存退出。不习惯的话可以设置编辑器:git config core.editor "code –wait"(用 VS Code)或 git config core.editor "nano"。
问 6:squash 和 fixup 有什么区别? squash 会把两个提交的提交信息都保留,让你编辑合并后的信息。fixup 直接丢弃被合并提交的信息,只保留第一个提交的信息。
13. 总结
13.1 一页速记
| rebase 是什么 | 把你的提交「搬家」到新基线上面,历史变直线 |
| 和 merge 的区别 | merge 保留分叉,rebase 改写成直线;最终代码一样 |
| 哈希会变 | 搬家 = 拆掉重建,提交是新的,哈希不同 |
| 交互式 rebase | -i 参数,可以 squash / reword / drop / reorder |
| 冲突处理 | 和 merge 一样看标记,解决后 add + –continue |
| 放弃 rebase | git rebase –abort 随时退回 |
| 铁律 | 永远不要 rebase 已推送到共享分支的提交 |
| 什么时候用 | 自己的本地功能分支同步主分支 / 整理提交 / 代码审查前 |
| 找回旧提交 | git reflog 查看,git reset –hard 回退 |
13.2 思维升华
rebase 改写的是「历史怎么讲」,不是「代码是什么」。
同样的代码,merge 讲了一个「两条路汇合」的故事,rebase 讲了一个「一路向上」的故事。故事不同,结局一样。但记住:公共的历史不能你一个人偷偷改写——改故事之前,先确认只有你一个人在读这本书。
13.3 延伸阅读
- Pro Git 中文版 — 变基
- Git 官方文档 — git-rebase
- Git 术语表
命令输出样例验证环境:Git 2.43.0;演示作者信息为虚构:Ada Example <ada@example.com>。
13.4 检查清单
- 能用「搬家到新楼层」的比喻解释 rebase 给同学听
- 能说出 rebase 和 merge 的 3 个区别
- 能独立完成一次基础 rebase(不分叉变成直线)
- 能用交互式 rebase 压缩多个提交
- 遇到 rebase 冲突不慌,知道如何解决
- 知道 –abort 可以随时放弃 rebase
- 牢记铁律:不 rebase 已推送到共享分支的提交
- 完成 4 个小实验
rebase 给了你改写历史的超能力,但「能力越大,责任越大」——只改自己的历史,不要动别人的。下一章我们讲 reflog:就算 rebase 翻车了,也还有办法救人。
网硕互联帮助中心




评论前必须登录!
注册