とことんDevOps | 日本仮想化技術のDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けする「とことんDevOps」では、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果、TipsなどDevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、コンテナ開発、IaCなど
読者登録と各種SNSのフォローもよろしくお願いいたします。

git resetで失った変更を取り戻す

つい最近、複数のブランチを同時進行で操作していた時、間違えて別のブランチで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です。

git-scm.com

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)

これで復元完了です。

まとめ

このコマンドを知らなくてもよいくらい安全に運用ができていればよかったのですが、とうとう知る日が来てしまいました。ただ、やってみると色々面白かったので、失敗もたまにはいいなぁっと再認識しました。失敗を恐れずガンガン行きます。 知らなかった