Восстановить файлы из .git / objects · GitHub



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

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

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


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

  tree [размер содержимого]   0 [Записи, имеющие ссылки на другие деревья и капли]  

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

  [режим] [имя файла/папки]  0 [SHA-1 ссылки на blob или дерево]  

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

дерево 192 040000 осьминога админ 0 a84943494657751ce187be401d6bf59ef7a2583c40000 осьминога развертывания 0 14f589a30cf4bd0ce2d7103aa7186abe0167427f40000 осьминога продукт 0 ec559319a263bc7b476e5f01dd2578f255d734fd100644 pom.xml 0 97e5b6b292d248869780d7b0c65834bfb645e32a40000 ЦСИ 0 6e63db37acba41266493ba8fb68c76f83f1bc9dd

Число 1 в качестве первого символа режима показывает, что это ссылка на большой двоичный объект/файл. В приведенном выше примере pom.xml — это blob, а остальные — деревья.

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

9


Я пытаюсь подробнее рассказать об ответе @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 0b6e66100644 blob 887ae9333d92a1d72400c210546e28baa1050e44 file1 040000 tree ab39965d17996be2116fe508faaf9269e903c85b folder1  

В данном случае первые шесть # 66 символов дерева являются первыми символами дерева. То же самое можно сделать для folder1 .

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

  $ git cat-file tree 0b6e66100644 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/6e66b04bc1448ca594f143a91ec458667f420etree 67 100644 file1 ▒z▒3 = ▒▒▒ $ ▒►Tn (▒▒ ♣ D40000 folder1 ▒9▒] ▒k▒◄  o▒▒▒i▒ ♥ ▒ [%  

Мы узнаем, что размер содержимого дерева равен 67.

Обратите внимание, что, поскольку терминал не создается для печати двоичных файлов он может съесть немного па rt строки или показать другое странное поведение. В этом случае передайте приведенные выше команды по конвейеру с помощью | od -c или воспользуйтесь ручным решением в следующем разделе.

Создайте вручную содержимое объекта дерева

Чтобы понять процесс создания дерева, мы можем сгенерировать он сам, начиная с его удобочитаемого содержания, например для верхнего дерева:

  $ git ls-tree 0b6e66100644 blob 887ae9333d92a1d72400c210546e28baa1050e44 file1 040000 tree ab39965d17996be2116fe508faaf9269e903c85IIA folder1   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 преобразует хэши SHA-1 ASCII в двоичные файлы. Содержимое дерева сначала помещается в переменную t , а затем его длина вычисляется и печатается в th e 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)} 'дерево 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 [размер содержимого]  0 [Object Entries]  

, где каждый Ввод объекта выглядит так:

  [режим] [Имя объекта]  0 [SHA-1 в двоичном формате]  

Режимы являются подмножеством режимов файловой системы UNIX. См. Подробности в руководстве по объектам дерева в Git.

Нам нужно убедиться, что результаты согласованы. Для этого мы могли бы сравнить контрольную сумму дерева, сгенерированного awk, с контрольной суммой дерева, хранимого Git..

Что касается последнего:

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

Что касается самодельного дерева:

  $ 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)} '|  shasum0b6e66b04bc1448ca594f143a91ec458667f420e * -  

Контрольная сумма такая же.

Вычислить контрольную сумму объекта дерева

Более или менее официальный способ получить ее:

  $ git  ls-tree 0b6e66 |  git mktree0b6e66b04bc1448ca594f143a91ec458667f420e  

Чтобы вычислить его вручную, нам нужно передать содержимое дерева, созданного скриптом, в команду 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 gc  

4


Выражается как шаблон, подобный BNF, git tree содержит данные в форме

  (?  tree (? & SP) (? & decimal)  0 (? & entry) +) (?  (?  & восьмеричный) (? & SP) (? & strnull) (? & sha1bytes)) (?  [^  0] +  0) (?  (? s:. {20})) (?  [  0-9] +) (?  [0-7] +) (?   x20)  

То есть дерево git начинается с заголовка

  1. буквальная строка tree
  2. SPACE ( т.е. byte 0x20 )
  3. Десятичная длина несжатого содержимого в кодировке ASCII

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

  1. восьмеричный режим в кодировке ASCII
  2. SPACE
  3. name
  4. NUL
  5. Хэш SHA1, закодированный как 20 байтов без знака

Затем Git передает данные дерева в deflate zlib для компактного хранения.

Помните, что капли git анонимны. Деревья 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 perluse strict; использовать предупреждения; использовать подпрограммы qw/git_tree_contents_pattern read_raw_tree_object/; использовать Compress :: Zlib; my $ treeobj = read_raw_tree_object; my $ git_tree_contents = git_tree_contents_pattern; /^ $ git_tree_contents  z/; die "$ 0: неожиданный заголовок", если $ treeobj = ~ s/^ (tree [0-9] +)  0//; print $ 1, " n"; # например, 100644 SP  .gitattributes  0 sha1-bytes while ($ treeobj) {#/s важно.  соответствует любому байту!  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: неожиданная запись в дереве";  }} sub git_tree_contents_pattern {qr/(? (DEFINE) (?  tree (? & SP) (? & decimal)  0 (? & entry) +) (?  (? & octal) (? & SP) (? & strnull  ) (? & sha1bytes)) (?  [^  0] +  0) (?  (? s:. {20})) (?  [0-9] +) (?  [0-7] +) (?   x20)) (? & tree)/x;} sub read_raw_tree_object {# $ git rev-parse v2.7.2 ^ {tree} # 802b6758c0c27ae910f40e1b4862cb72a71eee9f # # ПРИМЕЧАНИЕ: извлечено с использованием  git unpack-objects my $ tree = ".git/objects/80/2b6758c0c27ae910f40e1b4862cb72a71eee9f";  открой мой $ fh, ";  die "$ 0: ошибка распаковки", если не определено $ treeobj;  $ treeobj}  

Наблюдайте за git ls-tree нашего бедняги в действии. Результат идентичен, за исключением того, что он выводит маркер и длину tree .

 $ diff -u  

1


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

  git cat-file -p 4c975c5f5945564eae86d1e933192c4a9096bfe5  

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

  git cat-file tree 4c975c5f5945564eae86d1e933192c4a9096bfe5  

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

3


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

  [режим] [имя файла/папки]  0 [SHA-1 ссылки на blob или дерево]  

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

  entries = [line [0: 2] + (line [2] .encode ('hex'),) for  строка в re.findall ('( d +) (. *?)  0 (. {20})', body, re.MULTILINE)]  

#!/usr/local/bin/php
php
define (‘DIR’, ‘tree’ );
define (‘FILE’, ‘blob’);
function main () {
$ root_object = ‘f3c8ec92f83e2a87c1d00a5681598126e23ab356’;
restore_object ($ root_object, ‘Restored’);
}
function restore_object ($ object, $ dest) {
$ type = get_object_type ($ object);
if ($ type == FILE) {
echo ‘восстановление’. $ dest. » n»;
shell_exec (‘git cat-file -p’. $ object. ‘>’. $ dest);
} else {
echo ‘восстановление’. $ dest. » n»;
mkdir ($ dest);
restore_directory ($ object, $ dest);
}
}
function restore_directory ($ object, $ base_dest) {
$ files = parse_tree_object ($ object);
foreach ($ files как $ file) {
restore_object ($ file [‘объект’], $ base_dest. ‘/’. $ file [‘имя’]);
}
}
function parse_tree_object ($ object) {
if (DIR! = get_object_type ($ object)) {
throw new Exception («Попытка декодировать Объект TREE, но задан BLOB: {$ object} «);
}
$ contents = read_object ($ объект);
$ tree = [];
$ lines = explode (» n «, trim ($ contents));
foreach ($ lines как $ branch) {
$ leaves = preg_split («# s + #», $ branch, 4);
$ tmp = [
‘mode’ => $ leaves [0],
‘type’ => $ leaves [1],
‘object’ => $ leaves [2],
‘name’ => $ leaves [3],
];
array_map (‘обрезать’, $ tmp);
$ tree [] = $ tmp;
}
return $ tree;
}
function read_object ($ object) {
$ contents = trim (shell_exec (‘git кот-файл -p ‘. $ object));
return $ contents;
}
function get_object_type ($ object) {
$ type = shell_exec («git cat-file -t {$ object}»);
вернуть обрезку ($ type);
}
main ();
Оцените статью
clickpad.ru
Добавить комментарий