简体中文 ▾ 主题 ▾ 最新版本 ▾ git-reset 最后更新于 2.54.0

名称

git-reset - 将 HEAD 或索引设置为已知状态

概要

git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
git reset [-q] [<tree-ish>] [--] <pathspec>…​
git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]
git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>…​]

描述

git reset 执行以下任一操作:

  1. git reset [<模式>] <提交> 更改 HEAD 指向的提交。这使得撤销各种 Git 操作(例如提交、合并、变基和拉取)成为可能。

  2. 当你指定文件、目录或传递 --patch 参数时,git reset 会更新指定文件的暂存版本。

    git reset [<模式>] [<提交>]

    将当前分支的头指针(HEAD)设置为指向 <提交>。根据所选的 <模式>,还会更新工作目录和/或索引以匹配 <提交> 的内容。<提交> 默认为 HEAD。在操作之前,ORIG_HEAD 会被设置为当前分支的顶端。

    <模式> 必须是以下之一(默认为 --mixed

    --mixed

    保持工作目录不变。更新索引以匹配新的 HEAD,因此没有任何内容会被暂存。

    如果指定了 -N,则将删除的路径标记为“意图添加”(参见 git-add[1])。

    --soft

    保持工作树文件和索引不变。例如,如果你没有暂存的更改,你可以使用 git reset --soft HEAD~5; git commit 将最后 5 个提交合并为 1 个提交。即使工作树中有更改,此操作也有效,这些更改不会受到影响,但这种用法可能会导致混淆。

    --hard

    使用 <提交> 中的版本覆盖所有文件和目录,并可能覆盖未追踪的文件。移除 <提交> 中不存在的已追踪文件,以使工作树与 <提交> 保持一致。更新索引以匹配新的 HEAD,因此没有任何内容会被暂存。

    --merge

    重置索引并更新工作树中 <提交>HEAD 之间不同的文件,但保留那些索引与工作树之间不同的文件(即那些有未添加更改的文件)。主要用于重置未合并的索引条目,例如某些情况下 git am -3git switch -m 遗留的条目。如果 <提交> 和索引之间不同的文件具有未暂存的更改,则重置操作将中止。

    --keep

    重置索引条目并更新工作树中 <提交>HEAD 之间不同的文件。如果 <提交>HEAD 之间不同的文件存在本地更改,则重置操作将中止。

    --recurse-submodules
    --no-recurse-submodules

    当更新工作树时,使用 --recurse-submodules 将根据超级项目中记录的提交,递归重置所有活跃子模块的工作树,并将子模块的 HEAD 设置为在该提交处分离(detached)。

    git reset [-q] [<树对象>] [--] <路径规范>...
    git reset [-q] [--pathspec-from-file=<文件> [--pathspec-file-nul]] [<树对象>]

    对于所有指定的文件或目录,将其暂存版本设置为给定提交或树(默认为 HEAD)中的版本。

    这意味着 git reset <路径规范>git add <路径规范> 的逆操作:它取消暂存对指定文件或目录的所有更改。这等同于 git restore --staged <路径规范>...

    在此模式下,git reset 仅更新索引(而不更新 HEAD 或工作树文件)。如果您想同时更新文件和索引条目,请使用 git-restore[1]

    git reset (--patch | -p) [<树对象>] [--] [<路径规范>...]

    以交互方式选择索引与指定提交或树(默认为 HEAD)之间的差异。索引会根据所选更改进行修改。

    这意味着 git reset -pgit add -p 的逆操作,即您可以利用它选择性地取消暂存更改。请参阅 git-add[1] 的“交互模式”部分,了解如何使用 --patch 选项。

有关这三个命令之间的区别,请参阅 git[1] 中的“Reset、restore 和 revert”部分。

选项

-q
--quiet

保持安静,仅报告错误。

--refresh
--no-refresh

在混合(mixed)重置后刷新索引。默认启用。

--pathspec-from-file=<文件>

Pathspec 从 <file> 而不是命令行参数传递。如果 <file>-,则使用标准输入。Pathspec 元素由 LFCR/LF 分隔。Pathspec 元素可以按 core.quotePath 配置变量的解释进行引用(参见 git-config[1])。另请参见 --pathspec-file-nul 和全局 --literal-pathspecs

--pathspec-file-nul

仅在与 --pathspec-from-file 一起使用时有意义。路径规范元素由 NUL 字符分隔,所有其他字符都被视为字面值(包括换行符和引号)。

-U<n>
--unified=<n>

生成带有 <n> 行上下文的 diff。上下文行数默认为 diff.context,如果未设置该配置变量,则默认为 3。(由于历史原因,不带 <n>-U 被静默接受为 -p 的同义词)。

--inter-hunk-context=<n>

在差异块之间显示上下文,最多达指定行数 <number>,从而合并彼此接近的块。默认为 diff.interHunkContext,如果未设置配置选项则为 0。

--

不再将任何后续参数解释为选项。

<路径规范>...

限制受操作影响的路径。

有关更多详细信息,请参阅 gitglossary[7] 中的 pathspec 条目。

示例

撤销 add
$ edit                                     (1)
$ git add frotz.c filfre.c
$ mailx                                    (2)
$ git reset                                (3)
$ git pull git://info.example.com/ nitfol  (4)
  1. 你在快乐地做某事,发现这些文件中的更改顺序很好。当你运行 git diff 时,不想看到它们,因为你计划处理其他文件,而这些文件的更改会让你分心。

  2. 有人让你 pull,更改听起来值得合并。

  3. 但是,你已经弄脏了索引(即你的索引与 HEAD 提交不匹配)。但你知道你即将进行的 pull 不会影响 frotz.cfilfre.c,所以你还原了这两个文件的索引更改。你在工作树中的更改仍然保留在那里。

  4. 然后你可以 pull 和合并,让 frotz.cfilfre.c 的更改仍然保留在工作树中。

撤销提交并重做
$ git commit ...
$ git reset --soft HEAD^      (1)
$ edit                        (2)
$ git commit -a -c ORIG_HEAD  (3)
  1. 这通常在你记住刚提交的内容不完整,或者拼写错误提交信息,或者两者都有时执行。将工作树保持在“重置”之前的状态。

  2. 对工作树文件进行更正。

  3. “reset”将旧的 head 复制到 .git/ORIG_HEAD;从其日志消息开始重做提交。如果你不需要进一步编辑消息,可以使用 -C 选项。

    另请参见 git-commit[1]--amend 选项。

撤销提交,将其变成主题分支
$ git branch topic/wip          (1)
$ git reset --hard HEAD~3       (2)
$ git switch topic/wip          (3)
  1. 你做了一些提交,但意识到它们太早放入 master 分支。你想在主题分支中继续润色它们,所以从当前 HEAD 创建 topic/wip 分支。

  2. 重绕 master 分支以摆脱那三个提交。

  3. 切换到 topic/wip 分支并继续工作。

永久撤销提交
$ git commit ...
$ git reset --hard HEAD~3   (1)
  1. 最后三个提交(HEADHEAD^HEAD~2)很糟糕,你不想再看到它们。如果你已经将这些提交提供给其他人,请不要这样做。(有关执行此操作的含义,请参阅 git-rebase[1] 中的“从上游变基中恢复”部分。)

撤销合并或拉取
$ git pull                         (1)
Auto-merging nitfol
CONFLICT (content): Merge conflict in nitfol
Automatic merge failed; fix conflicts and then commit the result.
$ git reset --hard                 (2)
$ git pull . topic/branch          (3)
Updating from 41223... to 13134...
Fast-forward
$ git reset --hard ORIG_HEAD       (4)
  1. 尝试从上游更新导致了许多冲突;你现在还没准备好花很多时间合并,所以你决定以后再做。

  2. “pull”没有进行合并提交,所以 git reset --hard(这是 git reset --hard HEAD 的同义词)清除了索引文件和工作树中的混乱。

  3. 将主题分支合并到当前分支中,导致快进(fast-forward)。

  4. 但你决定主题分支尚未准备好供公众消费。“pull”或“merge”总是将当前分支的原始提示保留在 ORIG_HEAD 中,因此硬重置到它会使你的索引文件和工作树回到该状态,并将分支的提示重置为该提交。

在脏工作树中撤销合并或拉取
$ git pull                         (1)
Auto-merging nitfol
Merge made by recursive.
 nitfol                |   20 +++++----
 ...
$ git reset --merge ORIG_HEAD      (2)
  1. 即使你的工作树中有本地修改,当你确定另一个分支中的更改与它们不重叠时,你也可以安全地说 git pull

  2. 检查合并结果后,你可能会发现另一个分支中的更改不令人满意。运行 git reset --hard ORIG_HEAD 将让你回到原来的位置,但它会丢弃你不想丢弃的本地更改。git reset --merge 保留你的本地更改。

中断的工作流

假设你在进行大范围更改时被紧急修复请求中断。工作树中的文件还没有准备好提交,但你需要去另一个分支进行快速错误修复。

$ git switch feature  ;# you were working in "feature" branch and
$ work work work      ;# got interrupted
$ git commit -a -m "snapshot WIP"                 (1)
$ git switch master
$ fix fix fix
$ git commit ;# commit with real log
$ git switch feature
$ git reset --soft HEAD^ ;# go back to WIP state  (2)
$ git reset                                       (3)
  1. 此提交将被吹走,所以抛弃式日志消息是可以的。

  2. 这会从提交历史中删除 WIP 提交,并将你的工作树设置为你制作该快照之前的状态。

  3. 此时索引文件仍然拥有你作为 snapshot WIP 提交的所有 WIP 更改。这会更新索引,显示你的 WIP 文件为未提交状态。

    另请参见 git-stash[1]

重置索引中的单个文件

假设你已将文件添加到索引,但后来决定不想将其添加到提交中。你可以从索引中删除文件,同时使用 git reset 保留你的更改。

$ git reset -- frotz.c                      (1)
$ git commit -m "Commit files in index"     (2)
$ git add frotz.c                           (3)
  1. 这会从索引中删除文件,同时将其保留在工作目录中。

  2. 这会提交索引中的所有其他更改。

  3. 再次将文件添加到索引。

在丢弃一些先前的提交时保留工作树中的更改

假设你正在处理某事并提交它,然后你继续工作一点点,但现在你认为工作树中的内容应该在另一个与你之前提交的内容无关的分支中。你可以开始一个新的分支并重置它,同时保留工作树中的更改。

$ git tag start
$ git switch -c branch1
$ edit
$ git commit ...                            (1)
$ edit
$ git switch -c branch2                     (2)
$ git reset --keep start                    (3)
  1. 这在 branch1 中提交了你的第一次编辑。

  2. 在理想的世界中,当你创建并切换到 branch2(即 git switch -c branch2 start)时,你本可以意识到早期的提交不属于新主题,但没有人是完美的。

  3. 但你可以使用 reset --keep 在切换到 branch2 后删除不需要的提交。

将一个提交拆分为一系列提交

假设你创建了许多逻辑上独立的更改并一起提交了它们。然后,后来你认为将每个逻辑块与它自己的提交相关联可能会更好。你可以使用 git reset 在不更改本地文件内容的情况下重绕历史,然后连续使用 git add -p 以交互方式选择要包含在每个提交中的 hunk,使用 git commit -c 来预填充提交消息。

$ git reset -N HEAD^                        (1)
$ git add -p                                (2)
$ git diff --cached                         (3)
$ git commit -c HEAD@{1}                    (4)
...                                         (5)
$ git add ...                               (6)
$ git diff --cached                         (7)
$ git commit ...                            (8)
  1. 首先,将历史重置回一个提交,以便我们删除原始提交,但保持所有更改的工作树。-N 确保使用 HEAD 添加的任何新文件仍然被标记,以便 git add -p 会找到它们。

  2. 接下来,我们使用 git add -p 功能交互式地选择要添加的差异块(diff hunks)。这将按顺序询问你每个差异块,你可以使用简单的命令,例如“是,包括这个”,“不,不要包括这个”甚至非常强大的“编辑”设施。

  3. 一旦对要包含的块感到满意,你应该通过使用 git diff --cached 验证已为第一次提交准备的内容。这显示了所有已移动到索引并即将提交的更改。

  4. 接下来,提交存储在索引中的更改。-c 选项指定从你在第一个提交中开始的原始消息预填充提交消息。这有助于避免重新输入它。HEAD@{1} 是一个特殊的符号,表示 HEAD 在原始重置提交之前(1 次更改前)所处的提交。有关详细信息,请参阅 git-reflog[1]。你也可以使用任何其他有效的提交引用。

  5. 你可以多次重复步骤 2-4,将原始代码分解为任意数量的提交。

  6. 现在你已经将许多更改拆分为它们自己的提交,并且可能不再使用 git add 的补丁模式,以便选择所有剩余的未提交更改。

  7. 再次检查以验证你是否包含了想要的内容。你可能还希望验证 git diff 没有显示任何稍后要提交的剩余更改。

  8. 最后创建最终提交。

讨论

下表显示了运行以下命令时会发生什么:

git reset --option target

HEAD 重置为另一个提交(target),并根据文件的状态使用不同的重置选项。

在这些表中,ABCD 是文件的一些不同状态。例如,第一张表的第一行意味着,如果文件在工作树中处于状态 A,在索引中处于状态 B,在 HEAD 中处于状态 C,在目标中处于状态 D,那么 git reset --soft target 将使文件在工作树中保持状态 A,在索引中保持状态 B。它将 HEAD(即当前分支的尖端,如果你在分支上的话)重置(即移动)到 target(它拥有状态 D 的文件)。

working index HEAD target         working index HEAD
----------------------------------------------------
 A       B     C    D     --soft   A       B     D
			  --mixed  A       D     D
			  --hard   D       D     D
			  --merge (disallowed)
			  --keep  (disallowed)
working index HEAD target         working index HEAD
----------------------------------------------------
 A       B     C    C     --soft   A       B     C
			  --mixed  A       C     C
			  --hard   C       C     C
			  --merge (disallowed)
			  --keep   A       C     C
working index HEAD target         working index HEAD
----------------------------------------------------
 B       B     C    D     --soft   B       B     D
			  --mixed  B       D     D
			  --hard   D       D     D
			  --merge  D       D     D
			  --keep  (disallowed)
working index HEAD target         working index HEAD
----------------------------------------------------
 B       B     C    C     --soft   B       B     C
			  --mixed  B       C     C
			  --hard   C       C     C
			  --merge  C       C     C
			  --keep   B       C     C
working index HEAD target         working index HEAD
----------------------------------------------------
 B       C     C    D     --soft   B       C     D
			  --mixed  B       D     D
			  --hard   D       D     D
			  --merge (disallowed)
			  --keep  (disallowed)
working index HEAD target         working index HEAD
----------------------------------------------------
 B       C     C    C     --soft   B       C     C
			  --mixed  B       C     C
			  --hard   C       C     C
			  --merge  B       C     C
			  --keep   B       C     C

git reset --merge 旨在用于从冲突的合并中重置时。任何合并操作都保证参与合并的工作树文件在开始之前在索引方面没有本地更改,并且它将结果写入工作树。因此,如果我们看到索引和目标之间,以及索引和工作树之间存在差异,那么这意味着我们不是从合并操作在失败并产生冲突后留下的状态重置的。这就是为什么在这种情况下我们不允许 --merge 选项的原因。

git reset --keep 旨在用于删除当前分支中的最后一些提交,同时保留工作树中的更改。如果我们要删除的提交中的更改与我们要保留的工作树中的更改之间可能存在冲突,则不允许重置。这就是为什么如果有工作树和 HEAD 之间,以及 HEAD 和目标之间的更改,它是不允许的。为了安全起见,当存在未合并的条目时,它也是不允许的。

下表显示了存在未合并条目时会发生什么:

working index HEAD target         working index HEAD
----------------------------------------------------
 X       U     A    B     --soft  (disallowed)
			  --mixed  X       B     B
			  --hard   B       B     B
			  --merge  B       B     B
			  --keep  (disallowed)
working index HEAD target         working index HEAD
----------------------------------------------------
 X       U     A    A     --soft  (disallowed)
			  --mixed  X       A     A
			  --hard   A       A     A
			  --merge  A       A     A
			  --keep  (disallowed)

X 表示任何状态,U 表示未合并的索引。

GIT

Git[1] 套件的一部分