Каков формат файла объекта git commit?
Контекст: я надеялся, что мне удастся найти в моих сообщениях и коммитах git commit без необходимости выполнять запутанно сложную команду git grep, поэтому я решил посмотреть, как хранятся сообщения git commit.
Я посмотрел в папке.git, и мне кажется, что коммиты хранятся в
.git/objects
Папка объектов.git содержит несколько папок с именами, такими как a6 и 9b. Каждая из этих папок содержит файл с именем, похожим на коммит sha 2f29598814b07fea915514cfc4d05129967bf7. Когда я открываю один из этих файлов в текстовом редакторе, я получаю бред.
- Какой формат файла это бред / Как хранится объект git commit?
В этом журнале git commit папка 9b содержит один коммит
aed8a9f773efb2f498f19c31f8603b6cb2a4bc
Почему, и есть ли случай, когда в файле 9b будет храниться более одного коммита ша?
Есть ли способ преобразовать этот бред в обычный текст, чтобы я мог связываться с коммитами в текстовом редакторе?
4 ответа
Прежде чем идти по этому пути намного дальше, я мог бы рекомендовать вам прочитать раздел Руководства по Git о его внутренностях. Я обнаружил, что знание содержания этой главы - это обычно разница между симпатиями Git и ненавистью к ней. Понимание того, почему Git делает вещи так, как оно это делает, часто делает все виды странных команд, которые у него есть для вещей, более понятными.
Чтобы ответить на ваш вопрос, вы видите бред, связанный с данными после того, как он был сжат с помощью zlib. Если вы посмотрите под заголовком "Хранение объектов" в ссылке выше, вы можете увидеть некоторые подробности о том, как это работает. Это краткая версия того, как файлы хранятся в git:
- Создайте специальный заголовок git для содержимого.
- Создайте хэш конкатенации заголовка + контент.
- Сжать конкатенацию заголовка + контент.
- Сохраните сжатые данные на диск в папке с именем, равным первым двум символам хэша данных, и именем файла с оставшимися 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
Я использовал это для забавных вещей, таких как:
- Кто является пользователем с самой длинной полосой на GitHub?
- https://www.quora.com/Which-GitHub-repo-has-the-most-commits/answer/Ciro-Santilli
- https://github.com/isaacs/github/issues/1344
Вот аналогичный анализ формата объекта тега: что такое формат объекта тега git и как рассчитать его SHA?
Слово предостережения
Пожалуйста, не редактируйте объект в вашем редакторе. Вы можете испортить свой репозиторий Git, если не будете осторожны. Это стоит времени, чтобы научиться использовать git grep
, Это действительно не так уж отличается от grep
и это намного быстрее.
Под капотом у git есть понятие об объектах. Объекты обычно состоят из заголовка и некоторых данных. Содержимое файла сохраняется в виде объекта blob. Объекты дерева содержат имена файлов и указывают на объекты BLOB-объектов, которые представляют файлы, и объекты дерева, которые представляют другие каталоги. Затем существуют объекты коммитов, которые записывают сообщение журнала и указывают на объект дерева, который представляет соответствующее состояние дерева. Существуют также аннотированные объекты тегов, которые обычно указывают на тег, помещенный в коммит.
Онлайновая книга содержит некоторую информацию о различных видах объектов и способах их просмотра, а также включает некоторые сведения о формате, используемом для хранения объектов.
Имейте в виду, что вы смотрите на свободные объекты. Есть также объекты, содержащиеся в пакетных файлах, которые могут иметь другой формат.
Руководство пользователя Git также содержит полезную информацию о базе данных объектов.
Это довольно старый вопрос, но если кому-то все еще интересно, как читать содержимое объектов, самый простой способ - запустить
git cat-файл -p
Итак, если у вас есть папка со следующим объектом
├── 4a
│ ├── 2500cb9eb44d57fd7abfd04a9911ab8e2b6733
Вы бы побежали:
git cat-file -p 4a2500cb9eb44d57fd7abfd04a9911ab8e2b6733