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

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

「さっき git stash したはずの変更が消えた?」——そんなときでも、慌てなければ高確率で復元できます。
stashは内部的には“特別なコミット”として保存されており、reflogや“ぶら下がり(dangling)コミット”の探索で辿り着ける場合が多いからです。
本記事では、状況別に「見えるstashをそのまま戻す」「drop/popで消えたstashを掘り起こす」「最終手段として迷子コミットを救出する」の順で復元手順を解説します。

まず確認:本当にstashは存在しない?

最初に通常のリストとstash専用のreflogを確認します。stash@{n}が見つかれば、そのまま適用すればOKです。

# ふつうの一覧
git stash list

# 詳細・移動履歴(reflog)
git reflog show stash     # または: git log -g stash

# 中身の要約(差分の見出し)
git stash show stash@{0}

# 差分をパッチで確認
git stash show -p stash@{0}

見つかったstashは、作業ツリーへ適用(元の変更を復活)するか、別ブランチとして退避してから検討します。

# そのまま適用(stashは残る)
git stash apply stash@{0}

# 適用してから削除(成功時のみ削除される)
git stash pop stash@{0}

# 安全に保つなら、まずブランチを切る
git switch -c restore/stash-$(date +%Y%m%d-%H%M)
git stash apply stash@{0}

ケースA:誤って stash drop や stash pop で消した

多くの場合、refs/stashのreflogに直前のstashコミットのハッシュが残っています。まずはそこから救出します。

# 消した直後でも、まずreflogを確認
git reflog show stash

# 目的のエントリのハッシュ(例: abcdef1)を拾う
# そのコミットを直接適用できる場合がある
git stash apply abcdef1

# うまくいかない時は救出用ブランチを切って中身を確認
git branch recovered-stash abcdef1
git switch recovered-stash
git show --stat
git show -p | less

git stash apply <ハッシュ>が受け付けられない環境では、救出用ブランチ上で必要な変更だけを取り出します。
具体的には、パッチを作って現在の作業ブランチへ適用するのが安全です。

# recovered-stash 上でパッチを作成
git show abcdef1 > ../stash.patch

# 作業ブランチに戻って適用(indexも更新)
git switch <work-branch>
git apply --index ../stash.patch
git commit -m "Restore lost stash (abcdef1)"

ケースB:reflogにも出ない/より深い探索(danglingの救出)

参照から外れた“ぶら下がりコミット(dangling commit)”として残っている可能性があります。git fsckで総当たり探索します。

# ぶら下がりオブジェクトの列挙
git fsck --no-reflogs --lost-found | grep commit

# 候補ハッシュ(例: 1234abc)を順に中身確認
git show --stat 1234abc
git show -p 1234abc | less

# stashらしいコミット("WIP on ..." など)を見つけたら救出
git branch recovered-stash-2 1234abc
git switch recovered-stash-2
# 以降はA章と同様にパッチ経由で取り込み

候補が多い場合は、最近日時のものから確認すると効率的です。テキスト検索で「WIP」やファイル名を当てるのも手です。

ケースC:stash pop 後にコンフリクトで混乱した

stash pop適用が成功したときだけ stashを削除します。コンフリクトが残っている間はエントリが消えないのが基本挙動です。
解消できない場合は一旦中断し、やり直します。

# コンフリクトを解消して続行
git add .
git commit               # マージに準ずる流れ

# やり直したい場合(適用を取り消す)
git merge --abort        # 適用途中がマージ状態なら
# その後、applyに切り替えて慎重に進める
git stash apply stash@{0}

ケースD:そもそもstashに含まれていなかった(未追跡や無視ファイル)

git stashは既定では未追跡(untracked)や無視(ignored)ファイルを含めません。次回からはオプション付きで退避します。

# 未追跡も含める
git stash push --include-untracked -m "wip with untracked"
# 無視ファイルも含める(より広い)
git stash push --all -m "wip with ignored"

すでに失ってしまった未追跡・無視ファイルは、Git外にバックアップがない限り復元は困難です(Gitが管理していないため)。

ケースE:rebase/merge中に自動退避された変更の取り出し

rebase.autoStashやコマンドの--autostashで自動退避された変更は、完了時に自動適用されます。失敗時はstashに残るので、通常の手順で回収します。

# 自動stashの履歴もふつうのstashとして見える
git stash list
git reflog show stash

復元後の整え方(安全な取り込み)

取り戻した差分は、いきなり作業ブランチへ直適用せず、救出用ブランチで内容を確認してから選択的に取り込むと事故が減ります。
git add -pでハンク単位に選別すると、不要な変更が混ざりません。

# 選択的にステージ
git add -p
git commit -m "Restore selected changes from lost stash"

再発防止の設定と運用

スタッシュ紛失は「どれが何か分からない」「含まれていなかった」が主因です。メッセージを必ず付け、必要なら未追跡も含めましょう。
長めの作業は軽量ブランチで管理する運用に切り替えると、復元容易性が段違いに上がります。

# メッセージ付きで退避(推奨)
git stash push -m "wip: fix login flow"

# pull/rebase時の自動退避(衝突回避に有効)
git config --global rebase.autoStash true

まとめ

失ったstashの復元は、①通常のstash listreflogで見つけて適用、②drop/pop後はreflog show stashからハッシュを拾って救出、
③見つからない場合はgit fsckでdanglingを探索、の順で進めるのが定石です。
復元後は救出用ブランチで内容を吟味し、今後はメッセージ付きstashや軽量ブランチ運用で“消えたかも”を未然に防ぎましょう。