Git 将多个提交合并成一个用 squash

FreeGuideOnline 最新 2026-07-05

什么是 squash?为什么需要合并提交?

在 Git 中,squash 是一种将多个连续的提交压缩为一个提交的操作。当你完成一个功能开发,分支上留下了一堆零碎的“修正拼写”、“修复测试”、“再改一下”这类提交时,这些中间过程对代码审查和项目历史来说噪音很大。通过 squash,你可以把这些小提交整理成一个干净、语义清晰的提交,让主干历史保持整洁。

常见的 squash 场景:

  • 合并一个特性分支到主分支之前,清理该分支的提交记录。
  • 在代码审查完成后,将审查过程中的修改合并到原有提交中,避免产生“review fix”之类的零散提交。
  • 准备 Pull Request 时,将整个 PR 的变更浓缩为少量甚至一个高质量提交。

git squash 并不是一个独立命令,它通常是 git rebase -i(交互式变基)中的一种操作类型.

前置要求与环境准备

开始操作前,请确保:

  • 你已经提交了所有本地修改,工作区是干净的(git status 显示无变更)。
  • 你知道你要压缩哪些提交,通常是从当前分支的某一点到HEAD的若干提交。
  • 如果这些提交已经被推送到远程仓库并且有其他人基于它们工作,squash 需要谨慎,最好在未推送或仅自己使用的分支上进行。

本教程的示例假设你有一个分支 feature/add-login,上面最近有 4 个零碎提交,我们希望把它们合并成一个有意义的提交.

第一步:确认需要压缩的提交历史

首先查看提交日志,找到要压缩的提交范围。

git log --oneline -5

假设输出如下:

a1b2c3 (HEAD -> feature/add-login) fix typo in login form
d4e5f6 fix login button color
g7h8i9 add basic login logic
j1k2l3 set up login route
m3n4o5 (main) initial project setup

我们想把从 j1k2l3(设置路由)到 a1b2c3(修正拼写)这四个提交压缩成一个提交。压缩之后,我们希望这个新提交包含所有这些变更,并且有一个清晰的说明,比如“实现用户登录功能”。

第二步:使用交互式 rebase -i 进入编辑模式

运行 git rebase -i,指定要修改的提交范围的前一个提交。因为我们要修改从 j1k2l3 到 HEAD 的提交,需要告诉 rebase 从 j1k2l3 的父亲开始,也就是 m3n4o5

git rebase -i m3n4o5

你也可以用提交数量表示:git rebase -i HEAD~4(表示操作最近的 4 个提交)。使用哈希更精确,尤其是在复杂分支上。

执行后,Git 会打开一个文本编辑器(通常是 vim 或系统默认编辑器),显示类似以下内容:

pick j1k2l3 set up login route
pick g7h8i9 add basic login logic
pick d4e5f6 fix login button color
pick a1b2c3 fix typo in login form

# Rebase m3n4o5..a1b2c3 onto m3n4o5 (4 commands)
# ...

每一行代表一个提交,前面的 pick 表示保留该提交。

第三步:将 pick 改为 squash(或 s)

我们要保留第一个提交 j1k2l3(最旧的一个)作为基础,后面的提交全部融入它。因此,把后面三个提交的 pick 改为 squash 或简写 s

编辑后内容应为:

pick j1k2l3 set up login route
squash g7h8i9 add basic login logic
squash d4e5f6 fix login button color
squash a1b2c3 fix typo in login form

注意:如果你想把所有提交直接压成一个,留下最早的提交作为载体;如果想把最新的作为载体,则可以把上面的 pick 也改成 squash,但 Git 需要一个基础提交,所以习惯上保留第一个为 pick

保存并退出编辑器(vim 中 :wq)。

第四步:编辑合并后的提交信息

退出后,Git 会立即开始应用 rebase 操作。由于你选择了 squash,Git 会把这些提交的变更全部应用之后,再次打开一个编辑器,让你编写最终的合并提交信息。

你会看到所有被压缩的提交的信息都被列在一起:

# This is a combination of 4 commits.
# This is the 1st commit message:

set up login route

# This is the commit message #2:

add basic login logic

# This is the commit message #3:

fix login button color

# This is the commit message #4:

fix typo in login form

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.

你可以删除所有原有信息,写一个新的、精简的提交信息,例如:

实现用户登录功能

包含路由配置、基础登录逻辑、UI调整和拼写修正。

保存并退出。Git 会生成一个新的提交,它包含了之前四个提交的全部更改,提交信息就是你刚刚编写的内容。

第五步:验证结果

git log 查看历史:

git log --oneline -3

输出类似于:

f4a7b2d (HEAD -> feature/add-login) 实现用户登录功能
m3n4o5 (main) initial project setup

原来的四个提交已经被替换为一个新的提交,哈希也变了。此时 feature/add-login 分支的历史变得干净利落。

额外技巧与常见问题

如果你想压缩全部提交,包括第一个

可以直接将所有行的 pick 都改为 squash,但 Git 会报错:“error: cannot 'squash' without a previous commit”。解决方法是在第一行前面添加一个 pick 的基准,或者将第一个改为 reword 然后其他 squash,但这不符合“全合并”的心理预期。一个更直接的做法是使用 git reset --soft 回到某个起点,然后重新提交:

git reset --soft m3n4o5
git commit -m "实现用户登录功能"

压缩过程中想取消操作

在交互式 rebase 的编辑器中,不保存直接退出,或者在 squash 后的提交信息编辑器中留空信息(会终止 rebase),都可以回到操作前的状态。如果 rebase 已经在进行中且出现问题,使用 git rebase --abort 彻底回退。

压缩已经推送到远程的提交

如果你已经推送过那些零碎提交到远程分支(例如 origin/feature/add-login),在本地 squash 后,本地分支和远程分支会产生分叉。由于 squash 改变了提交历史,你不能直接 git push,需要强制推送:

git push --force-with-lease origin feature/add-login

--force-with-lease--force 更安全,它会在推送前检查远程分支是否被你之外的其他人更新过。注意:不要在共享分支(如 main)上执行此操作,除非团队明确允许重写历史。

为什么不用 git merge --squash

git merge --squash 也可以将特性分支的所有变更压缩为一个提交再合并到目标分支,但它的工作方式不同:它不会生成合并提交,而是把变更放到暂存区让你手动提交,并且不保留原分支的提交历史。相比之下,rebase -i 的 squash 是在当前分支上重写历史,保留了分支的独立性,更灵活。

总结

Git squash 是整理项目历史的必备技能。通过交互式 rebase,你可以轻松地将多个零散提交压缩为一个语义完整的提交。记住核心流程:

  1. git rebase -i 指定要修改的提交范围。
  2. 将后面的提交标记为 squash
  3. 编写最终的提交信息。
  4. 强制推送(如果需要)。

保持分支整洁,不仅让自己的开发过程更清晰,也让代码审查和团队合作更高效。