16. 仓库太大会卡顿?git gc + bfg 清理历史垃圾实战

「Git」系列已发布: 00. 改崩代码不敢回退?Git 版本控制 3 分钟救你狗命 | 01. Git 装完不会配?Windows/macOS/Linux 三端配置一次到位 | 02. Git 配置总踩坑?全局/仓库/SSH 密钥一次搞懂 | 03. add/commit/push 别再死记:Git 工作流核心概念实操 | 04. Git 分支管理:5 步彻底搞懂 branch 与 merge | 05. clone/fetch/pull/push 分不清?Git 远程协作实战 | 06. 给版本打上永久印记:Git 标签管理与发布实战 | 07. 切分支怕丢改动?Git stash 暂存恢复保护现场 | 08. rebase 不是 merge 的替代品:Git 故事线重构进阶 | 09. Git 日志与调试:log/blame/diff 让历史无所遁形 | 10. 提交代码自动检查:Git Hooks 实战,质量不再靠人工 | 11. 多仓库依赖怎么管?Git 子模块统一版本一键更新 | 12. GitFlow vs GitHub Flow vs 主干:团队工作流 3 选 1 | 13. 仓库越来越大?Git LFS 让图片视频不再撑爆仓库 | 14. 合并冲突不是噩梦:Git 合并策略与冲突解决 | 15. 常用命令记不住?Git 别名让操作变成快捷键 | 16. 仓库太大会卡顿?git gc + bfg 清理历史垃圾实战(本文) | 17. 把密码提交进 Git?安全实践:密钥管理与审计日志

克隆要十分钟、操作总转圈,多半是历史里塞了垃圾。git gc 日常维护 + bfg 批量删密/清大文件,给仓库来一次彻底瘦身。

基础操作(第 03 篇)和变基进阶(第 08 篇)要熟。历史重写操作有破坏性,务必先在克隆副本上验证。

一、仓库怎么变"胖"的

Git 仓库随时间积累赘肉,常见原因:

原因 例子 后果
大文件提交后又删掉 打包了 200MB 视频,下一提交删了 文件赖在历史里不走
二进制文件频繁修改 每次构建产出 jar/war 都提交 每次全量存储
误提交依赖目录 node_modules/没进 .gitignore 几千小文件
旧分支/标签不清理 上百个已合并的 feature 分支 冗余对象
长期不跑 gc 松散对象堆积 磁盘碎片

1.1 诊断健康状况

1.1.1 看大小和组成


# .git 目录大小

du -sh .git

# 按大小排

git count-objects -vH

# count: 3421

# size: 12.34 MiB

# in-pack: 12451

# packs: 3

# size-pack: 245.67 MiB

# prune-packable: 0

# garbage: 0

# size-garbage: 0 bytes

# 分析 pack

git verify-pack -v .git/objects/pack/*.idx | \
sort -k 3 -n | tail -10

1.1.2 找历史中的大文件


# 方法一:列最大对象

git rev-list --objects --all | \
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
awk '/^blob/ {print $4, substr($2,1,8), $3}' | \
sort -k3 -n -r | head -20

# 方法二:git-sizer(推荐装一个)

brew install git-sizer
git-sizer --verbose

1.1.3 检查未引用对象


# 看松散对象

git fsck --unreachable --no-reflogs

# 统计

git fsck --unreachable --no-reflogs | wc -l

# 预览 gc 会清理什么

git prune --dry-run --verbose

1.2 瘦身方案

1.2.1 方案一:清理分支和标签


# 列出远程分支

git branch -r

# 删远程分支

git push origin --delete old-feature-branch

# 清理本地已不存在的远程分支引用

git remote prune origin

# 删本地分支

git branch -d old-feature-branch

# 列出旧标签

git tag -l | sort -V | head

# 删旧标签

git tag -d old-tag
git push origin --delete old-tag

1.2.2 方案二

BFG Repo-Cleaner

BFG 是专门做 Git 历史清理的工具,比git filter-branch快 10-720 倍。


# 下载

wget https://repo1.maven.org/maven2/com/madgag/bfg/1.14.0/bfg-1.14.0.jar

# 清理大于 100MB 的文件

java -jar bfg.jar --strip-blobs-bigger-than 100M my-repo.git

# 清理特定文件类型

java -jar bfg.jar --delete-files '*.{jar,war,zip,tar.gz}' my-repo.git

# 清理特定目录

java -jar bfg.jar --delete-folders 'node_modules' my-repo.git

# 清理敏感信息(替换为***REMOVED***)

java -jar bfg.jar --replace-text passwords.txt my-repo.git

# 清理完必须跑 gc

cd my-repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive

注意:

  • 操作裸仓库(--bareclone)。
  • 不会改当前 HEAD 的提交。
  • 比filter-branch安全,不会出空提交。

1.3 方案三

git filter-repo(现代替代)

Git 2.38+ 官方推荐:

pip install git-filter-repo

# 从历史中移除特定文件

git filter-repo --path node_modules/ --invert-paths

# 只保留特定目录(如迁移到独立仓库)

git filter-repo --path src/ --path-rename src/:

# 移除大于 10MB 的文件

git filter-repo --strip-blobs-bigger-than 10M

# 改提交者信息

git filter-repo --email-callback '
return email.replace(b"@old-domain.com", b"@new-domain.com")
'

1.3.1 方案四:git gc


# 标准 gc

git gc

# 激进压缩

git gc --aggressive --prune=now

# 看效果

du -sh .git
git gc --aggressive --prune=now
du -sh .git

gc干四件事:

  1. 收集松散对象打包。
  2. 清理不再被引用的对象。
  3. 压缩 pack(--aggressive)。
  4. 清理 reflog 过期条目(--prune=now)。

1.4 怎么防

1.4.1 .gitignore 管好

# 二、编译产物
*.class
*.jar
*.war
*.o
*.so

# 三、依赖目录
node_modules/
vendor/
.venv/

# 四、IDE
.idea/
.vscode/
*.iml

# 五、系统
.DS_Store
Thumbs.db

# 六、构建输出
dist/
build/
target/

# 七、大文件
*.zip
*.tar.gz
*.mp4
*.psd

1.4.2 LFS 管大文件

见第 13 篇 Git LFS 大文件管理。

git lfs track "*.psd"
git lfs track "*.mp4"
git lfs track "datasets/*.csv"

1.4.3 定期维护

每季度:清已合并的 feature 分支、查仓库大小、更新 .gitignore。
每半年:git gc --aggressive、清旧标签、删除未引用对象。
每年:全量审计、评估 LFS 迁移。

1.5 pre-commit 挡大文件


#!/bin/bash

# .git/hooks/pre-commit

# 阻止 >100MB 的文件提交

MAX_SIZE=$((100**1024**1024)
git diff --cached --name-only --diff-filter=ACM | while read file; do
if [ -f "$file" ]; then
size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null)
if [ "$size" -gt "$MAX_SIZE" ]; then
echo "错误:$file 大小为 $(numfmt --to=iec $size),超过 100MB 限制。"
echo "请使用 Git LFS 管理大文件。"
exit 1
fi
fi
done

1.6 文章引用

📌 本文首发于微信公众号 【运维知识点滴】——专注运维实战干货,微信搜索“运维知识点滴”或扫下方二维码关注,更新不迷路。

微信公众号【运维知识点滴】 微信小程序【A5单据合并&大试卷拆分转A4打印】
运维实战干货 · 关注追更 Word 转 PDF · 试卷/发票打印神器 · 免费
运维实战干货 · 关注追更 Word 转 PDF · 试卷/发票打印神器 · 免费

💡 小程序【A5单据合并&大试卷拆分转A4打印】是干嘛的? 一个 Word 转 PDF 的打印神器,专治“文档版式和打印机对不上”:

  • A3 试卷打不出来? 学校发的电子版试卷多是 A3,家用打印机只吃 A4——上传 Word 一键转换:可缩到一页 A4(页数不变),也可对半拆成两张 A4(1:1 原大、字更清晰),两种模式自选;
  • 版式不乱:真实渲染引擎原样转换、绝不重排内容,转出的 PDF 和原 Word 排版一致,公式/表格/图片不跑版;
  • 两张 A5 单据拼一张 A4:出租车票、火车票等报销凭证拼版后,一次打印、整齐粘贴;
  • 隐私省心:无需注册,转换结果留存 5 天、最多下载 5 次即自动删除,不留任何历史记录;
  • 微信扫码即用,免费、免安装,支持 doc / docx(≤50MB)。

上一篇:15. 常用命令记不住?Git 别名让操作变成快捷键 | 下一篇:17. 把密码提交进 Git?安全实践:密钥管理与审计日志