Gitの.git/objectsからファイルを復旧する

色々と操作を間違えて、ローカルリポジトリから.git/objects以外のファイルをすべて消してしまいました。リモートリポジトリへプッシュしていないファイルが消失の危機です。ただ、こまめにコミットはしていたので残された.git/objectsから復旧を試みます。

手順

復旧用のローカルリポジトリの準備

作業用にきれいなローカルリポジトリがほしいので、リモートリポジトリからローカルへClone。git checkoutで復旧したいブランチを作業ブランチにします。そして.git/objectsを復旧したいリポジトリのもので置き換える。

復旧したいコミットのハッシュ値を探す

.git/objectsに移動し、Git Bashで以下のスクリプトを流す。

for i in $(find . -type d -name pack -prune -o -type f -print)
do
  # objects配下は「./ハッシュの先頭2文字/残りのハッシュ文字列」という規則でファイルが格納されているので「.」「/」を除去してハッシュ値にする
  hash=$(echo $i | tr -d './')
  # オブジェクトの種類を判定してcommitだけ取り出す
  if [ $(git cat-file -t ${hash}) = 'commit' ]; then
    # 選別がしやすいように出力ファイルの先頭にタイムスタンプを付与する
    git cat-file -p ${hash} > $(date  "+%Y%m%d%H%M%S" -r $i)_${hash}.txt
  fi
done

コミットの情報がファイル出力されるので、ファイル名でソートしたりコメントでgrepしながら探します。見つけたら当該コミットのハッシュ値をコピー。
※ ファイルに表示されるtreeやparentのハッシュ値ではないので注意

復旧したいコミットへ参照を上書きする

.git/refs/heads/(作業ブランチ)の中身をさきほどコピーしたハッシュ値で書き換え、作業ブランチのヘッダを復旧したいコミット断面にします。

git logでヘッダーが復旧したいコミットを指していること、git statusなどで復旧先と現在のファイルとの差分が出ていることを確認。

復旧

リセットして復旧。

$ git reset --hard HEAD

ファイルを取り出したりプッシュしたら、念のため作業に使ったローカルリポジトリは破棄する。(手作業で弄っていて気持ち悪いので。)

余談

こまめにコミットして後からrebase -iでまとめる派だったので助かった。