还原先前暂存的更改(或:撤消对.git / index的更改)

时间:2019-03-09 15:41:21

标签: git git-stage git-plumbing

当试图了解撤消各种git操作的方法时,我想到了一个不确定如何处理它的场景。免责声明:在实际使用git“生产”时,我没有这种情况,但我仍然认为这不仅是一个学术问题。

让我们看一下以下情况

  • 处理先前在echo "some content" >> example.txt
  • 上提交的文件
  • 进行更改:git add example.txt
  • 从最后一次提交到git checkout @ -- example.txt
  • 的结帐更改
  • 意识到您选择了错误的文件,并且想要撤消上一个命令以取回所做的更改("some content"

我想在幕后发生的事情

每次使用git add更改阶段时,都会在 .git / objects / 下创建一个blob对象,并获得索引文件( .git / index )更新。如果我多次更改和添加东西,将有多个斑点。不是立即收集旧的垃圾。

从索引上方运行checkout命令时,索引会立即更新(同样,我也假定内容仅位于我的工作目录中,但未暂存)。这样,引用就消失了,我无法使用git checkout-index之类的东西来还原它们。

除非技术上仍然存在垃圾回收,否则内容仍然存在。但是我不知道如何将其取回,然后手动尝试以某种方式查找哈希并使用git cat-file读取内容。相同的例如多次运行git add时确实如此,尽管这里想找回先前已进行的更改可能并不是真正的用例。 (或者也许是从隐藏弹出更改时??)


所有这些归结为以下问题:

  • 索引是否有git reflog之类的内容?
  • git checkout @ -- file是否被视为像git reset --hard这样的危险命令,可能会导致您的工作松动?

如果答案是“否” /“是”(到目前为止,我假设是这样):

  • 是否有用于手动更改/重写索引的管道命令? (请参见上面的示例,其中对象仍在其中)

奖金:是否有另一种方法可以在不立即登台的情况下检出单个文件?

1 个答案:

答案 0 :(得分:2)

您对引擎盖的描述大部分是正确的。唯一不是100%的事情与这部分有关:

  

每次使用git add更改阶段时,都会在 .git / objects /

下创建一个blob对象。

在内部,git add对工作树文件la git hash-object -w -t blob中的数据内容进行哈希处理。这不必不必要创建一个 new 对象:如果散列内容已经在存储库中 ,它只会重新使用现有对象。现有对象可能是打包的,即在.git/objects/pack中,而不是 loose 作为单独的blob。

此外,由于干净过滤器,写入blob对象 的内容可能任意与工作树中的内容不同。通常,由于行尾设置,CR-LF-行尾与工作树中的内容不同。干净的过滤器和行尾设置的一部分(或大部分,取决于您对Git的使用)通过.gitattributes文件进行控制,部分(或大部分)通过配置中的设置进行控制。

无论如何,重要的是您获得了Blob对象的哈希ID。 blob对象肯定存在于某个地方,在.git/objects目录中作为松散对象,或在pack文件中。现在git add可以写入.git/index(或任何其他文件GIT_INDEX_FILE表示):它将在暂存插槽零的索引中存储给定 {{1 }} ,使用计算后的blob哈希和模式path100644,具体取决于是否稍后将工作树文件标记为可执行。

如果您丢失了它,那基本上就没有运气了

[场景已被删除,但以100755破坏了索引条目而结束,其 git checkout HEAD -- path代表了$path和模式$blobhash 信息, 破坏 $mode 中文件的工作树副本。)

  

除非技术上仍然存在垃圾回收,否则内容仍然存在。但是我不知道如何将其取回,然后手动尝试以某种方式查找哈希并使用path读取内容。

实际上,您不能:哈希ID计算是trapdoor function,只有有哈希,您才能让Git散布内容,但是您需要如果没有哈希,则具有内容。那就是你的Catch-22 situation

If -这是一个非常重要的“ if”-内容 是唯一的,因此git cat-file确实创建了一个 blob对象,您刚刚覆盖了索引中的blob引用,该blob对象确实不再在任何地方引用。另一方面,如果git add最终重用了一些现有的Blob,则该Blob对象仍会被以前引用的对象引用。因此,现在有两种有趣的情况:blob 曾经是并且现在可以进行垃圾回收,或者blob是不是唯一并且不是。

使用git hash-object -wgit fsck --lost-foundgit fsck --unreachable(默认设置),您可以让Git遍历整个对象数据库,确定哪些对象可达以及哪些对象并告诉您一些或所有不可达的信息,和/或将信息或从中复制信息到git fsck --dangling中。如果blob对象 无法访问,则 将被列为这些不可访问或悬挂的blob之一,或者将其内容恢复到.git/lost-found中。

这里的缺点是可能有数十个甚至数百个悬挂的斑点对象。现在,您的任务已从“猜测哈希”(实际上是不可能的)切换为“在大海捞针中找到针”(不是那么困难,但很乏味,并且您很可能会发现错误针—不是)真的是干草堆,毕竟是一堆针)。而且,当然,这仅适用于“斑点是唯一的”情况。

回答特定问题

(顺便说一句,这实际上不是问题{em> 的重复Can git undo a checkout of unstaged files。但是这个问题仍然有用,所以也可以查看。)

  

索引是否有.git/lost-found之类的东西?

不。您可以制作自己的备份副本:只需git reflog。但是,Git不能自己做到这一点。您可能会在cp .git/index操作之前通过用来执行这种危险操作的别名或shell函数进行操作。

请注意,Git无法识别这些备份副本,因此git checkout HEAD -- path不会将引用的对象视为受保护的对象。要将备份与git gc之类的管道命令一起使用,请在该命令期间将路径名放入git ls-files中。

  

GIT_INDEX_FILE文件是否被视为像git checkout @ --这样的危险命令,您有可能会丢失工作?

答案取决于谁在考虑。我建议自己考虑这样做很危险,因为您根本在问这个问题。 :-)

  

是否有管道命令手动更改/重写索引? (请参见上面的示例,其中对象仍在其中)

是:git reset --hard是一次一次输入的更新程序(使用git update-index--cacheinfo提供原始索引输入数据,而不是让它们重复很多--stdin个工作)。许多其他命令也可以部分更新索引或进行索引更新。

如果您有一个在进行git add操作之前备份索引的过程,则可以从备份索引中读取条目(例如,使用git checkout HEAD -- ...),然后使用{{1 }},没有设置了GIT_INDEX_FILE=... git ls-files,可将信息放入常规索引。当然,这是一个索引覆盖Y操作,您可能希望首先对索引进行另一个备份。

  

是否有另一种方法可以在不立即登台的情况下检出单个文件?

否,但这仅是因为动词 checkout 在这里。要查看索引或任何提交中的文件的内容,以使内容具有git update-index可以理解的名称,请使用GIT_INDEX_FILE:< / p>

git rev-parse

还请注意,git show可以覆盖索引中的一个或多个文件,而无需触摸工作树中的文件:

git show :file          # file in index at stage zero
git show :3:file        # file in index at stage three, during merge conflict
git show HEAD:file      # file in current commit
git show master~7:file  # file in commit 7 first-parent hops back from master

如果为git reset提供目录路径,它将重置索引中已经存在并驻留在目录中的所有文件。