Detached HEAD
Что такое состояние detached HEAD в Git, почему оно возникает, как сохранить сделанные коммиты и вернуться на ветку.
Определение
Detached HEAD — это состояние, когда HEAD указывает непосредственно на коммит, а не на ветку. В обычном режиме HEAD ссылается на имя ветки (например, main), а ветка указывает на последний коммит — поэтому при создании нового коммита ветка автоматически продвигается вперёд. В состоянии detached HEAD эта цепочка разрывается: HEAD находится на конкретном коммите, и ни одна ветка не отслеживает новые коммиты.
Как обычно работает HEAD
HEAD — это ссылка, которая говорит Git «где вы сейчас находитесь». В обычном «присоединённом» состоянии ссылки образуют цепочку: HEAD → ветка → коммит. HEAD не указывает на коммит напрямую — он указывает на имя ветки, а та ветка указывает на последний коммит.
При создании нового коммита Git сдвигает указатель ветки на новый коммит, и поскольку HEAD указывает на ветку, он следует за ней автоматически. История остаётся привязана к именованной ветке.
Вы можете убедиться в этом самостоятельно. Файл .git/HEAD содержит символическую ссылку:
cat .git/HEAD
# ref: refs/heads/main <- attached: HEAD points at a branchЧто переводит HEAD в detached-состояние
HEAD отсоединяется всякий раз, когда вы переключаетесь на что-то, что не является веткой — чаще всего на конкретный коммит, тег или remote-tracking-ссылку:
git checkout a1b2c3d # a commit hash
git checkout v1.0.0 # a tag
git checkout origin/main # a remote-tracking ref
git switch --detach main # explicitly detach at main's tipGit предупреждает вас, когда это происходит. Сообщение выглядит следующим образом:
Note: switching to 'a1b2c3d'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.После отсоединения .git/HEAD больше не указывает на ветку — он содержит прямой хеш коммита:
cat .git/HEAD
# a1b2c3d4e5f6... <- detached: HEAD points at a commit directlyКоманда
git checkoutвыполняет двойную функцию (переключение веток и перемещение HEAD). В главах о git switch и git checkout объясняется, почему Git разделил эти задачи.
Когда detached HEAD полезен
Detached HEAD — это не ошибка, а нормальный инструмент. Вы намеренно используете его, когда хотите просмотреть или собрать историю, не затрагивая ни одну из веток:
- Просмотр старой версии — переключитесь на коммит или тег, чтобы прочитать код в том виде, каким он был в тот момент.
- Сборка или тестирование старого релиза — переключитесь на
v1.0.0, запустите сборку, затем вернитесь на свою ветку. - Поиск бага через bisect —
git bisectпроверяет коммиты в состоянии detached HEAD, разыскивая коммит, который привнёс ошибку. - Быстрый эксперимент — сделайте несколько экспериментальных коммитов, решите, что это тупик, и уйдите, не оставляя лишней ветки.
Почему это важно
Просматривать историю в состоянии detached HEAD совершенно безопасно — именно для этого оно и предназначено. Опасность появляется только тогда, когда вы делаете коммиты в этом состоянии. Поскольку ни одна ветка не отслеживает вашу работу, новые коммиты достижимы только через сам HEAD. Как только вы переключитесь на другую ветку, HEAD переместится, и ничто больше не будет указывать на сделанные коммиты. Они становятся «висячими»: некоторое время остаются в репозитории, но без ссылок на них в конечном итоге удаляются сборщиком мусора.
Решение простое — дайте этим коммитам имя, прежде чем уходить.
Сохранение работы, сделанной в detached HEAD
Если вы сделали коммиты в состоянии detached HEAD и хотите их сохранить, создайте ветку до того, как уйдёте. Это привяжет коммиты к имени:
git switch -c my-rescued-workВаши detached-коммиты теперь находятся на новой ветке — в безопасности и доступны по имени. Эквивалент с помощью старой команды — git checkout -b my-rescued-work.
Полный пример
Вот весь цикл — отсоединение, коммит, затем сохранение работы:
git checkout a1b2c3d # detach HEAD onto an old commit
# ... edit files ...
git commit -am "Experiment" # commit lives only on HEAD now
git switch -c experiment # name it -> commit is now safe on a branch
git log --oneline experiment # the new commit is reachable by nameКак только ветка создана, её можно смёрджить, перебазировать или удалить, как любую другую ветку.
Возврат к обычному состоянию
Чтобы выйти из состояния detached HEAD, не сохраняя новые коммиты, просто переключитесь на ветку:
git switch mainЕсли вы делали коммиты в detached-состоянии, забыли создать ветку и уже переключились на другую — не паникуйте. git reflog записывает каждую позицию, в которой побывал HEAD, включая коммиты, на которые не указывает ни одна ветка. Найдите хеш потерянного коммита там и восстановите его на новую ветку:
git reflog
# a1b2c3d HEAD@{1}: commit: Experiment <- your lost commit
# 8120552 HEAD@{2}: checkout: moving from main to 8120552
git switch -c recovered a1b2c3dЗаписи reflog устаревают (по умолчанию через 90 дней для достижимой истории и через 30 — для недостижимой), поэтому восстанавливайте коммиты как можно скорее.
Связанные темы
- git switch — современный, более безопасный способ переключения между ветками.
- git checkout — исходная команда, которая умеет и переключать ветки, и отсоединять
HEAD. - git branch — создание ветки, которая спасёт работу из detached-состояния.
- git reflog — восстановление коммитов после того, как вы уже вышли из detached HEAD.
- git tag — переключение на тег — распространённый способ оказаться в состоянии detached HEAD.