老板,我真干活了...
辛苦写了3天的代码,突然一下,全部消失了
我说我每次都git add .了,但是他就是消失了
你会相信我吗
这次真的跳进黄河都洗不清,六月都要飞雪了
一个安静的夜晚,主包正在和往常一样敲着代码,最后一个优化完成以后执行了git add .
看着长长的暂存区,主包想是时候git commit -m了
变故就发生在一瞬间,commit校验返回失败,伴随着电脑的终端闪烁了一下,主包的 所 有 改 动都消失了,文件内容停留在上次的提交...
老板,你相信我吗?我真的干活了
不过幸好,我的Gemini在我的指导下一步一步帮我把3天的劳动成果还原了,下面记录一下整个事件过程
首先。我已经执行了 git add .,这意味着 Git 已经将这些文件的内容以「blob 对象」的形式保存到本地 .git/objects 目录中,这些数据就不会凭空消失。
解决步骤
第一步:不要乱动
立即停止在仓库中执行任何覆写的操作!避免覆盖磁盘上的 blob 对象
- ❌ 不要执行
git reset --hard/git clean -fd/git gc/git prune; - ❌ 不要往仓库目录写入新文件
第二步:检查 Git 状态
git status查看当前暂存区的状态,如果暂存区有文件的话可以通过get checkout -- .进行恢复,可是我没有了,我的暂存区和我的脑袋一样空空

第三步:拉取悬空文件
如果 git status 显示「Working Tree Clean」,且 git checkout -- . 没效果,说明暂存区被清空,但 Git 仍保存了 git add 时的 blob 对象
需通过 git fsck --lost-fond 找回
该指令会扫描 .git/objects/ 目录,找出所有没有被任何分支/标签引用的对象,列出 悬空的 commits、blobs、trees(优先找最新的commit)
第四步:验证每个commit
执行 git show --stat <commit ID> 可以验证该悬空 commit 内容

- 如果「提交信息 / 时间 / 作者」匹配你刚才中断的 commit → 这就是包含你所有改动的快照;
- 如果显示
initial commit或旧提交信息 → 这是历史悬空 commit,换列表里下一个时间最新的 commit ID 重试。

- 这里会列出该 commit 中所有改动的文件路径 + 行数变化 → 如果能看到你丢失的文件(如
src/App.js),说明找对了; - 如果显示
0 files changed→ 这个 commit 是空的,换其他 commit ID 重试。
第五步:恢复提交
方式 1:仅恢复文件到工作区(推荐,不修改 HEAD)
git checkout commitId -- .
方式 2:#### 直接将 HEAD 指向该 commit(完成提交)
git reset --hard commitId // 等同于完成当时的 commit 操作
到这里基本就已经恢复了,可以check一下更改的文件,如果不全可以继续执行checkout进行恢复,如果已经完成了就尽快commit以防发生别的变故啦~
最后,还是简单讲解一下为什么优先恢复悬空commit,commit、tree、Blob的区别
核心结论先摆清楚
| 对象类型 | 中文名称 | 核心作用 | 类比(便于理解) | 能否直接恢复你的文件? |
|---|---|---|---|---|
| Blob | 数据对象 | 存储单个文件的内容(无路径) | 一本书里的某一页内容 | 能,但需匹配原文件路径 |
| Tree | 树对象 | 存储目录结构(文件 / 子目录映射) | 一本书的目录(章节→页码) | 不能直接恢复,仅辅助找路径 |
| Commit | 提交对象 | 存储完整的提交快照(关联 tree + 作者 / 时间 / 信息) | 一本书的版本记录(含目录 + 修改说明) | 最优选择,一键恢复所有文件 |
一、逐个拆解:悬空 blob/commit/tree 到底是什么?
Git 仓库的所有内容(文件、目录、提交记录)最终都会被存储为「对象」,存放在 .git/objects 目录下;「悬空」意味着这些对象没有被任何分支 / 标签 / HEAD 引用(比如 commit 中断、reset 后、删除分支等),但只要没执行 git gc(垃圾回收),就不会消失。
1. 悬空 Blob(数据对象)—— 「只存内容,不管路径」
- 本质:Git 中最小的存储单元,仅保存「单个文件的原始内容」,不包含文件名、路径、修改时间等信息;
- 举例:你修改了
src/App.js并执行git add .,Git 会把App.js的内容打包成一个 blob 对象(比如你看到的ec0529e46516594593b1befb48740956c8758884),存到.git/objects里; - 悬空原因:执行
git add后生成了 blob,但 commit 中断 / 执行git reset清空暂存区,导致这个 blob 没有被 tree/commit 引用; - 恢复特点:能拿到文件内容,但不知道原文件路径(比如你只知道 blob 是一段 JS 代码,却不知道它原本是
src/App.js还是src/Page.js)。
2. 悬空 Tree(树对象)—— 「只存目录结构,不存内容」
- 本质:描述「目录层级 + 文件映射关系」,相当于「文件路径 ↔ blob ID」的对照表,也能包含子 tree(对应子目录);
- 举例:一个 tree 对象可能记录:
src/ (子tree) → tree ID: bb0065eb...
package.json → blob ID: e90a82fe...
src/App.js → blob ID: ec0529e4... - 悬空原因:Tree 是 commit 的「子对象」,如果 commit 变成悬空(比如 reset 后),对应的 tree 也会悬空;
- 恢复特点:仅能看到「哪些 blob 对应哪些路径」,但本身不存储文件内容,需结合 blob 才能恢复完整文件。
3. 悬空 Commit(提交对象)—— 「完整的提交快照」
- 本质:Git 中最高级的对象,是「一次提交的完整记录」,包含:
- 指向一个 root tree(根目录的 tree 对象)→ 能拿到整个项目的目录结构 + 所有 blob;
- 作者、提交时间、提交信息;
- 父 commit ID(如果是后续提交);
- 举例:你执行
git commit -m "修改App.js"时,Git 会生成一个 commit 对象,关联 root tree(包含所有文件路径 + blob),记录你的操作信息; - 悬空原因:commit 执行中断、
git reset --hard后原 HEAD 指向的 commit 无引用、删除分支后分支上的 commit 无引用; - 恢复特点:✅ 最优选择!通过一个 commit 对象,能一键恢复「该提交时刻的所有文件(路径 + 内容)」,不用手动匹配 blob 和路径。
二、为什么你该优先恢复「悬空 Commit」?
你之前执行了 git add . + 尝试 git commit,大概率 Git 已经生成了 commit 对象(只是没被 HEAD 引用,变成悬空)—— 恢复 commit 有 2 个核心优势:
- 一键恢复所有文件:commit 关联了 root tree,能直接拿到「所有文件的路径 + 对应的 blob 内容」,执行
git checkout <commit ID> -- .就能把所有文件恢复到工作区,不用逐个处理 blob; - 不用手动匹配路径:如果只恢复 blob,你需要逐个查看 blob 内容,再手动命名 / 放到原路径;而 commit 直接包含路径信息,恢复后文件路径和名称完全和丢失前一致。
三、实操场景:不同悬空对象该怎么用?
场景 1:有可用的悬空 Commit(优先选)
# 1. 找时间最新的悬空 commit
git fsck --lost-found | grep 'dangling commit' | awk '{print $3}' | while read c; do
echo "$c | $(git log -1 --format='%ai' $c)"
done | sort -k2 -r
# 2. 验证该 commit 包含你的文件
git show --stat <最新的commit ID>
# 3. 一键恢复所有文件到工作区
git checkout <commit ID> -- .
场景 2:只有悬空 Blob/Tree(无可用 Commit)
# 1. 先通过 tree 找「blob ID ↔ 文件路径」的映射
git ls-tree -r <tree ID> # 列出该 tree 下的所有文件路径+blob ID
# 2. 按路径恢复 blob 内容
git cat-file -p <blob ID> > <原文件路径> # 比如 git cat-file -p ec0529e4 > src/App.js
场景 3:只有悬空 Blob(无 Tree/Commit)
只能批量导出 blob,通过内容匹配原文件:
mkdir -p recover
for blob in $(git fsck --lost-found | grep 'dangling blob' | awk '{print $3}'); do
git cat-file -p $blob > recover/$blob
# 自动补文件后缀(如 .js/.json)
file_type=$(file -b --mime-type recover/$blob | awk -F '/' '{print $2}')
[ "$file_type" != "octet-stream" ] && mv recover/$blob recover/$blob.$file_type
done
四、关键提醒:避免悬空对象被清理
Git 的 git gc(垃圾回收)默认会清理「超过 14 天的悬空对象」,所以恢复前务必:
- 不要执行
git gc/git prune; - 恢复完成后,尽快执行
git commit让对象被 HEAD 引用,避免后续被清理; - 如果暂时没恢复完,可执行
git fsck --full检查所有悬空对象,确认未被清理。
总结来说:优先找悬空 Commit(一键恢复)→ 其次用 Tree 匹配 Blob 路径 → 最后批量导出 Blob 手动匹配,这是最高效的恢复路径
来源:juejin.cn/post/7581678032336519210


