朕喜技术(ZhenxiJS)
2026.09.01
1 分钟阅读

Git 仓库 .git 目录膨胀的终极清理指南:彻底移除历史中的大文件

SEO 关键词:git 仓库太大, .git 目录膨胀, git 清理大文件, git filter-repo, git filter-branch, git clone 很慢, git 历史中删除文件, 移除 git 中的大文件

问题背景

Git 仓库 .git 目录异常膨胀,即使已经删除了大文件并提交,clone 和 pull 仍然极慢。这是因为 Git 的设计决定了历史提交中的所有文件都会永久保留 —— 仅仅从工作区删除文件并 commit 不会将其从 Git 历史中移除。

典型症状:

  • du -sh .git 显示远超工作区实际大小(如工作区 8KB,.git 51MB)
  • git clone 极慢,下载包含所有历史数据
  • git gc 压缩效果有限(约 0 差异)
  • git pull 每次拉取大量数据

复现步骤

# 1. 创建一个测试仓库
git init git-repo-bloat
cd git-repo-bloat
git config user.email "dev@example.com"
git config user.name "Developer"

# 2. 误提交一个 50MB 的二进制文件
dd if=/dev/urandom of=data.sql.bak bs=1M count=50
git add data.sql.bak
git commit -m "add database backup (MISTAKE)"

# 3. 删除文件并 commit(看起来"修复"了)
git rm data.sql.bak
git commit -m "remove large backup file"

# 4. 添加正常文件
echo 'print("hello")' > main.py && git add main.py && git commit -m "add main.py"

# 5. 观察问题
du -sh .git           # 显示 51M,远大于实际文件大小
git gc                # 几乎无效

报错/现象:.git 目录 51MB,git gc 后仅降到 51336KB(几乎无变化)。

根本原因

Git 使用 对象存储(object store) 保存每个文件每个版本的快照。每次 commit 都创建一个完整的提交图,其中的 blob 对象会被永久保留在 .git/objects/ 中,除非被显式清理。

git gc 只能压缩和去重当前可访问的对象,但大文件仍然被旧的 commit 引用着,所以无法被清理。

解决办法

方案一(推荐):git filter-repo

安装:

pip install git-filter-repo
# 或直接从 GitHub 下载
curl -sSLo /usr/local/bin/git-filter-repo \
  https://raw.githubusercontent.com/newren/git-filter-repo/main/git-filter-repo
chmod +x /usr/local/bin/git-filter-repo

使用:

# 1. 定位大文件
git rev-list --objects --all | \
  git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
  sort -k3 -n -r | head -10
# 输出: blob <hash> 52428800 data.sql.bak

# 2. 从历史中彻底移除
git filter-repo --path data.sql.bak --invert-paths --force

# 3. 验证
du -sh .git  # 51M → 140K

优势:一条命令完成历史重写 + reflog 清理 + gc 压缩,自动处理所有细节。

方案二(备选):git filter-branch

# 1. 移除文件(--index-filter 不 checkout,效率高)
git filter-branch --force \
  --index-filter "git rm --cached --ignore-unmatch data.sql.bak" \
  --prune-empty --tag-name-filter cat -- --all

# 2. ⚠️ 删除 filter-branch 保存的原始引用(关键!)
git for-each-ref --format='%(refname)' refs/original/ | xargs -n1 git update-ref -d

# 3. 清理 reflog 并强制 gc
git reflog expire --expire=now --all
git gc --prune=now --aggressive

# 4. 验证
du -sh .git  # 21M → 112K

⚠️ 关键坑点:

  • filter-branch 会把原始 refs 保存在 refs/original/ 下,不删除的话 gc 永远清理不了旧对象
  • git reflog expire 和 git gc --prune=now 缺一不可

推送到远程

git push --force --all origin
git push --force --tags origin

通知所有协作者重新 clone 或 rebase:

git fetch origin
git rebase --onto origin/main main

验证

修复前后对比:

指标修复前修复后效果
.git 大小51MB140KB↓99.7%
clone 后 .git~51MB124KB恢复正常
工作区文件正常正常无影响
提交历史含大文件已清理历史更干净

经验与避坑

1. 预防胜于治疗

  • 添加 .gitignore,忽略 *.bak、*.log、*.sql.bak 等文件
  • 大文件(>50MB)使用 Git LFS(Large File Storage)
  • 提交前用 git diff --stat 检查文件大小

2. 已提交的大文件

  • 首选 git filter-repo,它更智能、更安全
  • git filter-branch 是老方法,容易踩坑(refs/original/ 残留)
  • 修改历史会影响所有协作者,需要协调好推送时间

3. 常见误区

  • ❌ git rm + commit 就能删除大文件 → 不会,历史仍然保留
  • ❌ git gc 能清理 → 只能压缩当前可达对象
  • ❌ git reset 能解决 → 只改变 HEAD 指针,不清理 objects
  • ❌ 删除 .git/objects 中的大文件 → 会破坏仓库完整性

4. Docker 环境注意

  • Debian 12+ 的 pip3 install 需要 --break-system-packages
  • git-filter-repo 需要 Python3 和 requests/pyyaml

版权声明

本文为博主原创文章,遵循 CC BY-NC-SA 4.0 许可协议。 转载请注明出处:http://localhost/blogs/2026-09-01-1607-git

分享这篇文章