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

В интернете размещена масса руководств по командам Git, но в этой статье работа Git рассмотрена глубже, чем просто изучение команд.

Это первая часть гайда по Git из блога Pierre de Wulf в переводе команды Mail.ru Cloud Solutions

Новым пользователям бывает трудно освоиться с Git. Это мощный инструмент, но, к сожалению, не очень удобный в освоении. Масса новых концепций, команды, выполняющие разные действия, если файл передается как параметр или нет, неясная обратная связь…

Наверное, единственный путь преодолеть все эти трудности — узнать чуть больше, чем просто git commit/push, понять, как именно работает Git.

Папка .git


Когда вы создаете новый репозиторий командой git init, Git создает волшебную папку, .git. В ней содержится все необходимое для работы Git. Если вы хотите убрать Git из  вашего проекта, но оставить проектные файлы на диске, просто удалите папку .git. Хотя кому такое может потребоваться?

    +-- HEAD
    +-- branches
    +-- config
    +-- description
    +-- hooks
    ¦ +-- pre-commit.sample
    ¦ +-- pre-push.sample
    ¦ L-- ...
    +-- info
    ¦ L-- exclude
    +-- objects
    ¦ +-- info
    ¦ L-- pack
    L-- refs
     +-- heads
     L-- tags


Вот содержимое типовой папки .git перед вашим первым коммитом:

  1. HEAD — это мы рассмотрим позже.
  2. config — этот файл содержит настройки для вашего репозитория, здесь, например, хранится url вашего репозитория в хранилище, ваше имя, email и так далее. Каждый раз когда вы делаете git config, вы обращаетесь к этому файлу.
  3. description — используется gitweb для отображения описания репозитория.
  4. hooks — эта папка содержит скрипты, которые могут выполняться на различных этапах выполнения Git. Эти скрипты, называемые хуками, могут запускаться до/после commit/rebase/pull… Имя скрипта определяет время его выполнения. Примером хука может служить скрипт проверки стиля перед выполнением команды push в репозиторий.
  5. info — exclude — здесь описываются файлы, которые вы не хотите включать в репозиторий. Функционал этого файла такой же, как у файла .gitignore, за исключением того, что он не передается в репозиторий. На практике обычно для всех задач хватает .gitignore.

Что внутри коммита?


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

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

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

  1. Если файл в рабочей папке не изменялся, он просто добавляет имя сжатого файла (хеш) в снимок.
  2. Если файл в рабочей папке изменялся, он сжимает его, помещает в папку объектов и добавляет имя сжатого файла (хеш) в снимок.

Конечно, тут все описано несколько упрощенно, однако, этого достаточно для понимания происходящих процессов.

Как только снимок сделан, он также архивируется и именуется при помощи хеша, затем помещается в папку объектов.

+-- 4c
¦ L-- f44f1e3fe4fb7f8aa42138c324f63f5ac85828 // hash
+-- 86
¦ L-- 550c31847e518e1927f95991c949fc14efc711 // hash
+-- e6
¦ L-- 9de29bb2d1d6434b8b29ae775ad8c2e48c5391 // hash
+-- info // let's ignore that
L-- pack // let's ignore that too


Вот как выглядит папка объектов после того, как я создал файл file_1.txt  и закоммитил его. Пожалуйста, учтите, что если хеш вашего файла начинается на «4cf44f1e…», то Git сохранит его с именем «f44f1e…» в подпапке с именем «4c». Таким образом, файлы будут разложены по 256 подпапкам и в каждой не будет слишком много файлов.

У нас, как вы видите, три хеша. Один для файла file_1.txt, второй для снимка, сделанного при коммите. Для чего же третий? Третий хеш создается потому, что коммит — тоже объект, он также архивируется и помещается в папку объектов.

Вам нужно запомнить, что коммит состоит из четырех вещей:

  1. Имя (хеш) снимка рабочей директории.
  2. Комментарий.
  3. Информация о том, кто выполнил коммит.
  4. Хеш родительского коммита.

Посмотрите сами, что произойдет, если распаковать файл коммита:

git cat-file -p 4cf44f1e3fe4fb7f8aa42138c324f63f5ac85828

И вот, что вы увидите:

tree 86550c31847e518e1927f95991c949fc14efc711
author Pierre De Wulf 
<test[@gmail.com](mailto:pierredewulf31@gmail.com)> 1455775173 -0500
committer Pierre De Wulf 
<[test@gmail.com](mailto:pierredewulf31@gmail.com)> 1455775173 -0500
commit A

Вы видите, как и ожидалось, хеш снимка, автора и комментарий коммита. Тут важны две вещи:

  1. Хеш снимка «86550...» также является объектом и его можно увидеть в папке объектов.
  2. Так как это первый коммит, у него нет родительского коммита.

Что же в снимке в действительности?

git cat-file -p 86550c31847e518e1927f95991c949fc14efc711
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file_1.txt

Здесь мы видим объект, который находился в нашем хранилище объектов, единственный объект в нашем снимке.

Branch, tags, HEAD — это одно и то же


Теперь вы понимаете, что все в Git может быть получено через правильный хеш. Давайте теперь посмотрим на HEAD. Итак, что же там?

cat HEAD
ref: refs/heads/master

Там нет хеша, и в этом есть смысл, так как HEAD — это указатель на верхушку ветви, с которой вы работаете. Если вы посмотрите в файл refs/heads/master, то увидите:

cat refs/heads/master
4cf44f1e3fe4fb7f8aa42138c324f63f5ac85828
 

Выглядит знакомо? Естественно, ведь это хеш первого коммита! Это показывает, что теги (tags) и  ветви (branch) — всего лишь указатели на коммит. Понимая это, вы можете удалить все теги, какие захотите, все ветви, какие захотите, а коммит, на который они указывали, останется на месте. Единственное, к нему будет сложнее получить доступ. Если вам хочется больше узнать об этом, почитайте git book.

Последнее замечание


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

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

Переведено при поддержке Mail.ru Cloud Solutions.

Что еще почитать:

  1. Простые способы кэширования в GitLab CI: руководство в картинках.
  2. Как технический долг и лже-Agile убивает ваши проекты.
  3. Мой второй год в качестве независимого разработчика.