Git Rebase
一、直观理解
二、rebase 做了什么
三、为什么要这样做
四、要小心的地方
好问题。rebase 是 Git 里一个非常强大但也容易被误解的命令。
简单说,它的作用是:
把你的提交“挪到”另一个分支的最新节点上去,就像时间旅行一样,把你的工作“重演”一遍。
直观理解
假设你有这样的历史:
1 | A---B---C (dev) |
这里:
A-B-C是dev分支的提交。- 你从
C开了一个feature分支,做了D、E两次提交。 - 但后来
dev上别人又提交了F、G。
现在变成:
1 | A---B---C---F---G (dev) |
如果你现在要把 feature 合并回 dev,会出现分叉历史,看起来乱糟糟。
rebase 做了什么
执行:
1 | git checkout feature |
Git 会把 feature 的提交 D、E 拿下来、暂存起来,
然后接在最新的 dev(G)之后重新“播放”一遍。
于是历史就变成:
1 | A---B---C---F---G---D'---E' (feature) |
这里的 D' E' 是“重演版”的 D、E,内容一样但哈希不同。
现在看起来历史像是一条干净的直线。
为什么要这样做
优点是:
- 历史更干净:没有多余的“Merge commit”。
- 方便 review:每个提交按时间顺序排列。
- 避免冲突积压:提前在自己的分支里解决冲突,而不是到合并时才一锅端。
要小心的地方
- rebase 会改写提交历史,因此不要对公共分支(别人也在用的分支)执行。
否则别人拉代码时会出现冲突地狱。 - 自己的 feature 分支上用没问题(只影响你一个人)。
把你的 feature 分支的提交 “搬到” dev 的最新节点之后,改变的是 feature 分支的历史,是安全的。
rebase 不会删除分支,而是“重塑”这个分支的历史。
视觉图上
想象你原本的 feature 分支是一条从 C 出发的小支线:
1 | A---B---C (dev) |
当你执行:
1 | git checkout feature |
Git 会做:
- 把 D、E 拿下来暂存;
- 把 feature 分支“移到” dev 的最新节点;
- 再把 D、E 重新播放一遍(生成 D′、E′)。
于是结果变成:
1 | A---B---C---F---G (dev) |
feature 分支还在,只是它现在指向 D′、E′(新的提交),
而旧的 D、E 变成“悬空提交”,暂时没人引用(Git 会在几天后自动清理掉)。
技术层面
不会删除分支名:
feature仍存在,你依然可以git checkout feature。会改变提交哈希:D、E 的哈希变成新的 D′、E′。
旧历史仍在:Git 暂时保留被改写的提交(在
.git/refs/original中)。如果你立刻后悔,可以用:
1
2git reflog
git reset --hard <旧哈希>来恢复。
而真正会“销毁分支”的操作是:
1 | git branch -d feature |
额外的Tips
如果你在多人协作的远程仓库中使用:
本地 rebase 后推送时,必须加
--force或--force-with-lease:1
git push origin feature --force-with-lease
否则远程因为哈希不同会拒绝。
和merge的区别
初始状态(准备阶段)
假设我们有:
1 | A---B---C---F---G (dev) |
dev分支走到了 G。- 你从
C分出来开发feature,做了两个提交 D、E。
使用 merge
1 | git checkout dev |
结果:
1 | A---B---C---F---G---M (dev) |
使用 rebase
1 | git checkout feature |
结果:
1 | A---B---C---F---G---D'---E' (feature) |
所以,rebase是用于处理个人分支,merge是用于将个人分支合并到公共分支
合并回 dev
正如上面所示,这时候就可以用merge合并回dev,不过因为feature的历史干净,所以合并之后的历史也会比较漂亮