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:重写历史,形成线性提交链
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: 添加登录功能」。这样远程仓库里就只有一个干净的提交,而不是一堆「修修补补」的记录。
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 就会按新顺序重新应用提交。不过要注意:如果提交之间有代码依赖,调整顺序可能导致冲突。这时候就需要手动解决冲突了。
git rebase --continue 继续。如果想放弃这次 rebase,执行 git rebase --abort 回到原始状态。
11.6 知识体系图
下面这张图展示了本章的核心知识结构:
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 是个需要多练的技能,刚开始可能会觉得麻烦,但用顺手了就会发现——它真的是保持 Git 历史整洁的最佳工具。