Подтвердить что ты не робот

Каков внутренний формат древовидного объекта git?

Каков формат содержимого дерева git?

Содержимое объекта blob blob [size of string] NUL [string], но что это за объект дерева?

4b9b3361

Ответ 1

Формат объекта дерева:

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

Формат каждой записи, содержащей ссылки на другие деревья и капли:

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

Я написал script делящий древовидные объекты. Он выводится следующим образом:

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/file. Пример выше, pom.xml - это blob, а другие - деревья.

Обратите внимание, что я добавил новые строки и пробелы после \0 ради довольно печатания. Обычно у всего содержимого нет новых строк. Также я преобразовал 20 байтов (т.е. SHA-1 ссылок на капли и деревья) в шестую строку, чтобы лучше визуализировать.

Ответ 2

Я пытаюсь подробнее рассказать об ответах @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.

Чтобы получить тот же контент, но в необработанном формате, используйте:

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

Итак, blob 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 преобразует хэши ASCII SHA-1 в двоичные файлы. Содержимое дерева сначала помещается в переменную 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 

Чтобы вычислить его вручную, нам нужно передать содержимое дерева script в команду shasum. На самом деле мы уже сделали это выше (для сравнения сгенерированного и сохраненного контента). Результаты:

0b6e66b04bc1448ca594f143a91ec458667f420e *- 

и совпадает с git mktree.

Упакованные объекты

Вы можете обнаружить, что для вашего репо вы не можете найти файлы .git/objects/XX/XXX... сохранение объектов Git. Это происходит потому, что некоторые или все "свободные" объекты были упакованы в один или несколько файлов .git\objects\pack\*.pack.

Чтобы распаковать репо, сначала перенесите файлы пакетов в исходное положение, затем git -отпаковать объекты.

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

Чтобы переупаковать, когда вы закончите эксперименты:

$ git gc

Ответ 3

Выраженный как 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 начинается с заголовка

  • буквальная строка tree
  • ПРОСТРАНСТВО (т.е. байт 0x20)
  • ASCII-кодированная десятичная длина несжатого содержимого

После терминатора NUL (т.е. байта 0x00) дерево содержит одну или несколько записей формы

  • ASCII-кодированный восьмеричный режим
  • КОСМОС
  • имя
  • NUL
  • SHA1 хэш кодируется как 20 беззнаковых байтов

Git затем загружает данные дерева в zlibs сгибать для компактного хранилища.

Помните, что git blobs анонимны. Деревья git связывают имена с хэшами SHA1 другого содержимого, которые могут быть блобами, другими деревьями и т.д.

Чтобы продемонстрировать, рассмотрим дерево, связанное с тегом git s v2.7.2, которое вы можете захотеть просмотреть GitHub.

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

В приведенном ниже коде требуется, чтобы древовидный объект находился в свободном формате. Я не знаю, как извлечь один необработанный объект из файла packfile, поэтому я сначала запустил git unpack-objects в файлах пакетов из моего клонировать в новый репозиторий. Имейте в виду, что этот расширенный каталог .git, который начался примерно с 90 МБ, до 1,8 ГБ.

ОБНОВЛЕНИЕ: Благодаря 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 blob e5b4126bec557db55924b7b60ed70349626ea2c4   .mailmap

Ответ 4

Ответ на @lemiorhan правильный, но пропускает небольшие важные детали. Формат дерева:

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

Но важно то, что [SHA-1 of referencing blob or tree] находится в двоичной форме, а не в шестнадцатеричной форме. Это фрагмент Python для разбора дерева в записи:

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

Ответ 5

Как и было предложено, Pro Git хорошо объясняет структуру. Чтобы показать красивое дерево, используйте:

git cat-file -p 4c975c5f5945564eae86d1e933192c4a9096bfe5

чтобы показать одно и то же дерево в своей необработанной, но несжатой форме, используйте:

git cat-file tree 4c975c5f5945564eae86d1e933192c4a9096bfe5

Структура по существу то же самое, с хешами, хранящимися как двоичные и нулевые имена файлов.