Найти тему

Git — контентно-адресуемая файловая система

Оглавление

Механизмы работы внутренней базы данных, типы объектов и их жизненный цикл.

Хотите проверить свои представления по теме? Небольшой тест из 7 вопросов.

Основные принципы работы.

В большинстве сфер нашей жизни, и IT не стало здесь исключением, поведение чего-то становится для нас достаточно понятным и логичным, если есть представление о внутреннем устройстве данных механизмов. Одним из базовых определений, которое используют все, является репозиторий.

Репозиторий Git - это хранилище данных, представленное специальным набором файлов и "папок". В локальной копии обычно существует директория .git, которая и является непосредственным корнем репозитория, а не его частью. Файлы и директории, которые находятся на одном уровне с .git - это рабочая область/дерево (working directory/tree/space). Рабочая область является разделом проекта Git, но не частью репозитория. Третьим основным разделом проекта выделяют индекс (index/staging area), данные относящиеся к индексу хранятся внутри репозитория.

На серверах же создаются голые (bare) репозитории, т.е. какой-нибудь git@github.com:akorolev-dev/git-plumbing-and-porcelain-content.git представляет собой директорию аналогичной структуры, что и локальная .git. Рабочая область и индекс в них отсутствуют.

Обычно говорят, что есть два варианта создания нового репозитория:

  1. Инициализация пустого локального.
  2. Клонирование удаленного репозитория в локальный.

Информационные сообщения команды init подтверждают, что корнем репозитория является либо .git, либо корень проекта при варианте bare.


Но по факту клонирование просто начинается с инициализации пустого локального репозитория.
Это можно воспроизвести разными способами.
Например, запросить какой-то репозиторий на существующем сервере, где потребуется пройти аутентификацию вызова.
В момент остановки команды клонирования из-за запроса аутентификационных данных уже произойдет инициализация нового пустого репозитория.
А в файл конфигурации проекта Git будет добавлен первый псевдоним удаленного репозитория **origin**.
Если аутентификация происходит успешно, то начинается загрузка данных (git fetch).

Инициализация новых репозиториев
Инициализация новых репозиториев

Но по факту клонирование просто начинается с инициализации пустого локального репозитория. Это можно воспроизвести разными способами.
Например, запросить какой-то репозиторий на существующем сервере, где потребуется пройти аутентификацию вызова. В момент остановки команды клонирования из-за запроса аутентификационных данных уже произойдет инициализация нового пустого репозитория. А в файл конфигурации проекта Git будет добавлен первый псевдоним удаленного репозитория
origin. Если аутентификация происходит успешно, то начинается загрузка данных (git fetch).

Инициализация репозитория в процессе клонирования
Инициализация репозитория в процессе клонирования

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

Устройство файловой системы

Пустой репозиторий

Репозиторий Git реализован в виде набора файлов и директорий определенной структуры, с адресацией на основе содержимого. Подробно внутреннее устройство описано в 10 разделе книги, мы затронем только отдельные моменты.

Создадим новый проект
content с веткой content-article-01, и для отслеживания изменений внутренней структуры этого репозитория
сделаем его самого рабочей областью второго Git проекта, который для удобства будем называть наблюдателем (
watcher), с веткой watcher-article-01.

Файловая система пустого репозитория
Файловая система пустого репозитория

Зафиксируем это начальное состояние файловой системы.

Фиксация файловой системы пустого репозитория
Фиксация файловой системы пустого репозитория
Обратите внимание, что в репозитории уже существуют директории objects и refs, но в коммит они добавлены не были, так как еще не содержат никаких файлов.

Создание первого файла

Давайте в качестве первого файла создадим MIT лицензию, скачав её с github.

Файловая система после добавления в индекс нового файла
Файловая система после добавления в индекс нового файла

После создания нового файла, в репозитории никаких изменений не произошло.
Но вот после включения файла в индекс, создался не только сам файл индекса, но и новый файл в поддиректории внутри
objects.

В Git существует забавное разделение всех команд на два типа:

  1. "Фарфор" (Porcelain) - это как раз привычные большинству пользователей команды.
  2. "Сантехника" (Plumbing) - это команды низкого уровня, которые в первую очередь предназначены для создания инструментов работы с репозиторием Git. Но именно "сантехника" позволяет понять принципы работы данной системы контроля версий.

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

Содержимое первого blob объекта
Содержимое первого blob объекта

После первых 9 символов наблюдаем полный текст лицензии, которая была включена в индекс. Воспользовавшись некоторыми "сантехническими" командами, можно объяснить происхождение всех данных, авторами которых мы являемся опосредованно.

Анализ данных первого blob объекта
Анализ данных первого blob объекта

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

Любой объект репозитория создается в поддиректории каталога objects.
Наименование файла объекта и наименование его родительской директории получается путем разделения вычисленной контрольной суммы на две части:

  1. Первые 2 символа формируют название родительской директории.
  2. Оставшиеся 38 символов становятся наименованием файла с данными по объекту.

Blob - это первый тип создаваемых объектов, который представляет собой снимок отдельного файла. А 1069 - количество байт исходной информации в файле LICENSE, до её сжатия. Метаинформация о типе и размере отделяются от исходного содержимого нулевым символом (NUL/null byte). Стоит обратить внимание, что метаинформация не содержит сведений о наименовании и временных метках, как и назначенных на файл прав.

Модификация первого файла

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

Изменения в репозитории при незначительной модификации данных
Изменения в репозитории при незначительной модификации данных

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

Анализ данных второго blob объекта
Анализ данных второго blob объекта

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

  • База данных практически всегда работает только на добавление новых объектов, не торопясь удалять ранее созданные, для которых уже нет исходных файлов.
  • Даже при небольшом изменении файла (модификация 1 строки из 22, и добавлении 18 символов к 1069) производится полный снимок содержимого исходного файла.

Создание первого коммита

Что же, самое время отследить изменения, которые произойдут после создания первого коммита в репозитории "content".

Файловая система после создания коммита
Файловая система после создания коммита

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

Анализ файловой системы после создания первого коммита
Анализ файловой системы после создания первого коммита

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

Объект tree — это представление какой-то директории, в данном случае корня рабочей области. Он содержит ключи объектов дочерних tree и blob.
Именно в нем отражаются информация по правам доступа и наименованию файлов или директорий.

Таким образом любой коммит кроме метаинформации о себе содержит ключ полного снимка состояния рабочей директории, начиная с её корня. Да, Git не является системой контроля версий, работающей с отдельными дельтами изменений. Но и снимки состояний делаются только при необходимости.

Создание второго коммита

Анализ файловой системы после создания второго коммита
Анализ файловой системы после создания второго коммита

Если добавить второй файл и создать новый коммит, то будет создано три новых объекта:

  • commit
  • новое tree, так как теперь внутри него два файла
  • новый blob для main.txt

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

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

Дополнение

Внимательный читатель мог заметить, что мы рассмотрели объекты трех типов, а в тексте было упоминание, что всего их четыре. Это не опечатка. Четвертым типом является tag, но такие объекты порождаются для аннотированных тегов. А вот "лёгкие" теги работают только через механизм ссылок и не приводят к созданию нового объекта.

Создание легкого и аннотированного тегов
Создание легкого и аннотированного тегов