Git stash 内容找不回速查:pop 冲突丢改与 fsck 找回 dangling commit
更新时间:2026-10-24。FAQ 速查页,面向
git stash list空空如也、或者stash pop解决完冲突发现改的半截代码没了的同学。先说一个能救命的事实:stash 在 git 内部不是特殊存储,就是普通的 commit 对象(外加一个refs/stash引用指向它)——引用被 drop/clear 删掉之后,commit 对象本身还在.git/objects里躺着,只是没人指它了,变成「dangling commit」。只要还没被git gc回收,用git fsck --unreachable就能把它捞出来。病因收敛在六根轴上:自己手滑stash drop/clear、pop 冲突时解决失误把工作区改没了、在错误的分支/仓库里找 stash、stash 太老已被 gc 回收、把 stash 当成普通 commit 误操作覆盖、以及 ref 文件损坏导致引用丢失。前五种大多有救,找回窗口取决于 gc 节奏。误删的是已提交历史而不是 stash 的,转 tag、reflog 与 bisect。
本文要回答的问题
git stash list是空的,stash 是真的没了吗?stash pop冲突后改动不见了,还能找回吗?git fsck --unreachable输出一大堆哈希,哪个才是我的 stash?- 找回 dangling commit 之后,怎么把它变回可用的代码?
- stash 能放多久,什么操作会把它真正删掉?
一、报错原文与典型现象
stash 丢失的报错不多,更多是「该有的没有」(远端仓库地址以 git.example.com 占位,路径以 /proj/demo 占位):
$ git stash list
(没有任何输出——但昨天明明 stash 过)$ git stash pop
Auto-merging src/app.js
CONFLICT (content): Merge conflict in src/app.js
# 手忙脚乱解决完,发现 stash 里另一部分改动不见了
$ git stash list
(空——pop 成功后引用已被丢弃)$ git stash apply stash@{2}
fatal: 'stash@{2}' is not a valid reference读现象先定两类:「list 为空 / 引用无效」是引用层的问题——ref 没了,但对象大概率还在,走 fsck 找回路线;「pop 冲突后改动缺一块」是工作区层的问题——改动散在冲突解决现场和 reflog 里,走现场勘查路线。两类的共同点:动作要快,gc 和后续操作都在不断冲刷证据,拖得越久找回概率越低。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
| stash list 为空 | 被 drop/clear 或 ref 丢失 | git fsck --unreachable 找 dangling | 捞回 dangling commit 重建 stash |
| pop 冲突后改动缺失 | 冲突解决时覆盖了部分文件 | 看冲突现场与 reflog | 从 dangling commit 逐文件恢复 |
| stash@{n} 无效引用 | 引用层序号已随 pop/drop 位移 | git stash list 看现存编号 | 按现存编号操作;没了走 fsck |
| 在 A 仓库 stash 去 B 仓库找 | stash 是仓库本地状态,不随 push | 确认当前目录与远端分支 | 回原来的仓库目录找 |
| 老 stash 彻底找不到 | 已被 git gc 回收 | fsck 无任何相关 dangling | 接受丢失,以后 stash 转分支 |
| ref 文件损坏 | 异常关机写坏 refs/stash | cat .git/refs/stash 与 logs | 从 reflog 日志重建引用 |
三、排查决策图
下面这张图是标准排查路线,颜色按角色区分:红是报错入口与命中的根因,蓝是排查动作,绿是通过分支,橙是修复动作,紫是防复发沉淀。
读这张图抓分水岭:先确认没犯低级错(走错仓库、看错分支),然后按「引用在不在」分路——引用丢了走对象层找回(fsck 捞 dangling commit),引用在但内容缺走工作区层勘查。两支最后都汇到同一个动作:找回的内容立刻转成 stash 或分支钉住,别再让它处在「没引用指着」的悬空状态。
四、逐条排查展开
第一步永远是排除「走错了地方」。 stash 是仓库本地状态,不随 push/pull 同步,也不跨分支共享可见性混淆——在 A 克隆里 stash 的东西,B 克隆里永远没有。先 pwd 确认在正确的仓库目录,再 git stash list 看一次。多工作区(worktree)共用一个 .git 时 stash 是共享的,这一条不算走错。
引用丢失:fsck 捞 dangling commit 是主线。 git stash drop 和 clear 删的只是 refs/stash 这个引用,commit 对象还在。找回命令:git fsck --unreachable | grep commit,会列出一串悬空 commit。stash 的 commit 有特征——提交信息默认形如 WIP on main: <哈希> <最近提交信息>,且它有两个父提交(第二个父是未暂存改动,只有用了 -u 才有第三个)。逐个 git show <哈希> 看信息与 diff,认出你的那一份。找到后两种用法:直接 git stash apply <哈希> 把改动应用回工作区;或 git stash store -m "找回" <哈希> 重新登记成正式 stash 条目。怕认错可以先 git branch rescue <哈希> 钉个分支慢慢看。
时间窗口:gc 之前都来得及。 unreachable 对象默认约两周后才被 git gc --prune 回收(gc.pruneExpire 控制),期间自动 gc 也不会动它们。但手动 git gc --prune=now 会立即冲掉——找回动作要在跑任何激进 gc 之前做。
pop 冲突丢改:先查引用还在不在。 stash pop 的语义是「apply 成功才 drop 引用」——冲突时 apply 没成功,引用通常还在,git stash list 应该还能看到那条。改动「缺一块」多半是解决冲突时选了某一边覆盖了。处置:git checkout --theirs/--ours 或重新 git stash show -p stash@{0} 看 stash 里的完整 diff,逐文件比对补回。如果 pop 已显示成功(Dropped refs/stash),引用没了,回到上一条的 fsck 路线。
ref 文件损坏:从日志重建。 异常关机可能写坏 .git/refs/stash。看 .git/logs/refs/stash,每行末尾是历史引用值,取最近一条用 git update-ref refs/stash <哈希> 重建即可。
预防好于找回:重要改动别放 stash 里过夜。 stash 本质是「临时搁一下」的机制,引用脆弱、不可同步、无评审痕迹。改动做了一半要放几天,正确姿势是开一个 WIP 分支提交进去——分支有名字、能 push、有 reflog,找回难度和 stash 完全不是一个量级。
fsck 输出太多认不出时的筛选技巧。 老仓库的 unreachable commit 可能上百个,逐个 git show 太慢。两个筛选手段:一是按时间,git show --no-patch --format="%ci %s" <哈希> 先看提交日期和首行信息,和你 stash 的时间对不上的直接跳过;二是批量过滤,把 fsck 输出的哈希灌进循环里统一打印首行,一眼扫过找 WIP on 开头的条目。注意 stash 的提交信息可以被 -m 自定义,如果你 stash 时写过描述,认描述比认时间更准。
弹 pop 前的自保习惯。 对重要的 stash 条目,pop 之前先做两件小事:git stash show -p stash@{n} > /tmp/stash-backup.patch 把完整 diff 落一份到磁盘文件,再执行 pop。这样即使冲突解决翻车,patch 文件还在,大不了 git apply 重来。这个习惯成本三秒钟,换来的确定性远超任何事后找回手段——磁盘上的纯文本 patch 不依赖任何 git 内部结构,永远不会被 gc 碰。
找回动作要快,也要一次做对。 dangling 对象的存活窗口是「下次激进 gc 之前」,你没法预知谁什么时候会跑一次 git gc --aggressive 或者仓库维护脚本顺手 prune。所以一旦意识到 stash 丢了,当天就动手捞,不要拖。捞的时候把怀疑范围内的每个候选哈希都先用 git show 看过再决定怎么用,认定后立刻钉分支或落 patch——悬空状态下的对象每多放一天,风险就多一分。找回的内容也别急着继续改,先提交进分支让引用链完整,再在上面接着干活。
五、规范做法与预防
- stash 只放短期半成品:超过一天要用的改动转 WIP 分支提交。
- stash 必须写描述:
git stash push -m "说清楚是什么",找回时在 fsck 输出里一眼能认。 - 找回后立刻钉引用:捞回的 dangling commit 第一时间 apply 或转分支,别留在悬空状态。
- pop 前留后路:重要 stash 用
apply不用pop,确认无误后再手动 drop。 - 别随手
--prune=now:激进 gc 前git fsck --unreachable看一眼有没有还想留的东西。 - 多克隆多工作区记清位置:stash 不跨仓库同步,在哪 stash 的回哪找。
这一篇在知识体系里的位置
本篇是 Git 排错速查簇的历史找回入口篇,定位「未提交改动的找回」:已提交历史误删的找回看 tag、reflog 与 bisect;stash 基础用法看 rebase、cherry-pick 与 stash;引用指向异常看 Git 引用解析失败速查;仓库对象本身损坏看 Git 对象损坏速查。「引用丢了对象还在、fsck 捞 dangling、找回立刻钉引用」这条主线对一切悬空提交找回通用。
一句话总结
stash 丢了先别慌:drop/clear 删的只是引用,commit 对象还以 dangling 形态躺在对象库里,约两周内 git fsck --unreachable 能捞出来——按 WIP on 提交信息和双父结构认出它,git stash apply <哈希> 应用回工作区或 stash store 重新登记。pop 冲突丢改先看引用还在不在,在就 stash show -p 比对补回。根上的预防就一条:超过一天的半成品改动别放 stash,转 WIP 分支提交——有名字、能 push 的引用才是真正的保险。