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 を指しているにも関わらず, HEADmaster ブランチが指している C と異なる B を指しています.

本来 HEAD は特定のブランチと一緒にくっついているものなのですが, 離れてしまったのでこのように言われるわけです.

説明にも書かれているように, ‘detached HEAD’ の状態から新たなコミットをすることができます.

もしこの状態でコミットをすると次のようになります:

             D <- HEAD
            /
           /
    A --- B --- C <- master

HEADB から D に移動しましたね. このように HEAD は常に今現在自分がいるコミットを指し示します.

もうひとコミットすると次のようになります:

             D --- E <- HEAD
            /
           /
    A --- B --- C <- master

こんな感じでどんどんコミットしていくことができます.

もし DE が実験的なものであれば, 次のコマンドを打ち:

git checkout master

master をチェックアウトし, 将来的に実行される ‘garbage collection’ によって DE は削除されることになります.

ただ一つ注意していただきたいことは, checkout で master に戻った後にやっぱり DE のコミットを保持したいという場合です.

一度 master をチェックアウトしてしまうと次のように DE を紐付けるものがなく DE を参照することができません:

             D --- E
            /
           /
    A --- B --- C <- master <- HEAD

ただ checkout コマンドで HEADmaster に移動した後, 次のようなメーセージが表示されます:

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 つのコミットを置き去りにしています. もしそれらのコミットを新しくブランチを作って保持したいなら, これはいい機会です.

そんな内容です.

なので masterHEAD を移動させてしまったからといって, 宙吊り状態の DE のコミットを新たなブランチと関連づけることができないというわけではありません.

そのメッセージに書かれているように, 新たなブランチを作りE と関連づけて DE を将来的な ‘garbage collection’ の対象から外すことができます.

次のようにすると:

git branch feature <commit-id-1>

<commit-id-1> というのは E のコミット ID で, 実際は 16 進数の文字列になります.

このようになります:

             D --- E <- feature
            /
           /
    A --- B --- C <- master <- HEAD

こんな感じで Efeature に紐づけることができました.

実際にはブランチの名前は任意のものになりますので feature である必要はありません.

もし最初から DEfeature と紐付けるつもりだったら checkout HEAD^HEADB に動かして ‘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

DEfeature なのでそれらを参照することができなくなるという心配はありません.

まとめ

detached HEAD の状態で新たなコミットを保持したいという場合は, detached HEAD 状態になった直後に git checkout -b <branch-name> でブランチを作ってチェックアウトしてしまいましょう.

実験的なコミットだったら git checkout <branch-name>detached HEAD になる前のブランチをチェックアウトして破棄してしまうことができます.

detached HEAD の状態とはどういうもので, その時に注意することはどういったことがあるのかということを理解する手助けをすることができましたら幸いです.

最後までお読みくださいましてありがとうございました.