Git
Chapters ▾ 2nd Edition

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

Ссылки в Git

Для просмотра истории можно выполнить команду типа git log 1a410e, но вам всё ещё придётся запоминать, что именно коммит 1a410e является последним, чтобы иметь возможность найти все наши объекты. Было бы неплохо, если бы существовал файл с понятным названием, содержащий этот 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 (имя ветки), Git, по сути, выполняет update-ref для добавления хеша последнего коммита текущей ветки под указанным именем в виде новой ссылки.

HEAD

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

Файл HEAD — это символическая ссылка (не в терминах файловой системы) на текущую ветку. Символическая ссылка отличается от обычной тем, что она содержит не сам хеш SHA-1, а указатель на другую ссылку. Если вы заглянете внутрь 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, но есть ещё один. Метка очень похожа на коммит: она содержит имя автора метки, дату, сообщение и указатель. Разница же в том, что метка указывает на коммит, а не на дерево. Она похожа на ветку, которая никогда не перемещается: она всегда указывает на один и тот же коммит, просто давая ему понятное имя.

Как мы знаем из главы Основы Git, метки бывают двух типов: аннотированные и легковесные. Легковесную метку можно создать следующей командой:

$ 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

В репозитории ядра 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 поддерживает удалённые ветки в качестве закладок на определённые состояния в удалённом репозитории во время последнего контакта с сервером.