つい最近、複数のブランチを同時進行で操作していた時、間違えて別のブランチでgit reset --hard
を実行してしまいました。コミット前だったこともあり、あぁ、オワタ・・・/(^q^)\っと思ったのですが、ちょっと検索してみるとコミット前のファイルでも復旧できることがわかったので少し実験してみます。
準備
テスト用の環境を用意します。
$ mkdir test $ cd test $ git init $ git commit -m 'first commit' --allow-empty
テスト用のファイルを追加していきます。
$ echo A > a.txt $ echo B > b.txt $ git add . $ git commit -m 'add a.txt' a.txt
状態はこんな感じ
$ git status -u . On branch main Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: b.txt
a.txtだけがコミットされた状態です。それではここでgit reset --hardしてみましょう。
$ git reset --hard HEAD^ HEAD is now at 573f49a add a.txt $ ls a.txt
lsをしてみるとb.txtというファイル自体が消失してしまいまいた。
消えた変更を取り戻す
a.txtはコミットしてあるのでreflogなどで変更される前のHEADの位置を特定し、そこにgit reset --hard HEAD@{N}で戻ればいいでしょう。しかし、今回の場合、b.txtはコミットをしていないので、reflogでは変更前の状態というのを見つけることができません。1度もコミットしてないので、gitはこのファイルの状態を管理していないからです。そんな時に使えるのがfsckです。
Linuxのfsckコマンドのようにリポジトリの整合性をチェックして、変更を復元できるらしい?です。ヘルプを見てみるとそれらしいオプションがありました。
--lost-found Write dangling objects into .git/lost-found/commit/ or .git/lost-found/other/, depending on type. If the object is a blob, the contents are written into the file, rather than its object name.
--lost-found
オプションをつけて実行すると.git/lost-found/{commit,other}/
のどちらかに入るみたいですね?説明を読んでもよくわからないので実行してみます。
$ git fsck --lost-found Checking object directories: 100% (256/256), done. dangling tree 6acd0d064b2eb80372735fb557b6c104680da78a
ぶら下がっているtree(dangling tree)のハッシュ値でしょうか?catしてみます。
$ git cat-file -p 6acd0d064b2eb80372735fb557b6c104680da78a 100644 blob f70f10e4db19068f79bc43844b49f3eece45c4e8 a.txt 100644 blob 223b7836fb19fdf64ba2d3cd6173c6a283141f78 b.txt
余談ですが、cat-fileというコマンドは引数にオブジェクトを渡してあげると、オブジェクトの中身を表示できます。
$ find .git/objects -type f -printf '%P\n' | tr -d / | head -n1 573f49af5fb240832f4fdcac09481154494e909a $ git cat-file -p 573f49af5fb240832f4fdcac09481154494e909a tree 11ab7d5124894d58b4852a45c0242e92aea630c9 parent bd4ce1e1a9ca6c4870067ca6b12353ca879901c0 author 田中智明 <ttanaka@virtualtech.jp> 1725880718 +0900 committer 田中智明 <ttanaka@virtualtech.jp> 1725880718 +0900 add a.txt
コミットIDやgit hash-object a.txt
で出力されたハッシュ値を指定して、特定のファイルの中身を出力できます。
話は戻って、b.txtのハッシュ値が取得できたので、もう一度cat-fileしてみましょう。
$ git cat-file -p 223b7836fb19fdf64ba2d3cd6173c6a283141f78 B
b.txtの中身が取得できました。b.txtを復元するには、↑のコマンドをb.txtにリダイレクトしてあげればOKです。
$ git cat-file -p 223b7836fb19fdf64ba2d3cd6173c6a283141f78 > b.txt $ git status . On branch main Untracked files: (use "git add <file>..." to include in what will be committed) b.txt nothing added to commit but untracked files present (use "git add" to track)
これで復元完了です。
まとめ
このコマンドを知らなくてもよいくらい安全に運用ができていればよかったのですが、とうとう知る日が来てしまいました。ただ、やってみると色々面白かったので、失敗もたまにはいいなぁっと再認識しました。失敗を恐れずガンガン行きます。 知らなかった