Git
Chapters ▾ 2nd Edition

10.3 Git изнутри - Ссылки в Git

Ссылки в Git

Если вас интересует история репозитория начиная с определенного коммита, например 1a410e, то для её отображения вы можете воспользоваться командой git log 1a410e, однако при этом вам всё ещё необходимо помнить хеш коммита 1a410e, который является начальной точкой истории. Было бы неплохо, если бы существовал файл, в котрый можно было бы сохранить значение SHA-1 под простым именем, а затем использовать это имя вместо хеша SHA-1.

В Git такие файлы называются ссылками (“references” или, сокращённо, “refs”) и расположены в директории .git/refs. В нашем проекте эта директория пока пуста, но в ней уже прослеживается некая структура:

$ find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags
$ find .git/refs -type f

Чтобы создать новую ссылку, которая поможет вам запомнить SHA-1 последнего коммита, технически, достаточно выполнить примерно следующее:

$ echo 1a410efbd13591db07496601ebc7a059dd55cfe9 > .git/refs/heads/master

Теперь в командах Git вместо SHA-1 можно использовать только что созданную ссылку:

$ 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 схематично выглядит так, как показано на рисунке:

Объекты в директории .git
Рисунок 152. Объекты в директории .git, а также указатели на вершины веток.

При выполнении команды git branch <branch>, в действительности Git запускает команду update-ref, которая добавляет SHA-1 хеш последнего коммита текущей ветки в файл с именем указанной ветки.

HEAD

Как же Git получает хеш последнего коммита при выполнении git branch <имя ветки>? Ответ кроется в файле HEAD.

Файл HEAD — это символическая ссылка на текущую ветку. Символическая ссылка отличается от обычной тем, что она содержит не сам хеш SHA-1, а указатель на другую ссылку.

В некоторых случаях файл HEAD может содержать SHA-1 хеш какого-либо объекта. Это происходит при извлечении тега, коммита или удалённой ветки, что приводит репозиторий в состояние "detached HEAD".

Если вы заглянете внутрь HEAD, то увидите следующее:

$ cat .git/HEAD
ref: refs/heads/master

Если выполнить git checkout test, Git обновит содержимое файла:

$ 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

Символическую ссылку на файл вне .git/refs поставить нельзя:

$ git symbolic-ref HEAD test
fatal: Refusing to point HEAD outside of refs/

Теги

Мы рассмотрели три основных типа объектов Git, но есть ещё один. Объект тега очень похож на объект коммита: он содержит имя своего автора, дату, сообщение и указатель. Разница же в том, что объект тега указывает на коммит, а не на дерево. Он похож на ветку, которая никогда не перемещается: он всегда указывает на один и тот же коммит, просто давая ему понятное имя.

Как мы знаем из главы [ch02-git-basics], теги бывают двух типов: аннотированные и легковесные. Легковесный тег можно создать следующей командой:

$ 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

Теперь выполним git cat-file -p для этого хеша:

$ 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

В репозитории ядра Linux также есть метка, указывающая не на коммит: самая первая метка указывает на дерево первичного импорта.

Ссылки на удалённые ветки

Третий тип ссылок, который мы рассмотрим — ссылки на удалённые ветки. Если вы добавили удалённый репозиторий и отправили в него какие-нибудь изменения, 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 не установит HEAD на неё, а значит вы не сможете фиксировать свои изменения в ней с помощью git commit. Git воспринимает удалённые ветки как закладки на последние известные состояния веток на удалённых серверах.