
























虽然 filter-branch 已经重写了历史,但旧的损坏对象仍然以"松散对象"(loose object)的形式留在硬盘上。这些对象不被任何分支引用,但 git 仍能看到它们并报错。
具体来说,补齐缺失对象后 git gc 仍然报错。运行 git fsck 检查,报错的内容变了——不再是缺少 =4de66b3=,而是:
损坏的链接来自于 tree ab43ad01 → tree d0c6a58 缺失 tree d0c6a58
这说明 d0c6a58 这个 tree 仍在硬盘上,且它引用的子 tree 本来应该是 4de66b3=(就是之前缺失的那个),但现在这个引用关系断裂了。由于 =d0c6a58 本身还是一个可读的对象,它就成了新的"损坏源头"。
检查它是不是松散对象:
$ ls .git/objects/d0/c6a58cd6ccac7e967a1ec1923b86b6098c37cf .git/objects/d0/c6a58cd6ccac7e967a1ec1923b86b6098c37cf
文件存在,说明它是松散对象。注意路径的规律:git 取对象哈希的前两个字符 d0 作为目录名,后 38 个字符作为文件名。这样每个目录下最多只有 256 个文件(00~ff),避免单个目录的文件数量爆炸。再检查它是否还被当前分支引用:
$ git rev-list --objects main | grep d0c6a58 # (无输出)
无输出,说明 main 分支已经不需要它了。=rev-list –objects main= 的作用是遍历 main 分支能到达的所有对象(包括提交、目录树、文件),逐一列出它们的哈希。如果目标哈希出现在这个列表里,说明某个提交还用着它,删了会破坏历史。没输出就说明安全,可以删。
确认安全后删除:
$ rm -f .git/objects/d0/c6a58cd6ccac7e967a1ec1923b86b6098c37cf
然后重复这套流程——再跑 =git fsck=,看下一层是谁:
损坏的链接来自于 tree b46bf016 → tree ab43ad01 缺失 tree ab43ad01
这次 ab43ad01 浮出来了。它被另一个 tree 引用,自身也是个松散对象。
ls .git/objects/ab/43ad01abfddc06d9ae24d81d9fe96a8007e3dd ✓ 存在 git rev-list --objects main | grep ab43ad01 ✓ 不被 main 引用
删掉它:
$ rm -f .git/objects/ab/43ad01abfddc06d9ae24d81d9fe96a8007e3dd
如此反复,每删一层就跑 git fsck 看上一层,直到 git fsck 不再报错。
总结这个"顺藤摸瓜"的过程:
git fsck 告诉你哪个对象是"损坏的链接"的源头git rev-list --objects main | grep <哈希> 确认不被任何分支需要ls .git/objects/xx/xxx... 确认它是松散对象
注意这里的删除用的是直接 rm -f 删文件,而不是用 git 命令。因为松散对象就是硬盘上实实在在的文件,直接删掉文件系统层面的文件就可以了。但对于 reflog、分支引用这类 git 的"元数据",就要用 git 提供的命令(如 git reflog expire=)来操作,不能直接去删 =./git/ 里的文件。
#+END_EXAMPLE
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。