24. Git: система управления версиями для всех. За кулисами - основы основ

git init

Данная команда создает папку .git, вот ее содержимое:
  • hooks/ - это настраиваемые скрипты, которые могут встраиваться в различные Git команды и операции. Можно написать собственный hook и он попадет в эту папку. По умолчанию после git init создаются несколько hook-ов с именами файлов hook_name.sample, они остаются не активированными пока не будут переименованы hook_name. git help hooks выдаст больше информации по этой теме.
  • info/ - доп. информация о репозитории. Единственный файл внутри exclude содержит master список файлов, которые исключаются из мониторинга Git-ом. Отличается от .gitignore тем что любой шаблон исключений записанный в exclude отражается только на локальный репозиторий и не влияет на последующие клоны. А .gitignore является частью истории и может быть подвергнут другим командам: add, commit, merge, clone, pull, push.
  • config - project/repository-specific configuration file
  • description - user-defined описание репозитория, которое используется программой gitweb для отображения клиентам листинга репозитория. Пакет gitweb поставляется вместе со стандартной инсталляцией и нужен для предоставления веб-интерфейса к репозиториям.
  • objects/ - все что попадает под Git, рассматривается как объекты. Объекты бывают 4 типов: blobs (binary large object), trees (служат для внутреннего представление папок и структуры контента проекта, Git tree objects могут ссылаться на Git blobs и/или другие Git tree objects), commits (метаданные внесенных изменений: автор изменения, committer изменения, e-mail адреса, дата, время), и tags ("человеческие" имена для других объектов, обычно для commit-ов чтобы их было легче искать).
  • HEAD - указатель на активную ветвь. Например, для master branch: ref: refs/heads/master, а для test_release branch: ref: refs/heads/test_release
  • refs/ - SHA-1 IDs важных точек в репозитории, таких как tags (refs/tags) и branches (refs/heads). Каждый branch name это файл в refs/heads и этот файл содержит SHA-1 ID для commit-а конкретно из которого (parent по другому) создался данный branch. То же самое для tags - каждый tag это файл в папке refs/tags который имеет единственный SHA-1 ID на который он ссылается. Если папки heads и tags будут устроены иерархически то это просто означает что использовалось hierarchically structured name для branch или tag (например, git branch kamia/kashin приводит к heads/kamia/kashin).
  • index - содержимое этого файла станет следующим commit-ом. Это то место где Git хранит staging area information. Или другими словами это то место где хранятся файлы которые пользователь хочет за-commit-ить в репозиторий.

git add

Когда выполняется команда add, Git обновляет файл index используя текущий контент найденный в working tree (staging your changes), и подготавливает staged-контент для следующего commit-а, подготовка включает в себя следующие шаги:
  1. Вычисление hash для контента.
  2. Принятие решений, где создавать новый контент или ссылаться на существующий blob object.
  3. Фактическое создание или связывание ссылкой blob.
  4. Создание tree object для отслеживания расположения контента.
После этого index будет содержать snapshot контента в working tree для следующего commit-а.

Эта команда может быть выполнена несколько раз. Она только добавляет контент указанных файлов, каждый раз когда команда add запускается. Если нужно чтобы последующие изменения тоже вошли в следующий commit, нужно запустить git add снова чтобы добавить новый контент в index.
Большее значение должно быть уделено процессу, в котором оба объекта, blob и tree создаются и связываются с их соответствующими  hash IDs, как показано на рисунке:

Tree может указывать не только на blob, но также и на другое tree, образуя иерархическую сеть:

git commit

Когда выполняется эта команда, создается  commit-объект из метаданных контента/изменений, которые были добавлены командой git add ранее. Метаданные включают в себя следующее:
  • имя автора изменения, соответствующая дата и время;
  • имя автора commit-а данных изменений, соответствующая дата и время.
Потом созданный commit-объект связывается с tree-объектом, который уже связан с blob:

head содержит имя ветви, а не SHA-1 ID commit-а, на который он указывает. Это потому что трудно идентифицировать ветвь по ее commit ID когда объем commit-ов внутри ветви постоянно меняется, следовательно "branch moves".


blob и tree объекты создаются как часть add операции; они будут уничтожены процессом garbage collection через несколько месяцев.

Теперь если сделать git status то можно убедиться что staged-изменения больше не находятся в состоянии staged.

git status

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

Сравнение файловых путей проходит следующими этапами:


Первый статус означает изменения которые уже были добавлены (staged) но не за-commit-ены. Поэтому выполнение git commit завершит versioning процесс.

Второй и третий статусы означают что изменения еще не были добавлены (staged) для commit-а. Поэтому для завершения versioning процесса, нужно их сначала добавить командой git add и потом git commit.

git clone

Внутренний процесс:
  1. создание целевой папки и выполнение git init в ней;
  2. установка remote tracking branches в целевом репозитории для каждой ветви представленной в исходном репозитории (git remote);
  3. извлечение объектов, ссылок (внутри .git папки);
  4. выполение checkout.

git remote

Когда выполняет команда remote, Git проходит по всем remotes добавленным в репозиторий путем считывания их из секции remote в локальном конфигурационном файле .git/config
Пример:
[remote "capsource"]
url = https://github.com/cappuccino/cappuccino
fetch = +refs/heads/*:refs/remotes/capsource/*

Наименование capsource является псевдонимом для URL, который задается при добавлении нового remote в репозиторий. Параметры:
  • url - ссылка на удаленный репозиторий, который вы хотите отслеживать, и получать содержимое из него;
  • Fetch - информация которая говорит Git-у о refs (branches и tags) из remote которые должны отслеживаться. По умолчанию отслеживаются все refs из удаленного репозитория, что указывается как refs/heads/*. Они размещаются в папке локального репозитория capsource размещенной в refs/remotes/capsource/*

git branch

  1. сбор всех наименований ветвей из .git/refs/heads/
  2. поиск active/current working branch с помощью .git/HEAD
  3. отображение всех ветвей в возрастающем порядке со звездочкой (*) рядом с активной ветвью
Список ветвей отображаемый таким способом - это только локальные ветви в репозитории. Если нужно получить все ветви включая remote tracking branches которые хранятся в .git/refs/remotes/ надо выполнить команду git branch -a

git tag

  1. получение SHA-1 ID соответствующего commit-a;
  2. валидация наименования тега на конфликт с существующими именами;
  3. валидация наименования тега на соответствие правилам именования;
  4. создание объекта тега с данным именем, которое mapped на соответствующий SHA-1 ID, который может быть найден в .git/refs/tags/
Ассоциация тегового объекта с другими объектами:


git fetch

  1. проверка на URL или remote наименование, git fetch remote_name (или) url
  2. если ничего не указано, то считывается конф. файл по умолчанию для получения default remote
  3. если найден, извлекаются named refs (heads и tags) из удаленного репозитория вместе с ассоциированными с ними объектами
  4. полученные ref names сохраняются в .git/FETCH_HEAD в целях возможного слияния в будущем

git merge

  1. идентификация обоих merge кандидатов из папки heads, на основе указанных параметров
  2. поиск общего предка для обоих heads и загрузка всех их объектов в память
  3. выполнение diff (difference) между общим предком и head one
  4. применение diff к head two
  5. если есть изменения в пересекаемых областях, маркерами обозначается конфликт и происходит информирование пользователя об этом для того чтобы пользователь разрешил конфликт, добавил изменения и сделал commit
  6. если нет конфликтов, то выполняется слияние обоих содержимых и выполняется merge commit с метаданными об этом

git pull

  1. git fetch с заданными параметрами
  2. вызов git merge чтобы объединить полученный branch head в текущую ветвь

git push

  1. идентификация текущей ветви
  2. поиск default remote в конфигурационном файле (если не будет найдет то выдается просьба указать наименование remote или URK в качестве параметра для git push)
  3. получение информации об отслеживаемых remote's URL и heads (branches)
  4. проверка на то изменялся ли remote со времени последнего fetch: а) получение списка ссылок из удаленных репозиториев (git ls-remote) б) проверка на существование вхождений из списка локальной истории; если ссылка из remote является частью истории локального репозитория, это очевидно что не было изменений с момента последнего fetched/pulled из remote; поэтому Git позволяет напрямую сделать push изменений в remote; если ссылка из remote не является частью истории локального репозитория, Git понимает что удаленный репозиторий подвергался изменениям с момента вашего последнего fetched/pulled из него; поэтому он попросит сделать сначала git fetch или git pull

git checkout

  1. Fetches the named paths in the working tree.
  2. Fetches the related objects from the index.
  3. Updates the contents of the working tree with the ones from the index.
Параметры:
-b
Этот параметр используется для порождения новой ветви из checked out позиции характеризуемой commit ID.
git checkout -b <your_branch_name>
или
git checkout branch
git checkout <branch_name>
Эта команда создает новую ссылку в .git/refs/heads/ с конкретным commit ID.

--track
Этот параметр используется для установки upstream configuration обычно при создании новой ветви с параметром -b.
После выполнения, в .config файл в .git папке добавляется отдельная секция:
[branch "master"]
remote = origin
merge = refs/heads/master

Это случается когда выполняется команда
git checkout --track -b master origin/master

Git packfiles

Например, есть 2 одинаковых текстовых файла по 5 МБ каждый. Git создаст один blob, т.к. одинаковое содержимое дает одинаковый SHA-1 ID. Теперь изменим второй файл. Git создаст новый blob (5+ МБ) для второго файла, который изменился. Можно задать два вопроса:
  1. Почему Git создает новый blob для всего содержимого?
  2. Почему бы не оставить старый blob для двух файлов, и дополнительно не создать новый blob для разницы со вторым файлом, чтобы использовать пространство более эффективно?
Ответом являются packfiles. Созданные объекты как упоминается в сценарии выше, называются loose objects и автоматически, но случайно Git упаковывает несколько loose объектов в единый бинарный файл который называется packfile.

Передача packfiles

Git поддерживает не только передачу refs и ассоциированных с ними plain blob, tree, commit,
и tag objects, но и packfiles на таких операциях как clone, fetch, push, и pull. Git имеет 2 множества протоколов для передачи данных между remotes.
  • один для pushing data с клиента на сервер
  • другой для fetching data с сервера на клиент