git resetする開発者必見！ reflogを知れば--hardオプションも怖くない

「うわ...間違えてreset --hardしちゃった…！」「さっきまであったはずのコミットはどこへ？」

Gitの時空を旅するあなたなら，誰しも一度はそんな冷や汗をかく瞬間を経験したことがあるでしょう。しかし、ご安心を．あなたの「やらかし」を救ってくれる魔法の呪文，それがgit reflogです！

この記事では，Gitのコミット履歴を参照するgit logと，操作離席を遡るgit reflogの決定的な違いを解き明かします．HEAD~1とHEAD@{1}という二つの座標を頼りに，安全に過去へ戻れるようになりましょう！

# gitに関しては以下のチートシートで...

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


# git log vs git reflog：つの違い

## 1. 記録対象：コミット履歴 vs ポインタの移動履歴

-   **git log**:
**コミットの履歴**を記録する. 新しいコミットが作成されるたびに, そのコミットオブジェクトへの参照が記録されていく. これはプロジェクトの公式な変更履歴である.
-   **git reflog**:
**HEADやブランチなど参照 (ref) が指す先のコミットハッシュが変更された操作**の履歴を記録する. `commit`, `reset`, `checkout`, `merge`など, ポインタが移動するほぼすべての操作が対象となる. これは開発者個人のローカルな操作履歴である.

## 2. 共有範囲：パブリック vs プライベート

-   **git log**:
`git push`によってリモートリポジトリに送信され, 他の開発者と共有される**パブリックな履歴**である.
-   **git reflog**:
各開発者の**ローカルリポジトリにのみ存在するプライベートな記録**であり, `git push`で共有されることはない. 他の開発者があなたの`reflog`を見ることはできない.

## 3. 永続性：半永続的 vs 一時的

-   **git log**:
`git rebase`や`git reset --hard`などで意図的にコミットを履歴から消さない限り, コミット履歴は**半永続的**に残る.
-   **git reflog**:
安全装置としての役割を持つため, 履歴は永遠には保存されない. 設定された期間（デフォルトは90日）が経過すると,古いものから自動的に削除される**一時的な記録**である.

## 4. 主な用途：変更履歴の確認 vs 操作の復旧

-   **git log**:
プロジェクトがどのように変更されてきたか, 誰がいつどのような変更を加えたかといった**開発の文脈を理解する**ために使用する.
-   **git reflog**:
誤ってブランチを削除したり, `reset`でコミットを消してしまったりした場合に, 失われたコミットを探し出して**状態を復旧する**ために使用する. まさに「最後の頼みの綱」である.

## 5. 表示内容の違い

`git log`はコミットそのものに焦点を当てる.

```sh
$ git log --oneline
a1b2c3d (HEAD -> main) feat: 新機能を追加
f4g5h6i chore: 不要なファイルを削除
c7d8e9f fix: バグを修正
```

一方, `git reflog`はどのような操作でHEADが移動したかを表示する.

```sh
$ git reflog
a1b2c3d HEAD@{0}: commit: feat: 新機能を追加
f4g5h6i HEAD@{1}: reset: moving to HEAD~1
c7d8e9f HEAD@{2}: commit: feat: 新機能を追加（これは後でresetされた）
...
```

## 6. 参照方法：`~` vs `@{}`

-   **git log**:
コミットを遡るには, `HEAD~<n>`のように**チルダ (`~`)** を使用する. これはコミットの親子関係を`n`世代遡ることを意味する.
-   **git reflog**:
操作履歴を遡るには, `HEAD@{<n>}`のように**アットマークと波括弧 (`@{}`)** を使用する. これは`reflog`における`n`番前の状態を指す.

## 7. ブランチ削除時の挙動

-   **git log**:
マージされていないブランチを削除すると, そのブランチからのみ到達可能だったコミットは`git log`の出力には現れなくなり, 見失ったように見える.
-   **git reflog**:
ブランチが削除されても, そのブランチが指していたコミットへの参照は`reflog`内に残っているため, `reflog`を辿ることで失われたコミットを救出できる可能性がある.

---

# `reset`の仕組み：`HEAD~1` と `HEAD@{1}`

`git reset`コマンドで`HEAD~1`と`HEAD@{1}`の両方を使用できるが, これらは参照する対象が異なるため, 結果も変わってくる.

## `HEAD`とは何か

`HEAD`は, 現在作業中のブランチの先頭を指す特別なポインタである. つまり, 「今いる場所」を示している.

### コミットハッシュの生成とHEADの移動

Gitでは, 新しいコミットが作成されるたびに以下の手順でコミットハッシュが生成され, `HEAD`が移動する.

**1. コミットオブジェクトの作成**

`git commit`を実行すると, Gitは以下の情報を組み合わせてコミットオブジェクトを作成する:
- ファイルの内容（tree オブジェクト）
- 親コミットのハッシュ
- 作成者情報（名前、メールアドレス、タイムスタンプ）
- コミッター情報
- コミットメッセージ

**2. SHA-1ハッシュの計算**

これらの情報からSHA-1ハッシュ値（40文字の16進数）が計算され, これがコミットハッシュとなる.

**3. HEADとブランチポインタの更新**

新しいコミットが作成されると, `HEAD`が指すブランチのポインタが新しいコミットハッシュに更新される.

この一連の流れを具体例で見てみよう.

```sh
# 初期状態
$ git log --oneline
a1b2c3d (HEAD -> main) Initial commit

# ファイルを編集してステージング
$ echo "Hello World" > hello.txt
$ git add hello.txt

# コミットを作成
$ git commit -m "Add hello.txt"
[main f4e5d6c] Add hello.txt
 1 file changed, 1 insertion(+)
 create mode 100644 hello.txt

# HEADが新しいコミットを指すようになった
$ git log --oneline
f4e5d6c (HEAD -> main) Add hello.txt
a1b2c3d Initial commit

# さらにもう一つコミットを作成
$ echo "Updated content" >> hello.txt
$ git add hello.txt
$ git commit -m "Update hello.txt"
[main 7g8h9i0] Update hello.txt
 1 file changed, 1 insertion(+)

$ git log --oneline
7g8h9i0 (HEAD -> main) Update hello.txt
f4e5d6c Add hello.txt
a1b2c3d Initial commit
```

このHEADの移動を視覚化してみよう.

**1. 初期状態（1つのコミットのみ）**

```mermaid
gitGraph
    commit id: "HEAD(a1b2c3d)"
```

**2. 2つ目のコミット作成後（HEADがf4e5d6cに移動）**

```mermaid
gitGraph
    commit id: "a1b2c3d"
    commit id: "HEAD(f4e5d6c)"
```

**3. 3つ目のコミット作成後（HEADが7g8h9i0に移動）**

```mermaid
gitGraph
    commit id: "a1b2c3d"
    commit id: "f4e5d6c"
    commit id: "HEAD(7g8h9i0)"
```

### HEADの詳細な動作

`HEAD`は実際には`.git/HEAD`ファイルに格納されており, 通常は現在のブランチへの参照を含んでいる.

```sh
# HEADの内容を確認
$ cat .git/HEAD
ref: refs/heads/main

# mainブランチが指すコミットハッシュを確認
$ cat .git/refs/heads/main
7g8h9i0a1b2c3d4e5f6789012345678901234567

# HEADが間接的に指すコミットハッシュを確認
$ git rev-parse HEAD
7g8h9i0a1b2c3d4e5f6789012345678901234567
```

このように, `HEAD`は「現在のブランチ」を指し, そのブランチが「最新のコミットハッシュ」を指すという二段階の参照構造になっている. 新しいコミットが作成されるたびに, ブランチポインタが更新され, 結果として`HEAD`が指す先も新しいコミットに移動する.

### detached HEAD状態

特定のコミットハッシュに直接チェックアウトすると, `HEAD`がブランチではなく直接コミットを指す「detached HEAD」状態になる.

```sh
# 特定のコミットにチェックアウト
$ git checkout f4e5d6c
Note: switching to 'f4e5d6c'.

You are in 'detached HEAD' state...

$ cat .git/HEAD
f4e5d6ca1b2c3d4e5f6789012345678901234567

# この状態でコミットすると、ブランチから切り離された状態になる
```

**detached HEAD状態の図解**

```mermaid
gitGraph
    commit id: "a1b2c3d"
    commit id: "detached HEAD(f4e5d6c)" type: HIGHLIGHT
    commit id: "7g8h9i0"
```

この状態では, `HEAD`が`f4e5d6c`を直接指しており, `main`ブランチは`7g8h9i0`を指したままになっている.

## `HEAD~1`：親コミットを辿る

`~1`は**現在のコミットの1つ親のコミット**を指す. これはコミットが持つ親子関係の情報を辿る.

したがって, `git reset --hard HEAD~1`を実行すると, `HEAD`ポインタ（およびカレントブランチのポインタ）を, 現在のコミットの**親コミット**に移動させる.

```sh
# 状況: C3 (HEAD) -> C2 -> C1

# コミットログ
$ git log --oneline
C3 (HEAD -> main) Commit 3
C2 Commit 2
C1 Commit 1

# resetを実行
$ git reset --hard HEAD~1

# 結果: HEADはC2を指すようになる
$ git log --oneline
C2 (HEAD -> main) Commit 2
C1 Commit 1
```

この操作を視覚的に見てみよう. まず, `reset`前の状態は以下の通りである.

```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    commit id: "HEAD(C3)"
```

`git reset --hard HEAD~1`を実行すると, `HEAD`と`main`ブランチが`C3`の親である`C2`へ移動し, `C3`は履歴から外れる.

```mermaid
gitGraph
    commit id: "C1"
    commit id: "HEAD(C2)"
    commit id: "C3" type: HIGHLIGHT
```

図のようにコミット`C3`はブランチの履歴から外れる.

## `HEAD@{1}`：操作履歴を辿る(間違えてreset --hardしてしまった場合)

`@{1}`は **`reflog`における1つ前の`HEAD`の状態**を指す. これは時間的な操作履歴を辿る.

例えば, 間違えて`reset`してしまった状況を考えてみよう.

```sh
# 状況: C2 (HEAD -> main) <- 先ほどのreset直後

# reflogを確認
$ git reflog
C2 HEAD@{0}: reset: moving to HEAD~1
C3 HEAD@{1}: commit: Commit 3
C2 HEAD@{2}: commit: Commit 2
...

# 直前の操作(`reset`)を取り消したい
# `reset`する前のHEADはC3を指していた (reflogのHEAD@{1})
$ git reset --hard HEAD@{1}

# 結果: HEADは再びC3を指すようになり, resetが取り消された
$ git log --oneline
C3 (HEAD -> main) Commit 3
C2 Commit 2
C1 Commit 1
```

この例では, `reflog`が記録していた「`reset`する前の状態 (`HEAD@{1}`)」, すなわちコミット`C3`に戻している. この復元の様子を視覚化する.

これが`reset`で`C3`を見失った状態である.

```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    commit id: "C3" type: HIGHLIGHT
```

ここで`git reset --hard HEAD@{1}`を実行すると, `reflog`の情報をもとに`HEAD`と`main`ブランチが`C3`へ戻る.

```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    commit id: "C3"
```

このように, `~`はコミットの親子関係という**空間的な関係**を遡り, `@{}`は`reflog`に記録された**時間的な操作**を遡る, という明確な違いがあるのだ.

### `HEAD@{n}`の移動を見てみよう

`reflog`は各操作でHEADがどのコミットを指していたかを時系列で記録している. `HEAD@{n}`の`n`は「n回前の操作時点でのHEADの位置」を意味する.

具体的な例で`HEAD@{n}`の移動を追跡してみよう.

```sh
# 初期状態: 3つのコミットがある
$ git log --oneline
C3 (HEAD -> main) Third commit
C2 Second commit
C1 Initial commit

# 現在のreflog状態
$ git reflog
C3 HEAD@{0}: commit: Third commit
C2 HEAD@{1}: commit: Second commit
C1 HEAD@{2}: commit: Initial commit
```

この状態から様々な操作を行い, `HEAD@{n}`がどのように変化するかを見てみよう.

**操作1: reset --hard HEAD~1を実行**

```sh
$ git reset --hard HEAD~1

$ git reflog
C2 HEAD@{0}: reset: moving to HEAD~1
C3 HEAD@{1}: commit: Third commit
C2 HEAD@{2}: commit: Second commit
C1 HEAD@{3}: commit: Initial commit
```

**操作2: 新しいコミットを作成**

```sh
$ echo "new content" > new.txt
$ git add new.txt
$ git commit -m "New commit"

$ git reflog
D1 HEAD@{0}: commit: New commit
C2 HEAD@{1}: reset: moving to HEAD~1
C3 HEAD@{2}: commit: Third commit
C2 HEAD@{3}: commit: Second commit
C1 HEAD@{4}: commit: Initial commit
```

この一連の操作における`HEAD@{n}`の変化を視覚化してみよう.

**HEAD@{4} (最初の状態)**
```mermaid
gitGraph
    commit id: "C1"
```

**HEAD@{3} (2つ目のコミット後)**
```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
```

**HEAD@{2} (3つ目のコミット後)**
```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    commit id: "C3"
```

**HEAD@{1} (reset後、C3が見失われた状態)**
```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    commit id: "C3" type: HIGHLIGHT
```

**HEAD@{0} (新しいコミットD1を作成後)**
```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    commit id: "D1"
```

### `HEAD@{n}`を使った柔軟な復旧

この履歴を使って、任意の時点に戻ることができる.

```sh
# HEAD@{3}の時点（C3コミット直後）に戻る
$ git reset --hard HEAD@{3}

$ git log --oneline
C3 (HEAD -> experiment) Third commit
C2 Second commit
C1 Initial commit

# HEAD@{1}の時点（D1コミット直後）に戻る
$ git reset --hard HEAD@{1}

$ git log --oneline
D1 (HEAD -> experiment) New commit
C2 Second commit
C1 Initial commit

# HEAD@{2}の時点（reset直後）に戻る
$ git reset --hard HEAD@{2}

$ git log --oneline
C2 (HEAD -> experiment) Second commit
C1 Initial commit
```

このように, `HEAD@{n}`を使うことで, 操作履歴の任意の時点にピンポイントで戻ることができる. これは`HEAD~n`（親コミットを辿る）とは全く異なる概念で, **時間軸に沿った操作の巻き戻し**が可能になる.

### なぜHEADの操作履歴を辿れるのか：reflogの仕組み

`reflog`がHEADの操作履歴を辿れる理由は, Gitが内部的に**すべてのHEADの変更を専用のログファイルに記録している**からである. この仕組みを詳しく見てみよう.

#### reflogファイルの構造

Gitリポジトリの`.git/logs/`ディレクトリには, 各参照（ref）の変更履歴が記録されている.

```sh
# .git/logs/ディレクトリの構造を確認
$ find .git/logs -type f
.git/logs/HEAD
.git/logs/refs/heads/main
.git/logs/refs/heads/feature
```

**HEADの変更履歴**は`.git/logs/HEAD`ファイルに記録される.

```sh
# HEADの変更履歴を直接確認
$ cat .git/logs/HEAD
0000000000000000000000000000000000000000 a1b2c3d4e5f6789012345678901234567890abcd User Name <user@example.com> 1640995200 +0900	commit (initial): Initial commit
a1b2c3d4e5f6789012345678901234567890abcd f4e5d6c7a8b9012345678901234567890123cdef User Name <user@example.com> 1640995260 +0900	commit: Add hello.txt
f4e5d6c7a8b9012345678901234567890123cdef 7g8h9i0j1k2l345678901234567890123456789ef User Name <user@example.com> 1640995320 +0900	commit: Update hello.txt
7g8h9i0j1k2l345678901234567890123456789ef f4e5d6c7a8b9012345678901234567890123cdef User Name <user@example.com> 1640995380 +0900	reset: moving to HEAD~1
```

各行の構造は以下の通りである:
```
[変更前のハッシュ] [変更後のハッシュ] [作成者情報] [タイムスタンプ] [操作の説明]
```

#### reflogの記録メカニズム

HEADが変更されるたびに, Gitは以下の手順でreflogを更新する:

**1. 操作の検出**
```mermaid
graph TD
    A["Git operation executed"] --> B{"HEAD changed?"}
    B -->|Yes| C["Create reflog entry"]
    B -->|No| D["Do nothing"]
    C --> E["Append to .git/logs/HEAD"]
```

**2. エントリの作成と記録**

各操作でreflogエントリが作成される様子を視覚化してみよう.

**初期状態: 最初のコミット**
```mermaid
graph LR
    subgraph "Git Repository"
        C1[a1b2c3d<br/>Initial commit]
    end
    subgraph "reflog (.git/logs/HEAD)"
        R0["HEAD@{0}: commit (initial): Initial commit<br/>→ a1b2c3d"]
    end
    HEAD --> C1
```

**2つ目のコミット後**
```mermaid
graph LR
    subgraph "Git Repository"
        C1[a1b2c3d<br/>Initial commit]
        C2[f4e5d6c<br/>Add hello.txt]
        C1 --> C2
    end
    subgraph "reflog (.git/logs/HEAD)"
        R0["HEAD@{0}: commit: Add hello.txt<br/>→ f4e5d6c"]
        R1["HEAD@{1}: commit (initial): Initial commit<br/>→ a1b2c3d"]
    end
    HEAD --> C2
```

**reset操作後**
```mermaid
graph LR
    subgraph "Git Repository"
        C1[a1b2c3d<br/>Initial commit]
        C2[f4e5d6c<br/>Add hello.txt<br/>type: HIGHLIGHT]
        C1 --> C2
    end
    subgraph "reflog (.git/logs/HEAD)"
        R0["HEAD@{0}: reset: moving to HEAD~1<br/>→ a1b2c3d"]
        R1["HEAD@{1}: commit: Add hello.txt<br/>→ f4e5d6c"]
        R2["HEAD@{2}: commit (initial): Initial commit<br/>→ a1b2c3d"]
    end
    HEAD --> C1
```

#### reflogによる復旧の仕組み

`git reset --hard HEAD@{1}`を実行すると, Gitは以下の手順で復旧を行う:

**1. reflogエントリの検索**
```mermaid
graph TD
    A["git reset --hard HEAD@{1}"] --> B["Read .git/logs/HEAD"]
    B --> C["Search HEAD@{1} entry"]
    C --> D["Get target hash f4e5d6c"]
    D --> E["Move HEAD to f4e5d6c"]
    E --> F["Add new reflog entry"]
```

**2. 復旧後の状態**
```mermaid
graph LR
    subgraph "Git Repository"
        C1[a1b2c3d<br/>Initial commit]
        C2[f4e5d6c<br/>Add hello.txt]
        C1 --> C2
    end
    subgraph "reflog (.git/logs/HEAD)"
        R0["HEAD@{0}: reset: moving to HEAD@{1}<br/>→ f4e5d6c"]
        R1["HEAD@{1}: reset: moving to HEAD~1<br/>→ a1b2c3d"]
        R2["HEAD@{2}: commit: Add hello.txt<br/>→ f4e5d6c"]
        R3["HEAD@{3}: commit (initial): Initial commit<br/>→ a1b2c3d"]
    end
    HEAD --> C2
```

#### reflogの自動管理

reflogは以下の特徴を持つ:

**1. 自動的な記録**
- HEADが変更されるすべての操作を自動記録
- ユーザーが意識する必要がない

**2. 期限付きの保存**
```sh
# reflogの保存期間設定を確認
$ git config --get gc.reflogExpire
90.days

$ git config --get gc.reflogExpireUnreachable
30.days
```

**3. ガベージコレクション**
```mermaid
graph TD
    A["git gc executed"] --> B{"Check reflog entry age"}
    B -->|"Within 90 days"| C["Keep"]
    B -->|"Over 90 days"| D{"Reachable?"}
    D -->|Yes| E["Keep for 30 more days"]
    D -->|No| F["Delete"]
```

このように, reflogは**Gitの内部メカニズムとして自動的に動作**し, 開発者の操作履歴を安全に保護している. これにより, 誤った操作からの復旧が可能になっているのである.

### reflogに記録されるハッシュ値の決定方法

reflogに記録されるハッシュ値は, **その操作時点でHEADが指していたコミットのハッシュ値**である. これは操作の種類に関係なく, 常にHEADの移動先のコミットハッシュが記録される.

#### ハッシュ値の記録パターン

**1. コミット操作の場合**

新しいコミットが作成されると, そのコミットのハッシュ値がreflogに記録される.

```sh
# 新しいコミットを作成
$ echo "Hello World" > hello.txt
$ git add hello.txt
$ git commit -m "Add hello.txt"
[main f4e5d6c] Add hello.txt

# reflogを確認
$ git reflog
f4e5d6c HEAD@{0}: commit: Add hello.txt
a1b2c3d HEAD@{1}: commit (initial): Initial commit

# コミットハッシュを直接確認
$ git rev-parse HEAD
f4e5d6ca1b2c3d4e5f6789012345678901234567
```

この場合, `f4e5d6c`は**新しく作成されたコミットのハッシュ値**である.

**2. reset操作の場合**

`reset`操作では, 移動先のコミットハッシュが記録される.

```sh
# reset前の状態
$ git log --oneline
f4e5d6c (HEAD -> main) Add hello.txt
a1b2c3d Initial commit

# resetを実行
$ git reset --hard HEAD~1

# reflogを確認
$ git reflog
a1b2c3d HEAD@{0}: reset: moving to HEAD~1
f4e5d6c HEAD@{1}: commit: Add hello.txt
a1b2c3d HEAD@{2}: commit (initial): Initial commit
```

この場合, `a1b2c3d`は**reset後にHEADが指すようになったコミットのハッシュ値**である.

**3. checkout操作の場合**

ブランチ切り替えでは, 切り替え先のコミットハッシュが記録される.

```sh
# 新しいブランチを作成して移動
$ git checkout -b feature f4e5d6c

# reflogを確認
$ git reflog
f4e5d6c HEAD@{0}: checkout: moving from main to feature
a1b2c3d HEAD@{1}: reset: moving to HEAD~1
f4e5d6c HEAD@{2}: commit: Add hello.txt
```

この場合, `f4e5d6c`は**featureブランチが指すコミットのハッシュ値**である.

#### ハッシュ値の対応関係を視覚化

reflogのハッシュ値とGitリポジトリの関係を詳しく見てみよう.

**コミット作成時のハッシュ値記録**

```mermaid
graph TB
    subgraph "Git Object Database"
        CO1[Commit Object<br/>Hash: a1b2c3d<br/>Message: Initial commit]
        CO2[Commit Object<br/>Hash: f4e5d6c<br/>Message: Add hello.txt<br/>Parent: a1b2c3d]
        CO1 --> CO2
    end

    subgraph "reflog (.git/logs/HEAD)"
        R0["HEAD@{0}: commit: Add hello.txt<br/>→ f4e5d6c"]
        R1["HEAD@{1}: commit (initial): Initial commit<br/>→ a1b2c3d"]
    end

    CO2 -.->|"Hash copied"| R0
    CO1 -.->|"Hash copied"| R1

    HEAD --> CO2
```

**reset操作時のハッシュ値記録**

```mermaid
graph TB
    subgraph "Git Object Database"
        CO1[Commit Object<br/>Hash: a1b2c3d<br/>Message: Initial commit]
        CO2[Commit Object<br/>Hash: f4e5d6c<br/>Message: Add hello.txt<br/>Parent: a1b2c3d<br/>style: HIGHLIGHT]
        CO1 --> CO2
    end

    subgraph "reflog (.git/logs/HEAD)"
        R0["HEAD@{0}: reset: moving to HEAD~1<br/>→ a1b2c3d"]
        R1["HEAD@{1}: commit: Add hello.txt<br/>→ f4e5d6c"]
        R2["HEAD@{2}: commit (initial): Initial commit<br/>→ a1b2c3d"]
    end

    CO1 -.->|"Hash copied"| R0
    CO2 -.->|"Hash copied"| R1
    CO1 -.->|"Hash copied"| R2

    HEAD --> CO1
```

#### ハッシュ値の一意性と追跡可能性

reflogに記録されるハッシュ値の重要な特徴:

**1. 一意性の保証**
```sh
# 同じハッシュ値は同じコミットを指す
$ git show a1b2c3d
commit a1b2c3d4e5f6789012345678901234567890abcd
Author: User Name <user@example.com>
Date:   Fri Dec 31 12:00:00 2021 +0900

    Initial commit

$ git reset --hard HEAD@{2}  # a1b2c3dに戻る
$ git show HEAD
commit a1b2c3d4e5f6789012345678901234567890abcd  # 同じハッシュ値
Author: User Name <user@example.com>
Date:   Fri Dec 31 12:00:00 2021 +0900

    Initial commit
```

**2. 完全な復元可能性**
```sh
# reflogのハッシュ値を使って完全に復元
$ git reset --hard f4e5d6c  # reflogから取得したハッシュ値

$ git log --oneline
f4e5d6c (HEAD -> main) Add hello.txt
a1b2c3d Initial commit

# ファイルの内容も完全に復元される
$ cat hello.txt
Hello World
```

**3. ハッシュ値の継承**

reflogは既存のコミットハッシュを**参照**するだけで, 新しいハッシュ値を生成することはない. すべてのハッシュ値は, Gitオブジェクトデータベースに存在するコミットオブジェクトのハッシュ値である.

```mermaid
graph LR
    subgraph "Commit Creation"
        A[File changes] --> B[git add]
        B --> C[git commit]
        C --> D[Generate SHA-1 hash]
        D --> E[Store commit object]
    end

    subgraph "Reflog Recording"
        E --> F[Copy hash to reflog]
        F --> G[Add operation description]
        G --> H[Append to .git/logs/HEAD]
    end
```

このように, reflogのハッシュ値は**既存のコミットオブジェクトのハッシュ値をそのまま記録**したものであり, これによって過去の任意の状態への完全な復元が可能になっている.

---

# `reflog`はブランチ移動も記録する

`git reflog`は`reset`だけでなく, ブランチ間の移動(`checkout`)やマージ(`merge`)といった操作も記録している. これにより, 複雑なブランチ操作を行った後でも, 以前の状態に戻ることが可能である.

## ブランチ移動の記録例

以下のような一連の操作を行った場合を考えてみよう.

```sh
# 初期状態: mainブランチにいる
$ git log --oneline
C3 (HEAD -> main) Main branch commit 3
C2 Main branch commit 2
C1 Initial commit

# 新しいブランチを作成して移動
$ git checkout -b feature
Switched to a new branch 'feature'

# featureブランチで作業
$ git commit -m "Feature commit 1"
$ git commit -m "Feature commit 2"

$ git log --oneline
F2 (HEAD -> feature) Feature commit 2
F1 Feature commit 1
C3 (main) Main branch commit 3
C2 Main branch commit 2
C1 Initial commit

# mainブランチに戻る
$ git checkout main
Switched to branch 'main'

# featureブランチをマージ
$ git merge feature
Merge made by the 'recursive' strategy.

# reflogを確認
$ git reflog
M1 HEAD@{0}: merge feature: Merge made by the 'recursive' strategy.
C3 HEAD@{1}: checkout: moving from feature to main
F2 HEAD@{2}: commit: Feature commit 2
F1 HEAD@{3}: commit: Feature commit 1
C3 HEAD@{4}: checkout: moving from main to feature
C3 HEAD@{5}: commit: Main branch commit 3
...
```

この操作の流れを視覚化してみよう.

**1. 初期状態 (mainブランチ)**

```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    commit id: "C3"
```

**2. featureブランチを作成して移動後, コミットを追加**

```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    commit id: "C3"
    branch feature
    checkout feature
    commit id: "F1"
    commit id: "F2"
```

**3. mainブランチに戻ってマージ後**

```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    commit id: "C3"
    branch feature
    checkout feature
    commit id: "F1"
    commit id: "F2"
    checkout main
    merge feature
    commit id: "M1"
```

## `reflog`を使ったマージをreset --hardした際の復旧例

もし間違えてマージを取り消した場合, `reflog`を使って以前の状態に戻ることができる.

```sh
# 間違えてマージを取り消してしまった
$ git reset --hard HEAD~1

$ git log --oneline
C3 (HEAD -> main) Main branch commit 3
C2 Main branch commit 2
C1 Initial commit

# マージが消えてしまった！でもreflogには記録が残っている
$ git reflog
C3 HEAD@{0}: reset: moving to HEAD~1
M1 HEAD@{1}: merge feature: Merge made by the 'recursive' strategy.
C3 HEAD@{2}: checkout: moving from feature to main
F2 HEAD@{3}: commit: Feature commit 2
...

# マージ直後の状態に戻る
$ git reset --hard HEAD@{1}

$ git log --oneline
M1 (HEAD -> main) Merge branch 'feature'
F2 Feature commit 2
F1 Feature commit 1
C3 Main branch commit 3
C2 Main branch commit 2
C1 Initial commit
```

この復旧プロセスを視覚化してみよう.

**1. マージ後の正常な状態**

```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    commit id: "C3"
    branch feature
    checkout feature
    commit id: "F1"
    commit id: "F2"
    checkout main
    merge feature
    commit id: "M1"
```

**2. 間違えてreset --hard HEAD~1を実行してマージが消えた状態**

```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    commit id: "C3"
    branch feature
    checkout feature
    commit id: "F1" type: HIGHLIGHT
    commit id: "F2" type: HIGHLIGHT
```

**3. reflogを使ってHEAD@{1}に戻し、マージを復旧した状態**

```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    commit id: "C3"
    branch feature
    checkout feature
    commit id: "F1"
    commit id: "F2"
    checkout main
    merge feature
    commit id: "M1"
```

## ブランチ削除からの復旧

`reflog`は削除されたブランチの復旧にも役立つ.

```sh
# featureブランチを削除
$ git branch -D feature
Deleted branch feature (was F2).

# ブランチが消えた
$ git branch
* main

# でもreflogには記録が残っている
$ git reflog
M1 HEAD@{0}: reset: moving to HEAD@{1}
M1 HEAD@{1}: merge feature: Merge made by the 'recursive' strategy.
C3 HEAD@{2}: checkout: moving from feature to main
F2 HEAD@{3}: commit: Feature commit 2
F1 HEAD@{4}: commit: Feature commit 1
...

# 削除されたブランチを復活させる
# HEAD@{3}の時点でfeatureブランチのF2にいた
$ git checkout -b feature-recovered HEAD@{3}
Switched to a new branch 'feature-recovered'

$ git log --oneline
F2 (HEAD -> feature-recovered) Feature commit 2
F1 Feature commit 1
C3 (main) Main branch commit 3
C2 Main branch commit 2
C1 Initial commit
```

このブランチ削除と復旧のプロセスを視覚化してみよう.

**1. ブランチ削除前の状態（マージ済み）**

```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    commit id: "C3"
    branch feature
    checkout feature
    commit id: "F1"
    commit id: "F2"
    checkout main
    merge feature
    commit id: "M1"
```

**2. featureブランチを削除した状態**

```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    commit id: "C3"
    commit id: "M1"
```

**3. reflogを使ってfeature-recoveredブランチとして復活**

```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    commit id: "C3"
    branch feature-recovered
    checkout feature-recovered
    commit id: "F1"
    commit id: "F2"
    checkout main
    commit id: "M1"
```

このように, `reflog`はブランチ操作の履歴も詳細に記録しているため, 複雑なGit操作を行った後でも安全に以前の状態に戻ることができる. 特にチーム開発では, ブランチの作成・削除・マージが頻繁に行われるため, `reflog`の存在は非常に心強い安全装置となる.

---

`git log`と`git reflog`は, どちらもGitの履歴を扱う上で不可欠なツールであるが, その目的とメカニズムは全く異なる.

-   **`git log`**は共有される**プロジェクトの歴史**であり, 変更の文脈を理解するために使う.
-   **`git reflog`**はローカルでの**操作の記録**であり, 誤った操作からの復旧という安全装置の役割を担う.

また, `HEAD~1`と`HEAD@{1}`の違いは, コミットの親子関係を辿るか, 時間的な操作履歴を辿るかの違いである. この違いを理解することで, `git reset`をより安全かつ効果的に使いこなすことが可能になる.

特に`reflog`は, `reset`や`commit`だけでなく, ブランチの移動(`checkout`)やマージ(`merge`), さらにはブランチの削除からの復旧まで幅広くカバーしている. Git操作に慣れないうちはもちろん, 熟練した開発者にとっても強力な味方となる. 困ったときはまず`git reflog`を確認する癖をつけよう！.