git gc 经常无效是因为它只清理不可达对象,若未先删除分支、标签或用 git filter-repo 清理大文件使对象不可达,则体积不变;必须配合 --prune=now 才能立即回收。

git gc 为什么经常没用?
git gc 本身不删除任何你还能访问到的对象——它只清理“不可达”的垃圾,比如被 rebase 或 filter-branch 改写后残留的旧提交、悬空 blob。如果你没先做历史重写,直接跑 git gc,很可能体积纹丝不动。
常见错误现象:git count-objects -vH 显示 count: 0,但 .git/objects/pack 还是几百 MB;或者 git gc --aggressive 跑完,du -sh .git 几乎没变。
- 必须先让对象“不可达”:删分支、删 tag、用
git filter-repo或BFG清大文件,再git gc -
--prune=now是关键参数,否则 Git 默认只 prune 2 周前的对象 - 裸仓库(
--mirror)上运行更安全,避免工作区干扰
怎么安全删掉历史里的大文件?
别用 git filter-branch——它已弃用、慢、易出错。现在标准做法是用 git filter-repo(Python 工具,需 pip install git-filter-repo)。
使用场景:发现 .git/objects/pack 里有几十 MB 的二进制文件(比如误提交的 dist.zip 或 node_modules.tar.gz),且它藏在老提交里。
- 先定位:运行
git verify-pack -v .git/objects/pack/*.idx | sort -k3 -n | tail -20,看最大的几个 object ID - 反查路径:
git show --name-only <object-id>^! | head -20</object-id>(注意是^!,不是~1) - 删文件:
git filter-repo --path dist.zip --invert-paths --force - 删完立刻
git reflog expire --expire=now --all && git gc --prune=now --aggressive
克隆时就轻量:--filter 和 --depth 到底选哪个?
如果你只是编译或读代码,根本不需要历史——那就别克隆历史。但 --depth 1 和 --filter=blob:none 不是同一回事,选错会白费带宽。
性能影响:git clone --depth 1 仍会下载全部文件内容(只是单个 commit);而 --filter=blob:none 连文件内容都不下,只下目录树,后续 git checkout 才按需拉取。
- 要最新代码且不切版本 →
git clone --depth 1 https://github.com/php/php-src.git - 要某个 tag(如
php-8.3.0)且网络差 →git clone --filter=blob:none https://github.com/php/php-src.git,再git checkout php-8.3.0 - 完全不要 Git 功能 → 直接
curl -L https://github.com/php/php-src/archive/refs/tags/php-8.3.0.tar.gz | tar -xzf -(45MB,无.git)
瘦身后的仓库能直接 push 吗?
不能。历史重写(filter-repo、BFG)会改变所有后续 commit 的 SHA-1,远程仓库仍保留旧链。强行 git push --force 会把旧对象留在服务器上,别人 pull 时还可能把它们又拉回来。
正确做法是:清空远程,重推干净历史。但前提是团队同步停写、通知所有人重 clone。
- GitHub/GitLab 上删掉原仓库,新建同名空库
- 本地执行
git remote set-url origin <new-url></new-url> git push --all && git push --tags- 提醒所有协作者:旧 clone 失效,必须
git clone新地址
最容易被忽略的是:即使你本地瘦了,只要还有人从旧历史 pull 过,那些大文件就还在服务器 pack 中,等着被重新传播。瘦身不是单机操作,是协作契约。











