注册

老板,我真干活了...


辛苦写了3天的代码,突然一下,全部消失了


我说我每次都git add .了,但是他就是消失了


你会相信我吗


image.png





这次真的跳进黄河都洗不清,六月都要飞雪了


一个安静的夜晚,主包正在和往常一样敲着代码,最后一个优化完成以后执行了git add .


看着长长的暂存区,主包想是时候git commit -m


变故就发生在一瞬间,commit校验返回失败,伴随着电脑的终端闪烁了一下,主包的 所 有 改 动都消失了,文件内容停留在上次的提交...


老板,你相信我吗?我真的干活了


image.png


不过幸好,我的Gemini在我的指导下一步一步帮我把3天的劳动成果还原了,下面记录一下整个事件过程




首先。我已经执行了 git add .,这意味着 Git 已经将这些文件的内容以「blob 对象」的形式保存到本地 .git/objects 目录中,这些数据就不会凭空消失。


解决步骤

第一步:不要乱动

立即停止在仓库中执行任何覆写的操作!避免覆盖磁盘上的 blob 对象



  • ❌ 不要执行 git reset --hard/git clean -fd/git gc/git prune
  • ❌ 不要往仓库目录写入新文件

第二步:检查 Git 状态

git status查看当前暂存区的状态,如果暂存区有文件的话可以通过get checkout -- .进行恢复,可是我没有了,我的暂存区和我的脑袋一样空空


image.png


第三步:拉取悬空文件

如果 git status 显示「Working Tree Clean」,且 git checkout -- . 没效果,说明暂存区被清空,但 Git 仍保存了 git add 时的 blob 对象
需通过 git fsck --lost-fond 找回


该指令会扫描 .git/objects/ 目录,找出所有没有被任何分支/标签引用的对象,列出 悬空的 commits、blobs、trees(优先找最新的commit)


image.png


第四步:验证每个commit

执行 git show --stat <commit ID> 可以验证该悬空 commit 内容


image.png



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

image.png



  • 这里会列出该 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 个核心优势:



  1. 一键恢复所有文件:commit 关联了 root tree,能直接拿到「所有文件的路径 + 对应的 blob 内容」,执行 git checkout <commit ID> -- . 就能把所有文件恢复到工作区,不用逐个处理 blob;
  2. 不用手动匹配路径:如果只恢复 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 天的悬空对象」,所以恢复前务必:



  1. 不要执行 git gc/git prune
  2. 恢复完成后,尽快执行 git commit 让对象被 HEAD 引用,避免后续被清理;
  3. 如果暂时没恢复完,可执行 git fsck --full 检查所有悬空对象,确认未被清理。

总结来说:优先找悬空 Commit(一键恢复)→ 其次用 Tree 匹配 Blob 路径 → 最后批量导出 Blob 手动匹配,这是最高效的恢复路径


作者:呵阿咯咯
来源:juejin.cn/post/7581678032336519210

0 个评论

要回复文章请先登录注册