Close

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

Логотип Bitbucket
СМ. РЕШЕНИЕ

Изучите 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.

Люди сотрудничают друг с другом, используя стену со множеством инструментов

Блог Bitbucket

Рисунок: DevOps

Образовательные программы DevOps

Демонстрация функций в демо-зале с участием экспертов Atlassian

Как инструмент Bitbucket Cloud работает с Atlassian Open DevOps

Подпишитесь на информационную рассылку по DevOps

Thank you for signing up