内容提要
误用 git rm -f 删除已暂存但未提交的文件后,只要未执行 git gc,文件内容仍以 blob 形式保存在 Git 对象库中,可以恢复。恢复的关键是用内容指纹定位悬空 blob:已知哈希时可直接用 git cat-file -p 读回;否则遍历 .git/objects 筛选,或用 git fsck --lost-found 导出后 grep 查找。恢复后应尽快 add 并 commit。排查过程中避免运行 gc,并慎用 -f。
延伸解读
为什么 git status 干净反而说明文件曾进过 Git
文章指出,git rm -f 会同时删除工作区文件和暂存区条目,因此 git status 看不到删除记录。这容易让人误以为文件从未被 add 过。但恰恰相反:status 干净是因为暂存记录被抹掉了,而 git add 时写入对象库的 blob 仍然存在,只是变成没有引用指向的悬空对象。理解这一点,才能避免在排查时过早放弃。
三种恢复方案的本质区别在检索手段
文章给出的三种方案并非恢复原理不同,而是定位 blob 的方式不同:已知哈希时用 git cat-file -p 直接读回;不知道哈希时遍历 .git/objects 逐个用内容指纹匹配;或用 git fsck --lost-found 导出悬空对象后再 grep。前两种依赖 loose 对象,若对象已打进 pack,则需改用 fsck 导出或 --batch-all-objects 全库扫描。选择方案取决于你手里有什么线索。
内容指纹选得越独特,恢复越容易命中
文章反复强调,恢复的关键不是哈希,而是内容指纹。应选择这次改动新加的、只可能出现在目标文件里的字符串,如新类名、函数名或注释。公共类名或跨文件复用的名字会命中大量历史版本和无关文件,噪音极大。同时,过滤条件不宜一开始就叠加太多,应先用最少条件捞全候选,再逐步缩小,否则容易把正确答案层层过滤掉。
排查期间别跑 gc,恢复后尽快 commit 固定
悬空对象平时无害,但 git gc 或 git prune 会清理它们。文章提醒,整个排查恢复过程中千万不要运行 gc,否则对象一旦被清就真找不回来了。恢复内容写回原路径后,应先用 grep 和 wc 验证完整性,然后立即 git add 并 commit,避免再次被清理。若想清理 .git/lost-found 副本,可只删该目录,谨慎使用 gc --prune=now。
Q&A
误执行 git rm -f 后,为什么 git status 里看不到文件删除记录,文件好像从未存在过?
因为 git rm -f 不仅删除了工作区文件,还从暂存区移除了对应条目。git status 依赖暂存区记录来显示变更,暂存记录被删后,status 自然看不到删除。但这并不代表文件没被 git add 过,内容仍以 blob 形式保存在对象库中。
git rm -f 删除未提交文件后,文件内容到底还在不在 Git 里?
还在。git add 时文件内容已作为 blob 对象存入 .git/objects 对象库,git rm -f 只删除工作区文件和暂存区引用,不会删除对象库中的 blob。该 blob 变成悬空对象,只要没执行 git gc 或 git prune,内容就一直保留。
如果知道丢失文件的 blob 哈希,怎么快速恢复?
直接用 git cat-file -p <blob哈希> > <原文件路径> 将内容重定向回原文件即可。这是最快的恢复方式,前提是你手头有该 blob 的哈希。
不知道 blob 哈希,如何通过遍历 .git/objects 目录找回误删的文件?
用 find .git/objects -type f 列出所有 loose object,还原完整哈希后逐个用 git cat-file -t 筛选出 blob,再用 git cat-file -p 读出内容,并用你记得的独特关键词 grep 匹配。命中后打印哈希和大小,确认后写回原路径。
git fsck --lost-found 恢复悬空对象的步骤是什么?
第一步运行 git fsck --lost-found 将悬空对象导出到 .git/lost-found/other/;第二步用独特关键词 grep -rl 筛选候选;第三步按文件类型特征进一步缩小;第四步用 cp 写回原路径并验证内容;第五步恢复并 commit 后删除 .git/lost-found 目录。
恢复过程中有哪些容易踩的坑需要避免?
主要坑包括:把 status 看不到当成文件不存在;过滤条件叠加太多导致正确答案被筛掉;用公共类名/函数名当指纹引入噪音;按首行猜文件类型不可靠;恢复前运行了 git gc 或 git prune 导致悬空对象被清理。应先用最少条件捞全候选,再用独特指纹逐步缩小。
如果目标 blob 已经被打包进 pack 文件,find 和 fsck 都扫不到,怎么办?
可以用 git cat-file --batch-all-objects --batch-check 扫描全库所有对象(包括 pack 中的),筛选出 blob 后逐个用 git cat-file -p 读出内容并用独特关键词 grep 匹配,从而定位目标 blob。
如何避免以后再次发生 git rm -f 误删未提交文件的情况?
改完就尽快 commit,别让重要修改长期停在已 add 未 commit 状态;git rm 报“暂存内容不同”是警告,加 -f 前先想清楚;临时不想提交用 git stash 更安全;删除前先用 git status 和 git diff --cached 确认暂存区内容。