Как 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
Контрольная работа
У нас есть только контент внутри — никаких метаданных для файла.
Пример модификации
Давайте посмотрим, что произойдет, если мы внесем некоторые изменения в файл и добавим обновленную версию:
$ 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 хранит полный файл для каждой версии.
Дерево
Объекты дерева — это то, как 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
Все данные находятся в одной структуре папок.
Вложенный пример
Теперь добавим внутрь еще одну папку, чтобы посмотреть, как хранятся вложенные деревья:
- создать новую папку:
$ mkdir вложенный
- добавить файл и его содержимое
$ echo 'lorem' > вложенный/ipsum
- добавление его на сцену
$ git добавить.
Создание дерева сейчас даст нам новый ID:
$ git дерево записи
25517090ae5d0eb08f694de6d38d613615fe99e4
Его содержание:
$ git ls-дерево 25517090ae5d0eb08f694de6d38d613615fe99e4
040000 дерево bc9a36d27aa303a3b1cab543b64c6944fea5ce8b вложенное
100644 blob d277ba2806ce99d418b0b5d6c28643deca0e36dc test.txt
Мы видим, что «вложенный» был добавлен как ссылка на дерево. Посмотрим, что внутри:
$ git ls-дерево bc9a36d27aa303a3b1cab543b64c6944fea5ce8b
100644 блоб 3e9ffe066cd7b2ce4c6fb5c8f858496194e1c251 ipsum
Как видите, это еще один древовидный объект, описывающий содержимое папки. Имея множество древовидных объектов, вы можете описать любую вложенную структуру папок.
фиксирует
Коммит — это полное описание состояния репозитория. Он содержит следующую информацию:
- ссылка на объект дерева, описывающий самую верхнюю папку
- автор фиксации, коммиттер и время
- родительский коммит(ы) — коммиты, на которых основан этот коммит
Большинство коммитов имеют только одного родителя, за следующими исключениями:
- первый коммит в истории не имеет родителей
- коммиты слияния имеют более одного
Как и прежде, 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
Следующий коммит
Давайте восстановим первую версию нашего файла 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, потому что мы фиксируем поверх другого коммита.
Ветки и теги
Другие важные данные, хранящиеся в Git, — это просто ссылки на самый последний коммит. Итак, моя ветка main хранится в .git/refs/heads/main
, и ее содержимое
$ кошка .git/refs/heads/main
7f54a437d87cd1f241cfb893c4823bc7e60c19ec
или идентификатор его самой верхней фиксации. Мы можем найти всю необходимую информацию из постоянно расширяющегося дерева коммитов:
- история веток, как сообщается в сообщениях коммитов
- кто вносил изменения и когда они были внесены
- отношения между различными ветвями и тегами
Когда я создаю простой тег:
Тег $ git v1
Файл создается в .git/refs/tags
:
$ кошка .git/refs/теги/v1
7f54a437d87cd1f241cfb893c4823bc7e60c19ec
Как видите, и теги, и ветки являются явными ссылками на коммит. Единственная разница между ними заключается в том, как Git их обрабатывает, когда мы создаем новый коммит:
- текущая ветка перемещена в новый коммит
- теги оставлены без изменений
Резюме
Большой двоичный объект, дерево и коммиты — это то, как Git хранит полную историю вашего репозитория. Он делает все ссылки по хэшу объекта: невозможно манипулировать историей или файлами, отслеживаемыми в репозитории, не нарушая отношения.
Первоначально опубликовано [здесь] (https://how-to.dev/how-git-stores-data)
Оригинал