21、Git gc与性能优化:垃圾回收、仓库瘦身、大文件处理、git gc配置

说实话,Git 用久了,仓库会变得臃肿。你可能会发现 git status 变慢了,git log 要等好几秒。别急,这很正常。Git 的设计哲学是「快照」而非「差异」,每次提交都存一份文件快照。时间一长,那些被删除、被覆盖的旧对象就堆积在 .git/objects 里,成了垃圾。

我自己的一个项目,跑了两年,.git 文件夹飙到了 1.2GB。后来做了一次彻底的 gc,直接瘦身到 180MB。嗯,今天我们就聊聊怎么给 Git 仓库「减肥」。

1. 垃圾回收:git gc 到底干了什么?

git gc 是 Git 自带的垃圾回收命令。它不会删你的代码,它只清理那些「不再被任何分支、标签、reflog 引用的对象」。说白了,就是帮你把仓库里没用的历史包袱扔掉。

具体来说,git gc 会做这几件事:

  • 压缩松散对象:把 .git/objects 里零散的小文件打包成 .pack 文件。
  • 删除不可达对象:那些被 git resetgit rebase 抛弃的旧提交,会被清理掉。
  • 优化 reflog:清理过期的 reflog 条目(默认 90 天)。
  • 更新索引:重建包文件的索引,让查找更快。

核心原则:gc 只清理「安全」的垃圾。它不会动你当前分支、标签、以及 90 天内的 reflog 记录。所以放心跑,不会丢数据。

我习惯在每次发布大版本后,手动跑一次 git gc --aggressive。这个 --aggressive 参数会让 Git 花更多时间做深度压缩,效果更好。不过要注意,大仓库跑起来可能有点慢,建议在空闲时执行。

# 常规清理
git gc

# 激进压缩(推荐定期执行)
git gc --aggressive

# 查看 gc 统计信息
git gc --verbose

2. 仓库瘦身:从 1GB 到 100MB 的实战

有一次,我发现同事的仓库 .git 文件夹比源码还大。查了一下,原来是有人不小心提交了一个 500MB 的日志文件,然后又删除了。但 Git 里还留着那个对象的快照。这就是典型的「误提交大文件后删除,但仓库没瘦下来」的场景。

这时候光靠 git gc 是不够的。因为那个大文件虽然不在当前分支,但它还在 reflog 里,gc 默认不会清理。你需要用 git prunegit repack 来强制清理。

我的做法是:

  1. 先备份仓库(重要!)
  2. 清空 reflog:git reflog expire --expire=now --all
  3. 强制清理所有不可达对象:git prune --expire=now
  4. 重新打包:git repack -a -d -f --depth=250 --window=250
  5. 最后再跑一次 git gc --aggressive

警告git prune --expire=now 会立即删除所有不可达对象。如果你有未合并的分支或想保留的历史,请先确认。我一般只在确认「那个大文件确实没用了」之后才用这招。

你想想看,如果只是误提交了一个大文件,其实有更温和的办法:用 git filter-branchgit filter-repo 从历史中彻底抹掉它。但那是另一个话题了,今天我们聚焦 gc。

3. 大文件处理:Git LFS 与 gc 的配合

Git 本身不适合存大文件。超过 100MB 的二进制文件,每次修改都会让仓库膨胀。我见过一个游戏项目,美术资源用 Git 管理,结果 .git 文件夹 5GB,clone 一次要半小时。

解决方案是 Git LFS(Large File Storage)。它把大文件替换成指针文件,真正的文件内容存在远端服务器。但 LFS 也有自己的 gc 机制,需要配合使用。

场景 推荐做法 说明
普通代码仓库 定期 git gc --aggressive 每季度一次,或发布大版本后
含大文件的仓库 启用 Git LFS + git lfs prune LFS 的 gc 清理本地缓存的大文件旧版本
误提交大文件后 git filter-repo + git gc 从历史中彻底移除,再 gc 瘦身

如果你用了 Git LFS,记得在 git gc 之后跑一下 git lfs prune。它会清理本地 LFS 缓存中那些不再被任何提交引用的旧版本大文件。我曾经有个项目,LFS 缓存占了 800MB,跑完 prune 只剩 120MB。

# 清理 LFS 本地缓存
git lfs prune

# 查看 LFS 缓存占用
git lfs ls-files --size

4. git gc 配置:让自动清理更智能

Git 其实会自动运行 gc。当你执行某些命令(如 git pushgit pull)时,Git 会检查是否需要自动 gc。但默认配置比较保守,我建议根据项目规模调整。

下面是我常用的配置,你可以直接复制到 ~/.gitconfig 或项目级配置中:

[gc]
    # 自动 gc 的触发条件:松散对象超过 7000 个
    auto = 7000
    # 自动 gc 时是否显示详细信息
    autoDetach = false
    # 保留 reflog 的天数
    reflogExpire = 90
    # 保留远程 reflog 的天数
    reflogExpireUnreachable = 30
    # 打包时使用的线程数(0 表示自动)
    aggressiveWindow = 250
    # 激进压缩的深度
    aggressiveDepth = 250

我的经验:对于中小型项目(< 500MB),默认配置就够了。但对于大型 monorepo 或长期项目,我建议把 gc.auto 调大到 10000,避免频繁自动 gc 影响开发流程。同时把 gc.aggressiveWindowgc.aggressiveDepth 都设为 250,这样手动跑 git gc --aggressive 时压缩效果更好。

还有一个容易被忽略的参数:gc.pruneExpire。它控制不可达对象在被清理前保留的时间,默认是 2 周。如果你确定不需要保留任何历史垃圾,可以设为 now,但我不建议这么做——万一你后悔了,还有两周的后悔药。

# 查看当前 gc 配置
git config --global --get-regexp gc.*

# 设置 prune 保留时间为 1 天(保守做法)
git config --global gc.pruneExpire "1.day.ago"

5. 知识体系:一张图看懂 Git gc 与性能优化

下面这张 SVG 图总结了本章的核心逻辑。你可以看到,gc 不是孤立的一步,它和 reflog、prune、repack、LFS 环环相扣。

Git gc 与性能优化知识体系 git gc 垃圾回收:清理不可达对象 仓库瘦身:prune + repack 大文件处理:Git LFS + lfs prune gc 配置:auto / aggressive / pruneExpire reflog 过期清理 pack 文件深度压缩 最终目标:更快的 clone / fetch / log / status 更小的 .git 体积,更流畅的团队协作

你看,gc 不是万能的。它解决的是「正常使用产生的垃圾」。如果你误提交了大文件、或者想从历史中彻底删除敏感信息,需要结合 filter-repoBFG Repo-Cleaner。但日常维护中,git gc 加上合理的配置,已经能解决 80% 的性能问题。

最后分享一个我自己的小习惯:每次 clone 一个新仓库,我会先跑一次 git gc --aggressive。虽然第一次会慢一点,但后续的所有操作都会变快。尤其是团队里有人喜欢频繁 git push --force 的,gc 能帮你把那些废弃的引用清理干净。

一句话总结git gc 是 Git 的「大扫除」,定期做、配合好配置和 LFS,你的仓库就能一直保持清爽。

公众号:蓝海资料掘金营,微信deep3321