【Git】detached HEADは友達。元に戻す方法や使い方(detached HEADとは何か?の意味を理解して焦らず対応)

git-prograshi(プロぐらし)-kvgit/github

Git操作をしていると時折、You are in 'detached HEAD' state. と表示されることがあります。デタッチド ヘッド という状態です。

このdetached HEADがどういう状態で、どうすれば元に戻せるのか?や、どうやって活用するのか?について解説しています。

detached HEADとは?

detached HEADとは、HEAD(ヘッド)がどのブランチも指していない状態です。

detachedとは「切り離された」という意味です。つまり、HEADがブランチから切り離された状態です。

HEADとは?

HEADとは現在自分がいるブランチを指すポインターです。

例えば自分がmasterブランチにいる場合、git logで確認するとHEADはmasterを指しています。

$ git log
commit 66483ae81ca288f9cffdeb2859606bec5ec0363b (HEAD -> master, origin/master, default)
Author: prograshi
Date:   Thu Jul 15 13:20:09 2021 +0900

    [A]SplitChunnk設定
#onelineオプションを指定した場合
s01386$ git log --oneline
66483ae (HEAD -> master, origin/master, default) [A]SplitChunnk設定
point

(HEAD -> ブランチ名) が、今いるブランチになります。

(HEAD -> master, origin/master, default) のように複数のブランチがある場合は、現在のブランチがmasterで、残りの2つのブランチ、 origin/master と defaultも同じコミットを指していることを示しています。

origin/ブランチ名とは?

origin/ブランチ名 というのは、ローカルにプルしたリモートブランチのことです。 リモートレポジトリにoriginという名前がついているので、originとなっています。

このブランチの正式な名称は、 remote/origin/ブランチ名 です。git branch -a で確認することができます。

つまり、ローカルに同期したリモートレポジトリは remote/リモートレポジトリ名/ブランチ名 となり、指定するときは remote/ が省略できます。


イメージで見るdetached HEAD

detached HEADの例を図で確認してみます。

例えば、最新のコミットがdのmasterブランチに自分がいる場合、HEADはmasterを指しています。
(a,b,c,dはコミット番号です)

                 HEAD
                  |
                master
a --- b --- c --- d

ファイルを過去のコミットの状態にしたい場合に、昔のコミット(ここではb)にHEADを移動させたとします。

git checkout b

すると、HEADはコミットbを指します。ですが、ここにはどのブランチも存在しません。これがHEADがどのブランチも指していない状態、すなわちdetached HEADです。

     HEAD                 
      |          master
a --- b --- c --- d


detached HEADを元に戻す方法

detached HEADを元に戻すのは簡単です。git checkout <ブランチ名> でHEADが特定のブランチを指すようにすれば表示されなくなります。

実例

#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.


#現在のブランチを確認(HEADがどこを指しているか)
$ git branch
* (HEAD detached at HEAD)
  master


#detached HEADを直す
$ git checkout master
Previous HEAD position was d53f0fe 444
Switched to branch 'master'


#現在のブランチを確認(HEADがどこを指しているか)
$ git branch
* master


detached HEADの状態でコミットしてしまった場合

detached HEADの状態でファイルを編集してコミットをした後に、git chekoutでブランチを指定して移動しようとすると次のような警告がでます。

$ git checkout master
Warning: you are leaving 1 commit behind, not connected to
any of your branches:

  d792323 xxx

If you want to keep it by creating a new branch, this may be a good time
to do so with:

 git branch <new-branch-name> d792323

detached HEADで行ったコミットはどこのブランチとも紐付いてませんよ、という警告です。

コミットを行ったdetached HEADの状態から他のブランチに戻ると、同じdetached HEADの状態に戻ることはできません。

ですが、その変更内容自体はコミット番号が記録されています。

変更内容が不要な場合

detached HEADで行ったコミットが不要な場合は、この警告を無視して問題ありません。detached HEADで行ったコミットはどこにも引き継がれません。

コミット前の変更(ステージ前やステージ後)の場合は、そのファイルの変更内容が移動後のブランチに引き継がれてしまいます。

detached HEADの変更内容を破棄するには、次のコマンドを実行します。

git reset --hard @

git reset --hard <コミット> として使い、指定したコミットに戻るコマンドです。ファイルの変更内容は保持せず、完全に指定したコミットと同じ状態になります。

@は最新のコミットを指します。HEADと同じです(エイリアスです)。


変更内容を引き継ぎたい場合

detached HEADで行ったコミットを引き継ぎたい場合は、ブランチを移動後に、対象のコミット番号を指定してmergeします。

#特定のブランチにて(masterブランチなど)
git merge <コミット番号>

これで指定したコミットが、現在のブランチのコミットの中に取り込まれます。

実例

#detached HEADからmasterブランチに移動
$ git checkout master
Warning: you are leaving 1 commit behind, not connected to
any of your branches:

  d792323 xxx

If you want to keep it by creating a new branch, this may be a good time
to do so with:

 git branch <new-branch-name> d792323


#detached HEADのコミットを取り込む
$ git merge d792323
Updating 66483ae..d792323
Fast-forward
 config/routes.rb | 2 ++
 1 file changed, 2 insertions(+)


#ログの確認
$ git log --oneline
d792323 (HEAD -> master) xxx
66483ae (origin/master, default) [A]SplitChunnk設定

masterブランチに、detached HEADで行ったコミット「xxx」を取り込むことができました。

Fast-forwardとは?

Fast-forwardとは、現在のコミットを指しているHEADのポインタを前に進めることです。

$ git log --oneline
66483ae (HEAD -> master, origin/master, default) コミット1

↓ 現在のブランチ(master) で Fast-forwardが行われた場合

$ git log --oneline
d792323 (HEAD -> master) コミット2
66483ae (origin/master, default) コミット1


変更内容で新しブランチを作成したい場合

変更内容で新しいブランチを作成したい場合は、コミット番号を指定して新しいブランチを作成します。

git checkout -b <新しいブランチ名> <コミット番号>
tips

git checkoutに-bオプションをつけて、git checkout -b <ブランチ名> とすると、新しいブランチを作成した後に、そのブランチに移動することができます。

ブランチ名の後ろにコミット番号を指定すれば、そのコミットを最新としてブランチを作成することができます。

次の2つは同じ処理になります。

git checkout -b <新しいブランチ名> <コミット番号>
git branch <新しいブランチ名> <コミット番号>
git checkout <新しいブランチ名>

detached HEADの状態でも、ブランチを移動してしまった後でもやることは同じです。

実例

例として、ブランチを移動した後に、detached HEADで行ったコミットを、新しいブランチ「newBranch」に引き継ぎます。

#detached HEADから他のブランチに移動した場合
$ git checkout master
Warning: you are leaving 1 commit behind, not connected to
any of your branches:

  d792323 xxx

If you want to keep it by creating a new branch, this may be a good time
to do so with:

 git branch <new-branch-name> d792323


#新しいブランチを作成して移動
$ git checkout -b newBranch d792323
Switched to branch 'newBranch'


#ログを確認
$ git log --oneline
d792323 (HEAD -> newBranch, master) xxx
66483ae (origin/master, default) [A]SplitChunnk設定


detached HEADを発生させる方法

detached HEADの状態はとても簡単に作り出すことができます。

git checkoutでコミットのみ指定して、ブランチやファイルパスを指定しなければdetached HEADになります。

git checkout <コミット>

実例

現在のコミットを指す「@」を指定して、checkoutを実行します。

$ git checkout @
Note: switching to '@'.

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.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 66483ae [A]SplitChunnk設定


detached HEADの使い所

detached HEADは混乱させるために存在しているわけではありません。きちんと役立つ使い道があります。

主な使い道は次の2つです。

detached HEADの使い所
  • 過去のコミットの状態を再現する。
  • 実験的に変更を加える


過去のコミットの状態を再現する

detached HEADでできることは、一時的に過去のファイルの状態に戻すことができます。

git checkout <戻りたいコミット>

コミットの指定は、コミット番号以外に、@、HEADで指定することができます。

tips

@ は最新のコミットを表します。HEAD と同じです。

コミットを遡る方法

最新のコミットを@として、一つ前のコミットを指定するには^を。2つ前を指定するには^^を使います。

^の数だけ、コミットを遡ります。

記号意味
@現在のコミット
@^1つ前のコミット
@^^2つ前のコミット
@^^^3つ前のコミット

コミットをたくさん遡る場合

3個ぐらいまでならわかりやすいですが、5個遡るとなると、@^^^^^ となって、正しいコミットを指しているのかわかりにくくなります。

その時は、 @~n を使うとわかりやすくなります。 n に遡りたい数値を入れます。例えば、@~3 なら3つ前のコミットになります。

記号意味
@現在のコミット
@~11つ前のコミット(@^ と同じ)
@~33つ前のコミット (@^^^ と同じ)
@~77つ前のコミット

例えば、最新のコミットがdのmasterブランチに自分がいる状態で、一時的に過去のコミットbの状態を確認したい場合に使えます。

                 HEAD
                  |
                master
a --- b --- c --- d

git checkout b

これで自分がdetached HEADの状態になります。この時、最新のコミットはbになっています。ファイルの内容もbの状態になっています。

     HEAD                 
      |          master
a --- b --- c --- d

確認が終わって、元のブランチに戻りたい時は、git checkout master をするだけです。


実験的に変更を加える

更に、detached HEADで過去のコミットに遡った状態から変更を加えることもできます。

上のdetached HEADの状態で、ファイルを変更し、コミットeを行うと次のようになります。

 
       HEAD                 
        |          
        e
       /        master
a --- b --- c --- d

更にコミットfを加えると次のようになります。

 
             HEAD                 
              |          
        e --- f
       /        master
a --- b --- c --- d

確認が終わって、元のブランチに戻りたい時は、git checkout master をするだけです。

もし、この実験状態を新しいブランチにしたい場合は、

git checkout -b <新しいブランチ名> とすれば、この状態を維持したブランチを作成することができます。

例えば、newBranchを作成すると次のようになります。

 
             HEAD                 
              |    
          newBranch      
        e --- f
       /        master
a --- b --- c --- d

こうしておけば、masterブランチに移動した後も、またnewBranchに戻ることができます。


参考リンク


git checkoutを使うとステージ前の変更を取り消すこともできます。

【Git】ステージ前の変更を取り消す方法。ファイルの指定と全てのファイル(Changes not staged for commit:の一覧から外す)

タイトルとURLをコピーしました