Как Git хранит коммиты и из чего они состоят?

Ответ

Git хранит коммиты как неизменяемые объекты в своей внутренней базе данных, расположенной в директории .git/objects. Git является контентно-адресуемой файловой системой, где каждый объект идентифицируется своим уникальным SHA-1 хешем, вычисленным на основе его содержимого.

Каждый объект коммита содержит следующую информацию:

  • tree (Дерево файлов): Ссылка на объект "дерево" (tree object), который представляет собой снимок состояния рабочего каталога на момент создания коммита. Объект "дерево" содержит ссылки на другие объекты "дерево" (для подкаталогов) и объекты "blob" (для файлов), а также их имена и права доступа. Это позволяет Git эффективно хранить только изменения, а не полные копии файлов.
  • parent(s) (Родительские коммиты): Ссылка на один или несколько родительских коммитов. Для обычного коммита это один родитель, указывающий на предыдущее состояние истории. Для коммита слияния (merge commit) может быть два или более родителя, что формирует ветвящуюся историю.
  • author (Автор): Имя и адрес электронной почты автора коммита, а также временная метка, когда изменения были фактически написаны.
  • committer (Коммиттер): Имя и адрес электронной почты того, кто фактически применил коммит (это может быть другой человек, например, при применении патча), и временная метка, когда коммит был применен в репозиторий.
  • message (Сообщение коммита): Описание изменений, внесенных в этом коммите.

Пример внутренней структуры коммита (полученной командой git cat-file -p <commit-hash>):

tree 92b8b6ffb9f3a0d5a3a0e7c8d9f0a1b2c3d4e5f6  # Ссылка на объект дерева
parent 1a410efbd13591db074050c56302540300000000 # Ссылка на родительский коммит
author John Doe <john@example.com> 1625097600 +0300 # Автор и время
committer John Doe <john@example.com> 1625097600 +0300 # Коммиттер и время

Initial commit # Сообщение коммита

Ключевые свойства хранения коммитов в Git:

  • Неизменяемость: После создания коммит не может быть изменен. Любое изменение его содержимого (даже сообщения) приведет к созданию нового объекта коммита с новым SHA-1 хешем.
  • Целостность: SHA-1 хеш гарантирует целостность данных. Любое повреждение данных коммита или его родителей будет немедленно обнаружено.
  • Связанная история: Коммиты криптографически связаны друг с другом через ссылки на родителей, формируя направленный ациклический граф (DAG), который представляет полную историю проекта.

Ответ 18+ 🔞

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

Представь себе, что у тебя в папке .git/objects лежит не просто мусор, а целая контентно-адресуемая файловая система. Звучит страшно? А похуй, сейчас разберём. Каждый твой коммит, каждый файл, каждая папка — это неизменяемый объект. Как памятник Ленину, только полезнее. И у каждого такого объекта есть уникальный паспорт — SHA-1 хеш, который вычисляется на основе его содержимого. Изменил хоть запятую в сообщении коммита — получил совершенно новый хеш, новый объект. Старый остался лежать, как есть. Это, блядь, фундамент.

А что же внутри самого коммита, этого ёбанного кирпичика истории? Давай посмотрим, как на рентгене. Команда git cat-file -p <хэш> — наш аппарат УЗИ.

Вот, смотри, что она покажет:

tree 92b8b6ffb9f3a0d5a3a0e7c8d9f0a1b2c3d4e5f6
parent 1a410efbd13591db074050c56302540300000000
author John Doe <john@example.com> 1625097600 +0300
committer John Doe <john@example.com> 1625097600 +0300

Initial commit

Разбираем по косточкам, ебать:

  1. tree (Дерево файлов): Это ссылка на снимок твоего рабочего каталога в момент коммита. Представь, что это корень файловой системы на тот момент. Внутри этого объекта-дерева лежат ссылки на другие деревья (подпапки) и на блобы (файлы). И так по всей иерархии. Гениальность в том, что если файл не менялся, Git просто переиспользует ссылку на старый блоб. Экономия места — овердохуищная.

  2. parent(s) (Родительские коммиты): А вот это, сука, самое важное! Это криптографическая цепочка. Обычный коммит ссылается на одного родителя — предыдущий коммит. Merge-коммит — на двух (или больше) родителей. Так и строится этот направленный ациклический граф, или, проще говоря, история. Каждый коммит знает, откуда он вырос. Попробуй подделать историю — хеши не сойдутся, и Git тебе сразу: «Иди нахуй, мудила, что ты тут пытаешься провернуть?».

  3. author и committer (Автор и Коммиттер): Да, это могут быть разные люди! Author — тот, кто написал код. Committer — тот, кто его закоммитил в репозиторий. Например, ты применил чей-то патч — ты будешь committer'ом. И даты разные: когда написал и когда применил. Git всё помнит, блядь, он злопамятный.

  4. message (Сообщение): Ну, тут всё ясно. Твоё «пофиксил баг» или «добавил хуйню». Хотя лучше бы писал нормально, а то потом сам не разберёшься, что за пиздец тут творился.

Итожу, ебать мои старые костыли:

  • Неизменяемость: Создал коммит — он навеки в камне. «Аминь», как говорится. Хочешь «исправить» историю? Git создаст новые объекты, а старые так и будут лежать, пока сборка мусора не приберёт.
  • Целостность: SHA-1 хеш — это не просто имя. Это криптографическая печать. Повредил файл в объекте — хеш не совпадёт, Git заорёт на тебя и откажется работать. Доверия к данным — ебать сто процентов.
  • Связанная история: Вся эта цепочка из parent ссылок — это и есть твоя история. Не просто линейный список, а граф, где могут быть ветки и слияния. Каждый коммит крепко держит за руку своего родителя. Или родителей.

Вот так-то, друг. Не просто «сохранил файлы», а выстроил целую криптографически связанную вселенную. Ёперный театр, а не система контроля версий.