Ссылки в 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
Теперь в командах Git’а вместо хеша можно использовать ссылку, только что созданную в каталоге heads:
$ 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.

Рисунок 9-4. Объекты в каталоге .git, а также указатели на вершины веток.
Когда выполняется команда 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
fatal: 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'
Вот значение SHA-1 созданного объекта:
$ cat .git/refs/tags/v1.1
9585191f37f7b0fb9444f35a9bf50de191beadc2
Теперь выполним cat-file
для этого хеша:
$ git cat-file -p 9585191f37f7b0fb9444f35a9bf50de191beadc2
object 1a410efbd13591db07496601ebc7a059dd55cfe9
type commit
tag v1.1
tagger Scott Chacon <schacon@gmail.com> Sat May 23 16:48:58 2009 -0700
test tag
Заметьте, в поле object записан SHA-1 коммита, для которого мы делали метку. Также стоит отметить, что это поле не обязательно указывает на коммит, но на любой объект в Git’е. Например, в исходный код Git’а мейнтейнер добавил свой открытый GPG-ключ в качестве блоба и поставил для него метку. Увидеть этот ключ можно, выполнив команду
$ git cat-file blob junio-gpg-pub
в репозитории с исходным кодом Git’а. В репозитории ядра Linux также есть метка, указывающая не на коммит — первая метка указывает на дерево первичного импорта.
Ссылки на удалённые ветки
Третий тип ссылок, который мы рассмотрим — ссылка на удалённую ветку. Если вы добавили удалённый репозиторий и отправили (push) на него изменения, Git сохранит последнее отправленное значение SHA-1 в каталоге refs/remotes
для всех отправленных веток. Например, можно добавить удалённый репозиторий origin
и отправить туда ветку master
:
$ git remote add origin git@github.com:schacon/simplegit-progit.git
$ git push origin master
Counting objects: 11, done.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 716 bytes, done.
Total 7 (delta 2), reused 4 (delta 1)
To git@github.com:schacon/simplegit-progit.git
a11bef0..ca82a6d master -> master
Позже вы сможете посмотреть, где находилась ветка master
с сервера origin
во время последнего соединения с сервером, заглянув в файл refs/remotes/origin/master
:
$ cat .git/refs/remotes/origin/master
ca82a6dff817ec66f44342007202690a93763949
Ссылки на удалённые ветки отличаются от обычных веток (ссылки в refs/heads
) тем, что на них нельзя переключиться с помощью git checkout
. Git работает с ними как с закладками, указывающими на последнее состояние соответствующих веток на ваших серверах.