11、Git历史修改:git rebase(变基操作)、交互式rebase(合并提交、修改提交信息、调整提交顺序)

说到 Git 的历史修改,很多人第一反应是「历史还能改?那不是造假吗?」。嗯,我刚开始也有这个顾虑。后来在团队里踩过几次坑才明白——修改历史不是造假,而是让历史更清晰。你想想看,一个项目几百次提交,如果全是「fix bug」「update」「wip」这种信息,回头看的时候谁受得了?

今天我们就来聊聊 git rebase 这个神器。它有两个核心用途:一是变基,二是交互式重写历史。我个人习惯把 rebase 理解为「给提交历史做一次整理手术」——该合并的合并,该调整的调整,该改名的改名。

11.1 什么是变基(rebase)?

先说说变基。说白了,就是把一个分支的提交「移植」到另一个分支的顶端。

举个例子:你从 master 拉了个 feature 分支,开发了三天。这三天里 master 被别人推进了 5 个新提交。这时候你的 feature 分支就「落后」了。传统做法是用 merge,但 merge 会留下一个合并提交,历史看起来像一团乱麻。

rebase 的做法是:把你的 feature 分支上的提交,一个一个地「摘下来」,然后接到 master 的最新提交后面。这样历史就是一条直线,干净利落。

# 当前在 feature 分支
git rebase master

这条命令执行后,feature 分支的基底就从旧的 master 变成了最新的 master。我在项目中遇到过好几次这种情况——用 rebase 代替 merge,代码审查的时候 reviewer 都说「这历史看着真舒服」。

核心区别:
  • merge:保留所有分支历史,产生合并提交
  • rebase:重写历史,形成线性提交链
⚠️ 重要提醒: 永远不要对已经推送到远程且被其他人拉取的分支执行 rebase!因为 rebase 会重写提交的 SHA-1 值,别人再拉取时会冲突到怀疑人生。我曾经因为这个被同事「友好问候」过……

11.2 交互式 rebase:真正的历史手术刀

如果说普通 rebase 是自动移植,那交互式 rebase 就是手动精调。它让你可以:

  • 合并多个提交为一个
  • 修改提交信息
  • 调整提交顺序
  • 删除某个提交
  • 拆分一个提交为多个

启动交互式 rebase 的命令是:

git rebase -i HEAD~3

这个 HEAD~3 表示「从当前提交往前数 3 个提交」。执行后会打开一个编辑器,里面是这样的:

pick a1b2c3d 第一次提交
pick e4f5g6h 第二次提交
pick i7j8k9l 第三次提交

# Commands:
# p, pick = 使用该提交
# r, reword = 使用该提交,但修改提交信息
# s, squash = 合并到前一个提交
# f, fixup = 合并到前一个提交,丢弃提交信息
# d, drop = 删除该提交
# ...

你只需要把 pick 改成对应的命令就行。改完保存退出,Git 就会按你的指令重写历史。

11.3 合并提交(squash)

这是我最常用的操作。开发一个功能时,我习惯频繁提交——写一行代码就 commit 一次,方便随时回退。但推送到远程前,我会把这些零碎的提交合并成一个完整的提交。

比如我有三个提交:

pick a1b2c3d 添加登录功能-初步
pick e4f5g6h 添加登录功能-修复bug
pick i7j8k9l 添加登录功能-完善样式

我想把它们合并成一个,只需要把后两个的 pick 改成 squash:

pick a1b2c3d 添加登录功能-初步
squash e4f5g6h 添加登录功能-修复bug
squash i7j8k9l 添加登录功能-完善样式

保存退出后,Git 会打开另一个编辑器让你写新的提交信息。我一般会写一个概括性的描述,比如「feat: 添加登录功能」。这样远程仓库里就只有一个干净的提交,而不是一堆「修修补补」的记录。

💡 我的习惯: 本地开发时随便提交,推送到远程前用交互式 rebase 整理。这样既保留了本地开发的安全性,又保证了远程历史的整洁。

11.4 修改提交信息(reword)

有时候提交信息写错了,或者写得太随意。比如我经常写「fix bug」这种毫无信息量的信息。这时候可以用 reword 来修改。

在交互式 rebase 界面中,把对应提交的 pick 改成 reword:

reword a1b2c3d 修复用户无法登录的问题
pick e4f5g6h 添加密码重置功能

保存退出后,Git 会为每个标记为 reword 的提交打开编辑器,让你输入新的提交信息。改完一个继续下一个,直到全部改完。

我记得有一次,团队要求所有提交信息必须符合 Conventional Commits 规范。我对着几十个历史提交一个个改……嗯,从那以后我写提交信息就再也不敢偷懒了。

11.5 调整提交顺序

交互式 rebase 的另一个妙用是调整提交顺序。你只需要在编辑器中把行上下移动就行。

比如原始顺序是:

pick a1b2c3d 添加用户注册功能
pick e4f5g6h 添加用户登录功能
pick i7j8k9l 添加用户注销功能

我想把登录功能放在注册前面(虽然这不太合理,但假设有依赖关系),只需要把第二行剪切到第一行前面:

pick e4f5g6h 添加用户登录功能
pick a1b2c3d 添加用户注册功能
pick i7j8k9l 添加用户注销功能

保存退出,Git 就会按新顺序重新应用提交。不过要注意:如果提交之间有代码依赖,调整顺序可能导致冲突。这时候就需要手动解决冲突了。

⚠️ 冲突处理: 交互式 rebase 过程中如果遇到冲突,Git 会暂停。解决冲突后执行 git rebase --continue 继续。如果想放弃这次 rebase,执行 git rebase --abort 回到原始状态。

11.6 知识体系图

下面这张图展示了本章的核心知识结构:

Git Rebase 知识体系 git rebase 普通变基(git rebase master) 将当前分支提交移植到目标分支顶端 交互式 rebase(git rebase -i) 精细控制每个提交的处理方式 合并提交(squash) 将多个提交合并为一个 修改提交信息(reword) 更改提交的描述文字 调整提交顺序 重新排列提交的先后次序 删除提交(drop) 移除不需要的提交 ⚠️ 不要对已推送且被他人拉取的分支执行 rebase 冲突时:git rebase --continue / --abort

11.7 实战中的避坑指南

讲了这么多,最后分享几个我踩过的坑:

  • 不要 rebase 公共分支:我曾经在团队项目里 rebase 了 develop 分支,结果其他同事拉取时全乱了。从那以后我定了个规矩——只有自己的功能分支才能 rebase。
  • rebase 前先备份:可以用 git branch backup-feature 创建一个备份分支。万一 rebase 搞砸了,还能切回去重来。
  • 交互式 rebase 时看清范围:HEAD~3 表示最近 3 个提交。如果写成了 HEAD~30,那就要处理 30 个提交,中间可能冲突不断。我建议一次只处理 5-10 个提交,分批进行。
  • rebase 后强制推送要谨慎:如果分支只有你一个人用,git push --force 没问题。但如果多人协作,用 git push --force-with-lease 更安全——它会检查远程分支是否被其他人更新过。
一句话总结: rebase 是整理本地提交历史的利器,但用在公共分支上就是灾难。记住「本地随便改,远程要谨慎」这个原则,你就能用好它。

好了,关于 rebase 的内容就聊到这里。交互式 rebase 是个需要多练的技能,刚开始可能会觉得麻烦,但用顺手了就会发现——它真的是保持 Git 历史整洁的最佳工具。

公众号:蓝海资料掘金营,微信 deep3321