gitでpushしたコミットが消える現象の対処法、もしくはgit reflogの紹介

gitを使っていて、「pushしたはずのコミットがいつのまか消えた!」という話をよく聞く。はじめて遭遇すると、「あれ?コミットしたはずのコードが消えてる!今日の作業が台無しに!」と思って焦ってしまう。この記事ではそれの原因とgit reflogを使った対処法を紹介する。

なぜ起きるのか?

gitにはブランチがあり、ブランチごとにコミットを記録できる。gitのリポジトリの作業領域は現在のブランチのものだ。例えばgit branchで確認すると現在のブランチを確認できる。

git branch
* master

なんらかのブランチで作業するのが普通だが、現在の作業ブランチがno branchの状態になってしまうことがある。これは、コミットのハッシュIDを直接checkoutしたり、リモートのブランチを直接checkoutした時に起こる。そのときには以下のような警告が起きる。

$ git checkout 36f6e2fcb45dbee974a7b6dd49b

Note: checking out '36f6e2fcb45dbee974a7b6dd49b'.

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 36f6e2f... hoge

$ git branch 
* (no branch)
  master

このno branchの状態でコミットしてし別のブランチをcheckoutしてしまうと、そのコミットがどこからも参照されない状態となって各ブランチのgit logなどから消えてしまう。

冒頭のpushしたはずのコミットが消える現象というのは、no branchの状態でコミット&pushして別のブランチに移動したことから起こるようだ。

git reflogでコミットのハッシュを調べてサルベージする

こういった場合は、git reflogコマンドを使って、消えたコミットのハッシュIDを調べるのが良い。コミットのハッシュさえわかればgit cherry-pickコマンドを用いてブランチに取り込むことができる。

git logがブランチの履歴を調べるのコマンドであるのに対して、git reflogは作業の履歴を調べてくれるコマンドだ。あるブランチからno branchになった時のことも全て履歴に取ってくれるし、コミットのハッシュも併記してくれる。

$ git reflog
4f72d1d HEAD@{0}: commit: Added Buffer class to wrap canvas element.
ec24a16 HEAD@{1}: commit: Modified to use FPSTicker in Screen class.
f9576ec HEAD@{2}: commit: Modified demos to puts them on exmaples page.
0cc4c51 HEAD@{3}: commit: Added elemental tests.
a65ad30 HEAD@{4}: commit: Added spec template.
5f3dd74 HEAD@{5}: commit: Added new example and demo.
6b59f24 HEAD@{6}: commit: Fixed broken EventEmitterSpec.
d3b3b70 HEAD@{7}: commit: Added demo 02.
5c2c4f8 HEAD@{8}: commit (amend): Trivial fix.
3a7ff13 HEAD@{9}: commit: Tirivial fix.
9bed304 HEAD@{10}: commit: Added removeSelf() method to Node class.
bab0dc5 HEAD@{11}: commit: Added 'configure' and 'deconfigure' event to Node class.
1087bb3 HEAD@{12}: commit: Added FPSTicker class.
6a644f0 HEAD@{13}: commit: Change demo/01 to work independently on the demo/01 directory.
7e0fbc8 HEAD@{14}: commit: Fixed demo deployment failure.

ここから、欲しいコミットのハッシュを調べて取り込みたいブランチからgit cherry-pickするとよい。

終わり

この記事ではgitでno-branchの状態でコミットするとブランチのログに残らないコミットができてしまう問題とgit reflogを使った対処法を紹介した。git reflogはこの問題に限らずこういったトラブルの時に使えるコマンドなので覚えておきたい。