如何安全地撤销 Git 中的本地和远程提交

掌握基本的 Git 版本控制技术,安全地管理和纠正本地及远程错误。本指南详细介绍了 `git reset`(用于使用 soft、mixed 或 hard 模式重写本地历史)与 `git revert`(用于安全地撤销共享提交)之间的区别。学习如何利用 `git reflog` 作为您最终的本地安全网,并了解强制推送的最佳实践。

31 浏览量

如何在 Git 中安全地撤销本地和远程提交

在使用 Git 工作时,纠正错误的能力是确保开发流程顺畅的基础。无论是提交得太早、意外包含了敏感数据,还是仅仅需要回溯一系列更改,了解如何安全地撤销本地和远程操作都至关重要。本指南将解析用于版本控制清理的主要工具:git resetgit revertgit reflog。掌握这些命令可以让你自信地管理历史记录,确保你能够擦除或撤销更改,而不会丢失宝贵的工作。

区分修改本地历史(通常是安全的)和重写共享的远程历史(可能给协作者带来重大问题)是至关重要的。我们将重点介绍针对这两种场景的最安全方法。

了解用于恢复更改的核心工具

Git 提供了几种机制来处理你想删除或修改的提交。工具的选择完全取决于该提交是否已被推送到共享仓库,以及你是想将该提交完全从历史记录中擦除,还是想引入一个新的提交来抵消其效果。

1. git reset:重写本地历史

git reset 是操纵本地提交历史最强大(也可能最危险)的工具。它将当前分支指针(HEAD)移动到不同的提交。git reset 的关键在于它如何处理暂存区和工作目录,这由 --soft--mixed--hard 选项控制。

git reset 的模式

模式 对 HEAD 的影响 对暂存区(Index)的影响 对工作目录的影响
--soft 移动 HEAD 指针。 不变。 不变。
--mixed (默认) 移动 HEAD 指针。 重置以匹配新的 HEAD。 不变。
--hard 移动 HEAD 指针。 重置以匹配新的 HEAD。 重置以匹配新的 HEAD(危险:未提交的更改会丢失)。

使用场景: 当你意识到某些提交是本地进行的,但不应该被提交时,或者当你想取消暂存更改但将其保留在工作目录中时,使用 git reset

git reset 的实际示例

A. 取消提交(保留暂存的更改):
如果你进行了提交,但意识到在推送前还需要添加更多文件,请使用 --soft

# 将 HEAD 向后移动一个提交,但保留暂存的更改(准备好添加到下一个提交中)
git reset --soft HEAD~1

B. 取消暂存并保留本地更改:
如果你进行了提交,现在想取消暂存所有内容,但保留工作目录中的文件修改:

# 移动 HEAD 并取消暂存更改,但保留工作区中已修改的文件
git reset --mixed HEAD~1
# 或直接使用:
git reset HEAD~1

C. 完全擦除(对于最近的提交是危险的):
如果你想完全丢弃上次提交以及自上次提交以来所做的所有本地修改(返回到上一个提交的状态):

# 警告:这将丢弃自指定提交以来的所有工作。
git reset --hard HEAD~1

⚠️ 最佳实践警告: 永远不要对已推送到共享远程仓库的提交使用 git reset --hard,除非你绝对确定没有其他人基于这些提交进行了工作。重写共享历史记录会给协作者带来麻烦。

2. git revert:安全地撤销已推送的提交

git revert 是撤销已公开共享的更改的首选方法。它不是重写历史记录,而是创建一个提交,专门用于撤销特定先前提交引入的更改。历史记录保持完整,使其对协作友好。

使用场景: 当你需要回退远程服务器上已有的更改时(例如,某个功能引入了 bug),请使用 git revert

git revert 的实际示例

假设提交哈希 a1b2c3d4 引入了一个 bug。要创建撤销其效果的提交:

# 创建一个新的提交,用于反转 a1b2c3d4 中引入的更改
git revert a1b2c3d4

# 如果你不想打开编辑器编辑恢复提交信息:
git revert -n a1b2c3d4
# 然后手动提交更改
git commit -m "Revert: Fixed issue introduced by a1b2c3d4"

3. git reflog:安全网

如果你执行了破坏性的 git reset --hard,然后意识到刚刚删除了数小时的工作,会发生什么?这就是 git reflog 发挥作用的地方。引用日志 (reflog) 跟踪本地仓库中 HEAD 的每一次更改——每一次提交、重置、合并和检出。它是你本地的撤销历史记录。

使用场景: 恢复因激进的 git reset 而丢失的提交,或导航到你之前访问过的临时状态。

使用 git reflog 查看和恢复

首先,查看你 HEAD 移动的历史记录:

$ git reflog

a1b2c3d HEAD@{0}: reset: moving to HEAD~2
4f5e6d7 HEAD@{1}: commit: Finished feature X
b8a9c0d HEAD@{2}: commit: Started implementation of feature X
...

如果你不小心重置超过了提交 4f5e6d7 (它是 HEAD@{1}),你可以轻松地恢复它:

# 重置回你在进行破坏性操作前所处的状态
git reset --hard HEAD@{1}

提示: git reflog 条目通常在本地保留 90 天。它是涉及 reset 或分支删除等操作的终极本地安全网。

撤销远程更改(强制推送)

如果你使用 git reset 移除了已经推送到远程的提交,你的本地历史记录将与远程历史记录产生分歧。Git 会阻止标准的 git push,因为它涉及非快进式更新。

要将远程仓库与你新的、重写的本地历史记录同步,你必须使用强制推送。

# 使用 --force-with-lease 作为比 --force 更安全的替代方案
git push origin <branch-name> --force-with-lease

为什么要使用 --force-with-lease

--force-with-lease 比简单的 --force 选项更安全。它会检查以确保自你上次拉取以来,没有其他人向远程分支推送新的提交。如果有人在此期间确实更新了远程仓库,推送将被拒绝,从而防止你不知情地擦除他们的工作。

总结:何时使用哪个命令

选择合适的工具取决于你的提交状态和目的地:

  1. 本地、未推送的提交: 使用 git reset(soft、mixed 或 hard)来调整暂存或完全擦除历史记录。
  2. 已推送、共享的提交: 使用 git revert 来创建抵消性提交,从而保留公共历史记录。
  3. 意外的历史丢失: 使用 git reflog 来查找和恢复以前丢失的 HEAD 状态。
  4. 强制远程更新: 在本地使用 git reset 安全地重写了已推送提交的历史记录后,才使用 git push --force-with-lease