【Git】stashを失ったときの復元方法|reflog・fsckで5パターンから救出する完全ガイド

【Git】stashした内容を失ってしまったときの復元方法 Git

git stash popしたらconflictで混乱してgit stash dropで消してしまった」「git stash listが空っぽ、でもあの変更がない!」——作業途中のstashを失うと血の気が引きますが、焦って追加操作をする前に手を止めれば高確率で救済できます。

stashは内部的に特殊なcommitオブジェクトとして保存されており、dropやpopで「参照から外れた」だけで実体は.git内に残っています。git reflog show stashgit fsck --lost-foundで辿れば、数週間前のstashでも見つかることが多いのです。

この記事では、stash失い5パターンの救出フローと、reflogfsckによる段階的探索、そして同じ事故を繰り返さない予防策まで実務で役立つ形でまとめます。

この記事で学べること

  • stashの実体(特殊commit+stash reflog)と救済原理
  • 5パターンの失い方と復元手順
  • git reflog show stashによる直近救出
  • git fsck --lost-foundでdanglingから探索
  • stash popconflictで混乱した時の整理手順
  • 未追跡ファイルを含めてstashする正しい書き方(-u-a
  • GCと期限:いつまでに救出すべきか
スポンサーリンク

stashの仕組み:失っても救える理由

git stash pushを実行すると、Gitは2〜3個の特殊commitを作りrefs/stashという参照に結びつけます。droppopではこの参照が外されるだけで、commit本体は一定期間.git/objects/に残存します。この特性を利用して、reflogやfsckから失ったstashを辿ることができます。

stashの構造
# stash は特殊なcommit(親が複数)
# parent 1: stashした時点のHEAD
# parent 2: indexの状態(ステージされていた変更)
# parent 3: (オプション) untrackedファイル群

# stash参照の実体を覗く
cat .git/refs/stash
# → 40文字のSHA

# stash履歴
cat .git/logs/refs/stash
# → pushごとに1行記録される

ポイント:stashの「消えた」は正確には「参照だけ消えてオブジェクトは残る」状態です。commit SHAを特定できればgit stash apply <SHA>git cherry-pickで復活できます。時間との勝負になるのはGitのgarbage collection(gc)が走ると実体も消えるため。「失ったかも」と気付いたら即時救出を試みるのが鉄則です。

注意:Gitのgcは通常参照されなくなってから30日以上経過し、git gc手動実行またはgc.autoの閾値に達したときに走ります。直近の失いならほぼ100%救えますが、数ヶ月経つと消えている可能性が出てきます。

stash失い5パターンの早見表

状況 原因 復元難易度
① stash listに残っている 実はまだ見えていなかっただけ
② drop/pop後に消えた 意図/非意図の破棄 中(reflog救出)
③ stash clearで全消去 全stashを一括削除 中〜高(fsck救出)
④ pop中conflictで混乱 適用途中で中断 低(popは成功時のみ削除)
⑤ stashしたと思ったが実は未追跡だった -u-aを付け忘れ 高(OSバックアップから)

ポイント:多くは②drop/pop後の消失git reflog show stashで直前のSHAを特定→git stash apply <SHA>で復活、というフローが王道です。⑤のような「そもそもstashに入っていなかった」ケースはGit外で復元するしかないので、今回の教訓として次回からは-uを付ける習慣を。

STEP 0:まずはstash状態を正確に確認

状態チェック
# 通常のstash一覧
git stash list
# 見つかれば① パターン(pop/applyで戻す)

# stash専用のreflog
git reflog show stash
# または等価の
git log -g --pretty=oneline stash
# drop/pop後も履歴が残る(数週間単位)

# 見つからない場合の最後の手段
git fsck --no-reflogs --lost-found
# dangling commit/blob が列挙される

一度止まって手を動かさないこと

  • 追加のcommit/checkoutはしない(reflog entriesが押し出される)
  • git gcは絶対に手動実行しない
  • まずバックアップ:cp -r .git .git.backup
  • 落ち着いてreflog→fsckの順で探索

ケース①:stash listに残っている(見落とし)

「stashが消えた」と思っても、単にgit stash listで確認していなかっただけ、というケースも多いです。まずは通常の一覧を見て、見つかれば普通にpop/applyで復元できます。

通常のstash操作
# 一覧表示
git stash list
# stash@{0}: On main: wip: fix login
# stash@{1}: WIP on feature: rebase中

# 中身を確認
git stash show -p stash@{0}

# 作業ツリーに適用(stashは残る)
git stash apply stash@{0}

# 適用&stash削除
git stash pop stash@{0}

# ブランチ化して安全に検討
git stash branch feature/recovered stash@{0}

git stash branchのメリット

git stash branch <name> <stash>は、stash適用時点のbaseコミットから新ブランチを作って適用する強力なコマンドです。conflict回避が楽で、成功すればstashは自動削除。「applyでconflictが怖い」ときはbranchに切り替えると安全性が大きく上がります。

ケース②:drop/pop後に消えた(reflogで救出)

git stash dropgit stash popでstashエントリが削除された場合、refs/stashのreflogに履歴が残っているので、そこからSHAを取り出して救出できます。

reflogから救出
# stash専用reflog(pop/drop後も一定期間残る)
git reflog show stash
# 出力例:
# abc1234 stash@{0}: WIP on main: fix login
# def5678 stash@{1}: WIP on feature: authcheck

# 直接apply
git stash apply abc1234

# applyが受け付けない場合、コミットとして扱う
git checkout -b recovered/stash abc1234
# 中身を確認
git log --stat -1
git show -p HEAD

# 必要なら元のブランチにcherry-pick
git switch <original-branch>
git cherry-pick abc1234

reflog entry が出る形式

stash reflogの解釈
# 通常の pop だと
# abc1234 stash@{0}: WIP on main: ...  ← popで外れた

# messageを付けたstashなら検索しやすい
git reflog show stash | grep "fix login"

# 時刻付きで探す
git reflog --date=iso show stash | head -20

# 履歴が古ければglobal reflogも探す
git reflog --all | grep -i "stash\|wip"

ポイント:reflogはgit gcで消える可能性があるため、「数時間〜数日以内」の直近失いが最も成功しやすい。drop直後ならstash@{0}が空気入れ換わり前の参照として残っているので、git reflog show stashだけでほぼ救えます。

ケース③:stash clearで全消去された(fsckで探索)

git stash clearは全stashを削除し、stash reflogも消します。この場合はgit fsckぶら下がりcommit(dangling commit)として残っているstash本体を探します。

fsckでdangling commitを探索
# 参照されていないcommitを列挙
git fsck --no-reflogs --lost-found
# 出力例:
# dangling commit abc1234...
# dangling blob def5678...

# 各コミットの中身を順に確認
git show --stat abc1234
git show abc1234 | head -30

# stashらしいコミットの見分け方:
# - タイトルが "WIP on branch-name" で始まる
# - parentが2〜3個(通常のcommitは1個)

# 候補が大量な時はgrepで絞る
for sha in $(git fsck --no-reflogs --lost-found | grep "dangling commit" | awk '{print $3}'); do
  if git show --no-patch --format="%s" "$sha" | grep -q "^WIP on\|On "; then
    echo "Likely stash: $sha -- $(git show --no-patch --format='%s %cr' "$sha")"
  fi
done
見つけたstashを復活
# 救出用ブランチを作る
git branch recovered/stash abc1234
git switch recovered/stash

# 中身確認
git log --stat -1

# 元ブランチに必要部分だけ取り込む
git switch <original-branch>
git cherry-pick abc1234       # または
git show abc1234 > /tmp/stash.patch
git apply --index /tmp/stash.patch

注意:git fsck --lost-foundは.git内の全ぶら下がりオブジェクトを列挙するので、通常は数百〜数千件見つかります。時刻降順で新しいものから確認するか、「WIP on」で絞り込むのが現実的。メッセージを付けてstashする習慣があれば、検索が格段に楽になります。

dangling commit/blob全般の救出は誤ってmaster/mainを削除したときの復旧方法も類似の手法を使います。

ケース④:pop中のconflictで混乱

git stash pop適用成功時のみstashを削除する仕様です。conflictが出てapply途中で止まっている場合、実はstash entryはまだ残っていることが多い。慌ててdropせず、まず状況確認を。

pop中conflictの対処
# 現状確認
git status
# "You have unmerged paths"

# stash entryが残っているか確認
git stash list
# 残っていれば安心して解消に集中

# 方法A: 解消して続行
git add .
git commit                # マージコミットに準ずる
# または
git stash drop            # stashを消す

# 方法B: やり直す(適用取消)
git checkout .            # ファイル修正を捨てる
git reset HEAD .          # index戻す
# popをやめてapplyで慎重に
git stash apply stash@{0}

ポイント:pop失敗時もstashは残っているので、「消えた」と誤解してdropしないこと。不安ならgit stash branch recovered stash@{0}でブランチ化して別空間で解決するのが最も安全です。

ケース⑤:そもそもstashに含まれていなかった(untracked)

git stash pushはデフォルトでuntrackedファイルを含めません。「stashしたつもり」が実は未追跡ファイルに対して効いておらず、別ブランチに移動した瞬間にファイルが消えた・見えなくなったケース。Gitの管理外なので復元は難しくなります。

-u/-a で含める書き方
# untrackedも含めてstash
git stash push -u -m "wip with untracked"
git stash push --include-untracked -m "..."

# ignoredまで含める
git stash push -a -m "wip with ignored"
git stash push --all -m "..."

# 既定挙動をuntrackedも含めるように(Git 2.24+)
git config --global stash.showIncludeUntracked true

# いま作業ツリーにあるuntrackedを把握
git status -u

警告:Gitが一度も把握していないuntrackedファイルをstashに入れず、ブランチ切り替えやcheckoutでローカルから失った場合、Git経由での復元は不可能です。OSのゴミ箱・IDEのLocal History(VS Code Timeline、JetBrains Local History)・Time MachineやWindows File Historyなどのバックアップから探してください。

untracked全般の整理はuntracked filesの解消方法で詳しく解説しています。

実践シナリオ

シナリオ① drop直後に気付いた

直近救出
git reflog show stash
# abc1234 stash@{0}: WIP on main

git stash apply abc1234
# or
git branch recovered-stash abc1234

シナリオ② stash clearしてしまった

fsckで全探索
git fsck --no-reflogs --lost-found | grep "dangling commit"

# WIPらしきcommitを絞り込み
for sha in $(git fsck --no-reflogs --lost-found | awk '/dangling commit/ {print $3}'); do
  title=$(git show --no-patch --format="%s %cr" "$sha")
  echo "$sha: $title"
done | grep -i "WIP on"

# 目的のstashを救出
git branch recovered/stash <SHA>

シナリオ③ pop途中でconflict、パニックで追加drop

誤ってdropした場合の救出
# reflogで探す(popのapply + drop 両方記録されている)
git reflog show stash

# 見つけたら再apply
git stash apply <SHA>
# conflict解消を今度は慎重に

シナリオ④ untrackedファイルが消えた

Git外バックアップを探す
# VS CodeのTimeline機能
# JetBrainsのLocal History
# Time Machine(Mac)
# Windowsファイル履歴
# これ以降はstash時に必ず-uを付ける
git stash push -u -m "wip"

再発防止:stashを失わない運用

日常運用のコツ

  • 必ず-m "意味のあるメッセージ"を付ける:後から識別可能
  • untrackedファイルも含めたいなら-uを付ける
  • 長く持つならgit stash branchでブランチ化
  • 数日以上のstashはscratch branchへ昇格
  • popよりapply+確認後にdropが安全
  • 不要stashは定期整理、ただしclearは慎重に
  • 長期作業は一時コミットで保全(一時コミット&巻き戻し参照)
安全なstash運用エイリアス
# 必ずメッセージ付きでstash
git config --global alias.ss 'stash push -u -m'
# 使用例
git ss "fixing login flow"

# stash listの整形
git config --global alias.sl 'stash list --date=iso'

# 確認してから適用
git config --global alias.sa '!f() { git stash show -p "$@" | less; }; f'
git sa stash@{0}

やってはいけない落とし穴

「消えた」と誤解してdropを追加実行

pop途中でconflictが出て焦ると「消えた」と勘違いしてgit stash dropで実体も消してしまうパターン。popは成功時しか削除しないので、まずはgit stash listで残存を確認してから次の操作を。

gcを手動実行してオブジェクトを削除

stashを失った直後にgit gcを打つと、dangling commitが削除されて復元不可能になります。救出中は絶対にgcを手動実行せず、自動gcもgc.auto=0で一時的に停止しておくと安全です。

reflogを押し出してしまう大量commit

救出前に追加commit/checkout/resetを大量に実行すると、古いreflog entryが押し出されてstashエントリが消えることがあります。「消えたかも」と気付いたらまず手を止めるのが最重要。

untrackedを-u なしstashして消失

デフォルトのgit stashはuntrackedファイルを対象外。「stashしたはず」の新規ファイルが作業ツリーにない状態でcheckoutすると失われます。新規ファイルがある時は必ず-uを付ける習慣を。

stashを長期保管して忘れる

数ヶ月放置したstashは、内容を覚えていないことが多く結局捨てる羽目に。数日以上保管するならブランチ化git stash branch)して、commitにしてしまう方が管理しやすく、失いにくくなります。

よくある質問

Qgit stash listが空、本当に消えた?
Agit reflog show stashを実行してください。dropやpopで消したstashの履歴が数週間残っているはずです。全滅ならfsckでdangling commitから探します。
Q数ヶ月前のstashを復元したい
Areflogの既定期限(90日)を過ぎていると復元困難。git fsckでdanglingが残っていれば救出可能ですが、gcが走ると消えているので成功率は下がります。古いstashは運試しです。
Qstash apply が “is not a stash-like commit” エラー
Astashは特殊なmerge commit形式なので、通常のcommitとして救出する手があります。git branch recovered/stash <SHA>でブランチ化→中身確認→cherry-pickやgit show <SHA> > patch.diffgit applyでパッチ化も有効。
Qgit stash pop後のconflictでパニック、やり直したい
Agit stash listでまだ残っているか確認。残っていればgit checkout .git reset HEAD .で適用取消し、今度はgit stash applygit stash branchで慎重に適用。
Quntrackedファイルは本当にGitで復元できない?
Aはい、Gitが把握していないファイルは.gitに情報が残らないため不可。OSのゴミ箱・VS CodeのTimeline・JetBrainsのLocal History・Time Machine等のバックアップが唯一の頼り。今後は必ず-uオプションを付けましょう。
Qfsckの結果が数千件で絞り込めない
Astashらしさ(タイトルがWIP onで始まる、parentが2〜3個)で絞り込むのが有効です。メッセージを付けてstashする習慣があれば検索が楽に。記事内のbashスクリプト例を活用してください。
Qgcが自動で走る条件は?
A既定ではgc.auto=6700(ゆるい自動gc)で、追加commit/pullを繰り返すと条件を満たして実行されます。救出中は一時的にgit -c gc.auto=0 <cmd>で無効化できます。

関連記事

まとめ

  • stashは特殊なcommitオブジェクトとして.gitに残り、失っても救えることが多い
  • まずgit stash listgit reflog show stashgit fsckの順で探索
  • popのconflictは適用成功時のみ削除——慌てずlist確認
  • untrackedファイルは-uなしだとそもそもstashに入らない
  • 失った直後が最も成功率高、gc実行前に救出
  • 予防:メッセージ付きstash(-m)+-u+長期はbranch化
  • 長期作業はstashより一時コミット+scratch branch運用が安全

stash失いは焦りがちですが、Gitの内部構造を知れば復元確率は高い問題です。「消えた」と感じたらgit reflog show stashから始め、段階的にfsckまで進めば直近の失いはほぼ確実に救えます。日頃からメッセージ付きstash・-u利用・長期はブランチ化の運用を徹底し、「消えたかも」の事態自体を減らしていきましょう。