Как часто при изучении чего-то нового вы пребывали в состоянии неопределённости, неясности синтаксиса и внутреннего устройства предмета изучения? Мозг буквально “скрипит и закипает” при попытке усвоить и уяснить материал. Однако, со временем эти процессы утихают по мере того, как вы погружаетесь в соответствующий материал. Тем не менее, существует важное заблуждение о работе Git, которое часто встречается среди большинства новичков. Вот что, вероятно, они думают:
Git – это инструмент, хранящий изменения между файлами, верно?
Неверно. Git не полагается на diff (инструмент, фиксирующий изменения между версиями файла). Если бы это было так, то при просмотре определённой версии проекта потребовалось бы вычисление всех изменений, внесённых во все файлы проекта с момента первого commit’а. А что если репозиторий имеет историю в 10 лет? Выходит весьма затратная операция.
И как же тогда быть, если надо быстро и “безболезненно” залезть в глубь репозитория?
3 Столпа Git:
Таким образом, 99% вашей работы в Git это просто создание такого указанных выше объектов и манипуляции со ссылками между ними. Если ваш первый commit имеет следующую файловую структуру:
тогда Git сохранит это в следующей структуре:
Что же произойдёт, если мы внесем изменения в файл libs/base_libs/file.py и закомитим снова? При изменении файла Git создаст новый blob-объект, а также будет создан и новый tree-объект, потому что содержимое директории base_libs было также изменено, и её родительская директория тоже, выходит, была изменена. Таким образом, новый commit будет выглядеть так:
Обратите внимание, что для неизменённых файлов используются указатели на объекты от предыдущего коммита. И второй и первый комит ссылаются на одни и те же объекты. Эта простая концепция и есть движущей силой Git! А что произойдёт если мы изменим settings.py и закомитим снова? Поскольку этот файл расположен в корневом уровне, то потребуется создать лишь один единственный blob-объект и родительский к нему tree-объект. Такая операция оставит абсолютно не тронутым ветвь ‘libs’ директории, поэтому Git может использовать имеющиеся объекты снова.
Используя этот подход, Git нет необходимости применять огромное количество итераций сравнения файлов для достижения нужного состояния репозитория в любой указанный момент времени. Образ вашего проекта в любой момент времени может быть восстановлен путём простого обхода дерева, начиная с commit-объекта. Теперь вы понимаете, что Git является объектно-ориентированным, а не diff-ориентированным.
Так Git абсолютно не использует сравнение версий?
Не совсем. Поскольку Git пытается быть очень эффективным инструментом, когда дело доходит до хранения своих объектов на диске, а программные проекты могут очень быстро раздуваться, то Git сжимает ваши файлы и на этом он не останавливается. Что произойдёт, если вы измените лишь одну строчку в огромном файле? Исходя из того, что мы уже изучили, Git должен создать новый blob-объект. Получим 2 больших объекта в базе Git, которые очень похожи между собой.
Git учитывает такие инциденты и попытается создать файл, содержащий несколько объектов в одном файле. В этих объектных файлах Git хранит один файл полностью, а другой как дельту – разницу между второй версией и предыдущей. Полностью будет представлена более новая версия, т.к. наиболее вероятно, что вам понадобится именно она. Эта техника называется “дельта компрессия” и Git постоянно напоминает вам о ней, когда вы работаете с удалённым репозиторием.
Таким образом, Git старается быть максимально эффективным.
Подведём итоги
- Git основан не на хранении изменений в версиях (diff), а на деревьях состояний;
- Git не использует diff для представления версий проекта;
- Git использует обход объектных-деревьев для представления версий;
- Git использует diff для минимизации занимаемого дискового пространства.