Git树对象的内容格式是什么?blob对象的内容是blob [size of string] NUL [string],但树对象的内容是什么?
blob [size of string] NUL [string]
5jvtdoz21#
树对象的格式:
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个字节(即引用blob和树的SHA-1)转换为十六进制字符串,以便更好地显示。
\0
bkkx9g8r2#
我试着通过一个测试回购来详细说明@lemiorhan的答案。
在空文件夹中创建测试项目:
$ echo ciao > file1 $ mkdir folder1 $ echo hello > folder1/file2 $ echo hola > folder1/file3
那就是:
$ find -type f ./file1 ./folder1/file2 ./folder1/file3
创建本地Git存储库:
$ git init $ git add . $ git write-tree 0b6e66b04bc1448ca594f143a91ec458667f420e
最后一个命令返回顶层树的哈希值。
要以人类可读格式打印树的内容,请用途:
$ git ls-tree 0b6e66 100644 blob 887ae9333d92a1d72400c210546e28baa1050e44 file1 040000 tree ab39965d17996be2116fe508faaf9269e903c85b folder1
在本例中,0b6e66是顶层树的前六个字符,对于folder1也可以这样做。要获取原始格式的相同内容,请用途:
0b6e66
folder1
$ git cat-file tree 0b6e66 100644 file1 ▒z▒3=▒▒▒$ ▒►Tn(▒▒♣D40000 folder1 ▒9▒]▒k▒◄o▒▒▒i▒♥▒[%
其内容类似于以压缩格式物理存储为文件的内容,但缺少初始字符串:
tree [content size]\0
为了得到实际的内容,我们需要解压缩存储c1f4bf树对象的文件。
c1f4bf
.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。请注意,由于终端不是为打印二进制文件而设计的,它可能会吃掉字符串的某个部分或显示其他奇怪的行为。在这种情况下,使用| od -c管道传输上述命令或使用下一节中的手动解决方案。
| od -c
为了理解树的生成过程,我们可以从它的人类可读内容开始自己生成它,例如对于顶部的树:
每个对象的ASCII SHA-1散列值都以二进制格式转换和存储。如果您需要的只是ASCII散列值的二进制版本,则可以使用以下命令:
$ echo -e "$(echo ASCIIHASH | sed -e 's/../\\x&/g')"
因此,blob 887ae9333d92a1d72400c210546e28baa1050e44被转换为
887ae9333d92a1d72400c210546e28baa1050e44
$ echo -e "$(echo 887ae9333d92a1d72400c210546e28baa1050e44 | sed -e 's/../\\x&/g')" ▒z▒3=▒▒▒$ ▒►Tn(▒▒♣D
如果我们想创建整个树对象,下面是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哈希值转换为二进制值。首先将树的内容放入变量t中,然后计算其长度并将其输出到END{...}部分。如上所述,控制台不太适合打印二进制文件,因此我们可能需要将其替换为\x##的等效项:
bsha
t
END{...}
\x##
$ 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 *-
校验和相同。
得到它的官方方式或多或少是:
$ git ls-tree 0b6e66 | git mktree 0b6e66b04bc1448ca594f143a91ec458667f420e
型要手动计算,我们需要将脚本生成的树的内容通过管道传输到shasum命令中。实际上,我们已经在上面做过了(比较生成的内容和存储的内容)。结果是:
shasum
0b6e66b04bc1448ca594f143a91ec458667f420e *-
并且与git mktree相同。
git mktree
您可能会发现,对于您的repo,您无法找到存储Git对象的.git/objects/XX/XXX...文件。这是因为一些或所有“松散”对象被打包到一个或多个.git\objects\pack\*.pack文件中。要解包存储库,首先将包文件从其原始位置移开,然后git解包对象。
.git/objects/XX/XXX...
.git\objects\pack\*.pack
$ mkdir .git/pcache $ mv .git/objects/pack/*.pack .git/pcache/ $ git unpack-objects < .git/pcache/*.pack
完成实验后重新打包:
$ git gc
存储器
5jvtdoz23#
以类似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.字符串tree1.空格(* 即 * 字节0x20)1.未压缩内容的ASCII编码十进制长度在NUL(* 即 *,字节0x00)终止符之后,树包含一个或多个以下形式的条目
tree
0x20
0x00
$ git rev-parse v2.7.2^{tree} 802b6758c0c27ae910f40e1b4862cb72a71eee9f
下面的代码要求树对象为“松散”格式。我不知道如何从包文件中提取单个原始对象,所以我首先对包文件运行git unpack-objects,从我的克隆到一个新的仓库。请注意,这将扩展一个.git目录,从大约90 MB开始,结果大约1.8 GB。
git unpack-objects
.git
**更新:**感谢max630展示how to unpack a single object。
#! /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"; # e.g., 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 }
看一下我们可怜的git ls-tree的实际操作。除了输出tree标记和长度外,输出是相同的。
git ls-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 @@ +tree 15530 100644 blob 5e98806c6cc246acef5f539ae191710a0c06ad3f .gitattributes 100644 blob 1c2f8321386f89ef8c03d11159c97a0f194c4423 .gitignore 100644 blob e5b4126bec557db55924b7b60ed70349626ea2c4 .mailmap
eulz3vhy4#
@lemiorhan回答正确但遗漏了重要的小细节.树格式为:
但重要的是[SHA-1 of referencing blob or tree]是二进制形式,而不是十六进制形式。这是将树对象解析为条目的Python代码片段:
[SHA-1 of referencing blob or tree]
entries = [ line[0:2]+(line[2].encode('hex'),) for line in re.findall('(\d+) (.*?)\0(.{20})', body, re.MULTILINE) ]
g52tjvyc5#
正如建议的那样,Pro Git很好地解释了这个结构。要显示一个打印得很漂亮的树,请用途:
git cat-file -p 4c975c5f5945564eae86d1e933192c4a9096bfe5
要以原始但未压缩的形式显示同一棵树,请用途:
git cat-file tree 4c975c5f5945564eae86d1e933192c4a9096bfe5
其结构基本上是相同的,散列存储为二进制文件名和以空值结尾的文件名。
5条答案
按热度按时间5jvtdoz21#
树对象的格式:
引用其他树和blob的每个条目的格式:
我写了一个压缩树对象的脚本。它的输出如下:
模式的第一个字符是数字1,表示引用了一个blob/文件。在上面的例子中,pom.xml是一个blob,其他的是树。
请注意,我在
\0
后面添加了新的行和空格,以便于打印。通常所有的内容都没有新的行。此外,我将20个字节(即引用blob和树的SHA-1)转换为十六进制字符串,以便更好地显示。bkkx9g8r2#
我试着通过一个测试回购来详细说明@lemiorhan的答案。
创建测试存储库
在空文件夹中创建测试项目:
那就是:
创建本地Git存储库:
最后一个命令返回顶层树的哈希值。
读取树内容
要以人类可读格式打印树的内容,请用途:
在本例中,
0b6e66
是顶层树的前六个字符,对于folder1
也可以这样做。要获取原始格式的相同内容,请用途:
其内容类似于以压缩格式物理存储为文件的内容,但缺少初始字符串:
为了得到实际的内容,我们需要解压缩存储
c1f4bf
树对象的文件。此文件使用zlib压缩,因此我们使用以下命令获取其内容:
我们学习的树内容大小是67。
请注意,由于终端不是为打印二进制文件而设计的,它可能会吃掉字符串的某个部分或显示其他奇怪的行为。在这种情况下,使用
| od -c
管道传输上述命令或使用下一节中的手动解决方案。手动生成树对象内容
为了理解树的生成过程,我们可以从它的人类可读内容开始自己生成它,例如对于顶部的树:
每个对象的ASCII SHA-1散列值都以二进制格式转换和存储。如果您需要的只是ASCII散列值的二进制版本,则可以使用以下命令:
因此,blob
887ae9333d92a1d72400c210546e28baa1050e44
被转换为如果我们想创建整个树对象,下面是awk的一行代码:
函数
bsha
将SHA-1 ASCII哈希值转换为二进制值。首先将树的内容放入变量t
中,然后计算其长度并将其输出到END{...}
部分。如上所述,控制台不太适合打印二进制文件,因此我们可能需要将其替换为
\x##
的等效项:输出应该是理解树内容结构的一个很好的折衷。
其中每个对象条目类似于:
模式是UNIX文件系统模式的一个子集。请参阅Git手册上的Tree Objects以了解更多详细信息。
我们需要确保结果的一致性,为此,我们可以将awk生成的树的校验和与Git存储的树的校验和进行比较。
至于后者:
至于家造树:
校验和相同。
计算树对象校验和
得到它的官方方式或多或少是:
型
要手动计算,我们需要将脚本生成的树的内容通过管道传输到
shasum
命令中。实际上,我们已经在上面做过了(比较生成的内容和存储的内容)。结果是:并且与
git mktree
相同。打包的对象
您可能会发现,对于您的repo,您无法找到存储Git对象的
.git/objects/XX/XXX...
文件。这是因为一些或所有“松散”对象被打包到一个或多个.git\objects\pack\*.pack
文件中。要解包存储库,首先将包文件从其原始位置移开,然后git解包对象。
完成实验后重新打包:
存储器
5jvtdoz23#
以类似BNF的模式表示,git树包含的数据形式为
也就是说,git树以
1.字符串
tree
1.空格(* 即 * 字节
0x20
)1.未压缩内容的ASCII编码十进制长度
在NUL(* 即 *,字节
0x00
)终止符之后,树包含一个或多个以下形式的条目1.空间
1.姓名
1.无
然后Git将树数据提供给zlib’s deflate以进行压缩存储。
记住,git blob是匿名的。Git树将名称与其他内容的SHA1哈希值相关联,这些内容可能是blob、其他树等等。
为了进行演示,考虑与git的v2.7.2标签相关的树,你可能需要browse on GitHub。
下面的代码要求树对象为“松散”格式。我不知道如何从包文件中提取单个原始对象,所以我首先对包文件运行
git unpack-objects
,从我的克隆到一个新的仓库。请注意,这将扩展一个.git
目录,从大约90 MB开始,结果大约1.8 GB。**更新:**感谢max630展示how to unpack a single object。
看一下我们可怜的
git ls-tree
的实际操作。除了输出tree
标记和长度外,输出是相同的。eulz3vhy4#
@lemiorhan回答正确但遗漏了重要的小细节.树格式为:
但重要的是
[SHA-1 of referencing blob or tree]
是二进制形式,而不是十六进制形式。这是将树对象解析为条目的Python代码片段:g52tjvyc5#
正如建议的那样,Pro Git很好地解释了这个结构。要显示一个打印得很漂亮的树,请用途:
要以原始但未压缩的形式显示同一棵树,请用途:
其结构基本上是相同的,散列存储为二进制文件名和以空值结尾的文件名。