撤销之前暂存的更改(或:撤销对.git/index的更改)

15 浏览
0 Comments

撤销之前暂存的更改(或:撤销对.git/index的更改)

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

让我们看看以下情景:

- 修改之前提交的文件:echo "some content" >> example.txt

- 暂存修改:git add example.txt

- 恢复最后一次提交的更改:git checkout @ -- example.txt

- 意识到选择了错误的文件,想撤销最后一条命令以恢复你的更改("some content")

我认为底层发生了什么:

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

运行上述的checkout命令时,索引会立即更新(尽管我本以为内容只会存在于我的工作目录中但未暂存)。这样引用就消失了,我无法使用git checkout-index来恢复它们。

除非进行垃圾回收,否则内容仍然存在。但我不知道如何恢复它,除非手动尝试找到哈希值,并使用git cat-file读取内容。同样,多次运行git add也会出现同样的情况,尽管在这里想要恢复之前暂存的更改可能不是一个真正的用例。(或者在从暂存区恢复修改时?...)

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

- 是否有类似于git reflog用于索引的命令?

- git checkout @ -- file命令是否被认为是一条危险的命令,就像git reset --hard一样,可能会丢失你的工作?

如果答案是“否”/“是”(这是我迄今为止的假设):

- 是否有用于手动更改/重写索引的plumbing命令?(参考上述对象仍然存在的情况)

附加问题:是否有不立即暂存而只是检出单个文件的替代方法?

0
0 Comments

问题:如何撤销之前暂存的更改(或者撤销对.git/index的更改)?

原因:每次使用git add命令暂存更改时,都会在.git/objects/目录下创建一个blob对象。内部上,git add命令对工作树文件中的数据内容进行哈希处理,类似于git hash-object -w -t blob。这并不一定会创建一个新的对象:如果哈希内容已经存在于仓库中,它只会重新使用现有的对象。现有对象可以是已打包的,即位于.git/objects/pack目录中,而不是作为单独的blob存在。

此外,由于清洗过滤器或行尾设置的原因,写入blob对象的内容可能与工作树中的内容完全不同,更常见的情况是因为行尾设置而导致的CR-LF行尾不同。清洗过滤器和行尾设置在一定程度上(或者根据您对Git的使用程度而定)由.gitattributes文件和配置文件中的设置控制。

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

解决方法:如果您丢失了暂存的更改,那么您很大程度上是没有希望的。除非垃圾回收开始工作,否则内容仍然存在。但是,我不知道我如果不手动尝试找到哈希并使用git cat-file读取内容,我该如何找回它。

实际上,您无法:哈希ID计算是一个陷门函数,只有在您拥有哈希时,Git才会释放出内容,但是如果您没有哈希,您必须拥有内容。这是典型的“进退两难”的情况。

如果内容是唯一的,以便git add实际上创建了一个新的blob对象,并且您刚刚覆盖了索引中的blob引用,那么该blob对象确实不再被任何地方引用。另一方面,如果git hash-object -w重用了某个现有的blob,那么该blob对象仍然被引用。因此现在有两种有趣的情况:blob是唯一的,现在可以进行垃圾回收,或者blob不是唯一的,现在也不能进行垃圾回收。

您可以使用git fsck --lost-found或git fsck --unreachable或git fsck --dangling(默认)命令,让Git遍历整个对象数据库,确定哪些对象是可达的,哪些不可达,并告诉您一些或所有不可达的对象和/或将与其相关的信息从中复制到.git/lost-found目录中。如果blob对象是不可达的,它将被列为这些不可达或悬挂blob之一,或者它的内容将恢复到.git/lost-found目录中。

缺点是可能存在几十甚至几百个悬挂blob对象。您的任务现在从“猜测哈希”(几乎不可能)转变为“在干草堆中找针”(不那么困难,但是乏味,而且很可能找到错误的针——毕竟这不是一个真正的干草堆,而是一堆针)。当然,这仅适用于“blob是唯一的”情况。

对于特定问题的答案:

1.索引是否有类似于git reflog的东西?

答:没有。您可以自己备份索引:只需将.git/index复制到其他地方。但是Git本身不会自动执行此操作。您可以在进行git checkout HEAD -- path操作之前自己创建一个备份副本,通过某个别名或shell函数来执行这种有点危险的操作。

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

2.git checkout @ -- file命令是否像git reset --hard一样危险,可能会导致丢失工作?

答:答案取决于谁进行考虑。我个人建议考虑它是危险的,因为您提出了这个问题。:-)

3.是否有用于手动更改/重写索引的内部命令?(参见上面对象仍然存在的情况)

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

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

4.是否有一种在不立即将文件暂存的情况下检出单个文件的替代方法?

答:否,但仅仅是因为这里使用了“checkout”动词。要查看索引或任何提交中的文件内容(以便内容具有git rev-parse可以理解的名称),请使用git show命令:

git show :file # 索引中阶段零的文件

git show :3:file # 合并冲突期间索引中阶段三的文件

git show HEAD:file # 当前提交中的文件

git show master~7:file # 主分支后退7个一级提交的文件

另请注意,git reset命令可以在不触及工作树文件的情况下覆盖索引中的一个或多个文件:

git reset HEAD -- file # 将HEAD:file复制到:file,保持工作树文件不变

如果将目录路径提供给git reset命令,它将重置索引中已经存在且位于该目录中的所有文件。

感谢您提供这些详细的信息!这有助于更好地理解实际发生的情况。

0