Git の 'detached HEAD' 状態とその注意点
Git の checkout コマンドで前のコミットに遡ると 'detached HEAD' という状態に置かれますが, その意味とその状態の時に注意するべきことを紹介します.
次のような 3 つのコミットがあったとします:
HEAD
|
v
master
|
v
A --- B --- C
master
ブランチが C
を指していて, その master
ブランチを HEAD
が指しています.
この状態で C
の一つ前の B
のコミットの内容を少し確認したいということで B
をチェックアウトしたとします:
git checkout HEAD^
HEAD^
で HEAD
が指している C
の一つ前の B
を指定しています.
すると次のようになり:
master
|
v
A --- B --- C
^
|
HEAD
次のようなメーセージが表示されます:
Note: checking out '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 performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b <new-branch-name>
HEAD is now at <commit id> <commit message>
あなたは今 ‘detached HEAD’ 状態にいます. もし実験的なコミットをしたいならそういったコミットをすることができます. そして checkout もう一回することでそれらの実験的なコミットを破棄できます. もしコミットした内容を保持するために新しいブランチを作りたいのならそうしてください.
そんな感じの内容になります.
つまりはこの状態が ‘detached HEAD’ と言われるものです.
master
ブランチは一番先端の C
を指しているにも関わらず, HEAD
は master
ブランチが指している C
と異なる B
を指しています.
本来 HEAD は特定のブランチと一緒にくっついているものなのですが, 離れてしまったのでこのように言われるわけです.
説明にも書かれているように, ‘detached HEAD’ の状態から新たなコミットをすることができます.
もしこの状態でコミットをすると次のようになります:
D <- HEAD
/
/
A --- B --- C <- master
HEAD
が B
から D
に移動しましたね. このように HEAD
は常に今現在自分がいるコミットを指し示します.
もうひとコミットすると次のようになります:
D --- E <- HEAD
/
/
A --- B --- C <- master
こんな感じでどんどんコミットしていくことができます.
もし D
と E
が実験的なものであれば, 次のコマンドを打ち:
git checkout master
master
をチェックアウトし, 将来的に実行される ‘garbage collection’ によって D
と E
は削除されることになります.
ただ一つ注意していただきたいことは, checkout で master
に戻った後にやっぱり D
と E
のコミットを保持したいという場合です.
一度 master
をチェックアウトしてしまうと次のように D
と E
を紐付けるものがなく D
と E
を参照することができません:
D --- E
/
/
A --- B --- C <- master <- HEAD
ただ checkout コマンドで HEAD
を master
に移動した後, 次のようなメーセージが表示されます:
Warning: you are leaving 2 commits behind, not connected to
any of your branches:
<commid-id-1> <commit-message-1>
<commit-id-2> <commit-message-2>
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> <commit-id-1>
Switched to branch 'master'
あなたはいずれのブランチにも接続されていない 2 つのコミットを置き去りにしています. もしそれらのコミットを新しくブランチを作って保持したいなら, これはいい機会です.
そんな内容です.
なので master
に HEAD
を移動させてしまったからといって, 宙吊り状態の D
と E
のコミットを新たなブランチと関連づけることができないというわけではありません.
そのメッセージに書かれているように, 新たなブランチを作りE
と関連づけて D
と E
を将来的な ‘garbage collection’ の対象から外すことができます.
次のようにすると:
git branch feature <commit-id-1>
<commit-id-1>
というのは E
のコミット ID で, 実際は 16 進数の文字列になります.
このようになります:
D --- E <- feature
/
/
A --- B --- C <- master <- HEAD
こんな感じで E
を feature
に紐づけることができました.
実際にはブランチの名前は任意のものになりますので feature
である必要はありません.
もし最初から D
と E
を feature
と紐付けるつもりだったら checkout HEAD^
で HEAD
を B
に動かして ‘detached HEAD’ 状態になった時に:
master
|
v
A --- B --- C
^
|
HEAD
次のコマンドで feature
ブランチを作り, feature
をチェックアウトし B
と関連づけてしまいます:
git checkout -b feature
そしたら次のようになります:
master
|
v
A --- B --- C
^
|
feature
^
|
HEAD
この状態なら D
, E
とコミットを作っていってもそれらのコミットが宙吊りになるということはありません.
なぜなら今は feature
というブランチをチェックアウトしていて, 新たに作られるコミットは全て feature
のコミットとなるからです.
なので D
, E
とコミットすると次のようになります:
D --- E <- feature <- HEAD
/
/
A --- B --- C <- master
これだったら master
をチェックアウトしても:
git checkout master
このようになり:
D --- E <- feature
/
/
A --- B --- C <- master <- HEAD
D
と E
は feature
なのでそれらを参照することができなくなるという心配はありません.
まとめ
detached HEAD
の状態で新たなコミットを保持したいという場合は, detached HEAD
状態になった直後に git checkout -b <branch-name>
でブランチを作ってチェックアウトしてしまいましょう.
実験的なコミットだったら git checkout <branch-name>
で detached HEAD
になる前のブランチをチェックアウトして破棄してしまうことができます.
detached HEAD
の状態とはどういうもので, その時に注意することはどういったことがあるのかということを理解する手助けをすることができましたら幸いです.
最後までお読みくださいましてありがとうございました.
関連記事
Git のコミット履歴を大胆に書き換えるなら git rebase -i がオススメ2018.08.23
Git の mergetool で vimdiff を指定して Vim でマージコンフリクトを解決してみた2018.08.20
Git のマージコンフリクトを解決する方法2018.06.13
Git で変更されたファイルを部分的にステージする方法2018.11.01
Ruby の正規表現を備忘録としてまとめてみた2018.08.30