【Git】特定のコミットまで戻ってコードを確認したいだけのときは、git resetではなくgit switchを使おう

# はじめに
`git reset --hard [コミットのハッシュ値]`を実行すると、指定したコミット以降のコミット履歴が消去されてしまうので、
過去のコミット時のコードを一時的に見たいときは、コミット履歴に影響を与えない`git switch -d [コミットのハッシュ値]`を使ったほうが良さそうということに気づきました。
（`git reset --hard [コミットのハッシュ値]`を実行してコミット履歴を消去しても、一応復元できますが。）<br>
筆者自身、詳しいことを知らずに`git reset --hard [コミットのハッシュ値]`を使って焦ったことがあるので、両者の違いをまとめておきます。


# サンプルの解説
以下のようなテキストファイルがあるとします。
```text:sample.txt
first
second
third
fourth
```

ブランチについては、`feature-1`という作業ブランチにいるとします。
```:git branch
* feature-1
  main
```

コミットの履歴については、`main`ブランチで空のテキストファイルを作成したのち、最初のコミットをしています。
そして、`feature-1`ブランチを作成してそこに移動しています。<br>
その後、`add first`というコミットでテキストファイルに`first`を書いて、
`add second`というコミットでテキストファイルに`second`を書いて、...
という流れになっています。

```:git log
commit 444444444444444 (HEAD -> feature-1)
Author: your name
Date:  Mon Jan 4 00:00:00 2024

    add fourth

commit 333333333333333
Author: your name
Date:  Mon Jan 3 00:00:00 2024

    add third

commit 222222222222222
Author: your name
Date:  Mon Jan 2 00:00:00 2024

    add second

commit 111111111111111
Author: your name
Date:  Mon Jan 1 00:00:00 2024

    add first

commit 000000000000000 (main)
Author: your name
Date: Sun Dec 31 00:00:00 2023

    first commit
```

（※実際には`000000000000000`などの部分はもっと長いハッシュ値になっています。以降も同様。）

# git reset --hard をするとどうなるのか
はじめに、`git reset --hard [コミットのハッシュ値]`を実行するとどうなるのか見ていきましょう。

`git reset --hart 222222222222`を実行して、`add second`コミットの状態にリセットを行うと、

```:ターミナル
HEAD is now at 2222222222 add second
```

と表示され、ファイルの中身は

```text:sample.txt
first
second
```
になります。、`add second`コミット時の状態になっていますね。<br>

続いて、`git branch`でブランチを確認してみると、

```:git branch
* feature-1
  main
```

特に変化はないようです。<br>

続いて`git log`で履歴を見てみます。

```:git log
commit 222222222222222
Author: your name
Date:  Mon Jan 2 00:00:00 2024

    add second

commit 111111111111111
Author: your name
Date:  Mon Jan 1 00:00:00 2024

    add first

commit 000000000000000 (main)
Author: your name
Date: Sun Dec 31 00:00:00 2023

    first commit
```

見てのとおり、`add second`以降のコミットが消えてしまっています。<br>
過去のコミットの状態にリセットしたいときはこの方法で良いと思います。
しかし、一時的に過去のコミットに戻ってコードを見たいだけの場合、`git reset --hard [コミットのハッシュ値]`を実行すると、**最新のコミットに戻れなくなってしまいます**。

## 戻す方法について
「`git reset --hard [コミットのハッシュ値]`を実行すると、最新のコミットに戻れなくなってしまう」と書きましたが、一応戻す方法はあるようです。<br>
まず、`git reflog`を実行します。

```:git reflog
2222222 (HEAD -> feature-1) HEAD@{0}: reset: moving to 222222222222222
4444444 HEAD@{1}: commit: add fourth
3333333 HEAD@{2}: commit: add third
2222222 (HEAD -> feature-1) HEAD@{3}: commit: add second
1111111  HEAD@{4}: commit: add first
0000000 (main) HEAD@{5}: checkout: moving from main to feature-1
0000000 (main) HEAD@{6}: commit (initial): first commit
```
すると、このように操作履歴を見ることができます。<br>
今回の場合、一行目の

```:git reflog
2222222 (HEAD -> feature-1) HEAD@{0}: reset: moving to 222222222222222
```
が、「`git reset --hard [コミットのハッシュ値]`でリセットを実行した操作に該当します。<br>
リセットする前の状態、つまり二行目の

```:git reflog
4444444 HEAD@{1}: commit: add fourth
```

の状態に戻りたいので、
`git reset --hard 4444444`または`git reset --hard HEAD@{1}`を実行して、リセットする前の状態にリセットします。すると、

```text.sample.txt
first
second
third
fourth
```

このように、ファイルの中身が最後の`add fourth`コミットの状態に戻りました。<br>

`git log`でログを見てみても、

```:git log
commit 444444444444444 (HEAD -> feature-1)
Author: your name
Date:  Mon Jan 4 00:00:00 2024

    add fourth

commit 333333333333333
Author: your name
Date:  Mon Jan 3 00:00:00 2024

    add third

commit 222222222222222
Author: your name
Date:  Mon Jan 2 00:00:00 2024

    add second

commit 111111111111111
Author: your name
Date:  Mon Jan 1 00:00:00 2024

    add first

commit 000000000000000 (main)
Author: your name
Date: Sun Dec 31 00:00:00 2023

    first commit
```

元に戻せているのが確認できます。


# git switch -d を使うとどうなるのか
先ほどまでは、`git reset --hard [コミットのハッシュ値]`を使った場合を見てきました。<br>
次に`git switch -d [コミットのハッシュ値]`を使った場合を見ていきましょう。<br>
`add fourth`までコミットされた状態であることを確認して、

```text.sample.txt
first
second
third
fourth
```

```:git log
commit 444444444444444 (HEAD -> feature-1)
Author: your name
Date:  Mon Jan 4 00:00:00 2024

    add fourth

commit 333333333333333
Author: your name
Date:  Mon Jan 3 00:00:00 2024

    add third

commit 222222222222222
Author: your name
Date:  Mon Jan 2 00:00:00 2024

    add second

commit 111111111111111
Author: your name
Date:  Mon Jan 1 00:00:00 2024

    add first

commit 000000000000000 (main)
Author: your name
Date: Sun Dec 31 00:00:00 2023

    first commit
```

`git switch -d 222222222222222`を実行してみると、

```:ターミナル
HEAD is now at 2222222 add second
```

と表示され、テキストファイルを見てみると、

```text:sample.txt
first
second
```

`add second`コミット時のファイルが表示されています。<br>

`git branch`でブランチを見てみると、

```:git branch
* (HEAD detached at 2222222)
  feature-1
  main
```

現在`(HEAD detached at 2222222)`にいることがわかります。<br>
ただしこれは、`depatched HEAD`という状態であり、ブランチが作成されているわけではないので注意してください。
（この状態で作業をしても保存されません)<br>
`depatched HEAD`については以下の記事がわかりやすいかと思います。

https://www.r-staffing.co.jp/engineer/entry/20201225_1

`git log`でコミットの履歴を見てみると、

```:git log
commit 222222222222222
Author: your name
Date:  Mon Jan 2 00:00:00 2024

    add second

commit 111111111111111
Author: your name
Date:  Mon Jan 1 00:00:00 2024

    add first

commit 000000000000000 (main)
Author: your name
Date: Sun Dec 31 00:00:00 2023

    first commit
```

指定したコミットとそれ以前のコミットが表示されています。<br>
`add third`と`add fourth`コミットが消えているように見えますが、実際は消えておらず、`feature-1`ブランチに戻ると、きちんと全て表示されます。<br>
`git switch feature-1`でもとのブランチに戻ってみると、

```text.sample.txt
first
second
third
fourth
```

最後の`add fourth`コミットの状態になっていることがわかります。
`git log`で履歴を見てみても、

```:git log
commit 444444444444444 (HEAD -> feature-1)
Author: your name
Date:  Mon Jan 4 00:00:00 2024

    add fourth

commit 333333333333333
Author: your name
Date:  Mon Jan 3 00:00:00 2024

    add third

commit 222222222222222
Author: your name
Date:  Mon Jan 2 00:00:00 2024

    add second

commit 111111111111111
Author: your name
Date:  Mon Jan 1 00:00:00 2024

    add first

commit 000000000000000 (main)
Author: your name
Date: Sun Dec 31 00:00:00 2023

    first commit
```

コミットの履歴が全て残っていることが確認できます。<br>
ちなみにこの状態で、`git branch`でブランチ一覧を表示させると、

```:git branch
* feature-1
  main
```

先ほどまであった`(HEAD detached at 2222222)`が無くなっていることがわかります。<br>
これは、`(HEAD detached at 2222222)`が`depatched HEAD`という状態であり、ブランチが実際に作成されているわけではないためです。そのため、他のブランチに移動してしまうと`(HEAD detached at 2222222)`は消えてしまいます。<br>

## depatched HEADを避けるには
`depatched HEAD`状態を避けた状態で過去のコミットに戻りたいときは、ブランチを作ってあげる必要があります。<br>
`git switch -c [作成するブランチの名前] [コミットのハッシュ値]`
または
`git switch -d [コミットのハッシュ値]`を実行して`depatched HEAD`の状態で`git switch -c [作成するブランチの名前]`
を実行すると、特定のコミットの状態のブランチを作った上で、そのブランチに移動できます。<br>
試しに、`feature-1`ブランチにいる状態で

```text:sample.txt
first
second
third
fourth
```

`git switch -c add-second 222222222222222`を実行すると、

```text:sample.txt
first
second
```

```:git branch
* add-second
  feature-1
  main
```

`add second`コミットの状態になっており、さらに`add-second`ブランチが作成されていることがわかります。

## switchの代わりにcheckoutも使える
ちなみに`checkout`コマンドを使っても、同じようなことができます。

`git checkout 222222222222222`を実行すると、

```:ターミナル
NOTE: switching to '222222222222222'.

You are in 'detached HEAD' state. 以下略

HEAD is now at 2222222 add second
```

```text:sample.txt
first
second
```

```:git branch
* (HEAD detached at 2222222)
  feature-1
  main
```

となります。

# おわりに
間違い等ございましたら、教えていただけますと幸いです。

# 参考文献

https://zenn.dev/yutabeee/articles/034fb9383f441c

https://qiita.com/getty104/items/cfd98f5f0ea89ef07bf0

https://zenn.dev/kazuwombat/articles/eced2915b6adff

https://qiita.com/shadowmoon0721/items/fc84e93c6aa8432dfe06

https://www.r-staffing.co.jp/engineer/entry/20201225_1