Git refs: An overview
Изучение основ Git с помощью этого тематического обучающего руководства.
Если разобраться в способах обращения к коммиту, все эти команды можно использовать намного эффективнее. В этой главе мы прольем свет на внутреннее устройство распространенных команд, таких как git checkout
, git branch
и git push
, изучив множество методов обращения к коммиту.
Мы также расскажем, как восстановить потерянные, казалось бы, коммиты, получив к ним доступ с помощью журнала ссылок в Git.
Хеши
Самый очевидный способ указать на коммит — через хеш SHA-1. Он служит уникальным идентификатором каждого коммита. Хеши всех коммитов можно найти в выводе git log
.
commit 0c708fdec272bc4446c6cabea4f0022c2b616eba Author: Mary Johnson Date: Wed Jul 9 16:37:42 2014 -0500 Some commit message
При передаче коммита другим командам Git достаточно указать часть хеша, по которой можно однозначно определить коммит. Например, проверить приведенный выше коммит можно с помощью git show
, выполнив следующую команду:
git show 0c708f
Иногда необходимо разрешить ветку, тег или другую косвенную ссылку в соответствующий хеш коммита. Для этого можно использовать команду git rev-parse
. Следующая команда возвращает хеш коммита, на который указывает ветка main
:
Связанные материалы
Перемещение полного репозитория Git
СМ. РЕШЕНИЕ
Изучите Git с помощью Bitbucket Cloud
git rev-parse main
This is particularly useful when writing custom scripts that accept a commit reference. Instead of parsing the commit reference manually, you can let git rev-parse
normalize the input for you.
Ссылки
Ссылка — это косвенный указатель на коммит в Git, своего рода удобный псевдоним для хеша коммита. Это внутренний механизм представления веток и тегов в Git.
Ссылки хранятся в виде обычных текстовых файлов в каталоге .git/refs
, где файл .git
обычно называется .git
. Чтобы изучить ссылки в одном из репозиториев, перейдите в каталог .git/refs
. Он должен иметь показанную ниже структуру, но файлы в ней будут отличаться в зависимости от того, какие ветки, теги и удаленные репозитории есть в вашем репозитории:
.git/refs/ heads/ main some-feature remotes/ origin/ main tags/ v0.9
В каталоге heads
определены все локальные ветки репозитория. Каждое имя файла совпадает с именем соответствующей ветки, а внутри файла хранится хеш коммита. Этот хеш указывает на конец ветки. Чтобы убедиться в этом, попробуйте выполнить следующие две команды из корневого каталога репозитория Git:
# Output the contents of `refs/heads/main` file: cat .git/refs/heads/main # Inspect the commit at the tip of the `main` branch: git log -1 main
Хеш коммита, возвращаемый командой cat
, должен совпадать с идентификатором коммита, отображаемым командой git log
.
Чтобы изменить расположение ветки main
, Git нужно просто изменить содержимое файла refs/heads/main
. А чтобы создать новую ветку, достаточно просто записать хеш коммита в новый файл. Это одна из причин, по которой ветки Git настолько нетребовательны к ресурсам в сравнении с SVN.
Каталог tags
устроен таким же образом, но содержит теги вместо веток. В каталоге remotes
перечислены все удаленные репозитории, созданные с помощью git remote
, в виде отдельных подкаталогов. Внутри каждого из них находятся все удаленные ветки, загруженные в репозиторий.
Specifying refs
При передаче ссылки команде Git можно либо указать ее полное имя, либо использовать ее короткое имя, а Git самостоятельно найдет ссылку. Вы наверняка уже знакомы с короткими именами для ссылок — они используются для обращения к ветке по имени.
git show some-feature
Аргумент some-feature
в приведенной выше команде — это и есть короткое имя ветки. Git разрешает его в refs/heads/some-feature
перед использованием. Кроме того, полную ссылку можно указать в командной строке следующим образом:
git show refs/heads/some-feature
Это позволяет точно прописать местоположение ссылки, что может быть актуально, если у вас, например, есть и тег, и ветка под названием some-feature
. Впрочем, если у вас есть четкие правила именования, путаницы между тегами и ветками быть не должно.
Мы еще встретимся с полными именами в разделе о спецификациях ссылок.
Packed refs
В больших репозиториях Git периодически выполняет сбор мусора для удаления ненужных объектов и сжимает ссылки в один файл для повышения эффективности. Сжатие можно выполнить принудительно с помощью команды для сбора мусора:
git gc
Она перемещает все отдельные файлы веток и тегов в папке refs
в один файл с именем packed-refs
, расположенный на верхнем уровне каталога .git
. Если вы откроете этот файл, вы увидите сопоставление хешей коммитов со ссылками:
00f54250cf4e549fdfcafe2cf9a2c90bc3800285 refs/heads/feature 0e25143693cfe9d5c2e83944bbaf6d3c4505eb17 refs/heads/main bb883e4c91c870b5fed88fd36696e752fb6cf8e6 refs/tags/v0.9
На нормальную работу Git это никак не влияет, но если вы не могли понять, почему папка .git/refs
пуста, то теперь знаете, где хранятся ссылки.
Special refs
Помимо каталога refs
существует несколько специальных указателей, которые находятся на верхнем уровне каталога .git
. Они перечислены ниже.
HEAD
— коммит/ветка, на которые последний раз выполнялось переключение.FETCH_HEAD
— последняя полученная ветка из удаленного репозитория.ORIG_HEAD
— резервная ссылка наHEAD
до значительных изменений в ней.MERGE_HEAD
— коммиты, которые объединяются в текущую ветку с помощью командыgit merge
.CHERRY_PICK_HEAD
— коммит, отобранный командой cherry-pick.
Все эти указатели создаются и обновляются Git при необходимости. Например, команда git pull
сначала выполняет команду git fetch
, которая обновляет указатель FETCH_HEAD
. Затем она выполняет git merge FETCH_HEAD
, чтобы закончить извлечение полученных веток в репозиторий. Конечно, все эти указатели можно использовать, как обычные ссылки, что вы наверняка уже делали с указателем HEAD
.
Содержимое этих файлов различается в зависимости от их типа и состояния репозитория. Указатель HEAD
может содержать либо символическую ссылку, то есть просто другую ссылку вместо хеша коммита, либо хеш коммита. Например, взгляните на содержимое указателя HEAD
, когда вы находитесь в ветке main
:
git checkout main cat .git/HEAD
Команда выводит ref: refs/heads/main
, то есть HEAD
указывает на ссылку refs/heads/main
. Именно так Git узнает, что вы переключились на ветку main
. Если вы переключитесь на другую ветку, содержимое HEAD
соответствующим образом обновится. Но если вы переключитесь на коммит вместо ветки, HEAD
будет содержать хеш коммита, а не символическую ссылку. Так Git определяет, что находится в состоянии с открепленным указателем HEAD.
For the most part, HEAD
is the only reference that you’ll be using directly. The others are generally only useful when writing lower-level scripts that need to hook into Git’s internal workings.
Спецификации ссылок
Спецификация ссылки сопоставляет ветку локального репозитория с веткой удаленного. Это позволяет управлять удаленными ветками с помощью локальных команд Git и настраивать расширенные возможности команд git push
и git fetch
.
Спецификация ссылки задается следующим образом: [+]
<src>
:
<dst>
. Параметр <src>
— это исходная ветка локального репозитория, а <dst>
— требуемая ветка удаленного репозитория. Необязательный знак +
предназначен для принудительного неускоренного обновления удаленного репозитория.
Спецификации ссылок можно использовать с командой git push
, чтобы присвоить удаленной ветке другое имя. Например, следующая команда отправляет ветку main
в удаленный репозиторий origin
обычной командой git push
, но в репозитории origin
она будет называться qa-main
. Это полезно для отделов контроля качества, которым необходимо отправлять свои ветки в удаленный репозиторий.
git push origin main:refs/heads/qa-main
You can also use refspecs for deleting remote branches. This is a common situation for feature-branch workflows that push the feature branches to a remote repo (e.g., for backup purposes). The remote feature branches still reside in the remote repo after they are deleted from the local repo, so you get a build-up of dead feature branches as your project progresses. You can delete them by pushing a refspec that has an empty parameter, like so:
git push origin :some-feature
Это очень удобно, потому что не нужно заходить в удаленный репозиторий и удалять ветку вручную. Обратите внимание, что в версии Git 1.7.0 и выше вместо указанного метода можно использовать флаг --delete
. Следующая команда будет иметь тот же эффект, что и предыдущая:
git push origin --delete some-feature
Если добавить несколько строк в файл конфигурации Git, спецификации ссылок можно использовать, чтобы изменить поведение команды git fetch
. По умолчанию git fetch
получает все ветки в удаленном репозитории. Причина этого находится в следующем разделе файла .git/config
:
[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/*:refs/remotes/origin/*
Строка fetch
указывает, что команда git fetch
должна загружать все ветки из репозитория origin
. Но в некоторых рабочих процессах не нужно получать все ветки. Например, во многих рабочих процессах с непрерывной интеграцией интерес представляет только ветка main
. Чтобы получать только ветку main
, измените строку fetch
следующим образом:
[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main
Аналогичным образом можно настроить и команду git push
. Например, чтобы всегда отправлять ветку main
в ветку qa-main
удаленного репозитория origin
(как мы делали выше), нужно изменить файл конфигурации следующим образом:
[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main push = refs/heads/main:refs/heads/qa-main
Спецификации ссылок дают полный контроль над тем, как различные команды Git передают ветки между репозиториями. Они позволяют переименовывать и удалять ветки из локального репозитория, получать и отправлять данные в ветки с разными именами, а также настраивать git push
и git fetch
, чтобы они работали только с нужными ветками.
Relative refs
Кроме того, можно указывать на коммиты относительно другого коммита. Символ ~
позволяет обращаться к родительским коммитам. Например, следующая команда показывает коммит за два уровня до HEAD
:
git show HEAD~2
Но при слиянии коммитов все немного усложняется. Поскольку у коммитов слияния больше одного родителя, существует и несколько путей, по которым можно следовать. Для трехстороннего слияния первый родительский коммит берется из ветки, в которой вы работали, а второй — из ветки, которую вы передали команде git merge
.
Символ ~
всегда следует по первой линии родителей коммита слияния. Чтобы следовать по другой линии родительских коммитов, нужно выбрать их с помощью символа ^
. Например, если HEAD
указывает на коммит слияния, следующая команда возвращает второй родительский коммит от HEAD
.
git show HEAD^2
Можно использовать более одного символа ^
, чтобы переместиться больше чем на один уровень. Например, эта команда отображает «прародительский» коммит указателя HEAD
(если это коммит слияния) по второй линии родительских коммитов.
git show HEAD^2^1
Чтобы пояснить, как работают символы ~
и ^
, на следующем рисунке показано, как получить любой коммит от A
с помощью относительных ссылок. В некоторых случаях существует несколько способов обратиться к коммиту.
Относительные ссылки можно использовать с теми же командами, что и обычные. Например, все следующие команды принимают относительные ссылки:
# Only list commits that are parent of the second parent of a merge commit git log HEAD^2 # Remove the last 3 commits from the current branch git reset HEAD~3 # Interactively rebase the last 3 commits on the current branch git rebase -i HEAD~3
The reflog
Журнал ссылок — это страховочная привязь Git. В него попадают почти все изменения, внесенные в репозиторий, независимо от того, сделали вы коммит или нет. Его можно расценивать, как хронологическую историю всех действий в локальном репозитории. Чтобы просмотреть журнал ссылок, выполните команду git reflog
. Вывод должен выглядеть примерно так:
400e4b7 HEAD@{0}: checkout: moving from main to HEAD~2 0e25143 HEAD@{1}: commit (amend): Integrate some awesome feature into `main` 00f5425 HEAD@{2}: commit (merge): Merge branch ';feature'; ad8621a HEAD@{3}: commit: Finish the feature
Это можно перевести следующим образом:
- Вы только что переключились на
HEAD~2
. - До этого вы исправили сообщение к коммиту.
- До этого вы объединили ветку
feature
с веткойmain
. - До этого вы внесли коммит снимка состояния.
Конструкция HEAD{
позволяет обращаться к коммитам, хранящимся в журнале ссылок. Она очень похожа на ссылки HEAD~
из предыдущего раздела, но
указывает на запись в журнале ссылок, а не в истории коммитов.
Так можно вернуться к состоянию, которое в противном случае было бы потеряно. Предположим, вы только что отбраковали новую функцию с помощью git reset
. Журнал ссылок в этом случае может выглядеть примерно так:
ad8621a HEAD@{0}: reset: moving to HEAD~3 298eb9f HEAD@{1}: commit: Some other commit message bbe9012 HEAD@{2}: commit: Continue the feature 9cb79fa HEAD@{3}: commit: Start a new feature
Теперь у трех коммитов до git reset
нет ссылок, то есть нет способа обратиться к ним — помимо журнала ссылок. Допустим, вы поняли, что не стоило выбрасывать всю проделанную работу. Тогда достаточно переключиться на коммит HEAD@{1}
, и вы вернете репозиторий в состояние перед выполнением команды git reset
.
git checkout HEAD@{1}
При этом репозиторий окажется в состоянии с открепленным указателем HEAD
. Отсюда можно создать новую ветку и продолжить работу над функцией.
Резюме
Теперь у вас не должно быть сложностей со ссылками на коммиты в репозитории Git. Мы узнали, как ветки и теги хранятся в виде ссылок в подкаталоге .git
, как читать файл packed-refs
, как представлен указатель HEAD
, как расширить возможности отправки и получения с помощью спецификаций ссылок и как использовать относительные операторы ~
и ^
для прохода по иерархии веток.
Мы также познакомились с журналом ссылок, который позволяет обращаться к коммитам, недоступным с помощью других методов. Это отличный способ исправить положение в ситуации «Ой, а вот это я зря».
Все описанные команды и способы нужны для того, чтобы вы могли точно выбирать нужный вам коммит в любом сценарии разработки. Навыками, полученными в этой статье, очень легко дополнить уже имеющиеся у вас знания Git, поскольку ссылки в качестве аргументов принимают многие распространенные команды, включая git log
, git show
, git checkout
, git reset
, git revert
, git rebase
и многие другие.
Поделитесь этой статьей
Следующая тема
Рекомендуемые статьи
Добавьте эти ресурсы в закладки, чтобы изучить типы команд DevOps или получать регулярные обновления по DevOps в Atlassian.