Каков формат файла объекта git commit?

Контекст: я надеялся, что мне удастся найти в моих сообщениях и коммитах git commit без необходимости выполнять запутанно сложную команду git grep, поэтому я решил посмотреть, как хранятся сообщения git commit.

Я посмотрел в папке.git, и мне кажется, что коммиты хранятся в

.git/objects 

Папка объектов.git содержит несколько папок с именами, такими как a6 и 9b. Каждая из этих папок содержит файл с именем, похожим на коммит sha 2f29598814b07fea915514cfc4d05129967bf7. Когда я открываю один из этих файлов в текстовом редакторе, я получаю бред.

  1. Какой формат файла это бред / Как хранится объект git commit?
  2. В этом журнале git commit папка 9b содержит один коммит

    aed8a9f773efb2f498f19c31f8603b6cb2a4bc
    

    Почему, и есть ли случай, когда в файле 9b будет храниться более одного коммита ша?

  3. Есть ли способ преобразовать этот бред в обычный текст, чтобы я мог связываться с коммитами в текстовом редакторе?

4 ответа

Решение

Прежде чем идти по этому пути намного дальше, я мог бы рекомендовать вам прочитать раздел Руководства по Git о его внутренностях. Я обнаружил, что знание содержания этой главы - это обычно разница между симпатиями Git и ненавистью к ней. Понимание того, почему Git делает вещи так, как оно это делает, часто делает все виды странных команд, которые у него есть для вещей, более понятными.

Чтобы ответить на ваш вопрос, вы видите бред, связанный с данными после того, как он был сжат с помощью zlib. Если вы посмотрите под заголовком "Хранение объектов" в ссылке выше, вы можете увидеть некоторые подробности о том, как это работает. Это краткая версия того, как файлы хранятся в git:

  1. Создайте специальный заголовок git для содержимого.
  2. Создайте хэш конкатенации заголовка + контент.
  3. Сжать конкатенацию заголовка + контент.
  4. Сохраните сжатые данные на диск в папке с именем, равным первым двум символам хэша данных, и именем файла с оставшимися 38 символами.

Таким образом, это отвечает на ваш второй вопрос, папка будет содержать все сжатые объекты, которые начинаются с одинаковых двух символов, независимо от их содержимого.

Если вы хотите увидеть содержимое BLOB-объекта, все, что вам нужно сделать, это распаковать его. Если вы просто хотите просмотреть содержимое файла, это можно сделать достаточно легко на большинстве языков программирования. Однако я бы предостерег вас от попыток изменить данные. Изменение даже одного байта в файле изменит его хэш. Все метаданные в git (а именно, структуры каталогов и фиксации) хранятся с использованием ссылок на хэши, поэтому изменение одного файла означает, что вы также должны обновить все объекты ниже по потоку от этого файла, которые ссылаются на хеш этого файла. Затем вы должны обновить все объекты, которые ссылаются на эти хэши. И так далее, и так далее... Попытка достичь этого становится очень, очень сложной очень быстро. Вы сэкономите много времени и душевных страданий, просто изучая встроенные команды git.

Создайте минимальный пример и перепроектируйте формат

Создайте простой репозиторий и перед созданием каких-либо файлов-пакетов (git gc, git config gc.auto, git-prune-packed...), распакуйте объект commit одним из следующих способов: Как выполнить DEFLATE с помощью инструмента командной строки, чтобы извлечь объект git?

export GIT_AUTHOR_DATE="1970-01-01T00:00:00+0000"
export GIT_AUTHOR_EMAIL="author@example.com"
export GIT_AUTHOR_NAME="Author Name" \
export GIT_COMMITTER_DATE="2000-01-01T00:00:00+0000" \
export GIT_COMMITTER_EMAIL="committer@example.com" \
export GIT_COMMITTER_NAME="Committer Name" \

git init

# First commit.
echo
touch a
git add a
git commit -m 'First message'
python -c "import zlib,sys;sys.stdout.write(zlib.decompress(sys.stdin.read()))" \
  <.git/objects/45/3a2378ba0eb310df8741aa26d1c861ac4c512f | hd

# Second commit.
echo
touch b
git add b
git commit -m 'Second message'
python -c "import zlib,sys;sys.stdout.write(zlib.decompress(sys.stdin.read()))" \
  <.git/objects/74/8e6f7e22cac87acec8c26ee690b4ff0388cbf5 | hd

Выход:

Initialized empty Git repository in /home/ciro/test/git/.git/

[master (root-commit) 453a237] First message
 Author: Author Name <author@example.com>
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 a
00000000  63 6f 6d 6d 69 74 20 31  37 34 00 74 72 65 65 20  |commit 174.tree |
00000010  34 39 36 64 36 34 32 38  62 39 63 66 39 32 39 38  |496d6428b9cf9298|
00000020  31 64 63 39 34 39 35 32  31 31 65 36 65 31 31 32  |1dc9495211e6e112|
00000030  30 66 62 36 66 32 62 61  0a 61 75 74 68 6f 72 20  |0fb6f2ba.author |
00000040  41 75 74 68 6f 72 20 4e  61 6d 65 20 3c 61 75 74  |Author Name <aut|
00000050  68 6f 72 40 65 78 61 6d  70 6c 65 2e 63 6f 6d 3e  |hor@example.com>|
00000060  20 30 20 2b 30 30 30 30  0a 63 6f 6d 6d 69 74 74  | 0 +0000.committ|
00000070  65 72 20 43 6f 6d 6d 69  74 74 65 72 20 4e 61 6d  |er Committer Nam|
00000080  65 20 3c 63 6f 6d 6d 69  74 74 65 72 40 65 78 61  |e <committer@exa|
00000090  6d 70 6c 65 2e 63 6f 6d  3e 20 39 34 36 36 38 34  |mple.com> 946684|
000000a0  38 30 30 20 2b 30 30 30  30 0a 0a 46 69 72 73 74  |800 +0000..First|
000000b0  20 6d 65 73 73 61 67 65  0a                       | message.|
000000ba

[master 748e6f7] Second message
 Author: Author Name <author@example.com>
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 b
00000000  63 6f 6d 6d 69 74 20 32  32 33 00 74 72 65 65 20  |commit 223.tree |
00000010  32 39 36 65 35 36 30 32  33 63 64 63 30 33 34 64  |296e56023cdc034d|
00000020  32 37 33 35 66 65 65 38  63 30 64 38 35 61 36 35  |2735fee8c0d85a65|
00000030  39 64 31 62 30 37 66 34  0a 70 61 72 65 6e 74 20  |9d1b07f4.parent |
00000040  34 35 33 61 32 33 37 38  62 61 30 65 62 33 31 30  |453a2378ba0eb310|
00000050  64 66 38 37 34 31 61 61  32 36 64 31 63 38 36 31  |df8741aa26d1c861|
00000060  61 63 34 63 35 31 32 66  0a 61 75 74 68 6f 72 20  |ac4c512f.author |
00000070  41 75 74 68 6f 72 20 4e  61 6d 65 20 3c 61 75 74  |Author Name <aut|
00000080  68 6f 72 40 65 78 61 6d  70 6c 65 2e 63 6f 6d 3e  |hor@example.com>|
00000090  20 30 20 2b 30 30 30 30  0a 63 6f 6d 6d 69 74 74  | 0 +0000.committ|
000000a0  65 72 20 43 6f 6d 6d 69  74 74 65 72 20 4e 61 6d  |er Committer Nam|
000000b0  65 20 3c 63 6f 6d 6d 69  74 74 65 72 40 65 78 61  |e <committer@exa|
000000c0  6d 70 6c 65 2e 63 6f 6d  3e 20 39 34 36 36 38 34  |mple.com> 946684|
000000d0  38 30 30 20 2b 30 30 30  30 0a 0a 53 65 63 6f 6e  |800 +0000..Secon|
000000e0  64 20 6d 65 73 73 61 67  65 0a                    |d message.|
000000eb

Затем мы выводим, что формат выглядит следующим образом:

  • Верхний уровень:

    commit {size}\0{content}
    

    где {size} количество байтов в {content},

    Это следует той же схеме для всех типов объектов.

  • {content}:

    tree {tree_sha}
    {parents}
    author {author_name} <{author_email}> {author_date_seconds} {author_date_timezone}
    committer {committer_name} <{committer_email}> {committer_date_seconds} {committer_date_timezone}
    
    {commit message}
    

    где:

    • {tree_sha}: SHA объекта дерева, на который указывает этот коммит.

      Это представляет каталог Git репо верхнего уровня.

      Этот SHA происходит от формата объекта дерева: каков внутренний формат объекта дерева мерзавца?

    • {parents}: необязательный список родительских объектов коммитов формы:

      parent {parent1_sha}
      parent {parent2_sha}
      ...
      

      Список может быть пустым, если нет родителей, например, для первого коммита в репо.

      Два родителя происходят в регулярных слияниях.

      Более двух родителей возможны с git merge -Xoctopus, но это не обычный рабочий процесс. Вот пример: https://github.com/cirosantilli/test-octopus-100k

    • {author_name}: например: Ciro Santilli, Не может содержать <, \n

    • {author_email}: например: cirosantilli@mail.com, Не может содержать >, \n

    • {author_date_seconds}секунд с 1970 года, например 946684800 это первая секунда 2000 года

    • {author_date_timezone}: например: +0000 это UTC

    • поля коммиттера: аналогичны полям автора

    • {commit message}: произвольно.

Я сделал минимальный скрипт на Python, который генерирует git-репо с несколькими коммитами по адресу: https://github.com/cirosantilli/test-git-web-interface/blob/864d809c36b8f3b232d5b0668917060e8bcba3e8/other-test-repos/util.py

Я использовал это для забавных вещей, таких как:

Вот аналогичный анализ формата объекта тега: что такое формат объекта тега git и как рассчитать его SHA?

Слово предостережения

Пожалуйста, не редактируйте объект в вашем редакторе. Вы можете испортить свой репозиторий Git, если не будете осторожны. Это стоит времени, чтобы научиться использовать git grep, Это действительно не так уж отличается от grepи это намного быстрее.

Под капотом у git есть понятие об объектах. Объекты обычно состоят из заголовка и некоторых данных. Содержимое файла сохраняется в виде объекта blob. Объекты дерева содержат имена файлов и указывают на объекты BLOB-объектов, которые представляют файлы, и объекты дерева, которые представляют другие каталоги. Затем существуют объекты коммитов, которые записывают сообщение журнала и указывают на объект дерева, который представляет соответствующее состояние дерева. Существуют также аннотированные объекты тегов, которые обычно указывают на тег, помещенный в коммит.

Онлайновая книга содержит некоторую информацию о различных видах объектов и способах их просмотра, а также включает некоторые сведения о формате, используемом для хранения объектов.

Имейте в виду, что вы смотрите на свободные объекты. Есть также объекты, содержащиеся в пакетных файлах, которые могут иметь другой формат.

Руководство пользователя Git также содержит полезную информацию о базе данных объектов.

Это довольно старый вопрос, но если кому-то все еще интересно, как читать содержимое объектов, самый простой способ - запустить

git cat-файл -p

Итак, если у вас есть папка со следующим объектом

      ├── 4a
│   ├── 2500cb9eb44d57fd7abfd04a9911ab8e2b6733

Вы бы побежали:

git cat-file -p 4a2500cb9eb44d57fd7abfd04a9911ab8e2b6733

Другие вопросы по тегам