>> path = '.git/objects/' + sha1[0,2] + '/' + sha1[2,38] => ".git/objects/d8/a734f44240bdf766c8df342664fde23d421d64" >> require 'fileutils' => true >> FileUtils.mkdir_p(File.dirname(path)) => ".git/objects/bd" >> File.open(path, 'w') { |f| f.write zlib_content } => 32
Вот и всё, мы создали корректный объект-блоб для Git. Все другие объекты создаются аналогично, меняется только запись о типе в заголовке (blob, commit, tree). Стоит добавить, что хотя в блобе может храниться почти любое содержимое, содержимое объектов-деревьев и объектов-коммитов записывается в очень строгом формате.
Ссылки в Git
Для просмотра всей истории можно выполнить команду вроде git log 1a410e, но, опять же, требуется помнить, что именно коммит 1a410e является последним, чтобы иметь возможность найти все наши объекты. Нам нужен файл-указатель с простым именем, который бы содержал это значение хеша SHA-1, чтобы можно было пользоваться этим файлом вместо хеша.
В Git такие файлы, содержащие SHA-1, называются ссылками ("refs") и располагаются в каталоге .git/refs. В нашем проекте этот каталог пока пуст, но в нём уже существует некоторая структура каталогов:
$ find .git/refs .git/refs .git/refs/heads .git/refs/tags $ find .git/refs -type f $
Чтобы создать новую ссылку, которая поможет вам вспомнить, какой коммит последний, по сути, необходимо сделать всего лишь следующее:
$ echo "1a410efbd13591db07496601ebc7a059dd55cfe9" > .git/refs/heads/master
Теперь можно использовать только что созданную ссылку из каталога heads вместо хеша в командах Git:
$ git log --pretty=oneline master 1a410efbd13591db07496601ebc7a059dd55cfe9 third commit cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
Тем не менее, редактировать данные файлы напрямую не рекомендуется. Git предоставляет безопасную команду update-ref для изменения ссылок:
$ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9
Вот что такое по сути ветка в Git — простой указатель или ссылка на последнюю версию в работе. Для создания ветки, соответствующей состоянию второго коммита, можно выполнить следующее:
$ git update-ref refs/heads/test cac0ca
Данная ветка будет содержать только коммиты, предшествующие выбранному:
$ git log --pretty=oneline test cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
Теперь наша база данных Git схематично выглядит так, как показано на рисунке 9.4.
Когда выполняется команда git branch (имя ветки), Git, по сути, выполняет update-ref для добавления хеша последнего коммита текущей ветки под указанным именем в виде новой ссылки.
HEAD
Вопрос в том, как же Git получает хеш последнего коммита при выполнении git branch (имя ветки)? Ответ содержится в файле HEAD. Данный файл является символической ссылкой на текущую ветку. Символическая ссылка отличается от обычной тем, что она содержит не сам хеш SHA-1, а указатель на другую ссылку. Если вы загляните в этот файл, то увидите что-то такое:
$ cat .git/HEAD ref: refs/heads/master
Если выполнить git checkout test, то содержимое файла изменится:
$ cat .git/HEAD ref: refs/heads/test
При выполнении git commit, Git создаёт объект-коммит, указывая его родителем тот объект, SHA-1 которого содержится в файле, на который ссылается HEAD.
Данный файл, конечно, можно редактировать вручную, но безопаснее использовать команду symbolic-ref. Получить значение HEAD данной командой можно так:
$ git symbolic-ref HEAD refs/heads/master
Изменить значение HEAD можно так:
$ git symbolic-ref HEAD refs/heads/test $ cat .git/HEAD ref: refs/heads/test
Символическую ссылку на файл вне refs поставить нельзя:
$ git symbolic-ref HEAD test fataclass="underline" Refusing to point HEAD outside of refs/
Метки
Мы рассмотрели три основных типа объектов в Git, но есть и четвёртый. Объект-метка очень похож на объект-коммит: он содержит имя поставившего метку, дату, сообщение и указатель. Разница же в том, что метка указывает на коммит, а не на дерево. Она похожа на ветку, которая никогда не перемещается — она всегда указывает на один и тот же коммит, она просто даёт ему понятное имя.
Как было сказано в главе 2, метки бывают двух типов: аннотированные и легковесные. Легковесную метку можно сделать следующей командой:
$ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d
Вот и всё! Легковесная метка — это ветка, которая никогда не перемещается. Аннотированная метка имеет более сложную структуру. При создании аннотированной метки, Git создаёт специальный объект, на который будет указывать ссылка, а не просто указатель на коммит. Мы можем увидеть это создав аннотированную метку (-a задаёт аннотированные метки):
$ git tag -a v1.1 1a410efbd13591db07496601ebc7a059dd55cfe9 -m 'test tag'