重置、签出和还原
git reset、git checkout 和 git revert 命令是您的 Git 工具箱中最有用的三个工具。它们都允许您撤销存储库中的某种变更,前两个命令可以用来操作提交或单个文件。
因为它们非常相似,所以很容易混淆在任何给定的开发场景中应该使用哪个命令。在本文中,我们将比较 git reset、git checkout 和 git revert 的最常见配置。希望您能够放心地使用这些命令中的任何一个在存储库中导航。
从每个命令对 Git 存储库的三种状态管理机制(工作目录、暂存快照和提交历史记录)的影响来考虑每个命令很有帮助。这些部分有时被称为 Git 的“三棵树”。我们在 git reset 页面深入探索这三方面。阅读本文时,请记住这些机制。
签出是将 HEAD 引用指针移动到指定提交的操作。要演示这一点,请看下方示例。

此示例演示了 main 分支上的一系列提交。HEAD 引用和 main 分支引用当前指向提交 d。现在我们来执行 git checkout b

这是对“提交历史记录”树的更新。git checkout 命令可以在提交或文件级范围中使用。文件级签出会将文件的内容变更为特定提交内容。
还原是一种操作,它接受指定的提交并创建一个新的提交,该提交与指定提交相反。git revert 只能在提交级别范围运行,没有文件级功能。
重置是一种操作,它接受指定的提交,并将“三棵树”重置为与该指定提交时存储库的状态相匹配。重置可以在与三棵树相对应的三种不同模式下调用。
签出和重置通常用于在本地或私人“撤销”。它们修改了存储库的历史记录,推送到远程共享存储库时可能会导致冲突。还原被认为是“公共撤销”的安全操作,因为它会创建新的历史记录,可以远程共享,并且不会覆盖远程团队成员可能依赖的历史记录。
Git 重置、还原与签出引用
下表总结了所有这些命令的最常见用例。一定要随身携带这份参考资料,因为在您的 Git 职业生涯中,您肯定需要用到其中一些信息。
命令 | 范围 | 常见用例 |
|---|---|---|
| 提交级别 | 丢弃私有分支中的提交或抛弃未提交的变更 |
| 文件级 | 取消暂存文件 |
| 提交级别 | 在分支之间切换或检查旧快照 |
| 文件级 | 丢弃工作目录中的变更 |
| 提交级别 | 撤销公共分支中的提交 |
| 文件级 | (不适用) |
提交级别的操作
您传递给 git reset 和 git checkout 的参数决定了它们的范围。当您没有将文件路径作为参数时,它们会对整个提交进行操作。这就是我们将在本节探讨的内容。请注意,git revert 没有文件级对应物。
重置特定提交
在提交级别上,重置是一种将分支尖端移动到另一个提交的方法。这可以用来从当前分支中移除提交。例如,以下命令将 hotfix 分支向后移动两次提交。
git checkout hotfix git reset HEAD~2hotfix 末尾的两次提交现在处于未决状态,或者是孤立的提交。这意味着下一次 Git 执行垃圾回收时,它们会被删除。换句话说,您相当于明确要丢弃这些提交。这一过程可以直观表示为下图:

使用 git reset 是一种撤销未与他人共享的变更的简单方法。当您开始开发一个功能时发现自己在想:“哦该死,我在干什么?我应该从头开始。”
除了移动当前分支外,您还可以通过向 git reset 传递以下标志之一来更改暂存快照和/或工作目录:
--soft– 暂存快照和工作目录不会以任何方式更改。--mixed– 更新暂存的快照以匹配指定的提交,但工作目录不受影响。这是默认选项。--hard– 暂存的快照和工作目录都会更新以匹配指定的提交。
更容易将这些模式视为定义 git reset 操作的范围。如需更多详细信息,请访问 git reset 页面。
签出旧提交
git checkout 命令用于将存储库状态更新到项目历史记录中的特定点。当传入分支名称时,它允许您在分支之间切换。
git checkout hotfix在内部,以上命令所做的就是将 HEAD 移到不同的分支并更新工作目录以进行匹配。由于这有可能覆盖本地变更,因此 Git 会强制您在工作目录中提交或存储任何将在签出操作期间丢失的变更。与 git reset 不同,git checkout 不会移动任何分支。

您还可以通过传递提交引用而不是分支来签出任意提交。这与签出分支的作用完全相同:它将 HEAD 引用移动到指定提交中。例如,以下命令将签出当前提交的祖父级:
git checkout HEAD~2这对于快速检查项目的旧版本很有用。但是,由于没有对当前 HEAD 的分支引用,这会使您处于游离的 HEAD 状态。如果您开始添加新的提交,这可能很危险,因为在您切换到另一个分支之后将无法返回它们。因此,在向游离的 HEAD 添加提交之前,应始终创建一个新分支。
使用还原撤销公开提交
还原通过创建新的提交来撤销提交。这是撤销变更的安全方法,因为它没有机会重写提交历史记录。例如,以下命令将找出倒数第二次提交中包含的变更,创建一个新提交来撤销这些变更,然后将新提交粘贴到现有项目上。
git checkout hotfix git revert HEAD~2可以将其可视化为以下内容:
与此相比,git reset确实会改变现有的提交历史记录。因此,应使用 git revert 来撤销公共分支上的变更,而 git reset 应保留用于撤销私有分支上的变更。
您也可以将 git revert 视为撤销已提交变更的工具,而 git reset HEAD 用于撤销未提交的变更。
与 git checkout 一样,git revert 有可能覆盖工作目录中的文件,因此它会要求您提交或存储在还原操作期间会丢失的变更。
文件级别的操作
git reset 和 git checkout 命令也接受可选的文件路径作为参数。这会极大地改变它们的行为。不再对完整快照进行操作,而是强制将操作范围限定为单个文件。
Git 重置特定文件
使用文件路径调用时,git reset 会更新暂存的快照以匹配指定提交的版本。例如,此命令将在倒数第二个提交中获取 foo.py 的版本,并将其存放到下一次提交中:
git reset HEAD~2 foo.py与 git reset 的提交级别版本一样,它更常用于 HEAD,而不是任意提交。运行 git reset HEAD foo.py 将取消暂存 foo.py。它包含的变更仍将存在于工作目录中。
--soft、--mixed 和 --hard 标记对 git reset 的文件级版本没有任何影响,因为暂存快照始终更新,工作目录从不更新。
Git 签出文件
签出文件与使用带有文件路径的 git reset 类似,不同之处在于它更新工作目录而不是暂存区域。与这个命令的提交级别版本不同,它不会移动 HEAD 引用,这意味着您不会切换分支。
例如,以下命令使工作目录中的 foo.py 与倒数第二次提交中的相匹配:
git checkout HEAD~2 foo.py就像 git checkout 的提交级别调用一样,它可以用来检查项目的旧版本,但范围仅限于指定的文件。
如果您暂存并提交已签出的文件,则会“复原”到该文件的旧版本。请注意,这会删除文件的所有后续变更,而 git revert 命令仅撤销指定提交引入的变更。
与 git reset 一样,它通常使用 HEAD 作为提交引用。例如,git checkout HEAD foo.py 的效果是丢弃对 foo.py 的未暂存变更。这与 git reset HEAD --hard 的行为类似,但它只能在指定的文件上运行。
摘要
现在,您应该拥有撤销 Git 存储库中变更所需的所有工具。git reset、git checkout 和 git revert 命令可能会令人困惑,但是当您考虑它们对工作目录、暂存快照和提交历史记录的影响时,应该更容易辨别哪个命令适合手头的开发任务。