1

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

让我们看看下面的场景

  • 操作之前提交的文件:echo "some content" >> example.txt
  • 阶段变化:git add example.txt
  • 上次提交的结帐更改:git checkout @ -- example.txt
  • 意识到您选择了错误的文件并且您想要撤消最后一个命令以"some content"恢复您的更改 ()

我认为幕后发生的事情

每次在.git/objects/git add下创建带有 blob 对象的暂存更改并且索引文件 ( .git/index ) 都会更新。如果我多次更改和添加内容,将会有多个 blob。旧的不会立即被垃圾收集。

当从上面运行 checkout 命令时,索引会立即更新(我还假设内容只会在我的工作目录中,但未暂存)。这样参考就消失了,我不能使用诸如git checkout-index还原它们之类的东西。

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


所以这一切都归结为这些问题:

  • git reflog索引有类似的东西吗?
  • git checkout @ -- file认为是像 git 这样的危险命令reset --hard,您可能会在其中丢失您的工作?

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

  • 是否有手动更改/重写索引的管道命令?(见上面物体仍然存在的情况)

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

4

1 回答 1

2

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

每次在.git/objects/git add下创建带有 blob 对象的暂存更改时

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

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

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

如果你失去了它,你大多不走运

[场景被剪断,但它以破坏索引条目、其表示和模式信息以及破坏文件的工作树副本结束。)git checkout HEAD -- path$path$blobhash$modepath

除非垃圾收集在技术上仍然存在。但是我不知道如何取回它,然后手动尝试以某种方式找到哈希并使用git cat-file.

事实上,你不能:哈希 ID 计算是一个陷门函数,只有你哈希才能让 Git 溢出内容,但如果你没有哈希,你需要有内容。这就是你的Catch-22 情况

如果——这是一个非常重要的“如果”——内容唯一的,所以git add确实创建了一个的blob 对象,您刚刚覆盖了索引中的 blob 引用,那么该 blob 对象确实不再在任何地方引用. 另一方面,如果git hash-object -w最终重用某个现有的 blob,则该 blob 对象仍会被之前引用它的任何对象引用。所以现在有两个有趣的情况:blob唯一的,现在可以进行垃圾回收,或者,blob不是唯一的,现在不是。

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

这里的缺点是可能有数十个甚至数百个悬垂的 blob 对象。你的任务现在已经从“猜测哈希”(几乎不可能)切换到“大海捞针”(没那么难,但很乏味,而且你很可能会找针——这不是真正的大海捞针,而是一堆毕竟是针)。而且,当然,这只适用于“blob is unique”的情况。

具体问题的答案

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

git reflog索引有类似的东西吗?

不,您可以制作自己的备份副本:就在cp .git/index某个地方。但Git 不会自己做这件事。您可以在操作之前通过一些别名或 shell 函数在操作之前创建一个,用于执行这种危险操作。git checkout HEAD -- path

请注意,Git 不知道这些备份副本,因此git gc不会认为引用的对象是受保护的。要将备份与管道命令(如)一起使用git ls-files,请将路径名放入GIT_INDEX_FILE该命令的持续时间内。

文件是否git checkout @ --被认为是一个危险的命令git reset --hard,比如你可能会丢失你的工作?

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

是否有手动更改/重写索引的管道命令?(见上面物体仍然存在的情况)

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

如果您有一个在git checkout HEAD -- ...操作之前备份索引的过程,您可以从备份索引中读取条目(GIT_INDEX_FILE=... git ls-files例如使用),然后使用git update-index无需设置GIT_INDEX_FILE,将信息放入常规索引。当然,这是一个 index-overwrite-y 操作,您可能希望先对索引进行另一个备份。

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

不,只是因为这里的动词结帐。要查看位于索引或任何提交中的文件的内容——以便内容具有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目录的路径,它将重置已在索引中并驻留在目录中的所有文件。

于 2019-03-09T18:33:41.607 回答