10、Git rebase:变基的艺术与陷阱
说实话,Git 里面最容易让人迷惑的命令,rebase 绝对排前三。我刚开始用 Git 那会儿,看到 rebase 这个词就头大——什么叫「变基」?好好的分支为什么要动它的根?
后来在项目里踩了几次坑,才慢慢摸透它的脾气。今天我就把 rebase 的来龙去脉讲清楚,包括它和 merge 到底怎么选,交互式 rebase 怎么玩,以及那些你一定要注意的坑。
一句话总结:rebase 就是把一个分支的提交「移植」到另一个分支的最新提交之上,让历史变成一条直线。
10.1 rebase 到底是什么?
先看个场景。你从 master 拉了个 feature 分支,开始写代码。这时候 master 被别人推进了两个新提交。你的 feature 分支现在落后了。
用 merge 的话,会生成一个合并提交,历史变成分叉再合并的样子。用 rebase 呢?它会把你 feature 上的提交「摘下来」,放到 master 的最新提交后面。
说白了,rebase 就是重新设置你的「基准点」。原来你的基准是 master 的旧提交,现在改成最新的那个。
# 当前在 feature 分支
git rebase master
# 效果:feature 的提交被放到 master 最新提交之后
# 原来:A---B---C (master)
# \
# D---E (feature)
# 之后:A---B---C (master)
# \
# D'---E' (feature)
注意那个 D' 和 E'——它们是新的提交对象,虽然内容和原来一样,但 hash 变了。这意味着什么?嗯,后面我会讲,这是个关键点。
10.2 rebase 与 merge 的区别
这个问题几乎每个面试都会问到。我个人的理解是这样的:
| 对比维度 | merge | rebase |
|---|---|---|
| 历史记录 | 保留完整的分叉和合并记录 | 线性历史,没有分叉 |
| 提交对象 | 原有提交不变,新增合并提交 | 生成全新的提交(hash 改变) |
| 冲突处理 | 一次解决,合并提交时处理 | 每个提交都可能要解决冲突 |
| 适用场景 | 公共分支、长期分支 | 个人分支、整理历史 |
| 可追溯性 | 强,能看到谁合并了谁 | 弱,丢失了分支合并信息 |
我个人的习惯是:公共分支用 merge,个人分支用 rebase。为什么?因为 rebase 会改写历史,如果你在公共分支上 rebase,别人会疯掉的。
我的经验:有一次我在团队分支上用了 rebase,结果同事 pull 的时候报了一大堆冲突。从那以后我就记住了——已经推送到远程的分支,尤其是多人协作的,千万别 rebase。
10.3 交互式 rebase:整理历史的利器
交互式 rebase 是我最常用的功能之一。它让你在 rebase 的过程中,对每个提交进行操作——合并、修改、删除、重排,都可以。
# 对最近 3 个提交进行交互式 rebase
git rebase -i HEAD~3
# 会打开一个编辑器,内容类似:
# pick a1b2c3 第一个提交
# pick d4e5f6 第二个提交
# pick g7h8i9 第三个提交
# 你可以把 pick 改成:
# pick - 保留这个提交
# reword - 修改提交信息
# edit - 修改提交内容
# squash - 合并到上一个提交
# fixup - 合并但丢弃提交信息
# drop - 删除这个提交
举个例子。你写代码时经常「先提交再说」,结果提交信息全是「fix bug」「update」「wip」。等到提 PR 的时候,这些提交看着就很不专业。
这时候交互式 rebase 就派上用场了:
# 把最后 5 个提交合并成 1 个
git rebase -i HEAD~5
# 把除了第一个之外的 pick 改成 squash
# 然后统一写一个漂亮的提交信息
我曾经在一个项目里,把 20 多个零散的提交整理成了 3 个有意义的提交。PR 审核的人看了直呼舒服。
10.4 rebase 的注意事项
这里我要重点说几个坑,都是我自己踩过的。
警告:绝对不要对已经推送到远程的分支做 rebase!除非你确认只有你在用这个分支。
第一,rebase 后要强制推送。因为提交 hash 变了,普通的 git push 会被拒绝。你需要用 git push --force 或者更安全的 git push --force-with-lease。
# 推荐使用这个,它会检查远程分支是否有新的提交
git push --force-with-lease
第二,冲突可能多次出现。merge 是一次性解决所有冲突。rebase 是逐个提交应用,每个提交都可能产生冲突。如果你的分支有 10 个提交,可能要解决 10 次冲突。
我有个小技巧:如果冲突太多,可以先用 merge 合并一次,然后再 rebase。或者用 git rerere 功能自动记录冲突解决方案。
第三,rebase 会丢失分支的上下文。你想想看,rebase 之后,原来的分支分叉点就没了。别人看历史的时候,不知道这个功能分支是从哪里拉出来的。
第四,不要 rebase 已经打 tag 的提交。tag 是绑定在具体提交上的,rebase 之后提交变了,tag 就指向了旧提交,容易造成混乱。
我的建议:rebase 之前,先用 git branch backup-feature 创建一个备份分支。万一 rebase 出问题,还能切回去重来。
10.5 什么时候用 rebase,什么时候用 merge?
这个问题没有标准答案。我分享一下我的决策流程:
- 个人开发分支:用 rebase,保持历史整洁
- 多人协作分支:用 merge,保留协作信息
- 准备提 PR 前:用 rebase 整理提交,让 PR 更好审核
- 从主分支拉取更新:用 rebase,避免产生多余的合并提交
- 修复紧急 bug:用 merge,快速解决问题,不折腾历史
说白了,rebase 和 merge 没有谁对谁错,只有合不合适。我见过有些团队完全禁止 rebase,也见过有些团队只用 rebase。关键是大家约定好规则,保持一致。
最后说一句:rebase 是个强大的工具,但强大也意味着危险。用之前想清楚,用的时候小心点,用完之后检查一下。嗯,这样基本就不会出大问题。