Git 中撤销更改:Reset、Restore、Revert 详解
Git 是一个强大的版本控制系统,允许您跟踪代码库的更改。然而,错误总会发生,您可能需要撤销更改。Git 提供了几个命令来帮助实现这一点,但它们的功能和对仓库的影响方式截然不同。理解 git reset、git restore 和 git revert 之间的细微差别,对于有效管理项目历史和纠正错误而不会产生意外后果至关重要。
本文将揭开这三个命令的神秘面纱,解释它们的目的、工作原理以及何时使用每一个。我们将利用官方 Git 文档和常见用法模式,提供清晰的解释和实用的示例。阅读完本指南后,您将能够自信地撤销更改、管理暂存的文件,并维护一个干净、连贯的提交历史。
理解核心概念
在深入研究这些命令之前,了解一些 Git 概念非常重要:
- 工作目录 (Working Directory): 您当前正在编辑的文件。
- 暂存区 (Staging Area / Index): 在提交更改之前准备更改的存放区域。
git add将更改从工作目录移动到暂存区。 - 提交历史 (Commit History): 项目随时间变化的快照序列,由提交表示。
- HEAD: 指向当前分支上最新提交的指针。
这三个命令主要与这些区域交互,以修改或丢弃更改。
git restore:用于丢弃工作目录和暂存区中的更改
git restore 命令是 Git 中较新引入的,旨在完成撤销工作目录或取消暂存文件的直接任务。对于这些特定的操作,它通常比 git reset 更安全、更直观。
取消暂存文件
如果您不小心使用 git add 暂存了文件并想取消暂存,git restore 是要使用的命令。它将更改从暂存区移回工作目录,但不会丢弃修改本身。
-
取消暂存特定文件:
bash git restore <file>
此命令从索引(暂存区)获取文件的版本并将其放回索引中。本质上,它将文件从暂存状态移除,以便进行下一次提交,但更改仍保留在您的工作目录中。 -
取消暂存所有文件:
虽然没有像git reset那样直接的git restore .来取消暂存所有内容,但您通常会针对单个文件使用git restore,或者在需要时与其他命令结合使用。然而,最常见的用例是取消暂存特定文件。
丢弃工作目录中的更改
git restore 也可用于丢弃特定文件在工作目录中的所有未暂存更改,将其恢复到暂存区(索引)或最后提交时的版本。
-
丢弃文件中的未暂存更改:
bash git restore <file>
(注意:此命令可能因上下文而具有两种含义。在不带--staged参数的情况下使用时,它主要针对工作树。如果文件被暂存,它会取消暂存该文件。如果文件在工作树中被修改但未暂存,它会将工作树文件恢复到与索引匹配的状态。) -
丢弃文件的暂存和未暂存更改(恢复到 HEAD):
要完全丢弃文件的所有更改(暂存和未暂存),并将其恢复到HEAD提交时的状态:
bash git restore --staged --worktree <file>
这是一个强大的命令,可有效地将文件重置为其最后提交的状态。
从特定提交中恢复文件
git restore 也可以在不更改分支历史的情况下,从过去的提交中检索特定文件的版本。
git restore <file> --source <commit>
将 <commit> 替换为提交哈希或符号引用,例如 HEAD~1。
git reset:重写历史
git reset 是一个更强大的命令,可以通过移动当前分支的 HEAD 指针来修改提交历史。它还可以根据使用的模式影响暂存区和工作目录。
理解模式(--soft、--mixed、--hard)
git reset 有三种主要模式:
-
--soft:将HEAD移动到指定的提交,但保持暂存区和工作目录不变。来自被重置提交的更改将作为暂存的更改出现。
bash git reset --soft HEAD^ # HEAD 回退一个提交,被撤销提交的更改被暂存 -
--mixed(默认):将HEAD移动并重置暂存区以匹配指定的提交。来自被重置提交的更改存在于工作目录中,但处于未暂存状态。
bash git reset HEAD^ # 等同于 git reset --mixed HEAD^ # HEAD 回退一个提交,被撤销提交的更改处于未暂存状态 -
--hard:将HEAD移动,并将暂存区和工作目录都重置为与指定提交匹配。这将丢弃被重置提交以及工作目录中任何后续未提交更改的所有更改。请务必谨慎使用。
bash git reset --hard HEAD~ # 丢弃上一个提交以及之后在工作目录和暂存区中的所有更改
git reset 的用例:
- 取消暂存文件:
git reset <file>是git restore --staged <file>的快捷方式,它从暂存区移除文件,而不影响工作目录。 - 取消暂存所有内容:
git reset(不带参数或指定HEAD)取消暂存所有当前暂存的更改,将它们移回工作目录(等同于git restore --staged .)。 - 撤销上一次提交:
git reset HEAD^(或git reset --soft HEAD^)常用于修改上一次提交。前一个提交的更改现在已暂存,可以与修改或新消息一起重新提交。 - 丢弃所有本地更改:
git reset --hard用于完全丢弃所有本地修改(暂存和未暂存),并将仓库恢复到特定提交。这是一个破坏性操作。
重置旧提交
如果您需要撤销不是最新提交的更改,可以使用 git reset。例如,重置到问题提交之前的一个提交:
# 示例:撤销最后 2 次提交,保留更改为未暂存状态
git reset --mixed HEAD~2
警告: git reset 会重写历史。如果您已经推送了正在重置的提交,这可能会给协作者带来严重问题。重置仅存在于本地仓库中的提交通常是安全的。
git revert:创建新提交来撤销更改
git revert 是撤销共享或已发布历史中更改的最安全方法。它不是重写历史,而是创建一个新的提交,引入之前提交的反向更改。
工作原理
当您运行 git revert <commit> 时,Git 会分析指定的提交,计算相反的更改,并将它们应用于您当前的_工作目录和暂存区_。然后,它会提示您使用一个默认消息(指示正在撤销哪个提交)创建一个新提交。
-
撤销特定提交:
bash git revert <commit-hash>
这将创建一个新提交,撤销<commit-hash>引入的更改。如果出现合并冲突,Git 将暂停并要求您在提交前解决它们。 -
撤销多个提交:
您可以撤销一个范围的提交:
bash # 撤销从 HEAD~3(不包括)到 HEAD 的提交 git revert HEAD~3..HEAD
Git 将尝试为每个指定的提交创建一个反转提交。如果在该过程中发生冲突,您需要为每次反转解决它们。
git revert 的优势:
- 保留历史: 它不会改变现有提交,因此对于公共或共享分支是安全的。
- 清晰的审计跟踪: 反转提交明确指出了撤销了什么以及原因。
- 优雅地处理合并: Git 通常可以自动撤销合并提交,尽管复杂情况下可能需要手动干预。
何时使用 git revert:
- 当您需要在已推送到远程仓库的分支上撤销更改时。
- 当您希望保持所有更改(包括更正)的清晰且不可变的记录时。
- 当撤销合并提交时。
选择正确的命令
以下是一个简单的指南,可帮助您做出决定:
- 取消暂存文件: 使用
git restore <file>或git reset <file>。 - 丢弃文件中工作目录中的未暂存更改: 使用
git restore <file>。 - 丢弃文件中所有更改(暂存和未暂存): 使用
git restore --staged --worktree <file>。 - 撤销上一次提交并保持更改暂存(用于修订): 使用
git reset --soft HEAD^。 - 撤销上一次提交并保持更改未暂存: 使用
git reset HEAD^。 - 完全丢弃上一次提交以及所有后续更改(破坏性操作): 使用
git reset --hard HEAD^。 - 在不重写历史的情况下撤销共享分支上的提交: 使用
git revert <commit-hash>。 - 丢弃整个仓库中所有本地更改(破坏性操作): 使用
git reset --hard。
结论
掌握 git reset、git restore 和 git revert 是有效使用 Git 的基础。git restore 是您安全丢弃工作目录和暂存区更改的首选。git reset 提供了强大的历史重写功能,最好用于本地、未推送的提交。git revert 提供了一种安全、保留历史记录的撤销更改方法,这在协作环境中尤为重要。通过了解它们的独特行为并选择合适的命令,您可以自信地管理项目的演变并沿途纠正任何错误。