git add .; git reset --hard HEAD してしまったファイルのサルベージ

% git add .
(あ、add じゃなかった。取り消そ)
% git reset --hard HEAD
(あれ?…ファイルは!?)

ってなことが最近あって*1、サルベージ方法を調べたので覚書。
まず git fsck する。*2

% git fsck --full
Checking object directories: 100% (256/256), done.
Checking objects: 100% (316/316), done.
dangling blob 05fc993def3f6d6130e530d1a8fca9c81e05d4c0
dangling blob 089434c22dd051562c3ca2193ebe3e562274a555
dangling blob 2338da1104c033c8266d6ec82833f051521b1063
dangling blob 29101ea36dfceaa0743572e96267f07bce60150d
dangling blob 32e80398bfb9bed97d1ace2acdf01b1564f25a90
dangling blob 3b0c741be0508f4d15f74c8a88e0f57eff0eb422
dangling blob 3f74f28a34b79c8ad15143c20c26b3357311e4da
dangling blob 4bbcfcf56827bd347efb62c791f0a312f8255c2e
dangling blob 51c0e97d301c8002fc6809aef3629396df14753b
dangling blob 5698a5c931115a70a507265795e8e87a219fb181
dangling blob 67ac961c6e248e28081de65837f8ce3b0a1682e9
dangling blob 770c43f4a98958e6b7621a7bad020276f7c0cd3b
dangling blob 7e889d8830487fc26babff664d19e42f298d2563
dangling blob 7e5877e7d45ec6a00cb19598642f831f4ad80a43
dangling blob 9630e53b491fc6f89b44a6e3eda90ecb29c21f51
dangling blob b230b1837ccbed41fe5e6e14c324ba58782d30b4
dangling blob b6644d9d01197637fffc13cc1875e24e24deb20a
dangling blob b8986b4de01957b7862dd226fc89683d7c04329c
dangling blob c904d1e0e03ce87937a87a75ddeb8a94d42dd9c7
dangling blob d00491fd7e5bb6fa28c517a0bb32b8b506539d4d
dangling blob d38481df52fe380358922b8c691b53f2ada2a517
dangling blob eec08ab74c6c77c2769868ba4cf9eec5dbf1951b
dangling blob 12618ab0fece6fbafb949449667356073f6a7c2b
dangling blob 1545475d5a7c4a3b31e7ae56c19179e209b082a8
dangling blob 19d5e26ad17653e952add65dd1728817786e8fcb

するとこんな感じで dangling blob ってのが出てくる。ちなみに場合によっては commit や tree も出てくる。
この dangling ってのはどこからも参照されていない、つまり branch や reflog から辿れないオブジェクト。
この中のどれかがさっき消してしまったファイル。unpack-file するとファイルを取り出せる。

% git unpack-file 05fc993def3f6d6130e530d1a8fca9c81e05d4c0
.merge_file_QpAbAa

.merge_file_QpAbAa ってファイルが作成されるので、中身を開いて確認。中身が目的のファイルならサルベージ成功!
数が多いと面倒なので、私は git fsck の出力をファイルに出して編集して shell script にして一気にやった。

add しただけだと commit はもちろん、tree も生成されないので、ファイルの内容は復元できてもファイル名までは復元できない。つらい。と言うわけでぶっちゃけ最後の手段なので git reset --hard は計画的にね。


余談。今回は https://github.com/thinca/online-judge このリポジトリを作る作業中でやらかしたのだけど、消えてしまったファイルは各 online judge のページにソースコード残ってたので結局そっちからサルベージしたと言う…残念なオチ。

*1:実際は reset --hard HEAD は alias してあってそっちでやらかした。手軽に打てるから余計にorz

*2:なんとなく --full 付けてますが実はよくわかってません