我正在学习本教程(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"
为什么没有出现?这个哈希值是不是存储在任何地方?
答案 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哈希值,作为字符串(commit
,blob
,tree
或tag
) ,后跟一个空格,然后是一个十进制表示的对象长度,以字节为单位,后跟一个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对象中。
这是你在漂亮打印提交时所看到的(使用master
或git 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-refs
和x
,至少在信息理论意义上如此。它只是一个烦人的文件系统限制,你不能拥有一个名为x/y
的文件和一个名为x
的目录,其中包含一个名为x
的文件。 (除了POSIX要求之外,这个文件系统限制也没有特别好的理由。)
3 如果标签是带注释的标签,则它引用类型为&#34;标签&#34;的Git对象,然后指向下一个对象。实际上,这是带注释标签名称的定义:它是y
中指向带注释标签对象的名称。标记对象通常直接指向提交,尽管您可以标记标记对象,而不是直接标记提交,然后必须关闭两个标记层以获取基础提交。
Git将允许您将标记(轻量级或注释)指向任何Git对象,但通常只允许您将分支名称指向提交对象。
答案 2 :(得分:1)
试试这个:
git log --pretty=format:'%T %s'