はじめに

「git resetで消したコミットを復元したい」「作業中にブランチを切り替える必要があるけど、変更をコミットしたくない」「どこかで失くしたはずのコミットを取り戻したい」。Gitを使った開発では、こうした場面に遭遇することがあります。

git refloggit stashは、このような状況で変更を失わないための強力な安全網です。reflogはGitのすべての操作履歴を記録し、stashは作業中の変更を一時的に退避させます。これらを使いこなすことで、安心してGit操作を行えるようになります。

本記事では、git reflogによる履歴復元とgit stashによる一時退避の技術を体系的に解説します。この記事を読み終えると、以下のことができるようになります。

  • git reflogで操作履歴を確認し、失われたコミットを復元できる
  • git stashで作業中の変更を一時退避し、必要なときに復元できる
  • stash管理コマンド(list、pop、drop、apply)を使いこなせる
  • resetやrebaseで消えたコミットを安全に取り戻せる

実行環境と前提条件

本記事の内容は、以下の環境で動作確認を行っています。

項目 要件
Git 2.40以上
OS Windows 10/11、macOS 12以上、Ubuntu 22.04以上
ターミナル コマンドプロンプト、PowerShell、Terminal.app、bash等
エディタ VS Code推奨

前提条件として、以下の知識があることを想定しています。

  • コマンドライン操作の基礎知識(cdls/dirmkdir等)
  • テキストエディタの基本操作
  • Gitの基本コマンド(git addgit commitgit resetgit branch)の理解

git reflog - すべての操作履歴を記録する安全網

git reflogは、リポジトリ内でHEADが指した履歴をすべて記録するコマンドです。git logがコミットの親子関係に基づく履歴を表示するのに対し、reflogはブランチの切り替え、reset、rebase、amendなど、あらゆるHEADの移動を記録します。

reflogの基本概念

reflog(Reference Log)は、参照の変更履歴を保持する仕組みです。以下の操作がすべてreflogに記録されます。

操作 reflogへの記録
git commit 新しいコミットへのHEAD移動
git reset 指定コミットへのHEAD移動
git checkout / git switch ブランチ切り替え
git rebase リベース中のHEAD移動
git merge マージコミットへのHEAD移動
git pull fetch後のHEAD移動
git commit --amend 新しいコミットへの置き換え

reflogの確認方法

reflogを確認する基本コマンドは以下のとおりです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# reflogを表示(デフォルトはHEADのreflog)
git reflog

# より詳細な情報を表示
git reflog show --all

# 特定のブランチのreflogを表示
git reflog show main

# 日時付きで表示
git reflog --date=iso

実行例を見てみましょう。

1
2
3
4
5
6
7
$ git reflog
a1b2c3d (HEAD -> main) HEAD@{0}: commit: 新機能を追加
e4f5g6h HEAD@{1}: commit: バグを修正
i7j8k9l HEAD@{2}: reset: moving to HEAD~2
m1n2o3p HEAD@{3}: commit: 削除されたコミット
q4r5s6t HEAD@{4}: commit: もう一つの削除されたコミット
u7v8w9x HEAD@{5}: checkout: moving from feature to main

各行の意味は以下のとおりです。

要素 説明
a1b2c3d コミットハッシュ(短縮形)
(HEAD -> main) 現在のHEADの位置
HEAD@{0} reflogのインデックス(0が最新)
commit: / reset: 実行された操作の種類
メッセージ 操作の詳細説明

reflogの保持期間

reflogのエントリはデフォルトで以下の期間保持されます。

条件 保持期間
到達可能なコミット 90日(gc.reflogExpire
到達不可能なコミット 30日(gc.reflogExpireUnreachable

保持期間を確認・変更するには以下のコマンドを使用します。

1
2
3
4
5
6
# 現在の設定を確認
git config --get gc.reflogExpire
git config --get gc.reflogExpireUnreachable

# 保持期間を変更(例:到達不可能なコミットを60日保持)
git config gc.reflogExpireUnreachable 60.days

git reflogを使った履歴復元の実践

reflogの真価は、通常の方法では復元できない変更を取り戻せる点にあります。

git resetで消したコミットを復元する

git reset --hardでコミットを消してしまった場合の復元手順です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 現在の状態:3つのコミットがある
$ git log --oneline
a1b2c3d (HEAD -> main) 3番目のコミット
e4f5g6h 2番目のコミット
i7j8k9l 1番目のコミット

# 誤って2つ前のコミットまでリセット
$ git reset --hard HEAD~2
HEAD is now at i7j8k9l 1番目のコミット

# コミットが消えた
$ git log --oneline
i7j8k9l (HEAD -> main) 1番目のコミット

この状態から復元するには、reflogを確認して元のコミットを特定します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# reflogで履歴を確認
$ git reflog
i7j8k9l (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
a1b2c3d HEAD@{1}: commit: 3番目のコミット
e4f5g6h HEAD@{2}: commit: 2番目のコミット
i7j8k9l HEAD@{3}: commit: 1番目のコミット

# リセット前の状態(HEAD@{1})に戻す
$ git reset --hard HEAD@{1}
HEAD is now at a1b2c3d 3番目のコミット

# コミットが復元された
$ git log --oneline
a1b2c3d (HEAD -> main) 3番目のコミット
e4f5g6h 2番目のコミット
i7j8k9l 1番目のコミット

rebaseで失われたコミットを復元する

インタラクティブリベースやコンフリクト解消中に失敗した場合も、reflogから復元できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# リベース前のコミット履歴
$ git log --oneline
c1d2e3f (HEAD -> feature) 機能C
b4a5b6c 機能B
a7b8c9d 機能A

# リベースを実行(途中で失敗したと仮定)
$ git rebase -i main
# ... 何らかの問題が発生 ...

# リベースを中断
$ git rebase --abort

# もしabortできなかった場合、reflogから復元
$ git reflog
x1y2z3a (HEAD -> feature) HEAD@{0}: rebase (abort): ...
c1d2e3f HEAD@{1}: rebase (start): checkout main
c1d2e3f HEAD@{2}: commit: 機能C

# リベース前の状態に戻す
$ git reset --hard HEAD@{2}

amendで上書きされたコミットを復元する

git commit --amendで元のコミットを取り戻したい場合もreflogが有効です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 元のコミット
$ git log --oneline -1
a1b2c3d (HEAD -> main) 元のコミットメッセージ

# amendでコミットを修正
$ git commit --amend -m "修正後のコミットメッセージ"
[main x9y8z7w] 修正後のコミットメッセージ

# reflogで元のコミットを確認
$ git reflog
x9y8z7w (HEAD -> main) HEAD@{0}: commit (amend): 修正後のコミットメッセージ
a1b2c3d HEAD@{1}: commit: 元のコミットメッセージ

# 元のコミットを新しいブランチとして復元(内容を確認したい場合)
$ git branch recover-original HEAD@{1}

# または元の状態に完全に戻す
$ git reset --hard HEAD@{1}

reflogから特定のファイルを復元する

特定のファイルだけを過去の状態から復元することも可能です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# reflogでコミットを特定
$ git reflog
a1b2c3d HEAD@{0}: commit: ファイルを削除
e4f5g6h HEAD@{1}: commit: ファイルを編集
i7j8k9l HEAD@{2}: commit: ファイルを追加

# 特定のコミット時点のファイルを復元
$ git restore --source=HEAD@{2} -- path/to/file.txt

# または checkout を使用(古い書式)
$ git checkout HEAD@{2} -- path/to/file.txt

git stash - 作業中の変更を一時退避する

git stashは、コミットしていない変更を一時的に保存し、作業ツリーをクリーンな状態にするコマンドです。ブランチ切り替え時や緊急対応時に、現在の作業を失わずに別の作業を行いたい場合に使用します。

stashの基本概念

stashは、変更を一時的なスタック構造で保存します。

対象 stashへの保存
作業ツリーの変更 デフォルトで保存
ステージングエリアの変更 デフォルトで保存
未追跡ファイル --include-untrackedオプションで保存
無視されたファイル --allオプションで保存

基本的なstash操作

変更をstashに保存する基本コマンドです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 変更をstashに保存
git stash

# メッセージ付きで保存(識別しやすくなる)
git stash push -m "作業中の機能A"

# 未追跡ファイルも含めて保存
git stash push --include-untracked -m "未追跡ファイル含む"

# 特定のファイルのみstash
git stash push -m "特定ファイル" -- path/to/file.txt

実行例を見てみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 現在の状態
$ git status
On branch feature
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:   app.js
        modified:   index.html

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        new-feature.js

# 未追跡ファイルを含めてstash
$ git stash push --include-untracked -m "新機能開発中"
Saved working directory and index state On feature: 新機能開発中

# 作業ツリーがクリーンになった
$ git status
On branch feature
nothing to commit, working tree clean

stash管理コマンドの使い方

stashには複数の変更を保存でき、それぞれを管理するためのコマンドが用意されています。

git stash list - stash一覧の確認

保存されたstashの一覧を確認します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ git stash list
stash@{0}: On feature: 新機能開発中
stash@{1}: On main: バグ修正の途中
stash@{2}: On feature: 実験的な変更

# 詳細情報を表示
$ git stash list --stat
stash@{0}: On feature: 新機能開発中
 app.js       | 15 +++++++++++++++
 index.html   |  5 +++++
 new-feature.js |  30 ++++++++++++++++++++++++++++++
 3 files changed, 50 insertions(+)

stashはスタック構造のため、stash@{0}が最新です。

git stash show - stash内容の確認

stashに保存された変更内容を確認します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 最新のstashの概要を表示
$ git stash show
 app.js       | 15 +++++++++++++++
 index.html   |  5 +++++
 2 files changed, 20 insertions(+)

# 詳細な差分を表示
$ git stash show -p

# 特定のstashを指定
$ git stash show stash@{1} -p

git stash pop - stashを復元して削除

stashから変更を復元し、同時にstashリストから削除します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 最新のstashを復元
$ git stash pop
On branch feature
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:   app.js
        modified:   index.html

Dropped refs/stash@{0} (a1b2c3d4e5f6...)

# 特定のstashを復元
$ git stash pop stash@{1}

popはstashを削除するため、復元に成功した場合のみ使用することをおすすめします。

git stash apply - stashを復元(削除しない)

stashから変更を復元しますが、stashリストには残ります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 最新のstashを復元(削除しない)
$ git stash apply
On branch feature
Changes not staged for commit:
        modified:   app.js
        modified:   index.html

# 特定のstashを復元
$ git stash apply stash@{2}

# ステージング状態も復元
$ git stash apply --index

applyはstashを残すため、複数のブランチに同じ変更を適用したい場合に便利です。

git stash drop - stashを削除

不要になったstashを削除します。

1
2
3
4
5
6
7
8
9
# 最新のstashを削除
$ git stash drop
Dropped refs/stash@{0} (a1b2c3d4e5f6...)

# 特定のstashを削除
$ git stash drop stash@{1}

# すべてのstashを削除
$ git stash clear

git stash branch - stashから新しいブランチを作成

stashした変更を新しいブランチで作業したい場合に使用します。

1
2
3
4
5
6
7
8
# stashから新しいブランチを作成
$ git stash branch new-feature-branch
Switched to a new branch 'new-feature-branch'
On branch new-feature-branch
Changes not staged for commit:
        modified:   app.js

Dropped refs/stash@{0} (a1b2c3d4e5f6...)

コンフリクトが発生しそうな場合に、安全にstashを復元する方法として有効です。

stash活用の実践シナリオ

実際の開発でstashを活用するシナリオを紹介します。

シナリオ1:緊急のバグ修正対応

新機能の開発中に、本番環境で緊急のバグが発見された場合の対応です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 現在の状況:featureブランチで開発中
$ git branch
* feature/new-dashboard
  main

$ git status
Changes not staged for commit:
        modified:   src/dashboard.js
        modified:   src/components/Chart.js

# 1. 現在の作業をstash
$ git stash push -m "ダッシュボード開発中"
Saved working directory and index state On feature/new-dashboard: ダッシュボード開発中

# 2. mainブランチに切り替え
$ git switch main

# 3. 緊急修正用ブランチを作成
$ git switch -c hotfix/critical-bug

# 4. バグを修正してコミット
$ vim src/auth.js
$ git add src/auth.js
$ git commit -m "fix: 認証エラーを修正"

# 5. mainにマージしてpush
$ git switch main
$ git merge hotfix/critical-bug
$ git push origin main

# 6. 元のブランチに戻り、stashを復元
$ git switch feature/new-dashboard
$ git stash pop

シナリオ2:複数の作業を切り替える

複数の機能を並行して開発する場合のstash活用です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 機能Aの作業中
$ git stash push -m "機能A: ユーザー認証"

# 機能Bの作業を開始
$ git switch feature/feature-b
# ... 作業 ...
$ git stash push -m "機能B: 通知システム"

# 機能Aに戻る
$ git switch feature/feature-a
$ git stash list
stash@{0}: On feature/feature-b: 機能B: 通知システム
stash@{1}: On feature/feature-a: 機能A: ユーザー認証

# 機能Aのstashを復元
$ git stash apply stash@{1}

シナリオ3:実験的な変更を一時保存

試行錯誤中の変更を一時的に保存し、別のアプローチを試す場合です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 実験的な変更を保存
$ git stash push -m "実験: アプローチA"

# 別のアプローチを試す
# ... 作業 ...
$ git stash push -m "実験: アプローチB"

# stash一覧を確認
$ git stash list
stash@{0}: On feature: 実験: アプローチB
stash@{1}: On feature: 実験: アプローチA

# 各アプローチの差分を比較
$ git stash show stash@{0} -p > approach-b.diff
$ git stash show stash@{1} -p > approach-a.diff

# 良かった方を採用
$ git stash apply stash@{1}
$ git stash drop stash@{0}
$ git stash drop stash@{0}  # インデックスがずれる点に注意

stashのコンフリクト解消

stashを復元する際にコンフリクトが発生することがあります。

コンフリクトが発生した場合

1
2
3
4
$ git stash pop
Auto-merging app.js
CONFLICT (content): Merge conflict in app.js
The stash entry is kept in case you need it again.

popでコンフリクトが発生した場合、stashは削除されません。

コンフリクトの解消手順

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 1. コンフリクトしているファイルを確認
$ git status
Unmerged paths:
  (use "git restore --staged <file>..." to unstage)
  (use "git add <file>..." to mark resolution)
        both modified:   app.js

# 2. ファイルを編集してコンフリクトを解消
$ vim app.js
# <<<<<<< と >>>>>>> の間を適切に編集

# 3. 解消したファイルをステージング
$ git add app.js

# 4. stashを手動で削除(popの場合)
$ git stash drop

reflogとstashを組み合わせた高度な復元

reflogとstashを組み合わせることで、より高度な復元が可能になります。

誤ってstashをdropした場合の復元

git stash dropで削除したstashを復元する方法です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# stashを削除
$ git stash drop
Dropped refs/stash@{0} (a1b2c3d4e5f6g7h8i9j0...)

# stashのreflogを確認
$ git fsck --no-reflogs | grep commit
dangling commit a1b2c3d4e5f6g7h8i9j0...

# または直接ハッシュがわかっている場合
$ git stash apply a1b2c3d4e5f6g7h8i9j0

より確実な方法として、stashのreflogを確認します。

1
2
3
4
5
6
7
# stashのreflogを確認(git reflog show stash)
$ git reflog show stash
a1b2c3d stash@{0}: drop: ...
e4f5g6h stash@{1}: WIP on feature: ...

# 削除されたstashを復元
$ git stash apply a1b2c3d

複雑な復元シナリオ

リベース中にstashを使い、問題が発生した場合の復元です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 1. 変更をstash
$ git stash push -m "作業中"

# 2. リベースを開始したが失敗
$ git rebase main
# ... コンフリクト発生 ...

# 3. 元の状態に戻りたい場合
$ git rebase --abort

# 4. reflogでリベース前の状態を確認
$ git reflog
x1y2z3a HEAD@{0}: rebase (abort): ...
a1b2c3d HEAD@{1}: rebase (start): checkout main
e4f5g6h HEAD@{2}: stash: ...
i7j8k9l HEAD@{3}: commit: 元のコミット

# 5. stashを復元
$ git stash pop

reflogとstashの注意点

これらのコマンドを使用する際の注意点をまとめます。

reflogの注意点

注意点 説明
ローカル専用 reflogはローカルリポジトリにのみ存在し、pushされない
有効期限あり デフォルトで30〜90日後に古いエントリは削除される
gc実行で削除 git gc実行時に古いエントリが削除される可能性がある
clone時には存在しない リポジトリをcloneした直後はreflogが空

stashの注意点

注意点 説明
ローカル専用 stashはローカルリポジトリにのみ存在し、pushされない
長期保存向きではない 一時的な退避用であり、長期保存にはブランチを使用すべき
コンフリクトの可能性 時間が経つとpop時にコンフリクトが発生しやすくなる
未追跡ファイル デフォルトでは未追跡ファイルはstashされない

ベストプラクティス

推奨事項 説明
stashにはメッセージを付ける git stash push -m "説明"で識別しやすくする
stashは早めに処理する 長期間放置せず、こまめにpopまたはdropする
重要な変更はブランチで管理 stashよりもブランチのほうが安全に管理できる
reflogを定期的に確認 問題発生時の復元ポイントを把握しておく

まとめ

本記事では、git reflogによる履歴復元とgit stashによる一時退避の技術を解説しました。

git reflogは、Gitのすべての操作履歴を記録する安全網です。

  • git reflogでHEADの移動履歴をすべて確認できる
  • git reset --hard HEAD@{n}で過去の状態に復元できる
  • resetやrebase、amendで失われたコミットを取り戻せる

git stashは、コミットしていない変更を一時的に退避する機能です。

  • git stash push -m "メッセージ"で変更を保存
  • git stash listでstash一覧を確認
  • git stash popで復元して削除、git stash applyで復元のみ
  • git stash dropで不要なstashを削除

これらのコマンドを使いこなすことで、変更を失うリスクを大幅に減らし、安心してGit操作を行えるようになります。日常的な開発フローに組み込んで、効率的なバージョン管理を実現しましょう。

参考リンク