git gc无效主因是引用未清理,需先清reflog、远程分支及未合并分支,再执行git gc --prune=now --aggressive;大文件定位应使用git filter-repo --analyze而非du或ls-tree。

Git仓库变大不是因为“没删干净”,而是历史对象还在被引用——不清理引用,git gc 就不敢删;不重写历史,大文件就永远躺在 .git/objects 里。
为什么 git gc 有时没用?
执行 git gc --prune=now 后仓库体积几乎不变,常见原因是:引用日志(reflog)、远程分支、未合并的本地分支,仍在“保护”那些旧对象。Git 的垃圾回收只清理真正悬空(unreachable)的对象。
-
git reflog expire --expire=now --all必须先运行,否则 reflog 默认保留90天,锁住大量历史提交和 blob -
git remote prune origin清理已删除但本地仍存的远程跟踪分支(如origin/feature-dead) -
git branch -d删除本地已合并分支,git branch -D强制删未合并分支(确认无用再操作) - 执行完上述再跑
git gc --prune=now --aggressive,才能释放出明显空间
怎么定位真正占空间的大文件?
别靠 du -sh .git/objects 猜——它显示的是所有对象总和,包括已被打包但未 prune 的冗余 blob。真实元凶往往藏在历史快照里。
- 用
git rev-list --objects --all | grep "$(git verify-pack -v .git/objects/pack/*.idx 2>/dev/null | sort -k3 -n | tail -5 | awk '{print$1}')"找出体积最大的5个 blob 对应的路径 - 更稳的方式是用
git filter-repo --analyze(推荐替代filter-branch),它会生成reports/目录,列出每个文件的历史大小分布 - 注意:
git ls-tree -r --long HEAD只看当前树,对历史大文件完全无效
删历史文件后为什么推送失败?
因为重写历史(如用 git filter-repo 或 git filter-branch)改变了所有相关提交的 SHA-1,远程仓库拒绝非快进(non-fast-forward)推送。
- 必须用
git push --force-with-lease --all和git push --force-with-lease --tags,比裸--force更安全,能防止覆盖他人新提交 - 所有协作者需在你推送后执行:
git fetch origin && git reset --hard origin/main(假设主干是 main),不能git pull,否则会引入重复提交 - 如果团队用 PR/MR 流程,务必提前通知所有人暂停提交,并在清理完成后统一 rebase 到新基线
bfg 和 git filter-repo 怎么选?
git filter-branch 已被官方弃用,bfg-repo-cleaner 虽快但功能收敛(比如不支持按路径正则批量删、不支持重写 commit message),而 git filter-repo 是目前最可靠的选择。
- 删指定文件:
git filter-repo --path path/to/big.log --invert-paths - 删所有
*.zip:git filter-repo --path-glob "**/*.zip" --invert-paths - 注意:它默认会重写所有引用(包括 refs/original/),无需手动清理残留;但要求 Python 3.8+,且不能在 Windows Git Bash 下直接运行(建议 WSL 或原生终端)
- 执行后务必检查
.git/filter-repo/下的报告,确认目标文件是否真的从所有提交中消失
最易被忽略的一点:清理完别忘了更新 .gitignore 并提交——否则下次 git add 还会把同名大文件又塞回去,等于白干。


















