web-dev-qa-db-ja.com

gitツリーオブジェクトの内部形式は何ですか?

Gitツリーオブジェクトのコンテンツの形式は何ですか?

BLOBオブジェクトのコンテンツはblob [size of string] NUL [string]しかし、ツリーオブジェクトの場合はどうなりますか?

31
Bystysz

ツリーオブジェクトの形式:

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

他のツリーやブロブへの参照を持つ各エントリの形式:

[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)を16進文字列に変換しました。

33
lemiorhan

テストリポジトリを使用して、@ 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はトップツリーの最初の6文字です。 folder1についても同じことができます。

同じコンテンツを生の形式で取得するには、以下を使用します。

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

内容は、圧縮形式でファイルとして物理的に保存されているものと似ていますが、最初の文字列が欠落しています。

tree [content size]\0

実際のコンテンツを取得するには、c1f4bfツリーオブジェクトを格納するファイルを解凍する必要があります。必要なファイルは-2/38パス形式で指定-:

.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でパイプするか、次のセクションの手動ソリューションを使用してください。

ツリーオブジェクトのコンテンツを手動で生成する

ツリー生成プロセスを理解するために、人間が読めるコンテンツから始めて、ツリー生成プロセスを自分で生成できます。トップツリー:

$ 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')"

したがって、ブロブ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##形式の同等のものに置き換えることができます。

$ 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マニュアルの ツリーオブジェクト を参照してください。

結果に一貫性があることを確認する必要があります。この目的のために、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コマンドにパイプする必要があります。実際、これはすでに上記で行っています(生成されたコンテンツと保存されたコンテンツを比較するため)。結果は次のとおりです。

0b6e66b04bc1448ca594f143a91ec458667f420e *- 

git mktreeと同じです。

パックされたオブジェクト

リポジトリの場合、Gitオブジェクトを格納しているファイル.git/objects/XX/XXX...が見つからない場合があります。これは、一部またはすべての「緩い」オブジェクトが1つ以上の.git\objects\pack\*.packファイルにパックされているために発生します。

リポジトリを解凍するには、最初にパックファイルを元の位置から移動してから、オブジェクトをgit-unpackします。

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

実験が終わったら再梱包するには:

$ git gc
18
antonio

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. リテラル文字列tree
  2. スペース(つまり、バイト0x20
  3. 圧縮されていないコンテンツのASCIIエンコードされた10進数の長さ

NUL(、つまり、バイト0x00)ターミネータの後、ツリーにはフォームの1つ以上のエントリが含まれます

  1. ASCIIエンコードされた8進数モード
  2. SPACE
  3. 名前
  4. NUL
  5. 20の符号なしバイトとしてエンコードされたSHA1ハッシュ

その後、Gitはツリーデータを zlib’s deflateにフィードしてコンパクトストレージを実現します。

Gitblobは匿名であることに注意してください。 Gitツリーは、名前を、blobや他のツリーなどの他のコンテンツのSHA1ハッシュに関連付けます。

実例として、gitのv2.7.2タグに関連付けられているツリーを検討します。これは、 GitHubで参照 とすることができます。

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

以下のコードでは、ツリーオブジェクトが「緩い」形式である必要があります。パックファイルから単一のrawオブジェクトを抽出する方法がわからないため、最初に、クローンから新しいリポジトリへのパックファイルに対して git unpack-objects を実行しました。これにより、約90MBで始まった.gitディレクトリが拡張されて約1.8GBになったことに注意してください。

UPDATE:単一のオブジェクトを解凍する方法 を表示してくれた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";

# 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マーカーと長さを出力することを除いて、出力は同じです。

$ 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 b
12
Greg Bacon

@lemiorhanの答えは正しいですが、重要な細部が欠けています。ツリー形式は次のとおりです。

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

しかし、重要なのは[SHA-1 of referencing blob or tree]は16進数ではなくバイナリ形式です。これはPythonスニペットで、ツリーオブジェクトをエントリに解析します:

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

提案されているように、ProGitは構造をよく説明しています。きれいに印刷されたツリーを表示するには、次を使用します。

git cat-file -p 4c975c5f5945564eae86d1e933192c4a9096bfe5

同じツリーをそのままの圧縮されていない形式で表示するには、次のコマンドを使用します。

git cat-file tree 4c975c5f5945564eae86d1e933192c4a9096bfe5

構造は基本的に同じであり、ハッシュはバイナリおよびnullで終了するファイル名として格納されます。

2
Joe