Как Git хранит данные

Как Git хранит данные

9 мая 2022 г.

Когда я начал использовать Git, я делал то же, что и большинство людей. Я запоминал команды, чтобы выполнить работу, не понимая, что происходит под капотом. В большинстве случаев я получал желаемые результаты. Но я все еще был разочарован тем, что время от времени «ломал» репозиторий — приводил его в состояние, которого не ожидал, и не знал, как это исправить.


Ваш опыт похож?


Упрощенный подход к использованию репозитория — это попытка использовать инструмент, не выполняя основную домашнюю работу, чтобы узнать, как он работает. В моем случае все «щелкнуло», как только я прочитал о внутренней модели данных, используемой Git. Понимаете, Git — это своего рода база данных, и никто никогда не сможет работать с SQL, например, не зная, что такое таблица, запись и т. д. Давайте восполним пробелы в знаниях и немного посмотрим на внутренности репозитория Git.


.git


Git — это распределенное программное обеспечение для контроля версий, что означает, что вам не нужен внешний сервер для его использования. Все данные, которые нужны Git, хранятся в папке .git. Как пользователь Git, вы не имеете права изменять эти файлы, но для целей этой статьи мы заглянем внутрь, чтобы увидеть, как Git хранит данные.


Сразу после создания репозитория с помощью git init вы найдете внутри:


$ ls -R .git


Описание конфигурации HEAD перехватывает ссылки на информационные объекты


.git/хуки:


applypatch-msg.sample pre-applypatch.sample pre-rebase.sample update.sample


commit-msg.sample pre-commit.sample pre-receive.sample


fsmonitor-watchman.sample предварительная фиксация-слияния.sample подготовка-фиксация-msg.sample


post-update.sample pre-push.sample push-to-checkout.sample


.git/информация:


исключать


.git/объекты:


информационный пакет


.git/объекты/информация:


.git/объекты/пакет:


.git/ссылки:


теги головы


.git/ссылки/головы:


.git/refs/теги:


Сейчас там почти пусто: у нас есть несколько папок, в основном это примеры файлов для хуков. Мы проигнорируем их; в этой статье мы сосредоточимся в основном на содержимом .git/objects — основном хранилище данных в Git.


BLOB-объекты


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


Самый простой способ создать объект — добавить объект на сцену. То, что находится на стадии, станет частью следующего коммита. Staging — это состояние «до фиксации» в git. Здесь мы храним файлы, которые еще не зафиксированы, но уже отслеживаются Git.


Пример


Давайте создадим простой файл и создадим большой двоичный объект для его представления:


$ эхо "Тест" > test.txt


С помощью этой команды мы записываем «Test» в файл «test.txt». Чтобы сделать его большим двоичным объектом, нам просто нужно добавить его на сцену, выполнив:


$ git добавить.


После добавления нашего нового файла на сцену внутри .git/objects мы имеем:


$ ls -R .git/объекты


34 информационный пакет


.git/объекты/34:


5e6aef713208c8d50cdea23b85e6ad831f0449


.git/объекты/информация:


.git/объекты/пакет:


У нас есть новая папка «34», а внутри этой папки файл «5e6aef713208c8d50cdea23b85e6ad831f0449». Это связано с тем, что хэш содержимого равен 345e….: два символа спереди используются как каталог. Содержимое этого файла:


$ кошка .git/objects/34/5e6aef713208c8d50cdea23b85e6ad831f0449


xKOR0I-.


Он сжат для повышения эффективности хранения. Мы можем увидеть, что внутри, выполнив следующую команду Git:


$ git cat-файл blob 345e6aef713208c8d50cdea23b85e6ad831f0449


Контрольная работа


У нас есть только контент внутри — никаких метаданных для файла.


1_Blob.jpg


Пример модификации


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


$ echo "Тест 2" >> test.txt


Эта команда добавляет новую строку «Test 2» в существующий файл «test.txt».


Добавим текущую версию на сцену:


$ git добавить.


И посмотрим, что у нас внутри папки .git/objects:


$ ls -R .git/объекты


34 d2 информационный пакет


.git/объекты/34:


5e6aef713208c8d50cdea23b85e6ad831f0449


.git/объекты/d2:


77ba2806ce99d418b0b5d6c28643deca0e36dc


Теперь у нас есть два объекта, второй внутри подпапки d2. Его содержание:


$ git cat-файл blob d277ba2806ce99d418b0b5d6c28643deca0e36dc


Контрольная работа


Тест 2


Это то же самое, что и наш обновленный text.txt:


$ кошка test.txt


Контрольная работа


Тест 2


Как мы видим, Git хранит полный файл для каждой версии.


2_Blob.jpg


Дерево


Объекты дерева — это то, как Git хранит папки. Они ссылаются на другие вещи как на свое содержание:


  • файлы добавляются по их блобу

  • подпапки добавляются по их дереву

Для каждой ссылки в дереве хранится:


  • имя файла или папки

  • блоб или хэш дерева

  • тип объекта

  • разрешения

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


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


Создание дерева


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


Команды сантехники и фарфора происходят из аналогии, используемой в Git:


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

  • сантехника — внутренние команды, необходимые для работы фарфора. Так же, как водопровод в вашем доме.

Если вы не занимаетесь продвинутыми вещами, вам не нужно знать команды сантехники.


Пример


С нашей постановкой, как и раньше, мы запускаем:


$ git дерево записи


fd4f9947de2805e460bfeeca3346e3d36d617d37


Возвращаемое значение — это идентификатор нашего нового объекта дерева. Чтобы заглянуть внутрь, вы можете запустить:


$ git cat-файл -p fd4f9947de2805e460bfeeca3346e3d36d617d37


100644 blob d277ba2806ce99d418b0b5d6c28643deca0e36dc test.txt


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


$ ls -R .git/объекты


Информационный пакет 34 d2 fd


.git/объекты/34:


5e6aef713208c8d50cdea23b85e6ad831f0449


.git/объекты/d2:


77ba2806ce99d418b0b5d6c28643deca0e36dc


.git/объекты/фд:


4f9947de2805e460bfeeca3346e3d36d617d37


Все данные находятся в одной структуре папок.


3_Tree.jpg


Вложенный пример


Теперь добавим внутрь еще одну папку, чтобы посмотреть, как хранятся вложенные деревья:


  • создать новую папку:

$ mkdir вложенный


  • добавить файл и его содержимое

$ echo 'lorem' > вложенный/ipsum


  • добавление его на сцену

$ git добавить.


Создание дерева сейчас даст нам новый ID:


$ git дерево записи


25517090ae5d0eb08f694de6d38d613615fe99e4


Его содержание:


$ git ls-дерево 25517090ae5d0eb08f694de6d38d613615fe99e4


040000 дерево bc9a36d27aa303a3b1cab543b64c6944fea5ce8b вложенное


100644 blob d277ba2806ce99d418b0b5d6c28643deca0e36dc test.txt


Мы видим, что «вложенный» был добавлен как ссылка на дерево. Посмотрим, что внутри:


$ git ls-дерево bc9a36d27aa303a3b1cab543b64c6944fea5ce8b


100644 блоб 3e9ffe066cd7b2ce4c6fb5c8f858496194e1c251 ipsum


Как видите, это еще один древовидный объект, описывающий содержимое папки. Имея множество древовидных объектов, вы можете описать любую вложенную структуру папок.


4_Tree.jpg


фиксирует


Коммит — это полное описание состояния репозитория. Он содержит следующую информацию:


  • ссылка на объект дерева, описывающий самую верхнюю папку

  • автор фиксации, коммиттер и время

  • родительский коммит(ы) — коммиты, на которых основан этот коммит

Большинство коммитов имеют только одного родителя, за следующими исключениями:


  • первый коммит в истории не имеет родителей

  • коммиты слияния имеют более одного

Как и прежде, Git идентифицирует каждый коммит по хешу его содержимого. Таким образом, любое изменение файлов, папок или метаданных фиксации приведет к созданию новой фиксации.


Первый коммит


Мы можем создать нашу первую фиксацию с помощью стандартной команды фиксации:


$ git commit -m 'первая фиксация'


[основная (корневая фиксация) 26349a2] первая фиксация


2 файла изменено, 3 вставки(+)


режим создания 100644 вложенный/ipsum


создать режим 100644 test.txt


Вывод показывает усеченный идентификатор фиксации. Найдем полное значение:


$ git шоу


совершить 26349a25253f9b316db1a5d3c3f23c1ca5ca4e0e (HEAD -> главная)


Автор: Марцин Восинек marcin.wosinek@gmail.com


Дата: Чт, 28 апреля, 18:18:07 2022 +0200


первый коммит


Чтобы увидеть содержимое объекта фиксации, мы можем использовать:


$ git cat-файл -p 26349a25253f9b316db1a5d3c3f23c1ca5ca4e0e


дерево 25517090ae5d0eb08f694de6d38d613615fe99e4


автор Марчин Восинек marcin.wosinek@gmail.com 1651162687 +0200


коммиттер Марцин Восинек marcin.wosinek@gmail.com 1651162687 +0200


первый коммит


Ссылка на дерево такая же, как и в предыдущем примере. Мы видим, что коммиты остаются в той же папке, что и другие объекты:


ls -R .git/объекты


25 26 34 3e bc d2 fd информационный пакет


.git/объекты/26:


349a25253f9b316db1a5d3c3f23c1ca5ca4e0e


5_Commit.jpg


Следующий коммит


Давайте восстановим первую версию нашего файла test.txt:


$ эхо "Тест" > test.txt


Эта команда перезаписывает существующий файл на «Test».


$ git добавить.


Добавляет обновленную версию в staging.


$ git commit -m 'второй коммит'


[основной 7f54a43] второй коммит


1 файл изменен, 1 удален(-)


Фиксирует изменения.


Найдем полный ID:


$ git шоу


зафиксировать 7f54a437d87cd1f241cfb893c4823bc7e60c19ec (HEAD -> main)


Автор: Марцин Восинек marcin.wosinek@gmail.com


Дата: Чт, 28 апреля, 18:37:55 2022 +0200


второй коммит


Таким образом, содержимое коммита:


$ git cat-файл -p 7f54a437d87cd1f241cfb893c4823bc7e60c19ec


дерево 04b0192c1c88ac1c1a96f386e84e5388ef8a509a


родитель 26349a25253f9b316db1a5d3c3f23c1ca5ca4e0e


автор Марчин Восинек marcin.wosinek@gmail.com 1651163875 +0200


коммиттер Марцин Восинек marcin.wosinek@gmail.com 1651163875 +0200


второй коммит


Git добавил строку parent, потому что мы фиксируем поверх другого коммита.


6_Commit_Dos.jpg


Ветки и теги


Другие важные данные, хранящиеся в Git, — это просто ссылки на самый последний коммит. Итак, моя ветка main хранится в .git/refs/heads/main, и ее содержимое


$ кошка .git/refs/heads/main


7f54a437d87cd1f241cfb893c4823bc7e60c19ec


или идентификатор его самой верхней фиксации. Мы можем найти всю необходимую информацию из постоянно расширяющегося дерева коммитов:


  • история веток, как сообщается в сообщениях коммитов

  • кто вносил изменения и когда они были внесены

  • отношения между различными ветвями и тегами

Когда я создаю простой тег:


Тег $ git v1


Файл создается в .git/refs/tags:


$ кошка .git/refs/теги/v1


7f54a437d87cd1f241cfb893c4823bc7e60c19ec


Как видите, и теги, и ветки являются явными ссылками на коммит. Единственная разница между ними заключается в том, как Git их обрабатывает, когда мы создаем новый коммит:


  • текущая ветка перемещена в новый коммит

  • теги оставлены без изменений

7_Trees_.jpg


Резюме


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


Первоначально опубликовано [здесь] (https://how-to.dev/how-git-stores-data)



Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE