高级Git历史编辑:修改提交与交互式变基

使用 git commit --amend 和交互式变基清理本地历史,避免干扰共享分支。

高级Git历史编辑:修改提交与交互式变基

高级Git历史编辑能帮助你在提交成为共享历史的一部分之前清理它们。掌握修改提交和交互式变基后,你可以修复小错误、合并杂乱的提交,并为代码审查呈现更清晰的故事。

关键规则很简单:编辑本地历史是正常的,但重写共享历史需要谨慎。这些命令之所以强大,是因为它们会更改提交ID,而不仅仅是提交信息。

Git历史编辑实际改变了什么

每个Git提交的ID基于其内容、元数据、父提交、作者、日期和信息。如果你修改一个提交或在变基期间重写它,Git会创建一个具有新ID的新提交。

当提交仅存在于你的机器上时,这没问题。但当其他人已经拉取了旧提交时,这就存在风险。他们的分支可能仍指向旧历史,而你的分支指向重写后的历史。

历史编辑适用于:

  • 修复最新提交信息中的拼写错误。
  • 向最后一个本地提交添加遗漏的文件。
  • 在发起拉取请求前压缩多个工作进展中的提交。
  • 重新排序本地提交,使相关更改更易于审查。
  • 将一个大的本地提交拆分为多个逻辑清晰的提交。

避免在以下分支上进行历史编辑:

  • 主分支。
  • 发布分支。
  • 多人使用的共享功能分支。
  • 任何带有部署标签或审计敏感提交的分支。

开始之前,检查你当前所在的分支以及已推送的内容:

git status
git branch --show-current
git log --oneline --decorate -5

如果不确定重写是否安全,创建一个备份分支:

git branch backup-before-rebase

如果变基出现问题,该分支能为你提供一个已知的恢复点。

修改最新提交

git commit --amend 会用新提交替换最近一次提交。这是在其他人依赖它之前修复最后一次提交的最快方法。

仅编辑提交信息:

git commit --amend

Git会打开编辑器显示当前信息。保存更正后的信息,Git会创建一个替换提交。

向最后一次提交添加遗漏的文件:

git add path/to/file
git commit --amend --no-edit

--no-edit 标志保留现有信息。当提交信息已经正确,而你只是忘记暂存一个更改时,这很有用。

以下是一个常见场景。你提交了一个Dockerfile更新,然后注意到忘记了相关的.dockerignore更改:

git add Dockerfile
git commit -m "Improve Docker build caching"
git add .dockerignore
git commit --amend --no-edit

现在分支上只有一个干净的提交,而不是第二个写着“忘记文件”的提交。这使审查更容易。

如果原始提交已经推送,远程分支仍然有旧的提交ID。推送修改后的提交需要强制更新:

git push --force-with-lease

优先使用 --force-with-lease 而不是 --force。如果自上次拉取后有人推送了新工作,它会拒绝覆盖远程分支。

使用交互式变基清理多个提交

交互式变基允许你一次编辑多个提交。你选择一个范围,然后Git会打开一个待办列表,你可以在其中选择、重写、压缩、修复、重新排序或停止提交。

编辑最后五个提交:

git rebase -i HEAD~5

你会看到类似这样的列表:

pick a1b2c3d Add deployment script
pick b2c3d4e Fix typo
pick c3d4e5f Add health check
pick d4e5f6a Update docs
pick e5f6a7b Adjust timeout

更改每行开头的命令来控制发生什么。常见选项有:

  • pick:保持提交不变。
  • reword:保留更改但编辑信息。
  • squash:将此提交合并到前一个提交并编辑合并后的信息。
  • fixup:将此提交合并到前一个提交并丢弃此提交的信息。
  • edit:在此提交处停止,以便更改其内容。
  • drop:删除提交。

例如,如果“Fix typo”属于“Add deployment script”,更改第二行:

pick a1b2c3d Add deployment script
fixup b2c3d4e Fix typo
pick c3d4e5f Add health check
pick d4e5f6a Update docs
pick e5f6a7b Adjust timeout

保存并关闭编辑器。Git会重放提交并将拼写错误修复合并到较早的提交中。

交互式变基也有助于在拉取请求前改进提交信息。如果三个提交都写着“update”,使用 reword 将它们改为解释实际更改的信息。

如果出现冲突,像处理普通合并冲突一样解决它:

git status
git add resolved-file
git rebase --continue

如果你决定变基不值得:

git rebase --abort

这会将分支恢复到变基开始前的状态。

在变基期间拆分提交

有时一个提交包含两个不相关的更改。例如,你可能有一个提交同时更新了Kubernetes清单并修复了README部分。这些更改可能应该分开。

启动交互式变基:

git rebase -i HEAD~3

pick 改为 edit 用于你想要拆分的提交。当Git在该提交处停止时,重置它同时将更改保留在工作树中:

git reset HEAD^

现在暂存并提交第一个逻辑部分:

git add k8s/deployment.yaml
git commit -m "Update deployment health check"

然后暂存并提交第二部分:

git add README.md
git commit -m "Document health check behavior"

继续变基:

git rebase --continue

这将一个宽泛的提交变成了两个聚焦的提交。审查者可以理解每个更改,而无需在心理上分离不相关的工作。

何时在重写前寻求帮助

在重写其他人使用的任何分支之前,请寻求帮助。团队领导、发布工程师或仓库维护者可以告诉你该分支是否可以安全重写,以及团队如何处理强制推送。

如果变基影响到已经部署、标记或被事件报告引用的提交,请立即寻求帮助。在这些情况下,保留历史通常比让它看起来整洁更重要。

对于常规功能分支,历史编辑是正常的清理步骤。对最新提交使用 git commit --amend,对小的本地系列使用交互式变基,当你必须更新自己拥有的远程分支时使用 --force-with-lease