git - git中的树哈希存储在哪里?

标签 git hash

我正在按照本教程(https://jwiegley.github.io/git-from-the-bottom-up/1-Repository/3-blobs-are-stored-in-trees.html)学习git体系结构。

命令

$ git cat-file commit HEAD


给我哈希表引用的树的哈希值,“ 0563f77d884e4f79ce95117e2d686d7d6e282887”。现在,我尝试在.git中找到此哈希:

$ find .git/ | xargs grep "0563f77"


为什么什么都没发生?这个哈希值不存储在任何地方吗?

最佳答案

我认为您在这里将几个概念混在一起:


Git的内部对象名称(SHA-1散列)对于对象的内容是唯一的(并且完全由其决定,因此从哲学上讲是这样)。更正确的是,它们是对象类型名称的SHA-1哈希,它作为字符串(commitblobtreetag),后跟一个空格,然后是一个十进制表示形式。对象的长度(以字节为单位),后跟NUL或零字节,后跟基础对象的原始数据。

请注意,如果对同一对象进行两次哈希处理,则两次都将获得相同的哈希值。因此,如果名为README.txt的文件中包含一些文本,然后将该文件复制到read-me-too.txt并对该文件进行哈希处理,则将再次获得相同的哈希值。这是因为文件名不是哈希计算输入的一部分,仅是类型(在本例中为blob),空白,大小,零字节和内容。

如果两个文件仅包含一行读取hello的行(加上一个换行符,总共六个字节),则哈希函数的输入为blob 6\0hello\n(其中\0\n代表零字节,而新队)。实际上,这两个文件的哈希为ce013625030ba8dba906f756967f9e9ca394464a。 (我用git hash-object来找到该值,尽管任何SHA-1代码都可以解决这个问题:例如,您可以使用几行Python或Ruby代码或相当数量的C代码来找到此值。树是trickier。)

对象ID ce013625030ba8dba906f756967f9e9ca394464a表示一个包含单词hello后跟换行符的文件。1(如果我们知道文件包含的数据,则可以对数据进行哈希处理并找到Git对象ID。通常,我们采用另一种方法:我们从一个有效的Git对象ID开始,然后从存储库中检索数据,但是当我们git add一个文件时,我们采用这种方式,将数据转换为哈希并将其存储为Git对象(如果不是)已经存在于存储库中。如果已经存在,那就很好:我们只是再次使用相同的哈希值。)
对象本身(对象的数据)存储在Git存储库中的某个位置。

您找到的位置(对象0563f77d884e4f79ce95117e2d686d7d6e282887在名为05的目录中,该文件的名称以63f77开头,并继续其余的哈希值)是Git当前保留其称为松散对象的位置。但是,Git也会将对象打包到所谓的打包文件中。

打包文件的格式非常复杂,进入此处所需的时间太长。但是,我们可以说一个包文件可以存储成千上万个对象。 (Pack文件格式已被多次修订,以提高性能和单个对象的可访问性。)
我们需要一种方法来将人类可读的名称(例如分支名称)转换为Git哈希。这是您在评论中记下的搜索中找到的结果:


它适用于由$ git rev-parse HEAD返回的提交哈希。此哈希存储在.git/refs/heads/master [和两个引用日志]中


Git的设计提供了两种特别出色的外部名称形式,特别是分支名称和标记名称,通过它们我们可以记住特定的提交哈希。 Git对此的总称是参考。 Git的远程跟踪分支也是引用,存储在refs/remotes/下。除了这些分支和标记名称之外,您还可能会遇到注释和“隐藏”(git stash):它们还使用引用,特别是分别在refs/notes/和(单个)名称refs/stash中的引用。

与对象一样,参考值存储在Git存储库中的某个位置,但是不保证它们会保留在单独的文件中。从今天(Git版本2.9)开始,它们始终位于单个文件(如您找到的文件)中,或者位于单个名为packed-refs的特殊文件中(或者有时在两个文件中:在这种情况下,如果单个文件具有正确的值,则两个不同意)。


分支名称只是以refs/heads/ 2开头的引用。标签是一个以refs/tags/ 3开头的名称。任一种都可以让您找到提交的SHA-1哈希。两者之间的主要区别在于,分支名称预计会随着时间而改变,指向分支上的最新提交。但标记名称应永远指向同一提交。

实际上,不仅分支名称会更改,Git还会自动为您更改它。特别是,如果git status说您是on branch master,并且您进行了新提交,则Git将更改refs/heads/master指向新提交。 Git还使新提交具有恰好在您进行新提交之前指向的提交master作为其父提交ID。这就是分支增长的方式:根据定义,引用始终指向最尖端的提交。最尖端的提交通过其父ID指向较早的提交,该提交指向更远的历史,依此类推。 (如果提交是合并提交,则它有两个,甚至三个或更多父ID,而不是一个。)

这意味着您会在其他Git对象内部找到这些Git对象ID的关键位置。

这是在漂亮地打印提交(使用git cat-file -p HEADgit cat-file commit HEAD,它们都执行相同的操作)时看到的:查看当前分支的尖端提交的内容,然后看到tree <ugly-sha-1> 。因此,树ID存储在提交中。但是,如果提交本身在一个松散的对象中,并且在文件编辑器或查看器中显示.git/objects/05/...,则不会看到该哈希,甚至看不到单词tree。这是因为存储库数据已压缩(特别是使用zlib;使用修改后的xdelta版本,压缩了存储在打包文件中的对象,然后压缩了zlib)。这也是为什么您可以并且应该使用类似git cat-file的内容查看对象内容的原因:它使您与位置和格式详细信息隔离。您只需要对象的ID; git cat-file将查找并解压缩对象。

通过在树上使用git cat-file -p可以看到,树对象本身包含其他Git对象ID:

$ git cat-file -p 'HEAD^{tree}'
[snip]
100644 blob cb2ca2bb2e86aa4a4c3c9b08490c72b04a1778d3    rfuncs.h
040000 tree 05006c6f2e6119fede241cf6ec845291a5be665e    sbuf
[snip more]


因此,一个特定的Git Blob对象(cb2ca2b...)和一个其他的Git树对象(05006c6...)的Git对象名称已保存在与HEAD提交关联的树中。



1 Pigeonhole Principle告诉我们,如果我们对足够多的不同对象进行哈希处理,则至少两个不同的文件将得到ce013625030ba8dba906f756967f9e9ca394464a。那天,Git休息了。 :-)但是,需要大量的输入才能获得哈希冲突。概率数学表明,即使您有数十亿个文件,也很可能在Git哈希冲突发生之前很长时间就丢失了数千个磁盘驱动器上的数据。实际上,大约需要1.71个万亿次文件,才能将哈希冲突的概率提高到10-18中的一个,这是企业级存储介质的典型引用错误率。

当然,这些假设是随机机会输入,而不是使用密码学原理试图破坏Git的恶意构建文件。

2在master内找到文件refs/heads并非偶然。但是,总有一天,Git可能不再将名称存储在平面文件中,因为这对分支命名施加了文件系统限制:特别是,这使得不可能同时拥有名为x的分支和名为x/y的分支。请注意,当引用位于.git/packed-refs中时,至少在信息理论上,可能同时具有xx/y。这仅仅是一个令人讨厌的文件系统限制,您不能拥有一个名为x的文件以及一个名为x的目录,其中包含名为y的文件。 (除了POSIX要求之外,也没有特别好的理由来限制此文件系统。)

3如果标签是带注释的标签,则它指向类型为“标签”的Git对象,然后指向下一个对象。实际上,这就是带注释标签名称的定义:它是refs/tags/中的一个指向带注释标签对象的名称。标记对象通常直接指向一个提交,尽管您可以标记一个标记对象,而不是直接标记一个提交,然后必须剥离两个标记层才能到达基础提交。

Git允许您将标签(轻量或带注释)指向任何Git对象,但通常仅允许您将分支名称指向提交对象。

关于git - git中的树哈希存储在哪里?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38176781/

相关文章:

git - 为什么不应该从服务器中删除标签?

regex - 忽略与 git diff 中的字符串匹配的更改

perl - 在 Perl 中更改哈希值的输出

php - 根据要求进行 Laravel 哈希检查

php - 每个单词的哈希输出都相同

node.js - NodeJS 与 MongoDB - 将项目推送到 Github

git - 在 Github 上 fork 一个不稳定的 repo 的问题

git - 在 Visual Studio 中使用 Cygwin Git+SSH

php - 如何用该行中的用户名生成的哈希填充 mysql 中的整列

python - 重复散列任意 Python 元组