我正在关注本教程(https://jwiegley.github.io/git-from-the-bottom-up/1-Repository/3-blobs-are-stored-in-trees.html)了解git体系结构。
命令
$ git cat-file commit HEAD
Run Code Online (Sandbox Code Playgroud)
给我哈希表引用的树的哈希值,“ 0563f77d884e4f79ce95117e2d686d7d6e282887”。现在,我尝试在中找到此哈希.git:
$ find .git/ | xargs grep "0563f77"
Run Code Online (Sandbox Code Playgroud)
为什么什么都没出现?这个哈希值不存储在任何地方吗?
我认为您在这里将几个概念混在一起:
Git的内部对象名称(SHA-1散列)对于对象的内容是唯一的(并且完全由其决定,因此从哲学上讲是)。更正确地,它们是该对象的类型名称的SHA-1散列作为一个字符串(commit,blob,tree,或tag),后跟一个空格,然后以字节为单位的对象的长度的一个十进制化表示,然后是NUL或零字节,后跟基础对象的原始数据。
请注意,如果对同一对象进行两次哈希处理,则两次都将获得相同的哈希值。因此,如果一个名为的文件中README.txt包含一些文本,然后将该文件复制到该文件read-me-too.txt并对其进行哈希处理,则将再次获得相同的哈希值。这是因为文件名不是哈希计算输入的一部分,仅是类型(在这种情况下为blob),空白,大小,零字节和内容。
如果两个文件只包含一行读hello(外加一个换行符,六个字节总数),输入到散列函数blob 6\0hello\n(其中\0,并\n代表零字节和换行)。实际上,这两个文件的哈希为ce013625030ba8dba906f756967f9e9ca394464a。(我曾经git hash-object找到这个值,尽管任何SHA-1代码都可以解决这个问题:例如,您可以使用几行Python或Ruby代码或相当数量的C代码来找到此值。哈希树比较棘手)
对象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的通用术语是reference。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 HEAD或git cat-file commit HEAD,两者都执行相同的操作)时所看到的:查看当前分支的尖端提交的内容,然后看到tree <ugly-sha-1>。因此,树ID存储在commit中。但是,如果提交本身在一个松散的对象中,并且.git/objects/05/...在文件编辑器或查看器中启动,则不会看到该哈希,甚至看不到单词tree。这是因为存储库数据是经过压缩的(特别zlib是使用的修改版xdelta,然后使用zlib 压缩),压缩包文件中存储的对象的方式有所不同。这也是为什么您可以并且应该使用类似git cat-file查看对象的内容:使您与位置和格式详细信息隔离。您只需要对象的ID;git cat-file将找到并解压缩对象。
树对象本身包含其他Git对象ID,如您git cat-file -p在树上使用所看到的:
$ git cat-file -p 'HEAD^{tree}'
[snip]
100644 blob cb2ca2bb2e86aa4a4c3c9b08490c72b04a1778d3 rfuncs.h
040000 tree 05006c6f2e6119fede241cf6ec845291a5be665e sbuf
[snip more]
Run Code Online (Sandbox Code Playgroud)
因此,一个特定的Git Blob对象(cb2ca2b...)和一个其他的Git树对象(05006c6...)的Git对象名称保存在与HEAD提交关联的树中。
1的鸽巢原理告诉我们,如果我们足够的哈希不同的对象,我们将获得ce013625030ba8dba906f756967f9e9ca394464a至少有两个不同的文件。在那一天,Git休息了。:-)但是,需要大量输入才能获得哈希冲突。概率数学表明,即使您有数十亿个文件,也很可能在Git哈希冲突发生之前很长时间就丢失了数千个磁盘驱动器上的数据。事实上,大约需要1.71万亿的文件,以提高在10哈希碰撞到一个概率-18,这是企业级的存储介质中的典型引述错误率。
当然,这些假设是随机机会输入,而不是使用密码学原理试图破坏Git的恶意构建文件。
2,这并非巧合,你找到的文件master中refs/heads。不过,总有一天,Git可能不再将名称存储在平面文件中,因为这对分支命名施加了文件系统限制:特别是,这使得不可能同时具有分支x和分支x/y。注意,当引用是.git/packed-refs,它是可以兼得x,并x/y至少在一个信息理论意义。这仅仅是一个恼人的文件系统的限制,你不能有一个文件名为x 和目录也被命名x包含命名文件y。(除了POSIX要求之外,也没有特别好的理由来限制此文件系统。)
3如果标签是带注释的标签,则它指向类型为“标签”的Git对象,然后指向下一个对象。实际上,这就是带注释的标记名称的定义:它refs/tags/是指向带标记的标记对象的名称。标记对象通常直接指向一个提交,尽管您可以标记一个标记对象,而不是直接标记一个提交,然后必须剥离两个标记层才能到达基础提交。
Git允许您将标签(轻量或带注释)指向任何Git对象,但通常仅允许您将分支名称指向提交对象。