Git Objects

Это типа конспект, который я веду для себя, чтобы получше разобраться и запомнить. Вероятно, вам лучше сразу читать оригинал: 10.2 Git Objects.

Также, поскольку оригинальная статья игнорит тему annotated tags, то внизу есть добавочный раздел про тэги, сделанный на основе статьи Types of git objects из Curious git. UPD: Про тэги написано в следующей главе.

Короткие выводы

Четыре стандартных вида объектов: blob, tree, commmit, tag.

Три режима блобов в дереве:

  • 100644 обычный файл,
  • 100755 исполняемый,
  • 120000 симлинк.

Любой объект читается командой git cat-file -p <obj-id>

Тип объекта берётся той же командой, только вместо флага -p ставится флаг -t

Эксперименты

Subj

Мы можем точно воспроизводить все действия и получать в точности те же самые хэши, что и в мануале. Но лишь до тех пор, пока не дойдём до раздела про коммиты.

Команда git commit-tree берёт системное время, оно каждый раз разное и поэтому получается каждый раз разный хэш. Но мы, конечно, хотим в точности воспроизвести мануал и этого раздела тоже. Поехали!

Накидываем блобы, повторяя команды из мануала:

Я хотел записывать деревья через git hash-object, но у меня не получилось, т. к. там какой-то хитрый формат, причём, кажется — бинарный. Поэтому тут тоже повторяем команды из мануала:

Теперь коммиты:

Эксперимент № 1

Зафиксируем тело коммита в файле:

И, запишем коммит через git hash-object:

К сожалению, хэш коммита получается равен либо 70d4408b5020e81d19906d6abdd87a73233ebf34, либо c5c23f68145bf32dedda1929153b3be544b72186, в зависимости от того, оставляем ли мы перевод строки в конце файла commit-content.txt, так что воспроизвести хэши из мануала не получилось. Эксперимент № 1 провалился.

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

Эксперимент № 2

Идём на сайт, где лежит репозиторий Хромиума. Ищем там коммит попроще: без мёрджей и с коротким описанием. Благо он там на ребэйзах построен и не ветвится. Короткие описания там на релизных коммитах.

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

Возьмём этот: 7eeeda7f8.

Попробуем сформировать содержание коммита на основе того, что мы видим на гитхабе:

Вопросиками обозначено то, чего мы не видим на сайте гитхаба: ссылку на дерево, почту авторов и их таймзоны. Придётся клонировать.

Чтобы не клонировать все 18G мы склонируем только один коммит. Если бы мы брали недавний коммит, то пришлось бы скачать 1.12G, чего с мобильного интернета делать не хочется. Поэтому коммит взяли старый и скачали 200М:

Возьмём недостающие данные:

Построим хэш коммита:

Ололо, хэш совпал! Можно считать, что эксперимент № 2 удался!

Заметим, что на предыдущем шаге мы лишь вычисляли хэш объекта, но не создавали объект:

Давайте создадим:

Получается, что теперь у нас есть коммит, который указывает на дерево и родителя, которых нет:

Собственно, конспект

Git — это key-value storage. Это значит, что вы можете запихать туда любое содержимое, а вам вернут ключ, по которому вы сможете до этого содержимого добраться.

Blob

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

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

Такой вид объектов называется blob:

Дерево

Дерево можно прочитать той же командой, которой читается blob:

Для блобов в дереве доступно всего три режима:

  • 100644 обычный файл,
  • 100755 исполняемый,
  • 120000 симлинк.

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

В примере выше мы добавили в дерево существующий объект, но могли добавить и файл с диска:

Заметим, что команда write-tree не сбрасывает индекс, там всё остаётся, повторый вызов write-tree вернёт тот же хэш, а дальнейшие манипуляции проводятся поверх того, что уже есть в индексе. Также состояние индекса отражается в выводе команды git status.

Также можно добавить в индекс сущетсвующее дерево поддеревом с префиксом:

Коммит

Чтобы создать коммит, вы пишете git commit-tree <the-tree> [-p <the-prev-commit-if-any>]. Коментарий к коммиту он читает с входящего потока и… попросит вас указать user.name и user.email:

Создадим цепочку:

Можем посмотреть лог:

На этом этапе стоит сходить в оригинал, чтобы посмотреть картинки.

Тэги

Этот раздел сделан на основе другой статьи: Curious git — Types of git objects

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

После этих действий у нас будет три объекта:

Просто тэг — это не объект. Добавим просто тэг:

В списке тэгов он есть:

Но объектов по прежнему три:

Тэг с аннотацией, это объект. Добавим тэг, тэга будет два, а объекта четыре:

Исследуем его:

Тэг можно добавить не только на коммит, но и на любой другой объект:

На другой тэг:

На дерево:

На блоб:

Все наши тэги есть в списке:

Как хранятся объекты:

Сначала заголовок: <type> <content.bytesize>\0 — тип, пробел, длина контента и нулевой байт. blob 16\0

Гит соединяет заголовок с контентом и берёт от него SHA-1, получается хэш объекта:

Содержимое вместе с заголовком (<store>) сжимается при помощи ZLib. Затем сохраняется в файл .git/objects/<hash[0,1]>/<hash[2-39]>

Так его можно прочитать (why-1, why-2, why-3):

Полезно почитать

  • git cat-file --help
  • git hash-object --help