1. 概述

在 Git 工作流中,“squash”(合并)是一个常见的术语。

本文将简要介绍 Git 提交合并的概念,说明何时需要合并提交,最后重点讲解如何通过 Git 命令合并最近的 X 个提交。

2. 什么是 Git Squash?

在 Git 中,“squash” 表示将多个连续的提交合并为一个提交。

举个例子:

          ┌───┐      ┌───┐     ┌───┐      ┌───┐
    ...   │ A │◄─────┤ B │◄────┤ C │◄─────┤ D │
          └───┘      └───┘     └───┘      └───┘

合并 B、C、D 提交后:

          ┌───┐      ┌───┐
    ...   │ A │◄─────┤ E │
          └───┘      └───┘

          ( 提交 E 包含了 B、C、D 的改动 )

在这个例子中,我们将 B、C 和 D 三个提交合并为一个新提交 E。

接下来我们讨论一下何时应该进行这种操作。

3. 何时进行提交合并?

一句话总结:为了保持分支历史清晰,我们通常会进行提交合并。

比如在开发一个新功能时,我们可能会多次提交,比如修复 bug、测试等。但在功能完成后,这些中间提交就显得冗余了,我们通常会将其合并为一个提交。

另一个常见场景是 合并分支

比如我们从主分支(如 master)拉出一个 feature 分支,进行了 20 次提交。在将 feature 分支合并回 master 时,我们希望将这 20 次提交合并为一个,以保持 master 分支的简洁性。

4. 如何进行提交合并?

现代 IDE(如 IntelliJ IDEA、Eclipse)都提供了图形化界面来进行 Git 提交合并操作。例如在 IntelliJ IDEA 中,可以右键点击提交记录选择 “Squash Commits”:

intellij git

不过本文重点讲解通过 Git 命令行进行提交合并的两种方式:

交互式变基(Interactive Rebase)
合并时使用 --squash 选项

5. 使用交互式变基合并提交

准备工作

我们先定义一个 Git 别名 slog 来方便查看提交日志:

git config --global alias.slog "log --graph --all --topo-order --pretty='format:%h %ai %s%d (%an)'"

示例提交历史如下:

$ git slog
* ac7dd5f 2021-08-23 23:29:15 +0200 Commit D (HEAD -> master) (Kai Yuan)
* 5de0b6f 2021-08-23 23:29:08 +0200 Commit C (Kai Yuan)
* 54a204d 2021-08-23 23:29:02 +0200 Commit B (Kai Yuan)
* c407062 2021-08-23 23:28:56 +0200 Commit A (Kai Yuan)
* 29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
* 34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
* cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

5.1 合并最近 X 个提交

语法如下:

git rebase -i HEAD~[X]

比如我们想合并最近 4 个提交:

git rebase -i HEAD~4

执行后 Git 会打开默认编辑器,列出这四个提交,默认都是 pick

rebase

我们将后三个提交改为 squashs

rebase1

保存退出后,Git 会自动进行变基操作:

$ git rebase -i HEAD~4
[detached HEAD f9a9cd5] Commit A
 Date: Mon Aug 23 23:28:56 2021 +0200
 1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/master.

查看提交日志可以看到这四个提交已被合并为一个:

$ git slog
* f9a9cd5 2021-08-23 23:28:56 +0200 Commit A (HEAD -> master) (Kai Yuan)
* 29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
* 34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
* cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

查看完整日志可以看到合并后的提交信息:

$ git log -1
commit f9a9cd50a0d11b6312ba4e6308698bea46e10cf1 (HEAD -> master)
Author: Kai Yuan
Date:   2021-08-23 23:28:56 +0200

    Commit A
    
    Commit B
    
    Commit C
    
    Commit D

5.2 当 X 较大时

如果提交数太多,手动数 HEAD~X 容易出错。这时可以使用以下方式:

找到你想合并到的提交哈希值(比如 29976c5),然后执行:

git rebase -i 29976c5

然后将后续所有提交改为 squash 即可完成合并。

⚠️ 注意:如果你合并的是已经推送到远程仓库的提交,你需要使用 git push --force 强制推送。

但要小心使用 --force,因为它会覆盖远程分支,可能影响其他开发者。

推荐做法:

  • 设置 push.default=current,只推送当前分支
  • 或者使用 git push origin +feature,加 + 号表示强制推送该分支

6. 使用 --squash 合并分支

当我们开发完一个 feature 分支,并希望将其合并回 master 分支时,可以使用 git merge --squash 将 feature 分支的所有提交合并为一个提交。

示例

假设我们有如下提交历史:

$ git slog
* 0ff435a 2021-08-24 15:28:07 +0200 finally, it works. phew! (HEAD -> feature) (Kai Yuan)
* cb5fc72 2021-08-24 15:27:47 +0200 fix a typo (Kai Yuan)
* 251f01c 2021-08-24 15:27:38 +0200 fix a bug (Kai Yuan)
* e8e53d7 2021-08-24 15:27:13 +0200 implement Feature2 (Kai Yuan)
| * 204b03f 2021-08-24 15:30:29 +0200 Urgent HotFix2 (master) (Kai Yuan)
| * 8a58dd4 2021-08-24 15:30:15 +0200 Urgent HotFix1 (Kai Yuan)
|/  
* 172d2ed 2021-08-23 23:28:56 +0200 BugFix #2 (Kai Yuan)
* 29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
* 34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
* cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

我们现在想将 feature 分支合并到 master,并只保留一个提交:

$ git checkout master
Switched to branch 'master'

$ git merge --squash feature
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requested

此时 Git 会将 feature 分支的所有修改变成当前工作区的改动:

$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    modified:   readme.md

最后我们手动提交即可:

$ git commit -am'Squashed and merged the Feature2 branch'
[master 565b254] Squashed and merged the Feature2 branch
 1 file changed, 4 insertions(+)

查看合并后的提交历史:

$ git slog
* 565b254 2021-08-24 15:53:05 +0200 Squashed and merged the Feature2 branch (HEAD -> master) (Kai Yuan)
* 204b03f 2021-08-24 15:30:29 +0200 Urgent HotFix2 (Kai Yuan)
* 8a58dd4 2021-08-24 15:30:15 +0200 Urgent HotFix1 (Kai Yuan)
| * 0ff435a 2021-08-24 15:28:07 +0200 finally, it works. phew! (feature) (Kai Yuan)
| * cb5fc72 2021-08-24 15:27:47 +0200 fix a typo (Kai Yuan)
| * 251f01c 2021-08-24 15:27:38 +0200 fix a bug (Kai Yuan)
| * e8e53d7 2021-08-24 15:27:13 +0200 implement Feature2 (Kai Yuan)
|/  
* 172d2ed 2021-08-23 23:28:56 +0200 BugFix #2 (Kai Yuan)
* 29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
* 34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
* cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

可以看到 feature 分支的改动已合并到 master,并且 master 分支只多了一个提交。

7. 小结

本文介绍了 Git 中的提交合并(squash)操作:

  • ✅ 什么是 Git squash
  • ✅ 何时使用 squash
  • ✅ 如何使用交互式变基合并提交
  • ✅ 如何使用 merge --squash 合并分支

合理使用 squash 能让我们的 Git 提交历史更清晰、更易于维护。特别是在合并 feature 分支时,推荐使用 git merge --squash 保持主分支干净整洁。


原始标题:Squash the Last X Commits Using Git