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) を参照してください ↩