git gc不会立即删除手动删掉的分支本身,而是清理其产生的不可达对象,需显式执行git gc --prune=now才能立刻释放空间,因其默认仅清理过期(gc.pruneexpire,默认2周)的不可达对象。

git gc 不会主动删除你手动删掉的分支本身,但它会清理那些分支删掉后变成“不可达”的提交和松散对象——前提是这些对象没被其他引用保护,且满足 prune 条件。
死分支删了但磁盘没变小?因为对象还没被 git gc 标记清除
手动执行 git branch -d feat/login 或 git push origin --delete feat/login 只是删掉 ref(即 .git/refs/heads/feat/login 文件),并不立即删除该分支指向的提交及其依赖的树、blob 对象。这些对象变成“不可达”,但 Git 默认保留它们 2 周(由 gc.pruneExpire 控制),以防误操作需要恢复。
常见错误现象:
- 删完几十个分支,
du -sh .git/objects几乎不变 -
git fsck --unreachable仍能列出大量提交 -
git count-objects -v显示count: N(松散对象数)很高,garbage: M(待回收数)不为 0
真正触发清理的是 git gc 的 prune 阶段,而它默认只清理“过期”的不可达对象。所以必须显式加 --prune=now 才能立刻释放空间。
git gc --prune=now 怎么判断哪些对象该删?靠可达性分析
Git 从所有“根引用”出发做深度遍历:包括 HEAD、所有本地分支(refs/heads/*)、标签(refs/tags/*)、reflog 条目(如果未过期)、以及 refs/stash。所有能从这些起点到达的对象都被标记为“存活”;其余全是垃圾。
关键细节:
- 即使你删了分支,只要某次
git checkout abc123进入过那个提交,它可能还在.git/reflog/HEAD里,就暂时受保护 -
git gc默认不 touch reflog,除非你加--prune-expired或运行git reflog expire --all - 松散对象(每个 commit/tree/blob 单独一个文件)只有在打包(repack)或 prune 阶段才被物理删除
--aggressive 不是“更狠地删”,而是“更用力地压缩”
加 --aggressive 不会让 git gc 删除更多对象,它只影响 repack 阶段的压缩强度:启用更高层级的 delta 压缩,把相似 blob(比如同一文件的多次修改)压得更紧,从而减小 .git/objects/pack/*.pack 文件体积。
但代价明显:
- CPU 和内存占用飙升,尤其对 >1GB 的 pack 文件
- 耗时可能是普通
git gc的 5–10 倍 - 在 CI 或低配机器上容易超时或 OOM
日常清理推荐用 git gc --prune=now;只有仓库体积异常膨胀(比如 .git/objects/pack 占比突然暴涨)才考虑加 --aggressive。
自动 GC 为什么经常“失效”?看这三个配置项
Git 自动触发 git gc 的条件很具体,不是“一有变化就扫”,而是依赖三个配置:
-
gc.auto:默认 6700,表示松散对象数超过该值才触发。很多仓库长期低于此阈值,自动 GC 就 never run -
gc.autoPackLimit:默认 50,指 pack 文件数超过该值才触发 repack。但现代 Git 很少生成多个 pack,这个条件基本失效 -
gc.pruneExpire:默认 "2.weeks.ago",决定prune阶段的过期窗口。如果你希望删完分支立刻释放空间,这个值必须配合--prune=now手动覆盖
最容易被忽略的一点:自动 GC 默认是后台执行(gc.autoDetach=true),你根本看不到日志,也不知道它有没有真跑成功——建议定期用 git count-objects -v 检查松散对象数量,而不是等磁盘报警才行动。











