git树对象的内部格式是什么?

什么是混帐树对象的内容的格式?

blob对象的内容是blob [size of string] NUL [string] ,但是树对象又是什么?

树对象的格式:

 tree [content size]\0[Entries having references to other trees and blobs] 

每个条目的格式引用了其他树和blob:

 [mode] [file/folder name]\0[SHA-1 of referencing blob or tree] 

我写了一个缩小树对象的脚本。 它输出如下:

 tree 192\0 40000 octopus-admin\0 a84943494657751ce187be401d6bf59ef7a2583c 40000 octopus-deployment\0 14f589a30cf4bd0ce2d7103aa7186abe0167427f 40000 octopus-product\0 ec559319a263bc7b476e5f01dd2578f255d734fd 100644 pom.xml\0 97e5b6b292d248869780d7b0c65834bfb645e32a 40000 src\0 6e63db37acba41266493ba8fb68c76f83f1bc9dd 

作为模式的第一个字符的数字1显示的是对Blob /文件的引用。 上面的例子中,pom.xml是一个blob,其他的是树。

请注意,为了漂亮打印,我在\0之后添加了新的行和空格。 通常所有的内容都没有新的行。 我也转换了20个字节(即引用斑点和树的SHA-1)到hexstring,以更好地形象化。

我尝试通过一个testing回购来详细阐述@lemiorhan的答案。

创build一个testing回购

在一个空的文件夹中创build一个testing项目:

 $ echo ciao > file1 $ mkdir folder1 $ echo hello > folder1/file2 $ echo hola > folder1/file3 

那是:

 $ find -type f ./file1 ./folder1/file2 ./folder1/file3 

创build本地Git回购:

 $ git init $ git add . $ git write-tree 0b6e66b04bc1448ca594f143a91ec458667f420e 

最后一个命令返回顶级树的散列。

阅读树内容

要以可读的格式打印树的内容,请使用:

 $ git ls-tree 0b6e66 100644 blob 887ae9333d92a1d72400c210546e28baa1050e44 file1 040000 tree ab39965d17996be2116fe508faaf9269e903c85b folder1 

在这种情况下, 0b6e66是顶部树的前六个字符。 您可以对folder1执行相同的操作。

要获得相同的内容,但以原始格式使用:

 $ git cat-file tree 0b6e66 100644 file1 ▒z▒3=▒▒▒$ ▒►Tn(▒▒♣D40000 folder1 ▒9▒]▒k▒◄o▒▒▒i▒♥▒[% 

内容类似于以压缩格式物理存储为文件的内容,但是它忽略了最初的string:

 tree [content size]\0 

要获取实际内容,我们需要解压缩存储c1f4bf树对象的文件。 我们想要的文件是 – 给出2/38path​​格式 – :

 .git/objects/0b/6e66b04bc1448ca594f143a91ec458667f420e 

这个文件是用zlib压缩的,因此我们通过以下方式获取它的内容:

 $ openssl zlib -d -in .git/objects/0b/6e66b04bc1448ca594f143a91ec458667f420e tree 67 100644 file1 ▒z▒3=▒▒▒$ ▒►Tn(▒▒♣D40000 folder1 ▒9▒]▒k▒◄o▒▒▒i▒♥▒[% 

我们学习的树内容大小是67。

请注意,由于terminal不是用于打印二进制文件,所以可能会吃掉string的一部分或者显示其他奇怪的行为。 在这种情况下,使用| od -c上面的命令 | od -c或在下一节中使用手动解决scheme。

手动生成树对象内容

为了理解树的生成过程,我们可以从人类可读的内容开始自己生成树,例如顶层树:

 $ git ls-tree 0b6e66 100644 blob 887ae9333d92a1d72400c210546e28baa1050e44 file1 040000 tree ab39965d17996be2116fe508faaf9269e903c85b folder1 

每个对象ASCII SHA-1哈希都被转换并以二进制格式存储。 如果你需要的只是ASCII哈希的二进制版本,你可以这样做:

 $ echo -e "$(echo ASCIIHASH | sed -e 's/../\\x&/g')" 

所以blob 887ae9333d92a1d72400c210546e28baa1050e44被转换为

 $ echo -e "$(echo 887ae9333d92a1d72400c210546e28baa1050e44 | sed -e 's/../\\x&/g')" ▒z▒3=▒▒▒$ ▒►Tn(▒▒♣D 

如果我们要创build整个树对象,这里是一个awk单行:

 $ git ls-tree 0b6e66 | awk -b 'function bsha(asha)\ {patsplit(asha, x, /../); h=""; for(j in x) h=h sprintf("%c", strtonum("0x" x[j])); return(h)}\ {t=t sprintf("%d %s\0%s", $1, $4, bsha($3))} END {printf("tree %s\0%s", length(t), t)}' tree 67 100644 file1 ▒z▒3=▒▒▒$ ▒►Tn(▒▒♣D40000 folder1 ▒9▒]▒k▒◄o▒▒▒i▒♥▒[% 

函数bsha将SHA-1 ASCII散列转换为二进制文件。 首先将树的内容放入variablest ,然后计算其长度并打印在END{...}部分。

如上所述,控制台不太适合打印二进制文件,所以我们可能希望用它们的\x##格式replace它们:

 $ git ls-tree 0b6e66 | awk -b 'function bsha(asha)\ {patsplit(asha, x, /../); h=""; for(j in x) h=h sprintf("%s", "\\x" x[j]); return(h)}\ {t=t sprintf("%d %s\0%s", $1, $4, bsha($3))} END {printf("tree %s\0%s", length(t), t)}' tree 187 100644 file1 \x88\x7a\xe9\x33\x3d\x92\xa1\xd7\x24\x00\xc2\x10\x54\x6e\x28\xba\xa1\x05\x0e\x4440000 folder1 \xab\x39\x96\x5d\x17\x99\x6b\xe2\x11\x6f\xe5\x08\xfa\xaf\x92\x69\xe9\x03\xc8\x5b% 

输出应该是理解树内容结构的良好折衷。 将上面的输出与一般树内容结构进行比较

 tree [content size]\0[Object Entries] 

每个对象条目如下所示:

 [mode] [Object name]\0[SHA-1 in binary format] 

模式是UNIX文件系统模式的一个子集。 有关更多详细信息,请参阅Git手册上的Tree Objects 。

我们需要确保结果是一致的。 为此,我们可以比较awk生成树的校验和与Git存储树的校验和。

至于后者:

 $ openssl zlib -d -in .git/objects/0b/6e66b04bc1448ca594f143a91ec458667f420e | shasum 0b6e66b04bc1448ca594f143a91ec458667f420e *- 

至于自制的树:

 $ git ls-tree 0b6e66 | awk -b 'function bsha(asha)\ {patsplit(asha, x, /../); h=""; for(j in x) h=h sprintf("%c", strtonum("0x" x[j])); return(h)}\ {t=t sprintf("%d %s\0%s", $1, $4, bsha($3))} END {printf("tree %s\0%s", length(t), t)}' | shasum 0b6e66b04bc1448ca594f143a91ec458667f420e *- 

校验和是一样的。

计算树对象校验和

官方或多或less地得到它是:

 $ git ls-tree 0b6e66 | git mktree 0b6e66b04bc1448ca594f143a91ec458667f420e 

为了手动计算,我们需要将脚本生成树的内容shasumshasum命令中。 其实我们已经完成了上述(比较生成和存储的内容)。 结果是:

 0b6e66b04bc1448ca594f143a91ec458667f420e *- 

git mktree

打包的对象

您可能会发现,对于您的回购,您无法find文件.git/objects/XX/XXX...存储Git对象。 发生这种情况的原因是部分或全部“松散”对象已被打包到一个或多个.git\objects\pack\*.pack文件中。

要打开回购包,首先将包文件从原始位置移开,然后将对象解包。

 $ mkdir .git/pcache $ mv .git/objects/pack/*.pack .git/pcache/ $ git unpack-objects < .git/pcache/*.pack 

当您完成实验时重新包装:

 $ git gc 

表示为类似BNF的模式,git树包含表单的数据

 (?<tree> tree (?&SP) (?&decimal) \0 (?&entry)+ ) (?<entry> (?&octal) (?&SP) (?&strnull) (?&sha1bytes) ) (?<strnull> [^\0]+ \0) (?<sha1bytes> (?s: .{20})) (?<decimal> [0-9]+) (?<octal> [0-7]+) (?<SP> \x20) 

也就是说,一个git树开头的头

  1. stringtree
  2. 空格( 字节0x20
  3. 未压缩内容的ASCII编码的十进制长度

在NUL( 字节0x00 )终止符后,树包含一个或多个表单

  1. ASCII编码的八进制模式
  2. 空间
  3. 名称
  4. NUL
  5. SHA1哈希编码为20个无符号字节

Git然后将树数据提供给zlib的紧缩存储器。

请记住,git blob是匿名的。 Git树会将名称与其他内容(可能是blob,其他树等)的SHA1哈希关联起来。

为了演示,考虑与git的v2.7.2标签相关的树,你可能想在GitHub上浏览它 。

 $ git rev-parse v2.7.2^{tree} 802b6758c0c27ae910f40e1b4862cb72a71eee9f 

下面的代码要求树对象是“松散”的格式。 我不知道从包文件中提取单个原始对象的方法,所以我首先将git unpack-objects从我的克隆中运行到新的存储库。 请注意,这扩大了一个开始大约90 MB的.git目录,导致大约1.8 GB。

更新:感谢max630显示如何解压单个对象 。

 #! /usr/bin/env perl use strict; use warnings; use subs qw/ git_tree_contents_pattern read_raw_tree_object /; use Compress::Zlib; my $treeobj = read_raw_tree_object; my $git_tree_contents = git_tree_contents_pattern; die "$0: invalid tree" unless $treeobj =~ /^$git_tree_contents\z/; die "$0: unexpected header" unless $treeobj =~ s/^(tree [0-9]+)\0//; print $1, "\n"; # eg, 100644 SP .gitattributes \0 sha1-bytes while ($treeobj) { # /s is important so . matches any byte! if ($treeobj =~ s/^([0-7]+) (.+?)\0(.{20})//s) { my($mode,$name,$bytes) = (oct($1),$2,$3); printf "%06o %s %s\t%s\n", $mode, ($mode == 040000 ? "tree" : "blob"), unpack("H*", $bytes), $name; } else { die "$0: unexpected tree entry"; } } sub git_tree_contents_pattern { qr/ (?(DEFINE) (?<tree> tree (?&SP) (?&decimal) \0 (?&entry)+ ) (?<entry> (?&octal) (?&SP) (?&strnull) (?&sha1bytes) ) (?<strnull> [^\0]+ \0) (?<sha1bytes> (?s: .{20})) (?<decimal> [0-9]+) (?<octal> [0-7]+) (?<SP> \x20) ) (?&tree) /x; } sub read_raw_tree_object { # $ git rev-parse v2.7.2^{tree} # 802b6758c0c27ae910f40e1b4862cb72a71eee9f # # NOTE: extracted using git unpack-objects my $tree = ".git/objects/80/2b6758c0c27ae910f40e1b4862cb72a71eee9f"; open my $fh, "<", $tree or die "$0: open $tree: $!"; binmode $fh or die "$0: binmode: $!"; local $/; my $treeobj = uncompress <$fh>; die "$0: uncompress failed" unless defined $treeobj; $treeobj } 

看着我们这个可怜的人在行动 除了输出tree标记和长度之外,输出是相同的。

  $ diff -u <(cd〜/ src / git; git ls-tree 802b6758c0)<(../ rawtree)
 --- / dev / fd / 63 2016-03-09 14:41:37.011791393 -0600
 +++ / dev / fd / 62 2016-03-09 14:41:37.011791393 -0600
 @@ -1,3 +1,4 @@
 +树15530
  100644 blob 5e98806c6cc246acef5f539ae191710a0c06ad3f .gitattributes
  100644 blob 1c2f8321386f89ef8c03d11159c97a0f194c4423 .gitignore
  100644 blob e5b4126bec557db55924b7b60ed70349626ea2c4 .mailmap 

@lemiorhan答案是正确的,但错过了很小的重要细节。 树格式是:

 [mode] [file/folder name]\0[SHA-1 of referencing blob or tree] 

但重要的是[SHA-1 of referencing blob or tree]是二进制forms,而不是hex。 这是用于将树对象parsing为条目的Python代码片段:

 entries = [ line[0:2]+(line[2].encode('hex'),) for line in re.findall('(\d+) (.*?)\0(.{20})', body, re.MULTILINE) ] 

正如所build议的,Pro Git很好地解释了结构。 要显示一棵漂亮的树,请使用:

 git cat-file -p 4c975c5f5945564eae86d1e933192c4a9096bfe5 

以原始但未压缩的forms显示相同的树,请使用:

 git cat-file tree 4c975c5f5945564eae86d1e933192c4a9096bfe5 

结构本质上是相同的,哈希以二进制和空终止的文件名forms存储。