树形哈希存储在git中的哪个位置?

时间:2016-07-04 04:02:36

标签: 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

给了我HEAD引用的树的哈希," 0563f77d884e4f79ce95117e2d686d7d6e282887"。现在,我尝试在.git中找到此哈希:

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

为什么没有出现?这个哈希值是不是存储在任何地方?

3 个答案:

答案 0 :(得分:4)

哈希不会出现在grep中,因为它的前两位是目录的标题:

.git/objects/05/63f77d884e4f79ce95117e2d686d7d6e282887

git在.git/objects中存储信息的方式在此处讨论:https://git-scm.com/book/en/v2/Git-Internals-Git-Objects

答案 1 :(得分:4)

我认为你在这里混合了几个概念:

  • Git的内部对象名称(SHA-1哈希)是独一无二的(并且完全由哲学意义决定,因此 >)对象的内容。更准确地说,它们是对象的类型名称的SHA-1哈希值,作为字符串(commitblobtreetag) ,后跟一个空格,然后是一个十进制表示的对象长度,以字节为单位,后跟一个NUL或零字节,后跟底层对象的原始数据。

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

    如果这两个文件只包含一行hello(加上换行符,总共六个字节),则哈希函数的输入为blob 6\0hello\n(其中\0和{{ 1}代表零字节和换行符。实际上,这两个文件的哈希是\n。 (我使用ce013625030ba8dba906f756967f9e9ca394464a来找到这个值,虽然任何SHA-1代码都可以解决这个问题:你可以用几行Python或Ruby代码或者相当多的C代码行来找到它,例如哈希树是trickier。)

    对象ID git hash-object表示包含单词ce013625030ba8dba906f756967f9e9ca394464a后跟换行符的文件。 1 (如果我们知道文件包含哪些数据,我们可以散列数据并找到Git对象ID。通常我们采用另一种方式:我们从一个有效的Git对象ID开始,然后从存储库中检索数据。但是当我们hello一个文件时,我们就这样转动将数据存入哈希值并将其存储为Git对象(如果它尚未存储在存储库中)。如果 已经存在,我们就会好起来:我们只是再次使用相同的哈希值。 )

  • 对象本身 - 对象的数据 - 存储在Git存储库中的某个位置。

    您找到的位置,其中对象git add位于名为0563f77d884e4f79ce95117e2d686d7d6e282887的目录中,其名称以05开头,并继续使用其余的哈希值,这是Git的位置目前保留它所谓的松散物体。但是,Git还对象打包成它所谓的包文件

    包文件的格式相当复杂,进入这里需要很长时间。但是,我们可以说单个包文件可以存储数万个对象。 (包文件格式已经多次修改,以提高性能和单个对象的可访问性。)

  • 我们需要一种方法将人类可读的名称(如分支名称)转换为Git哈希值。这是您在评论中提到的搜索中找到的内容:

      

    它适用于由63f77返回的提交哈希。此哈希存储在$ git rev-parse HEAD [和两个reflogs]

    Git的设计提供了两个特别明显的外部名称形式,特别是分支名称标记名称,我们可以记住特定的提交哈希值。 Git的一般术语是引用。 Git的远程跟踪分支也是引用,存储在.git/refs/heads/master下。除了这些分支和标签名称,您可能会遇到 notes 和" stash" (refs/remotes/):这些也分别使用引用,特别是git stash和(单个)名称refs/notes/中的引用。

    与对象一样,引用值存储在Git存储库中的某个位置,但不承诺它们保留在单个文件中。截至今天(Git版本2.9),它们总是在您找到的单个文件中,或者在名为refs/stash的单个特殊文件中(或偶尔在两者中:在这种情况下,单个文件具有正确的值,如果两人不同意。

分支名称只是以packed-refs 2 开头的引用。标记是以refs/heads/ 3 开头的名称。任何一个都可以让你找到提交的SHA-1哈希。两者之间的关键区别是分支名称​​预期随时间变化,指向分支上的最新提交;但是标签名称应该永远指向相同的提交。

事实上,不仅预期分支名称会发生​​变化,Git将自动为您自动更改。特别是,如果refs/tags/表示您是git status,并且您进行提交,则Git会将on branch master更改为指向新提交。 Git还使新提交作为其父提交ID,在您进行新提交之前指向了提交refs/heads/master。这就是分支增长的方式:根据定义,引用总是指向最尖端的提交。这个提示大多数提交点通过其父ID提交到先前的提交,这些提交在历史记录中进一步指向,依此类推。 (如果提交是合并提交,它有两个,甚至三个或更多个父ID,而不是一个。)

这意味着一个关键位置,你会发现这些Git对象ID在其他Git对象中

这是你在漂亮打印提交时所看到的(使用mastergit cat-file -p HEAD,两者都做同样的事情):你查看当前分支的提示提交的内容,你看到git cat-file commit HEAD。因此树ID存储在提交。但是,如果提交本身是一个松散的对象,并且您在文件编辑器或查看器中调出tree <ugly-sha-1>,则您将无法看到该哈希,甚至是.git/objects/05/...这个词。这是因为存储库数据是压缩的(具体而言,使用zlib;存储在包文件中的对象使用修改后的xdelta版本进行了不同的压缩,然后也是zlib-瘪)。这也是为什么你可以而且应该使用像tree这样的东西来查看对象的内容的原因:它将你与位置和格式细节隔离开来。您只需要对象的ID; git cat-file将查找并解压缩该对象。

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

git cat-file -p

因此,一个特定的Git blob对象($ git cat-file -p 'HEAD^{tree}' [snip] 100644 blob cb2ca2bb2e86aa4a4c3c9b08490c72b04a1778d3 rfuncs.h 040000 tree 05006c6f2e6119fede241cf6ec845291a5be665e sbuf [snip more] )和一个额外的Git树对象(cb2ca2b...)将其Git-object-names保存在与05006c6...关联的树内提交。

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

当然,这些假设是随机机会输入,而不是使用加密理论来试图打破Git的恶意构造文件。

2 您在ce013625030ba8dba906f756967f9e9ca394464a内找到文件master并非巧合。但是,有一天,Git可能不再在平面文件中存储名称,因为这会对分支命名施加文件系统限制:特别是它使得无法同时拥有名为refs/heads的分支和名为x的分支。 。请注意,当引用位于x/y时, 可能同时拥有.git/packed-refsx,至少在信息理论意义上如此。它只是一个烦人的文件系统限制,你不能拥有一个名为x/y 的文件和一个名为x的目录,其中包含一个名为x的文件。 (除了POSIX要求之外,这个文件系统限制也没有特别好的理由。)

3 如果标签是带注释的标签,则它引用类型为&#34;标签&#34;的Git对象,然后指向下一个对象。实际上,这是带注释标签名称的定义:它是y中指向带注释标签对象的名称。标记对象通常直接指向提交,尽管您可以标记标记对象,而不是直接标记提交,然后必须关闭两个标记层以获取基础提交。

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

答案 2 :(得分:1)

试试这个:

git log --pretty=format:'%T %s'