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

Revert previously staged changes (or: undo changes to .git/index)

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

让我们看看下面的场景

我认为幕后发生了什么

每次使用 git add 更改暂存时,都会在 .git/objects/ 和索引文件 ( 下创建一个 blob 对象。 git/index) 得到更新。如果我多次更改和添加内容,将会出现多个斑点。旧的不会立即被垃圾收集。

当 运行 索引上方的签出命令立即更新时(我还假设内容只在我的工作目录中但未暂存)。这样引用就消失了,我不能使用 git checkout-index 之类的东西来恢复它们。

除非垃圾收集启动,内容在技术上仍然存在。但我不知道如何取回它,然后手动尝试以某种方式找到散列并使用 git cat-file 读取内容。同样的,例如对于 运行 git add 多次是正确的,尽管这里想要返回先前上演的更改可能不是一个真正的用例。 (或者也许当从存储中弹出更改时?...)


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

如果答案是 "No" / "Yes"(到目前为止我的假设):

奖励:是否有另一种方法可以在不立即暂存文件的情况下签出单个文件?

您的幕后描述大部分是正确的。唯一不是 100% 的事情与这部分有关:

Every time when staging changes with git add a blob object is created under .git/objects/

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

此外,由于清洁过滤器。由于行结束设置,它通常与工作树中的内容不同。清理过滤器和行尾设置部分(或大部分,取决于您对 Git 的使用)通过您的 .gitattributes 文件控制,部分(或大部分)通过您的配置中的设置控制。

无论如何,重要的是您获得了 blob 对象的哈希 ID。 blob 对象肯定存在于某个地方——作为松散对象在 .git/objects 目录中,或者在包文件中。现在 git add 可以写入 .git/index(或 GIT_INDEX_FILE 指示的任何其他文件):它将在暂存槽零处的索引中存储给定 [ 的条目=21=],使用计算的 blob-hash 和模式 100644100755,具体取决于工作树文件是否应在以后标记为可执行文件。

如果你丢了,多半是你运气不好

[场景被剪掉了,但它以 git checkout HEAD 结束——<em>path</em> 破坏了索引条目,它的$path 表示 $blobhash 和模式 $mode 信息, 破坏文件的工作树副本在 path.)

Unless garbage collection kicks in the content is still there technically. But I don't know how I would get it back other then manually trying to find the hash somehow and reading the content with git cat-file.

事实上,您不能:哈希 ID 计算是 trapdoor function, and only if you do have the hash can you have Git spill out the content, but you need to have the content if you don't have the hash. That's your Catch-22 situation.

如果——这是一个非常重要的"if"——内容独一无二的,所以git add确实确实创建了一个 new blob 对象,and 你刚刚覆盖了索引中的 blob 引用,那个 blob 对象确实不再存在在任何地方引用。另一方面,如果 git hash-object -w 最终重用了一些现有的 blob,则 blob 对象仍然被之前引用它的任何对象引用。所以现在有两个有趣的情况:blob 唯一的,现在可以进行垃圾收集,或者,blob 不是 是唯一的,并且不是.

使用git fsck --lost-foundgit fsck --unreachablegit fsck --dangling(默认),你可以让Git遍历整个对象数据库,确定哪些对象是可达,哪些不可达,并告诉您一些或所有无法到达的 and/or 将来自或关于它们的信息复制到 .git/lost-found。如果 blob 对象 不可访问的,它 列为这些不可访问或悬空的 blob 之一,或者将其内容恢复到 .git/lost-found.

这里的缺点是可能有几十个甚至上百个悬挂的 blob 对象。您的任务现在已从 "guess the hash"(几乎不可能)切换到 "find the needle in the haystack"(不那么困难,但很乏味,您可能会发现 错误的 针——它不是真的是大海捞针,毕竟是一堆针)。当然,这仅适用于 "blob was unique" 情况。

具体问题的答案

(顺便说一下,这个问题 并不是 真正与 Can git undo a checkout of unstaged files 重复的问题。但是那个问题仍然有用,所以也看看.)

Is there something like git reflog for the index?

没有。您 可以 制作自己的备份副本:只需 cp .git/index 某处即可。但是 Git 本身并不会这样做。您可能会在 git checkout HEAD -- <em>path</em> 操作之前创建一个,通过一些别名或 shell-function你用来做这种危险的操作。

请注意 Git 不知道这些备份副本,因此 git gc 不会将引用的对象视为受保护的。要将备份与 git ls-files 等管道命令一起使用,请在该命令执行期间将路径名放入 GIT_INDEX_FILE

Is git checkout @ -- file considered to be a dangerous command like git reset --hard where you could potentially lose your work?

这个问题的答案取决于谁在考虑。我建议我自己考虑它是危险的,因为你根本就在问这个问题。 :-)

Are there plumbing commands to manually change/rewrite the index? (see the case above where the objects are still there)

是:git update-index 是一次一个条目的更新程序(使用 --cacheinfo--stdin 提供原始索引条目数据而不是让它复制一个很多 git add 工作)。许多其他命令也会部分或整体更新索引。

如果您有一个在 git checkout HEAD -- ... 操作之前备份索引的过程,您可以从备份索引中读取条目(例如使用 GIT_INDEX_FILE=... git ls-files),然后使用 git update-index, without having GIT_INDEX_FILE set, to put the information into the regular index.当然,这是一个索引覆盖操作,您可能希望先对索引进行另一个备份。

Is there an alternative way to checkout a single file without instantaneously staging it?

没有,只是因为这里的动词checkout。要查看索引或任何提交中文件的内容——以便内容具有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 可以在不触及工作树中的文件的情况下覆盖索引中的一个或多个文件:

git reset HEAD -- file  # copy HEAD:file to :file leaving work-tree file undisturbed

如果您给 git reset 一个目录的路径,它会重置所有已经在索引中并驻留在该目录中的文件。