如何使用 Git Squash 合并最近 N 次提交?

Git Squash:合并最近 N 次提交的详尽指南

在 Git 的日常使用中,我们经常会进行多次细小的提交。这些提交可能只是一些微小的修改、调试信息的添加、或者代码格式的调整。虽然这些细小的提交在开发过程中很有帮助,可以记录我们的工作轨迹,但在将代码合并到主分支(如 mainmaster)之前,通常希望将这些提交合并成一个更干净、更有意义的提交。这样做的好处有很多:

  • 保持提交历史的整洁: 一个干净的提交历史更容易阅读和理解,方便团队成员回顾代码变更。
  • 简化代码审查: 代码审查者可以一次性审查多个相关的更改,而不是被大量的细小提交淹没。
  • 方便回滚: 如果发现问题,回滚一个合并后的提交比回滚多个细小的提交更容易。
  • 避免不必要的噪音: 细小的提交(如修复拼写错误、调整代码格式)通常不值得单独出现在提交历史中。

Git 提供了多种方式来合并提交,其中 git squash 是最常用的方法之一。本文将深入探讨 git squash 的各种用法,并通过详细的示例来演示如何使用它来合并最近 N 次提交。

1. 什么是 Git Squash?

git squash 并不是一个独立的 Git 命令,而是一种合并提交的策略。它通常通过 git rebase -i(交互式变基)命令来实现。squash 的含义是将多个提交“压缩”成一个提交。

在交互式变基过程中,你可以选择将一个或多个提交标记为 squash(或简写为 s)。Git 会将这些标记为 squash 的提交合并到它们之前的那个提交中。最终,这些提交会变成一个单独的提交,包含所有被合并提交的更改。

2. git rebase -i 的基本用法

git rebase -i 是实现 git squash 的关键命令。它的基本语法如下:

bash
git rebase -i <commit-ish>

<commit-ish> 是一个提交引用,它可以是一个提交哈希、分支名、标签,或者一个相对引用(如 HEAD~3 表示当前提交的前三个提交)。

执行 git rebase -i <commit-ish> 后,Git 会打开一个文本编辑器(通常是 Vim 或 Nano,取决于你的 Git 配置),显示一个提交列表。这个列表包含了从 <commit-ish> 到当前提交之间的所有提交。

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

```
commit ddddddd (HEAD -> feature/my-feature)
Author: Your Name your.email@example.com
Date: Tue Oct 24 10:00:00 2023 +0800

Fix: Minor typo in README.md

commit ccccccc
Author: Your Name your.email@example.com
Date: Tue Oct 24 09:50:00 2023 +0800

Refactor: Extract function for better readability

commit bbbbbbb
Author: Your Name your.email@example.com
Date: Tue Oct 24 09:40:00 2023 +0800

Fix: Bug in user authentication

commit aaaaaaa
Author: Your Name your.email@example.com
Date: Tue Oct 24 09:30:00 2023 +0800

Add: New feature implementation

```

如果我们执行 git rebase -i HEAD~3,Git 会打开一个编辑器,显示以下内容:

```
pick aaaaaaa Add: New feature implementation
pick bbbbbbb Fix: Bug in user authentication
pick ccccccc Refactor: Extract function for better readability
pick ddddddd Fix: Minor typo in README.md

Rebase 7890123..ddddddd onto 7890123 (4 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 log message

x, exec = run command (the rest of the line) using shell

b, break = stop here (continue rebase later with 'git rebase --continue')

d, drop = remove commit

l, label

t, reset

m, merge [-C | -c ]

. create a merge commit using the original merge commit's

. message (or the oneline, if no original merge commit was

. specified). Use -c to reword the commit message.

These lines can be re-ordered; they are executed from top to bottom.

If you remove a line here THAT COMMIT WILL BE LOST.

However, if you remove everything, the rebase will be aborted.

Note that empty commits are commented out

```

在这个编辑器中,你可以修改每一行开头的命令来改变 Git 的行为。默认情况下,所有提交都使用 pick 命令,表示使用该提交。

3. 使用 squash 合并提交

要合并提交,我们需要将 pick 命令替换为 squash(或 s)。例如,如果我们想将最近的三个提交(dddddddcccccccbbbbbbb)合并到 aaaaaaa 提交中,我们可以将编辑器中的内容修改为:

```
pick aaaaaaa Add: New feature implementation
squash bbbbbbb Fix: Bug in user authentication
squash ccccccc Refactor: Extract function for better readability
squash ddddddd Fix: Minor typo in README.md

Rebase 7890123..ddddddd onto 7890123 (4 commands)

... (其余部分省略)

```

修改完成后,保存并关闭编辑器。Git 会按照你指定的顺序执行这些命令。它会首先应用 aaaaaaa 提交,然后将 bbbbbbbcccccccddddddd 提交合并到 aaaaaaa 提交中。

在合并过程中,Git 会再次打开一个编辑器,让你编辑最终的提交信息。这个编辑器会包含所有被合并提交的提交信息,你可以对它们进行修改,整合成一个更简洁、更有意义的提交信息。

例如,你可以将提交信息修改为:

```
Add: New feature implementation

This commit includes the following changes:

  • Fix: Bug in user authentication
  • Refactor: Extract function for better readability
  • Fix: Minor typo in README.md
    ```

保存并关闭编辑器后,Git 会完成合并操作,并创建一个新的提交,包含所有被合并提交的更改。新的提交历史如下:

```
commit fffffff (HEAD -> feature/my-feature)
Author: Your Name your.email@example.com
Date: Tue Oct 24 10:10:00 2023 +0800

Add: New feature implementation

This commit includes the following changes:

- Fix: Bug in user authentication
- Refactor: Extract function for better readability
- Fix: Minor typo in README.md

commit aaaaaaa
Author: Your Name your.email@example.com
Date: Tue Oct 24 09:30:00 2023 +0800
... (之前的提交)
```

可以看到,bbbbbbbcccccccddddddd 提交已经消失了,它们的更改都被合并到了一个新的提交 fffffff 中。

4. 合并最近 N 次提交的通用步骤

总结一下,合并最近 N 次提交的通用步骤如下:

  1. 确定要合并的提交数量 N。
  2. 执行 git rebase -i HEAD~N 命令。 例如,要合并最近 5 次提交,执行 git rebase -i HEAD~5
  3. 在编辑器中,将除了第一个提交之外的其他提交的 pick 命令改为 squash(或 s)。
  4. 保存并关闭编辑器。
  5. 在下一个编辑器中,编辑最终的提交信息。 将所有被合并提交的提交信息整合成一个简洁、有意义的提交信息。
  6. 保存并关闭编辑器。

5. fixup 命令:丢弃提交信息

除了 squash 命令,还有一个类似的命令 fixup(或 f)。fixup 的作用与 squash 类似,都是将提交合并到前一个提交中。但与 squash 不同的是,fixup 会丢弃被合并提交的提交信息。

如果你有一些提交只是修复了之前提交的小错误,或者是一些不值得保留提交信息的更改,可以使用 fixup 命令。

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

```
commit eeeeee (HEAD -> feature/my-feature)
Author: Your Name your.email@example.com
Date: Tue Oct 24 11:00:00 2023 +0800

Fix: Another typo

commit ddddddd
Author: Your Name your.email@example.com
Date: Tue Oct 24 10:50:00 2023 +0800

Fix: Typo

commit ccccccc
Author: Your Name your.email@example.com
Date: Tue Oct 24 10:40:00 2023 +0800

Add: New feature

```

如果我们想将 eeeeeeeddddddd 提交合并到 ccccccc 提交中,并且丢弃 eeeeeeeddddddd 的提交信息,我们可以执行 git rebase -i HEAD~3,然后将编辑器中的内容修改为:

```
pick ccccccc Add: New feature
fixup ddddddd Fix: Typo
fixup eeeeee Fix: Another typo

Rebase 7890123..eeeeeee onto 7890123 (3 commands)

... (其余部分省略)

``
保存并关闭编辑器后,Git 会自动完成合并操作,并使用
ccccccc提交的提交信息作为最终的提交信息。dddddddeeeeeee`的提交信息会被丢弃, 不会出现在最终的提交信息中。

6. 注意事项和常见问题

  • 不要对已推送到远程仓库的提交进行变基操作。 变基会改变提交历史,如果你对已推送到远程仓库的提交进行变基,会导致其他人的本地仓库与远程仓库不一致,造成混乱。
  • 在变基过程中,可能会出现冲突。 如果多个提交修改了同一文件的同一部分,Git 无法自动合并它们,会产生冲突。你需要手动解决冲突,然后执行 git rebase --continue 继续变基操作。
  • 如果变基过程中出现错误,可以使用 git rebase --abort 中止变基操作。 这会撤销所有变基操作,回到变基前的状态。
  • 如果你不确定HEAD~N会涵盖哪些提交, 可以先使用git log --oneline -n来查看最近N次提交. 这可以帮助你更精确地控制rebase的范围. 例如: git log --oneline -5 会显示最近5次提交的简略信息.
  • 在进行复杂的rebase操作之前,建议先创建一个备份分支。 这样,即使rebase操作出现问题,你也可以轻松地恢复到之前的状态。例如:git branch backup-branch

7. Squash 合并与 Merge 合并的区别

Git 中还有另一种合并分支的方式:git mergegit mergegit squash 的主要区别在于:

  • git merge 会创建一个新的合并提交。 这个合并提交有两个父提交,分别指向被合并的两个分支的最新提交。
  • git squash 会将多个提交合并成一个提交。 它不会创建新的合并提交,而是修改现有的提交历史。

一般来说,git squash 更适合用于合并本地开发分支的提交,保持提交历史的整洁。git merge 更适合用于合并不同的功能分支或发布分支,保留完整的合并记录。

8. 实际案例分析

案例 1:合并多个小的修复提交

假设你在开发一个新功能时,提交了以下几次提交:

```
commit aaaaaaa (HEAD -> feature/new-feature)
Author: Your Name your.email@example.com
Date: Tue Oct 24 12:00:00 2023 +0800

Fix: Minor bug in user input validation

commit bbbbbbb
Author: Your Name your.email@example.com
Date: Tue Oct 24 11:50:00 2023 +0800

Fix: Typo in error message

commit ccccccc
Author: Your Name your.email@example.com
Date: Tue Oct 24 11:40:00 2023 +0800

Add: New feature implementation

``aaaaaaabbbbbbb提交都是对ccccccc提交的修复。为了保持提交历史的整洁,我们可以将它们合并到ccccccc` 提交中。

执行 git rebase -i HEAD~3,将编辑器中的内容修改为:

pick ccccccc Add: New feature implementation
squash bbbbbbb Fix: Typo in error message
squash aaaaaaa Fix: Minor bug in user input validation

保存并关闭编辑器。在下一个编辑器中,编辑最终的提交信息:

```
Add: New feature implementation

This commit includes the following fixes:

  • Fix: Typo in error message
  • Fix: Minor bug in user input validation
    ```

保存并关闭编辑器。最终的提交历史如下:

```
commit ddddddd (HEAD -> feature/new-feature)
Author: Your Name your.email@example.com
Date: Tue Oct 24 12:10:00 2023 +0800

Add: New feature implementation

This commit includes the following fixes:

- Fix: Typo in error message
- Fix: Minor bug in user input validation

```

案例 2:合并多个功能相关的提交

假设你在开发一个新功能时,提交了以下几次提交:

```
commit aaaaaaa (HEAD -> feature/user-profile)
Author: Your Name your.email@example.com
Date: Tue Oct 24 13:00:00 2023 +0800

Add: User profile page layout

commit bbbbbbb
Author: Your Name your.email@example.com
Date: Tue Oct 24 12:50:00 2023 +0800

Add: User profile data fetching

commit ccccccc
Author: Your Name your.email@example.com
Date: Tue Oct 24 12:40:00 2023 +0800

Add: User profile API endpoint

```

这三个提交都与用户个人资料功能相关。为了将它们合并成一个更有意义的提交,我们可以执行 git rebase -i HEAD~3,将编辑器中的内容修改为:

pick ccccccc Add: User profile API endpoint
squash bbbbbbb Add: User profile data fetching
squash aaaaaaa Add: User profile page layout

保存并关闭编辑器。在下一个编辑器中,编辑最终的提交信息:

```
Add: User profile feature

This commit implements the user profile feature, including:

  • API endpoint for retrieving user profile data
  • Data fetching logic for populating the user profile page
  • User profile page layout and styling
    ```
    保存并关闭编辑器。最终的提交历史将变为一个单一的, 描述完整的"Add: User profile feature"提交.

9. 总结

git squash 是一种强大的工具,可以帮助你合并最近 N 次提交,保持提交历史的整洁和易读性。通过 git rebase -i 命令,你可以灵活地选择要合并的提交,并编辑最终的提交信息。

掌握 git squash 的用法,可以提高你的 Git 使用效率,让你的代码仓库更易于维护和协作。但是,请记住,不要对已推送到远程仓库的提交进行变基操作,以免造成不必要的麻烦。

希望本文能够帮助你更好地理解和使用 git squash。如果你有任何问题或建议,欢迎留言讨论。

THE END