Git Squash 实战:快速合并与整理你的提交记录 – wiki词典


Git Squash 实战:快速合并与整理你的提交记录

在日常的软件开发中,Git 已经成为版本控制的行业标准。我们经常会创建大量细碎的提交,例如“WIP”(Work In Progress)、“修复拼写错误”、“小幅调整”等。这些提交在开发过程中虽然有用,但如果直接合并到主分支,会导致项目历史变得混乱、难以阅读和维护。这时,Git Squash 就成为了一项强大的武器,它能帮助我们整理提交历史,使项目变得更加清晰、可追溯。

什么是 Git Squash?

Git Squash 的核心理念是将一系列相关的 Git 提交合并成一个单一的、更有意义的提交。想象一下,你在一个功能分支上迭代开发,进行了多次保存和修改,最终完成了一个完整的功能。通过 Squash,你可以将这些零散的、只反映部分进度的提交“压缩”成一个描述该功能完整实现的提交。

Git Squash 的主要优势:

  • 清晰简洁的提交历史: 避免了大量零碎的提交信息污染主分支,使得 git log 输出更加精炼,项目历史一目了然。
  • 提高代码审查效率: 代码审阅者可以专注于一个包含所有相关更改的逻辑单元,而不是逐个审查多个小提交,降低了认知负担。
  • 更容易回溯与撤销: 当需要撤销某个功能时,只需回滚一个 Squash 后的提交,而不是多个,大大简化了操作。
  • 更好的项目可维护性: 简洁、有条理的提交历史有助于长期维护和理解项目,尤其是当新的开发者加入时。

何时使用 Git Squash?

Git Squash 在以下场景中尤其适用:

  • 合并功能分支前: 在将一个功能分支(feature branch)合并到 maindevelop 等主分支之前,清理并整合你的工作。
  • 处理零散提交: 当你有一系列小的、迭代性的提交,如“修复拼写错误”、“进行中”、“尝试不同方法”等,可以将它们合并成一个有意义的提交。
  • 代码审查期间: 根据代码审查的反馈,你可能需要修改代码并创建新的提交。在最终合并前,可以将这些修复提交与原始功能提交 Squash 到一起。
  • 贡献开源项目: 许多开源项目要求每个功能或修复只对应一个干净、单一的提交。
  • 推送前清理本地提交: 在将本地分支推送到远程仓库之前,整理你的提交,保持远程仓库的整洁。

Git Squash 实战:使用交互式 Rebase

最常用且最灵活的 Squash 方式是使用 git rebase --interactive (或 git rebase -i) 命令。

步骤一:查看提交历史并确定 Squash 范围

首先,使用 git log --oneline 命令查看你当前分支的提交历史,以确定你想要合并的提交以及它们的父提交。

bash
git log --oneline

输出可能类似:

a1b2c3d (HEAD -> feature-branch) 添加功能A的最后部分
e4f5g6h 重构辅助函数
i7j8k9l 修复文档中的拼写错误
m0n1o2p 添加功能A的中间部分
q3r4s5t 添加功能A的初始部分
... (更早的提交)

假设我们想把 a1b2c3de4f5g6hi7j8k9l 这三个提交合并成一个。

步骤二:启动交互式 Rebase

你需要告诉 Git 从哪个提交开始进行交互式 Rebase。这通常是你想要 Squash 的第一个提交的父提交。你可以通过 HEAD~N 来指定最近的 N 个提交,或者直接使用提交的哈希值。

如果你想 Squash 最近的 3 个提交,可以这样做:

bash
git rebase -i HEAD~3

或者,使用你想保留的第一个提交(在这里是 m0n1o2p)的父提交哈希值:

bash
git rebase -i q3r4s5t

执行此命令后,Git 会打开一个文本编辑器(通常是 Vim 或你配置的默认编辑器),显示类似以下内容的提交列表:

“`
pick i7j8k9l 修复文档中的拼写错误
pick e4f5g6h 重构辅助函数
pick a1b2c3d 添加功能A的最后部分

Rebase q3r4s5t..a1b2c3d onto q3r4s5t (3 commands)

Commands:

p, pick = 使用提交

r, reword = 使用提交,但编辑提交信息

e, edit = 使用提交,但暂停修改内容

s, squash = 使用提交,但与前一个提交合并

f, fixup = 类似于 “squash”, 但丢弃此提交的日志信息

x, exec = 运行shell命令

d, drop = 删除提交

… (其他命令说明)

“`

步骤三:编辑 Rebase 指令

在编辑器中,你需要修改每个提交前的命令。我们的目标是将 e4f5g6ha1b2c3d 合并到 i7j8k9l 中。

  • 将第一个你希望保留的提交标记为 pick
  • 将所有你希望合并到 pick 提交中的后续提交标记为 squashfixup

    • squash (s): 将此提交与前一个提交合并。Git 会在合并后让你编辑新的提交信息,其中会包含所有被 Squash 提交的原始信息。
    • fixup (f): 类似于 squash,但它会丢弃当前提交的日志信息,只使用前一个提交的日志信息。适用于那些你不想保留其独立提交信息的微小修改。

示例:e4f5g6ha1b2c3d 合并到 i7j8k9l 中。

pick i7j8k9l 修复文档中的拼写错误
squash e4f5g6h 重构辅助函数
squash a1b2c3d 添加功能A的最后部分

如果你不希望保留 e4f5g6ha1b2c3d 的提交信息,可以直接使用 fixup

pick i7j8k9l 修复文档中的拼写错误
fixup e4f5g6h 重构辅助函数
fixup a1b2c3d 添加功能A的最后部分

步骤四:保存并关闭编辑器

修改完成后,保存文件并关闭编辑器。Git 将开始执行 Rebase 操作。

步骤五:编辑合并后的提交信息

如果使用了 squash,Git 会再次打开一个编辑器,其中包含所有被合并提交的原始提交信息。你需要编辑这些信息,创建一个清晰、简洁且有意义的合并提交信息。删除你认为不必要的行,并总结所有更改。

“`

This is a combination of 3 commits.

The first commit’s message is:

修复文档中的拼写错误

This is the 2nd commit message:

重构辅助函数

This is the 3rd commit message:

添加功能A的最后部分

请输入你的提交信息。以 ‘#’ 开头的行将被忽略,空消息将中止提交。

“`

将其修改为一个有意义的总结性提交信息:

“`
feat: 完成功能A的开发,包括核心逻辑、辅助函数重构及文档更新

  • 增加了功能A的核心逻辑。
  • 重构了相关辅助函数,提高了可读性。
  • 更新了文档,修复了拼写错误。
    “`

保存并关闭编辑器。

步骤六:完成 Rebase

如果一切顺利,Git 会提示 Rebase 成功。现在,你的提交历史将变得更加简洁。

bash
git log --oneline

输出:

new_hash (HEAD -> feature-branch) feat: 完成功能A的开发,包括核心逻辑、辅助函数重构及文档更新
q3r4s5t 添加功能A的初始部分
... (更早的提交)

替代方案:git merge --squash

除了交互式 Rebase,你还可以使用 git merge --squash 命令来合并一个分支的所有提交,并将其作为单个提交应用到当前分支。这通常用于将一个功能分支的所有工作合并到主分支,但保留一个干净的提交记录。

  1. 切换到目标分支: 首先,切换到你希望接收 Squash 提交的目标分支(例如 maindevelop)。

    bash
    git checkout main

  2. 执行 Squash 合并: 执行 Squash 合并命令。

    bash
    git merge --squash feature-branch

    这条命令会将 feature-branch 上所有提交的更改应用到 main 分支的工作区,但不会自动创建合并提交。所有的更改都将暂存起来。

  3. 手动提交更改: 现在,你需要手动创建一个提交来封装这些更改。

    bash
    git commit -m "feat: 合并功能分支 feature-branch 到主分支"

    这将创建一个新的提交,其中包含 feature-branch 上所有更改的合并结果。

强制推送 (git push --forcegit push --force-with-lease)

由于 Squash 操作会重写提交历史,如果你已经将原始的、未经 Squash 的提交推送到远程仓库,那么在本地 Squash 后,你需要使用强制推送才能更新远程分支。

bash
git push --force

或者,更安全的选择是:
bash
git push --force-with-lease

重要警告:

  • 不要在共享分支上随意强制推送! 强制推送会覆盖远程历史,如果其他团队成员正在基于旧的历史工作,可能会导致他们的工作丢失或出现复杂的冲突。在进行强制推送之前,务必与团队成员沟通并获得同意。
  • git push --force-with-lease 是一个更安全的强制推送选项。它会检查远程分支是否在你上次拉取后被其他人更新过。如果被更新过,它会拒绝推送,从而避免覆盖他人的工作,降低了风险。

最佳实践和注意事项

  • 只 Squash 相关提交: 确保你合并的提交逻辑上属于同一个功能或修复,避免将不相关的更改混入一个提交。
  • 在本地或未共享的分支上 Squash: 最好在你的本地分支上进行 Squash,或者在尚未推送到共享远程仓库的分支上进行。一旦提交被推送到远程并被他人拉取,重写历史将变得复杂且危险。
  • 创建有意义的提交信息: 合并后的提交信息应该清晰、简洁地描述所有被 Squash 提交所代表的完整变更,而不仅仅是其中某个小步骤。
  • 与团队沟通: 如果你在团队环境中工作,请确保团队对 Squash 的使用有共识,并了解何时以及如何使用它。
  • 先练习: 在重要的项目上使用 Squash 之前,可以在一个测试仓库中进行练习,熟悉操作流程,避免误操作。
  • 备份分支: 在进行任何可能重写历史的操作(如 Rebase)之前,可以创建一个备份分支(例如 git branch backup-branch),以防万一出现问题,你可以回滚到之前的状态。
  • 何时不 Squash: 如果每个单独的提交都具有重要的历史价值,例如它们代表了重要的里程碑、独立的逻辑步骤,或者对于调试和审查非常有用,那么可能不应该 Squash。

总结

Git Squash 是一项强大的 Git 功能,能够帮助你将零散的提交整合成干净、有逻辑的提交,从而大大优化你的项目提交历史。通过熟练掌握交互式 Rebase 和 git merge --squash 等工具,你可以更好地管理代码,提高团队协作效率,并使项目更易于理解和维护。在享受 Squash 带来的便利的同时,务必牢记其对历史的改写特性,并谨慎在共享仓库中使用强制推送。


滚动至顶部