git reset --hardを間違えてしてしまった時の対処法

おっと手が滑って...reset --hardしちゃった場合...ありませんか？(あったらえらいこっちゃですが...)

本記事ではgit reset --hardを実行した後の復元方法について，addしていない場合，addした場合，commitした場合のそれぞれについて説明する．

https://qiita.com/JavaLangRuntimeException/items/6b46551f56e0def76eba

## addしていない場合（ワーキングディレクトリのみに変更がある場合）:
この状況では，変更は完全に失われる．Gitの機能だけでは復元は不可能である．ファイルシステムの復元ツールを使用する必要があるが，成功の保証はない．

例えばgit statusをしてワーキングエリアとステージングエリアの状態を確認する
```bash
git status
```
すると，example.txtがステージングエリア(インデックス)になく，addされていないことがわかる．
```bash
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   example.txt
```
この状態でgit reset --hardをする．
```bash
git reset --hard
```
HEAD が1つ前のコミットを指すようになる．
```bash
HEAD is now at 1a2b3c4 Previous commit message
```
この状態でgit statusをすると
```bash
git status
```
ワーキングエリアやステージングエリアにもexamle.txtの変更差分がなくなっていることがわかる．
```bash
On branch main
nothing to commit, working tree clean
```
このようにaddをしずにreset --hard するとどうすることもできないので要注意

## commitしてaddした場合（ステージングエリア(インデックス)に変更がある場合）:
example.txtをaddして，git statusをしてワーキングエリアやステージングエリアの状態を確認する．
```bash
git add example.txt
git status
```
するとステージングエリアにexample.txtの変更差分が存在することがわかる．
```bash
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   example.txt
```
この時点でgit reset --hard をすると...
```bash
git reset --hard
```
HEADが1つ前のコミットを指すようになる．
```bash
HEAD is now at 1a2b3c4 Previous commit message
```
この状態でgit statusをすると
```bash
git status
```
ワーキングエリアやステージングエリアにもexamle.txtの変更差分がなくなっていることがわかる．
```bash
On branch main
nothing to commit, working tree clean
```
しかしaddをした場合blobオブジェクトが作成され，どのコミットやタグもそのblobオブジェクトに対して参照を持たない状態になる．そのため参照を持たないblobオブジェクトを探すコマンドが以下である，．
```bash
git fsck --lost-found
```
2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0という参照されていないblobオブジェクトが出力された．
```bash
Checking object directories: 100% (256/256), done.
Checking objects: 100% (1234/1234), done.
dangling blob 2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0
```
中身をgit showするとファイルの内容を見ることができる．
```bash
git show 2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0
```
正しい内容だった場合，該当のblobオブジェクトをaddする．
```
git add .git/lost-found/other/2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0
```
今度こそコミットを忘れない．
```
git commit -m "ふっかーーつ"
```
:::note info
**git add したファイルはgit reflogで探せないのか**
> **git reflogとは**
> HEADが移動する操作(commit branch reset)をした際のローカルリポジトリの操作履歴を参照する

結論から言うと，git addしたファイルはgit reflogに残らない．そのため，git reflogでは参照できない．
git reflogは主にHEADの移動履歴を記録する．つまり，以下のような操作が記録される．
- コミットの作成
- ブランチの切り替え
- リセット操作

このようにresetした履歴は残るがaddした履歴は残らないのでgit reflogではaddしたgit オブジェクトを参照できない．

しかし，git addしたファイルはblobオブジェクトとして保存されるのでgit fsckで参照がないblobオブジェクトを参照できる
> **blobオブジェクトとは**
> Gitには主に4種類のオブジェクトがある．
>
> - blob: ファイルの内容
> - tree: ディレクトリ構造
> - commit: コミットの情報
> - tag: タグの情報
:::

# commitした場合
コミットをして
```bash
git commit -m "New commit"
```
コミットした際の出力
```bash
[main 3f4g5h6] New commit
 1 file changed, 1 insertion(+)
```
これでリセットをした場合...
```bash
$ git reset --hard HEAD~1
```
HEADhは1つ前のコミットを指している．
```bash
HEAD is now at 1a2b3c4 Previous commit message
````
この場合commitをしている(=HEADを移動した)のでreflogで参照できる操作である．ためreflogでcommitをした操作を探す．
```bash
git reflog
```
するとresetした操作やcommitした操作を参照できるので3f4g5h6がコミットした操作で得ることがわかる．
```bash
1a2b3c4 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~1
3f4g5h6 HEAD@{1}: commit: New commit
1a2b3c4 HEAD@{2}: commit: Previous commit message
```
ここで3f4g5h6nにresetする．つまりresetしてしまったらresetで復活させることができる．
```bash
git reset --hard 3f4g5h6
```
これでresetをresetできた．
```bash
HEAD is now at 3f4g5h6 New commit
```