首页 > 解决方案 > 当本地有新的重命名文件时如何恢复最新提交?

问题描述

myBranch正在进行一些更改,包括重命名文件。

我想进行实验,然后如果出现问题,可能会擦除我的所有更改(恢复到最新的提交/HEAD)。

通常我会这样做: git checkout -- .就是这样。

但是,删除重命名的暂存文件以进行提交(也将它们从我的工作目录中删除)存在问题。

因此,当我想重置时,我正在处理的场景是:

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        renamed:    old_file.txt -> new_file.txt
                    ...
                    < files >

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        ...
        < files >

可能有很多不同的文件。

如何恢复到头丢弃所有已提交和未提交的更改,包括重命名文件?

我想出的最干净的解决方案是:

git reset
git clean -f
git checkout -- .

标签: git

解决方案


TL;博士

只需删除任何多余的不需要的副本,之后您的git checkout -- .技巧就会正常工作。

如果您非常确定不会丢失您想要保存的任何工作,您可以git reset --hard一次完成所有工作(但请注意,这会影响整个工作树,而不仅仅是您可能位于的任何子目录)。

在 Git 中,文件重命名操作不会记录在任何地方。它们是根据内容检测的。暂时从“恢复工作树内容”的直接问题退后一步,考虑一下当你有两个现有的 Git 提交时会发生什么,这两个提交包含所有文件的两个不同的即时快照。

比较两个提交

在旧提交中,您有文件 A、B 和 C。我们将此提交放在左侧。在新提交中,您有文件 B、C 和 N。我们将此提交放在右侧。他们来了:

old       new
---       ---
 A
 B         B
 C         C
           N

当 Git 比较左侧和右侧时,它看到两个 B 文件和两个 C 文件,然后对自己说:啊哈,每次都必须是相同的文件,可能内容不同。我将比较旧 B 和新 B;然后我将比较 old-C 和 new-C。如果配对的文件是一样的,我就不说了。如果配对文件不同,我会告诉你有什么不同。

剩下的是旧的A和新的N。在 Git 只是说删除文件 A 并使用全新的内容创建 N之前,你可以——而且 Git 会自动这样做,因为 Git 2.12 左右——让 Git检查: N 中的内容与 A 中的内容很相似吗?* 如果是, Git 会说:将文件 A 重命名为 N,然后,如果需要,还显示要更改哪些内容以使文件 A 变为文件 N。

Git 对你的工作树文件做同样的事情

我将在这里快速讨论一些重要的事情:Git 实际上每个文件都有三个副本,而不仅仅是两个。当前提交中的每个文件都有一个永久冻结的副本,然后每个文件的一个副本准备进入您现在可以进行的下一次提交,存储在 Git 的index中,最后,有一个副本每个文件都作为普通文件,以便您可以查看并使用它。

冻结的副本采用特殊的、只读的、仅限 Git 的格式,自动针对所有其他提交进行重复数据删除。索引“副本”——它不是真正的副本,因为它使用带有重复数据删除的冻结格式——不是直接可见的。1 只有您的工作树副本是可见的:该副本是您计算机上所有普通的非 Git 程序都可以使用的格式,因此当您查看目录时,您看到的就是这些。

Git 大部分时间不使用这个工作树副本:它只是在您签出该提交时从提交中复制出来(首先将提交复制到 Git 的索引,然后将索引副本解冻到工作树中) . 然而,该git add命令将工作树副本复制回 Git 的索引,替换旧的索引副本。这就是为什么git add即使 Git 已经知道文件,你也必须保存它的原因:在进行新提交时,Git 使用索引副本,而不是工作树副本。

要重命名文件,通常同时重命名索引副本工作树副本。(您不能更改任何现有提交的任何部分,因此这个问题甚至永远不会出现。)通常,您可能git mv会这样做。然而,真正重要的是,一个名为的文件的索引副本path/to/file现在不见了,取而代之的是,索引现在包含一个名为new/name/of/somefile. 同样,工作树文件path/to/file现在消失了,取而代之的是工作树包含一个名为new/name/of/somefile.


1实际上,您可以直接查看索引内容,使用git ls-files --stage. 只是这不是普通用户真正想要的:该ls-files命令用于编写 Git 命令,而不是日常使用。git status对于日常工作,查看打印的内容更为有用。


git status

当你运行时git status,Git 会运行两个内部git diff操作,并始终启用重命名检测:

  • 首先,Git 将HEAD提交的文件(在提交的HEAD名称中永久冻结)与索引中的所有文件进行比较。如果某个文件在左侧消失了,但右侧出现了一些不在左侧的名称,Git 会检查:新的右侧文件真的只是左侧文件的重命名吗? 如果是这样,git status将告诉您在暂存为提交的更改中,某些文件正在被重命名。

    对于每个在和索引中相同的文件,什么都不说。对于以某种方式不匹配的每个文件,告诉您它不匹配的方式。这些是为 commit 上演的更改。他们实际上并没有承诺!HEADgit statusgit status

  • 然后,Git 将索引中的所有文件与工作树中的文件进行比较。如果某个文件从索引中消失了,但工作树中出现了一些新名称,Git 将检查这是否可能来自重命名文件。

    和以前一样,当索引和工作树副本不匹配时,git status说明这一点。这些是未暂存的更改 commit。提交实际上使用了 Git 索引中的文件副本,而不是工作树副本,所以这就是为什么不暂存这些文件的原因。

因此,就像任何两次提交一样,Git 实际上并不存储重命名操作。它只是说:呵呵,左边有文件A,右边没有A,但右边有新文件N。让我们看看我们是否可以通过重命名A来构建文件N。如果可以 ,Git要求将A重命名为N。

因此,如果您的工作树中有一些不应该存在的额外文件 F,并且您删除了 F,那么现在它已经消失了。如果你有一个丢失的文件 G,你可以从提交中恢复它,现在它就在那里。现在没有重命名。如果你想要重命名,你只需要删除 G 并创建 F(具有正确的内容),现在 Git 将比较左边(有 F,没有 G)和右边(没有 F,有 G)哦嘿,看,改名!

这意味着您需要做的所有(?)操作就是操作索引和工作树的内容。要将新文件添加到索引中,请在工作树中创建文件并运行. 要从索引中删除文件,请使用:git add file

  • git rm file: 从索引和工作树中删除文件,或
  • git rm --cached file: 从 Git 的索引中删除该文件,但将其单独留在您的工作树中。

git restore命令(在输出中提到git status)通过从以下位置复制文件来工作:

  • 任何提交,或
  • 索引

并将副本放入:

  • 索引,和/或
  • 你的工作树

您的任务是以所需名称创建正确的文件,并删除使用任何不需要的名称存储的文件。

git reset --hard

使用git reset可以使这无痛,但请注意,这git reset可能具有很大的破坏性。我们之前提到了关于 Git 的索引保存将进入下一次提交的文件副本的整个事情。该reset命令执行以下一项、两项或全部三项操作:

  1. 首先,git reset影响当前分支名称。

    您必须告诉 Git如何影响名称。如果你什么都不说,Git 假定效果是将它“重置”为HEAD,它已经在哪里了。因此,默认重置是无操作重置。这本身没有用,但是如果您告诉git reset继续执行第 2 步,或者同时执行第 2 步和第 3 步,它就会变得有用。

    如果使用--soft,Git 会在执行完第 1 步后停止。否则继续执行第 2 步。

  2. 然后,git reset重置 Git 的索引。

    请记住,索引已经包含应该进入下一次提交的每个文件。如果您没有对索引做任何事情,那么索引的文件就是那些从您上次签出的提交中复制出来的文件。如果您从那时对索引进行了处理,则索引具有这些文件的新版本,和/或其中更改了某些文件名,和/或有新文件和/或缺少旧文件(重命名是与删除旧名称并创建具有相同内容的新名称相同)。

    重置命令现在将使索引内容与提交内容匹配。如果您在步骤 1 中选择了其他提交,这就是其他提交的内容。如果不是,这就是同一个提交的内容——所以这个步骤只有在你以某种方式更新了索引的文件时才真正有用。但是,如果您希望 Git 继续执行第 3 步,则必须让它执行第 2 步。

    如果您使用--mixed或默认值,Git 将在此处停止。

  3. 最后,git reset重置您的工作树。这实际上第 2 步一起发生:每当 Git 必须在第 2 步中删除文件的索引副本时,它也会删除文件的工作树副本。如果 Git 必须在步骤 2 中添加文件的索引副本,它将清除工作树中副本的内容,并将其替换为更新索引中的(解冻)副本。但是,如果该文件在索引中不存在,则它是未跟踪的,Git 不会在第 3 步中触及它。

    如果您使用--hard. 如果您修改了索引内容,或者使用了两个单独git reset的 s,一个是 s,一个是 s,--mixed--hard可能会在您的工作树中留下一些文件,因为步骤 2 可能使它们无法跟踪。但是,如果此时它们妨碍了它们,您只需要使用系统上的普通日常文件删除器将它们删除即可。

要记住的主要事情git reset是,它会选择性地重置 Git 的索引(通常包含您可以从某个地方恢复的文件,因为它们大多来自一些较早的提交)您的工作树文件。那些工作树文件不在 Git 中,如果它们不是来自某个提交,则无法从 Git 中取回它们。因此,在使用之前git reset --hard,请确保您不会丢失要保留的工作树文件


推荐阅读