学会使用 git rebase -i 整理提交历史,掌握 pick、squash、reword、edit、fixup、drop 六大指令,让 Git 历史干净清晰。

变基 — 给你的提交历史做一次整容手术

你是否有过这样的经历:一个功能开发完,回头看提交历史,全是 “fix”、”update”、”again” 这样的碎片提交?

交互式变基(rebase -i)就是用来整理和优化提交历史的利器。它可以让你合并、删除、重排、修改历史提交,让 Git 历史变得干净清晰。


一、什么是交互式变基?

普通 rebase vs 交互式 rebase

1
git rebase main

把当前分支的提交”搬”到 main 分支最新提交的后面,主要用于同步主分支

1
git rebase -i HEAD~5

打开编辑器,让你逐个决定每个提交怎么处理,主要用于整理提交历史

一个真实场景

假设你开发了一个登录功能,产生了 5 个提交:

1
2
3
4
5
* 5426370 fix: 又修了个样式问题
* f86c2b0 fix: 修复密码验证
* d353d8f style: 调整按钮样式
* ded04a1 fix: 修复输入框焦点问题
* 19fc895 feat: 开始开发登录功能

这 5 个提交其实都是同一个功能,合并成一个更合理:

1
* a1b2c3d feat: 添加用户登录功能      ← 干净!

二、基本语法

1
git rebase -i HEAD~N
部分解释
rebase变基操作,重新应用提交到新的基准上
-i--interactive 的缩写,交互式模式,打开编辑器
HEAD~N从当前提交往回数 N 个。~ 是”往回”的意思

常用写法

1
2
3
4
5
6
7
8
# 编辑最近 5 个提交
git rebase -i HEAD~5

# 编辑从某个提交之后的所有提交
git rebase -i abc1234

# 编辑最近 3 个提交
git rebase -i HEAD~3

注意HEAD~N 中的 N 必须大于等于 2,因为变基至少需要 2 个提交才有意义。


三、六大指令详解

执行 git rebase -i HEAD~5 后,编辑器会出现类似内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pick 19fc895 feat: 开始开发登录功能
pick ded04a1 fix: 修复输入框焦点问题
pick d353d8f style: 调整按钮样式
pick f86c2b0 fix: 修复密码验证
pick 5426370 fix: 又修了个样式问题

# Rebase abc1234..5426370 onto abc1234 (5 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like squash, but discard this commit's message
# d, drop = remove commit

重要:提交列表是从上到下按时间顺序排列的,最上面的是最早的提交,最下面的是最新的。

指令速查表

指令全称含义使用场景
pickpick保留这个提交(默认)不需要修改
rreword保留内容,修改提交信息改提交描述
eedit保留内容,暂停让你修改代码需要改代码
ssquash合并到前一个提交,保留双方信息合并小提交
ffixup合并到前一个提交,丢弃本次信息合并修复提交
ddrop删除这个提交不需要的提交

四、实战:压缩 5 个提交成 1 个

这是最常见的用法,把零碎提交合并成一个完整提交。

第 1 步:执行 rebase -i

1
git rebase -i HEAD~5

第 2 步:编辑指令

编辑器中会出现 5 个提交,把后 4 个改为 s(squash):

1
2
3
4
5
pick 19fc895 feat: 开始开发登录功能    ← 第一个保留 pick,作为"基础"
s ded04a1 fix: 修复输入框焦点问题 ← 合并到上一个
s d353d8f style: 调整按钮样式 ← 合并到上一个
s f86c2b0 fix: 修复密码验证 ← 合并到上一个
s 5426370 fix: 又修了个样式问题 ← 合并到上一个

为什么第一个保留 pick,其余用 s?

因为 squash 是”合并到前一个提交”,所以需要一个”前一个”作为基础。第一个提交用 pick 保留,后面的都会被揉进去。

第 3 步:编写新的提交信息

保存关闭后,Git 会打开另一个编辑器让你写合并后的提交信息:

1
2
3
4
5
6
7
8
9
10
11
# This is a combination of 5 commits.
# The first commit's message is:
feat: 开始开发登录功能

# The 2nd commit's message is:
fix: 修复输入框焦点问题

# The 3rd commit's message is:
style: 调整按钮样式

# ...

你可以把上面的内容全部删掉,写一个新的:

1
2
3
4
5
feat: 添加用户登录功能

- 用户名密码登录
- 输入验证
- 样式适配

保存退出,5 个提交就变成了 1 个!

1
2
3
4
5
6
7
8
9
# 变基前
* 5426370 fix: 又修了个样式问题
* f86c2b0 fix: 修复密码验证
* d353d8f style: 调整按钮样式
* ded04a1 fix: 修复输入框焦点问题
* 19fc895 feat: 开始开发登录功能

# 变基后
* a1b2c3d feat: 添加用户登录功能 ← 干净!

五、实战:修改提交信息(reword)

有时候提交信息写错了,需要修改。

修改单个提交信息

1
git rebase -i HEAD~3
1
2
3
pick a1b2c3d feat: 添加用户登录功能
r e4f5g6h feat: 修改用户注册功能 ← 把 pick 改为 r
pick i7j8k9l docs: 更新文档

保存后,Git 会在 reword 的提交处暂停,让你修改提交信息:

1
2
3
feat: 修改用户注册功能                  ← 在这里修改

# Please enter the commit message for your changes...

修改完保存退出即可。


六、实战:暂停修改代码(edit)

当你发现某个提交有代码错误,需要修改代码时:

1
git rebase -i HEAD~3
1
2
3
pick a1b2c3d feat: 添加用户登录功能
e e4f5g6h feat: 修改用户注册功能 ← 把 pick 改为 e
pick i7j8k9l docs: 更新文档

保存后,Git 会在 edit 的提交处暂停:

1
2
3
Stopped at e4f5g6h... feat: 修改用户注册功能
You can amend the commit now, with:
git commit --amend

这时你可以修改代码:

1
2
3
4
5
6
7
8
9
10
11
# 修改代码...
vim src/register.js

# 暂存修改
git add src/register.js

# 修改提交
git commit --amend

# 继续 rebase
git rebase --continue

七、实战:删除提交(drop)

如果某个提交完全不需要了:

1
git rebase -i HEAD~3
1
2
3
pick a1b2c3d feat: 添加用户登录功能
d e4f5g6h fix: 临时调试代码 ← 把 pick 改为 d
pick i7j8k9l docs: 更新文档

保存后,drop 的提交会被永久删除,就像从未存在过。

drop 是危险操作:被删除的提交中的代码改动也会丢失。如果只是不想保留提交记录但想保留代码,用 fixup 合并到其他提交中。


八、squash vs fixup 的区别

这两个指令都是合并到前一个提交,区别在于提交信息:

指令代码提交信息
squash(s)合并到前一个提交保留两个提交的信息,让你编辑
fixup(f)合并到前一个提交丢弃当前提交的信息,只保留前一个的

什么时候用 fixup?

当你提交了一堆 “fix typo”、”fix again” 这种修复提交时,用 fixup 最干净:

1
2
3
pick 19fc895 feat: 添加用户登录功能
f ded04a1 fix: typo ← 丢弃这条信息,代码合并到上面
f d353d8f fix: 又修了个 bug ← 同上

结果:

1
* a1b2c3d feat: 添加用户登录功能         ← 信息干净,代码完整

九、实战:重新排序提交

你可以通过改变行的顺序来重排提交:

1
2
3
4
5
6
7
8
9
# 变基前
pick a1b2c3d feat: 添加登录功能
pick e4f5g6h docs: 更新文档
pick i7j8k9l feat: 添加注册功能

# 把 docs 移到最后
pick a1b2c3d feat: 添加登录功能
pick i7j8k9l feat: 添加注册功能
pick e4f5g6h docs: 更新文档
注意

重排序可能导致冲突,因为后面的提交可能依赖前面提交的代码。如果出现冲突,Git 会暂停让你解决。


十、变基冲突处理

变基过程中经常遇到冲突,别慌,按以下步骤处理:

1
2
3
4
5
6
7
8
9
10
11
# 1. 解决冲突(编辑文件,删除冲突标记)
vim 冲突文件

# 2. 标记为已解决
git add 冲突文件

# 3. 继续变基
git rebase --continue

# 如果冲突太多想放弃
git rebase --abort
  • git rebase --continue:解决冲突后继续
  • git rebase --skip:跳过当前提交(代码会丢失)
  • git rebase --abort:完全放弃变基,回到之前的状态

十一、变基的注意事项

黄金法则:不要对已推送到远程的公共分支执行变基!

变基会重写提交历史(生成新的 commit hash),如果你已经 push 到远程,别人基于你的提交做了开发,你变基后他们的分支就会出问题。

什么时候可以变基?

场景可以变基?
本地分支,还没 push可以
个人分支(feature/xxx),还没合并可以
已经 push 到远程的公共分支不可以
别人正在基于开发的分支不可以

如果已经 push 了怎么办?

变基后只能强制推送:

1
git push --force-with-lease origin 分支名
提示

--force-with-lease--force 更安全,它会检查远程分支是否有别人的新提交,避免覆盖别人的代码。


十二、IDEA 中的变基操作

如果你使用 IntelliJ IDEA / WebStorm,变基操作可以在图形界面中完成:

压缩提交

在 Git 提交记录面板中:

  1. 选中要压缩的提交
  2. 右键 → 压缩提交(Squash Commits)
  3. 输入新的提交信息

交互式变基

  1. 选中一个提交
  2. 右键 → 从这里执行交互式变基(Interactively Rebase from Here)
  3. 在弹出窗口中拖拽排序、修改指令(pick/squash/drop 等)
  4. 点击 Start Rebase

其他右键菜单

菜单项等价命令说明
撤销提交git reset --soft HEAD~1保留修改,撤销提交
删除提交git reset --keep HEAD~1删除提交,保留不冲突的文件
压缩提交git rebase -i (squash)合并多个提交
修改提交信息git rebase -i (reword)修改提交描述
从这里执行交互式变基git rebase -i <commit>完整变基操作

总结:变基指令速查卡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────────────────────────────────────────────┐
│ git rebase -i HEAD~N 速查 │
├──────────┬──────────────────────────────────────────────────┤
│ pick │ 保留提交(默认) │
│ s/squash │ 合并到前一个提交,保留双方信息 │
│ f/fixup │ 合并到前一个提交,丢弃当前信息 │
│ r/reword │ 保留内容,修改提交信息 │
│ e/edit │ 暂停,让你修改代码 │
│ d/drop │ 删除提交(代码也丢失) │
├──────────┼──────────────────────────────────────────────────┤
│ 冲突处理 │ git add → git rebase --continue │
│ 放弃变基 │ git rebase --abort │
│ 跳过当前 │ git rebase --skip │
├──────────┼──────────────────────────────────────────────────┤
│ 安全原则 │ 只对本地的、未 push 的分支执行变基 │
└──────────┴──────────────────────────────────────────────────┘