Git に慣れてくると、reset --hard を多用することになりますが、 表1 のように add しただけのファイルは reset --hard で消えてしまう、ということに注意が必要です。
表1: add/commit 有無による reset --hard 前後でのファイルの状態変化
| reset --hard前 | reset --hard後 | 
|---|---|
| addしていない | ワークツリーにそのまま残る | 
| addしている | ワークツリーから消える | 
| commit済み | ワークツリーから消えるが、 reflogで辿れる | 
「reflog で辿れる」と言っても実際に reflog の出力から探すのはそれなりに骨の折れる作業です。
reset --hard のような破壊的な操作をするときに共通して使えるテクニックとして、そのような操作をする前に git tag -f backup などとバックアップ用のタグを打っておく、というものがあります。 reflog を頑張って探さなくても git reset --hard backup 一発で reset 前の状態に戻れるようになります。 (-f は force オプションで、 backup という名前のタグがすでに存在していても上書きしてタグを作成します)
では git add しただけのファイルを git reset --hard で消してしまったときにはどうすれば良いのでしょうか。
対処法
reset --hard で add だけしたファイルを消してしまった場合、次のような操作でファイルを復活できます。
$ git fsck --lost-found
$ ls $(git rev-parse --git-path lost-found)/other
1dcdad3e568e5462239cc4d3741b8591f79cf379
0b4487101f12b248cc11c12f5be6e66492f4a64e
...
この操作を行うと .git/lost-found/other ディレクトリに 「どのような参照(ブランチ、タグ等)からもたどり着けないblob(一般ファイル)オブジェクト」 が、 Git のオブジェクトデータベースから取り出されて格納されます。 add した時点で Git のオブジェクトデータベースには格納されているので、「add だけして commit していないファイル」はこれに含まれます。
残念ながらファイル名はやツリー構造は復活できないので、そこには恐ろしげな SHA1 ハッシュ名のファイルがずらりと並ぶことになりますが、 表2 に示す各種コマンドで対象を絞り込みましょう。
表2: 絞り込みに使えるコマンドの例
| コマンド | 取得できる情報 | 
|---|---|
| file | ファイル種別 | 
| ls -l | ファイルサイズや実行ビット | 
| wc -l | ファイル行数 | 
| head | ファイル先頭数行 | 
例えば file コマンドを使うと次のような出力を得ることができ、ファイルを探す助けになります。
$ file $(git rev-parse --git-path lost-found)/other/*
/path/to/.git/lost-found/other/1dcdad3e568e5462239cc4d3741b8591f79cf379: Unicode text, UTF-8 text
/path/to/.git/lost-found/other/0b4487101f12b248cc11c12f5be6e66492f4a64e: C++ source text, Unicode text, UTF-8 text
/path/to/.git/lost-found/other/442608b02c4ccb59b37339b8220cd39fc18586f9: C++ source text, Unicode text, UTF-8 text
/path/to/.git/lost-found/other/05b574a877a28970b4307d6cd64a060494aebaf4: makefile script text, ASCII text
/path/to/.git/lost-found/other/bf09997fdf86a395e4b71ea53bca226edcb53187: C++ source text, Unicode text, UTF-8 text
...
/path/to/.git/lost-found/other/fc201e11ea72e5b77ca371e48148c7962cde2ad3: Unicode text, UTF-8 text
/path/to/.git/lost-found/other/a9b08c2eec97d00672cac752ebced1817e2fc8ea: C++ source text, Unicode text, UTF-8 text
/path/to/.git/lost-found/other/92bdc2cfd82bee18782859e1ff43883338029a02: makefile script text, ASCII text
/path/to/.git/lost-found/other/e2ce39059570040f4db47fc21fba7587dd68decf: makefile script text, ASCII text
/path/to/.git/lost-found/other/5c0a9d26193bbdfb1235415b2cfce9415d4204cd: ASCII text
file コマンドは意図しない種別を表示することもあるので注意が必要です。
例えば file コマンドが認識しない言語のソースコードなどは Unicode text, UTF-8 text と表示されたり、その言語がC/C++風の文法を持っている場合は C++ source text と誤って表示されることもあります。
探しているファイル形式のファイルが file コマンドでどのような表示になるのか、あらかじめ手元にある複数のファイルで確認しておきましょう。
lost-found 内のファイルを探すときのコツ
確実に違うファイルは随時 rm していくと探しやすいです。lost-found は先ほどの操作で何回でも復活できるので、間違って rm してしまった場合は最初からやり直せば良いです。
また、ファイルを編集して add するたびに新たにオブジェクトが登録されていくので、同じファイルを複数回 add していた場合は lost-found にも同じファイルの複数のバージョンが存在することがあります。ひとつそれらしい物を見つけたとしても、一通り全てのファイルをチェックして候補を洗い出すことをお勧めします。最後に最終候補同士を diff で比較して、一番新しいと思われるファイルを復活させると良いでしょう。
予防方法: add だけしたファイルを reset --hard 前後で保持するには
そもそも add だけしたファイルを reset --hard で失ってしまい、 lost-found を調べるような事態そのものを予防できないのでしょうか。
reset 前に status を確認する
reset --hard するときに add だけされたファイルがあるのは大抵の場合誤りなので、 reset --hard するときは常に git status -s | egrep '^(M|.M)' などでチェックすることを習慣づけるのは一つの予防法になります。
$ git status -s | egrep '^(M|.M)'
M  newfile
このように何か表示されたら add だけしたファイルが存在していますので、失っても問題ないか確認します。
stash を使う
例えばステージング用の使い捨て統合ブランチ 1 を再構築するようなケースでは、意図的に add だけして commit しない変更を保持しておきたいことがあります。
このような場合には reset --hard を実行する前に git stash しておくと、 add だけしたファイルを stash 領域に退避できます。例えば次のように操作すると再構築前後で add だけしたファイルを保持できます。
$ git stash
$ git reset --hard master
$ git merge topic-1
$ git merge topic-2
...
$ git merge topic-n
$ git stash pop
なお、再構築し直した変更内容と stash した変更内容がコンフリクトしてしまう場合はあります。この場合 stash pop コマンドの出力としてコンフリクトの発生が表示され、ワーキングディレクトリのファイルにコンフリクトマーカーが挿入された状態になるので、対象ファイルを git status で確認し、必ずコンフリクトを解消しましょう。
おわりに
git add しただけのファイルを git reset --hard で消してしまったときの対処法を紹介しました。誤りや、より良い方法などの提案があればコメントしていただけると嬉しいです。
以上です。
- 
使い捨て統合ブランチについての詳細は公式マニュアルの gitworkflows(7) を参照してください ↩