Git detached HEADから抜け出す — 原因とコミットの復旧
過去のコミットを確認しようとコミットハッシュをチェックアウトしたら、見慣れない警告が長々と出力されます。
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.警告の長さから大きな事故のように見えますが、結論から整理します。detached HEADは故障ではなく、HEADがブランチではなくコミットを直接指している状態です。状態そのものは何も壊しません。ただし、この状態でコミットを作って何の対処もなしに離れると、そのコミットを失う可能性があるため、構造と抜け出し方を知っておく必要があります。
HEADが分離されるということの意味 #
普段のHEADはコミットではなくブランチを指しています。mainの上でコミットを作るとmainポインタが新しいコミットへ前進し、HEADはmainに従って自動的に一緒に動きます。
ところが、ブランチではなく特定のコミットのハッシュやタグを直接チェックアウトすると、HEADはブランチを経由せずにそのコミットを直接指すようになります。ブランチから離れたという意味でdetached、つまり分離されたHEADと呼びます。
[通常 — attached]
HEAD ──▶ main ──▶ C3
[分離 — detached]
HEAD ──────────▶ C1 (ブランチを経由しない)detached状態に入る経路は、だいたい3つです。
- 過去のコミットやタグを直接チェックアウト —
git checkout 1a2b3c4、git checkout v2.1.0、または明示的にgit switch --detach 1a2b3c4 - bisectの進行中 — 二分探索が中間のコミットを1つずつチェックアウトするため、探索の間はずっとdetached状態です。
git bisect resetで終えれば元に戻ります。 - サブモジュールの内部 — サブモジュールは特定のコミットに固定して参照する構造のため、そのディレクトリの中はデフォルトでdetached状態です。
何が問題か — どのブランチにも属さないコミット #
detached状態でコードを見るだけなら、何の危険もありません。問題はコミットを作ったときです。
ブランチの上で作ったコミットは、ブランチポインタが参照を保ってくれます。ところがdetached状態で作ったコミットは、どのブランチからも指されていません。この状態からgit switch mainで離れた瞬間、作ったばかりのコミットは到達できない状態になり、時間が経てばガベージコレクションの整理対象になります。Gitもこれを知っているため、離れる時点で最後の警告を出力します。
Warning: you are leaving 2 commits behind, not connected to
any of your branches:
8f3c2d1 実験: キャッシュサイズ調整
5e9a7b4 実験: キャッシュ統計を出力
If you want to keep them by creating a new branch, this may be a good time
to do so with:
git branch <new-branch-name> 8f3c2d1警告が解決策まで教えてくれています。コミットにブランチを付ければよいのです。
状況別の抜け出し方 #
detached HEADから抜け出す方法は、そこまでに何をしたかによって3つに分かれます。
眺めただけなら — そのまま戻ります。
git switch --は直前にいたブランチを意味します。コミットを作っていなければ、この1行で状況は終わりです。
コミットを作って残したいなら — ブランチを付けてから出ます。
git switch -c experiment-cacheいまHEADが指しているコミットの上に新しいブランチができて、HEADもそのブランチに再び接続されます。コミットはブランチが参照している状態になったので、失う心配はありません。
すでに離れてコミットを失ったなら — ハッシュを見つけてブランチを付けます。
離れるときに出力された警告にコミットハッシュが書かれているので、そのハッシュからブランチを作ります。
git branch experiment-cache 8f3c2d1ターミナルを閉じて警告を失った場合は、reflogから探します。detached状態で作ったコミットもreflogにはすべて記録されています。ハッシュを探す手順は削除したGitコミットとブランチをreflogで復旧するで整理しました。
git switch <ブランチ>でブランチに接続してからコミットします。そもそも安全に眺める習慣 #
過去時点のコードを確認することがあるなら、入る前にブランチを作っておくほうがすっきりします。
git switch -c temp-look 1a2b3c4
# 確認が終わったら
git switch main
git branch -d temp-lookdetached状態そのものを作らないので、警告もコミット消失の可能性もありません。確認だけしてコミットしなかったなら、ブランチの削除で跡形なく片付きます。
まとめ #
この記事の要点は3つです。
- detached HEADは、HEADがブランチの代わりにコミットを直接指している状態で、それ自体は何も壊しません。
- 危険はこの状態で作ったコミットだけにあります。離れる前に
git switch -c <ブランチ>でブランチを付ければ安全で、すでに離れたなら警告のハッシュかreflogで復旧します。 - 過去時点を確認するとき、最初から一時ブランチを作って入れば、この状態にはそもそも出会いません。
警告メッセージは長くて威圧的に読めますが、内容は案内に近いものです。構造を一度理解しておけば、detached HEADは事故ではなく、Gitが提供する自由な探索モードとして使えます。