Каков внутренний формат объекта дерева мерзавца?
Каков формат содержимого объекта Git Tree?
Содержимое объекта BLOB-объекта blob [size of string] NUL [string]
, а что это за объект дерева?
5 ответов
Формат объекта дерева:
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 - это большой двоичный объект, а остальные - деревья.
Обратите внимание, что я добавил новые строки и пробелы после \0
ради красивой печати. Обычно весь контент не имеет новых строк. Также я конвертировал 20 байтов (то есть SHA-1 ссылающихся BLOB-объектов и деревьев) в шестнадцатеричную строку для лучшей визуализации.
Я пытаюсь немного подробнее рассказать об ответе @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')"
Итак, капля 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. См. Tree Objects в 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/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
Выраженный как BNF-подобный образец, дерево мерзавца содержит данные формы
(?<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
- ПРОБЕЛ (то есть, байт
0x20
) - ASCII-кодированная десятичная длина несжатого содержимого
После NUL (т. Е. Байта 0x00
) терминатор, дерево содержит одну или несколько записей вида
- ASCII-кодированный восьмеричный режим
- ПРОСТРАНСТВО
- название
- NUL
- Хэш SHA1, закодированный как 20 байтов без знака
Затем Git передает данные дерева в дефлят zlib для компактного хранения.
Помните, что мерзавцы являются анонимными. Деревья Git связывают имена с хэшами SHA1 другого контента, который может быть каплями, другими деревьями и так далее.
Для демонстрации рассмотрим дерево, связанное с тегом git v2.7.2, которое вы можете просмотреть на GitHub.
$ git rev-parse v2.7.2^{tree}
802b6758c0c27ae910f40e1b4862cb72a71eee9f
Код ниже требует, чтобы объект дерева был в "свободном" формате. Я не знаю способа извлечения одного необработанного объекта из файла пакета, поэтому я сначала запустил 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 @@ + дерево 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]
в двоичном виде, а не в шестнадцатеричном. Это фрагмент Python для разбора объекта дерева на записи:
entries = [
line[0:2]+(line[2].encode('hex'),)
for line in
re.findall('(\d+) (.*?)\0(.{20})', body, re.MULTILINE)
]
Как и предполагалось, Pro Git хорошо объясняет структуру. Чтобы показать красивое дерево, используйте:
git cat-file -p 4c975c5f5945564eae86d1e933192c4a9096bfe5
чтобы показать то же дерево в необработанном, но несжатом виде, используйте:
git cat-file tree 4c975c5f5945564eae86d1e933192c4a9096bfe5
Структура по сути та же самая: хэши хранятся в виде двоичных файлов и имен файлов с нулевым символом в конце.