Hatena::ブログ(Diary)

西尾泰和のはてなダイアリー

2011-05-13

gitのHEADがブランチから外れてしまう現象とその直し方

detached HEADって言う名前で入門Gitにも書いてあるんだけど、そういうことが起こりうるってメンタルモデルができてないと起きていることにすら気づかないので書いておく。

まず説明用のリポジトリを用意します。

t$ git init
Initialized empty Git repository in /Users/nishio/gittest/pygit2/t/.git/

t$ touch a
t$ git add a
t$ git commit -m "add a"
[master (root-commit) 6f6eb7c] add a
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 a

t$ touch b
t$ git add b
t$ git commit -m "add b"
[master 62e58df] add b
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 b

さて、ここでHEADってなんでしょう。こういう普通の状態ではHEADはmasterへのリファレンスになっている。masterは「ブランチ」とか呼ばれているけど、物理的には特定のコミットオブジェクトへのリファレンスだ。

t$ cat .git/HEAD 
ref: refs/heads/master
t$ cat .git/refs/heads/master
62e58dfd8d36db9f649a163b097f35cef99b91a3

さて、ここでうっかりブランチではなく特定のコミットオブジェクトをcheckoutしてしまったら何が起こる?

t$ git checkout 62e5
Note: checking out '62e5'.

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 62e58df... add b

gitってわかりにくいわかりにくいと叩かれるけど、よくよく読んでみると警告メッセージはわりと丁寧だったりする。

'detached HEAD'状態になっちゃったよ。このコミット時点のファイルがどうなってるか見て回ったり、実験的な変更を加えてコミットしてみたり、そしてそのコミットを他のブランチに影響を与えずにポイっと捨てられるよ。それには別のブランチをチェックアウトすればOK。

もし行った変更を残しておくために新しいブランチを作りたいなら-bをつけて(新しいブランチをつくりつつ)チェックアウトすればいいよ。例:

git checkout -b new_branch_name

HEAD は今は 62e58df... add bだよ。

というわけで書いてあるとおりだけどもこの状態で変更やコミットをしてもどのブランチにも変更が反映されない。それを意図してやってるならいいんだけど、上のメッセージをスルーしちゃったら気づかずにmasterにいるつもりで作業しちゃったりするかも。

この状態でHEADはどうなっているかというと、どのブランチも指していない。直接コミットオブジェクトを指している。

t$ cat .git/HEAD 
62e58dfd8d36db9f649a163b097f35cef99b91a3

変更とコミットをしてみよう。

t$ touch c
t$ git add c
t$ git commit -m "add c"
[detached HEAD b17d3fd] add c
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 c

masterは変更されていない。

t$ git log --oneline master
62e58df add b
6f6eb7c add a

さて、masterにいるつもりでこういう作業をしてしまった場合、どうやったらmasterに戻せるだろうか?方法はいろいろあるかも知れないけども、gitでブランチを作るのは単にリファレンスを張るだけのコストの安いものなんだから、ややこしいことになったらとりあえずブランチを作ればいいと思う。

t$ git branch tmp1

そしてmasterに移動してmerge

t$ git checkout master
Previous HEAD position was b17d3fd... add c
Switched to branch 'master'
t$ git merge tmp1
Updating 62e58df..b17d3fd
Fast-forward
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 c

はい、tmp1からcのコミットを持ってくることができました。めでたしめでたし。

いらなくなったブランチは消しておこう。

t$ git branch -d tmp1
Deleted branch tmp1 (was b17d3fd).

ブランチを消すっていっても、ブランチはリファレンスなんだから言ってみれば「貼ってあった付箋をはがして捨てる」って感じ。重要な変更が破棄されたりしないので安心して消せる。

投稿したコメントは管理者が承認するまで公開されません。

トラックバック - http://d.hatena.ne.jp/nishiohirokazu/20110513/1305290792