跳转到内容

Git 缩减仓库 优化 Git 仓库大小 提高性能

Git Repository Optimization

随着项目的不断发展,Git 仓库可能会变得越来越大,导致克隆、推送和拉取操作变慢。本文将详细介绍如何优化 Git 仓库大小,提高性能和效率。

为什么需要优化 Git 仓库?

在以下情况下,你需要考虑优化 Git 仓库:

  • 仓库体积过大:超过几百 MB 甚至 GB 级别
  • 克隆速度慢:新成员克隆仓库需要很长时间
  • CI/CD 效率低:持续集成构建时间过长
  • 存储成本高:托管平台对大仓库有限制或收费
  • 历史遗留问题:曾误提交大文件或敏感信息

方法一:使用 git filter-repo 清理大文件(推荐)

git filter-repo 是现代 Git 仓库清理的首选工具,比旧的 git filter-branch 更快、更安全。

安装 git filter-repo

bash
# 使用 pip 安装
pip install git-filter-repo

# 或使用 Homebrew(macOS)
brew install git-filter-repo

清除特定类型的文件

bash
# 清除垃圾文件 - 大量无用的 mp3 文件
git filter-repo --path-glob '*.mp3' --invert-paths --force

# 清除所有图片文件
git filter-repo --path-glob '*.jpg' --path-glob '*.png' --invert-paths --force

# 清除特定目录
git filter-repo --path node_modules/ --invert-paths --force

# 清除大于 100MB 的文件
git filter-repo --strip-blobs-bigger-than 100M --force

查找并删除大文件

在实际操作前,先找出仓库中的大文件:

bash
# 方法1:使用 git rev-list 查找大文件
git rev-list --objects --all | \
  git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
  sed -n 's/^blob //p' | \
  sort --numeric-sort --key=2 | \
  tail -n 20 | \
  cut -c 1-12,41- | \
  $(command -v gnumfmt || echo numfmt) --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest

# 方法2:使用 git ls-tree 递归查找
git ls-tree -r -t --long HEAD | sort -k 3 -n -r | head -20

# 方法3:查看整个历史中的大文件
git verify-pack -v .git/objects/pack/*.idx | \
  sort -k 3 -n -r | \
  head -20

找到大文件后,将其从历史中彻底删除:

bash
# 假设发现 large-video.mp4 占用了大量空间
git filter-repo --path large-video.mp4 --invert-paths --force

# 或者批量删除多个文件
git filter-repo \
  --path video1.mp4 \
  --path video2.mp4 \
  --path dataset.csv \
  --invert-paths \
  --force

替换敏感信息

如果不小心提交了密码、密钥等敏感信息:

bash
# 替换所有历史中的敏感字符串
git filter-repo --replace-text <(echo "OLD_PASSWORD==>NEW_PASSWORD") --force

# 创建 replacements.txt 文件
cat > replacements.txt << EOF
password123==>REDACTED
api_key_abc123==>REDACTED
secret_token==>REDACTED
EOF

git filter-repo --replace-text replacements.txt --force

方法二:清理标签和分支

列出并删除不需要的标签

bash
# 查看所有本地标签
git tag -l

# 查看远程标签
git ls-remote --tags origin

# 删除本地标签
git tag -d v1.0.0
git tag -d old-release

# 删除远程标签
git push origin --delete v1.0.0
git push origin --delete old-release

# 批量删除符合模式的标签(谨慎使用!)
git tag -l 'v0.*' | xargs git tag -d
git push origin --delete $(git tag -l 'v0.*')

清理过时分支

bash
# 查看所有本地分支
git branch -a

# 查看已合并到主分支的分支
git branch --merged main

# 查看未合并的分支
git branch --no-merged main

# 删除已合并的本地分支
git branch -d feature/old-feature
git branch -d bugfix/fix-123

# 强制删除未合并的分支(谨慎!)
git branch -D abandoned-feature

# 删除远程分支
git push origin --delete feature/old-feature
git push origin --delete bugfix/fix-123

# 清理本地对远程已删除分支的引用
git remote prune origin

自动化清理脚本

bash
#!/bin/bash
# cleanup-branches.sh - 安全清理过时分支

echo "=== 开始清理过时分支 ==="

# 1. 更新远程信息
git fetch --prune

# 2. 切换到主分支
git checkout main

# 3. 列出并删除已合并的本地分支
echo "以下分支已合并到 main,将被删除:"
git branch --merged main | grep -v "\* main" | grep -v "develop"

read -p "确认删除?(y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
    git branch --merged main | grep -v "\* main" | grep -v "develop" | xargs git branch -d
    echo "本地分支清理完成"
fi

# 4. 清理远程已删除的分支引用
git remote prune origin

echo "=== 清理完成 ==="

方法三:深度清理和垃圾回收

完整的清理流程

bash
# 1. 清理 reflog(引用日志)
# reflog 记录了 HEAD 的变化历史,会占用空间
git reflog expire --expire=now --all

# 2. 执行激进的垃圾回收
# --aggressive 选项会更彻底地优化对象存储
git gc --prune=now --aggressive

# 3. 重新打包对象
# -A: 将所有对象打包
# -d: 删除多余的包文件
git repack -Ad

# 4. 修剪悬空对象
# 删除不再被任何引用指向的对象
git prune

# 5. 清理 stashes(如果有)
git stash clear

# 6. 验证仓库完整性
git fsck --full

各命令详解

git reflog expire

bash
# 默认情况下,reflog 条目保留 90 天
# 立即过期所有 reflog 条目
git reflog expire --expire=now --all

# 只过期特定分支的 reflog
git reflog expire --expire=now refs/heads/main

# 设置不同的过期时间
git reflog expire --expire=30.days.ago --all

git gc(Garbage Collection)

bash
# 基本垃圾回收
git gc

# 立即清理,不等待
git gc --prune=now

# 激进模式(更彻底但更慢)
git gc --aggressive

# 激进 + 立即清理(最彻底)
git gc --prune=now --aggressive

# 查看 gc 配置
git config --get gc.aggressiveDepth
git config --get gc.aggressiveWindow

gc 的参数说明:

  • --prune=now:立即删除不可达对象,而不是等待默认的两星期
  • --aggressive:更积极地优化打包,适合大仓库
  • --auto:仅在必要时自动运行(默认行为)

git repack

bash
# 重新打包所有对象
git repack -Ad

# 参数说明:
# -A: 将所有 unreachable 对象也打包
# -d: 删除旧的包文件
# -f: 强制重新打包,即使没有变化
# -l: 仅处理本地对象

# 带 delta 压缩的重新打包
git repack -a -d -f --depth=250 --window=250

git prune

bash
# 删除所有悬空对象
git prune

# 带过期时间的修剪
git prune --expire=2.weeks.ago

# 显示将要删除的对象(dry run)
git prune --dry-run --verbose

一键清理脚本

bash
#!/bin/bash
# aggressive-cleanup.sh - 激进的仓库清理

set -e

echo "⚠️  警告:此操作将永久删除历史数据!"
echo "建议先备份仓库或创建裸克隆作为备份"
read -p "确认继续?(yes/no) " -r
echo

if [[ ! $REPLY == "yes" ]]; then
    echo "操作已取消"
    exit 1
fi

echo "📊 清理前的仓库大小:"
du -sh .git

echo ""
echo "🔧 开始清理..."

# 1. 清理 reflog
echo "  [1/6] 清理 reflog..."
git reflog expire --expire=now --all

# 2. 清理 stashes
echo "  [2/6] 清理 stashes..."
git stash clear

# 3. 删除未跟踪的文件(可选,谨慎使用)
# echo "  [3/6] 清理未跟踪文件..."
# git clean -fdx

# 4. 垃圾回收
echo "  [3/6] 执行垃圾回收..."
git gc --prune=now --aggressive

# 5. 重新打包
echo "  [4/6] 重新打包对象..."
git repack -Ad

# 6. 修剪
echo "  [5/6] 修剪悬空对象..."
git prune

# 7. 验证
echo "  [6/6] 验证仓库完整性..."
git fsck --full --no-dangling

echo ""
echo "✅ 清理完成!"
echo "📊 清理后的仓库大小:"
du -sh .git

echo ""
echo "💡 提示:如果需要推送到远程,请执行:"
echo "   git push origin --force --all"
echo "   git push origin --force --tags"

方法四:推送修改后的历史到远程

⚠️ 重要警告: 重写历史后推送到远程是破坏性操作,会影响所有协作者!

推送策略

bash
# 1. 添加远程仓库(如果还没有)
git remote add origin https://github.com/username/repository.git

# 2. 强制推送所有分支
git push origin --force --all

# 3. 强制推送所有标签
git push origin --force --tags

协作团队的最佳实践

如果你的项目有多个协作者,请按以下步骤操作:

bash
# 步骤 1:通知所有团队成员
echo "重要通知:我们将重写 Git 历史以优化仓库大小。
请在今天下班前:
1. 提交并推送所有未完成的工作
2. 备份本地的重要分支
3. 明天早上重新克隆仓库"

# 步骤 2:在维护窗口期间执行清理
# (按照前面的步骤进行清理)

# 步骤 3:强制推送
git push origin --force --all
git push origin --force --tags

# 步骤 4:通知团队成员重新克隆
echo "清理完成!请执行以下操作:
1. 删除本地旧仓库
2. 重新克隆:git clone <repository-url>
3. 恢复你备份的本地分支"

替代方案:创建新的干净仓库

如果担心影响协作者,可以创建新仓库:

bash
# 1. 克隆为裸仓库
git clone --bare https://github.com/username/old-repo.git
cd old-repo.git

# 2. 执行清理操作
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git repack -Ad
git prune

# 3. 创建新的远程仓库
# 在 GitHub/GitLab 上创建新仓库 new-repo

# 4. 推送到新仓库
git push --mirror https://github.com/username/new-repo.git

# 5. 归档旧仓库,将新仓库设为主要仓库

方法五:预防仓库膨胀的最佳实践

1. 使用 .gitignore

bash
# 完善的 .gitignore 示例
# 依赖目录
node_modules/
vendor/
.pnp/

# 构建输出
dist/
build/
*.exe
*.dll
*.so
*.dylib

# 日志文件
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# 环境变量
.env
.env.local
.env.*.local

# IDE 配置
.vscode/
.idea/
*.swp
*.swo

# 操作系统文件
.DS_Store
Thumbs.db

# 临时文件
tmp/
temp/
*.tmp
*.bak

# 大型媒体文件(建议使用 Git LFS)
*.mp4
*.avi
*.mov
*.psd
*.ai

2. 使用 Git LFS 管理大文件

bash
# 安装 Git LFS
git lfs install

# 追踪大文件类型
git lfs track "*.psd"
git lfs track "*.mp4"
git lfs track "*.zip"

# 查看追踪的文件类型
git lfs track

# 提交 .gitattributes 文件
git add .gitattributes
git commit -m "Configure Git LFS for large files"

# 推送 LFS 对象
git lfs push --all origin

3. 定期维护计划

bash
# 添加到 crontab,每月执行一次
# 编辑 crontab
crontab -e

# 添加以下行(每月1号凌晨2点执行)
0 2 1 * * cd /path/to/repo && git gc --auto

# 或者每季度执行一次深度清理
0 2 1 */3 * * cd /path/to/repo && bash /path/to/aggressive-cleanup.sh

4. 监控仓库大小

bash
# 创建监控脚本 monitor-size.sh
#!/bin/bash

REPO_DIR="/path/to/repo"
MAX_SIZE_MB=500

cd $REPO_DIR

# 获取 .git 目录大小(MB)
SIZE_MB=$(du -sm .git | cut -f1)

echo "当前仓库大小: ${SIZE_MB}MB"

if [ $SIZE_MB -gt $MAX_SIZE_MB ]; then
    echo "⚠️  警告:仓库大小超过 ${MAX_SIZE_MB}MB!"
    echo "建议执行清理操作"
    # 可以在此处添加邮件通知
    # mail -s "Git Repo Size Alert" admin@example.com <<< "Size: ${SIZE_MB}MB"
else
    echo "✅ 仓库大小正常"
fi

常见问题排查

问题 1:清理后仓库大小没有明显减少

bash
# 原因:可能有其他引用仍指向这些对象

# 解决方案:
# 1. 确保清理了所有引用
git reflog expire --expire=now --all
git stash clear

# 2. 检查是否有其他远程引用
git remote -v
git fetch --all --prune

# 3. 执行最彻底的清理
git gc --prune=now --aggressive
git repack -Ad
git prune

# 4. 验证效果
du -sh .git

问题 2:强制推送后被拒绝

bash
# 错误:protected branch update failed

# 解决方案:
# 1. 在 GitHub/GitLab 上临时解除分支保护
# 2. 执行强制推送
git push origin --force --all
# 3. 重新启用分支保护

# 或者使用 --force-with-lease(更安全)
git push origin --force-with-lease --all

问题 3:清理后出现仓库损坏

bash
# 检查仓库完整性
git fsck --full

# 如果有错误,从备份恢复
# 这就是为什么要先备份的原因!

# 尝试修复
git gc --prune=now
git reflog expire --expire=now --all

问题 4:想知道哪些文件占用最多空间

bash
# 综合查询脚本
#!/bin/bash

echo "=== Top 20 Largest Files in Git History ==="
git rev-list --objects --all | \
  git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
  sed -n 's/^blob //p' | \
  sort --numeric-sort --key=2 | \
  tail -n 20 | \
  awk '{printf "%-50s %s\n", $4, $3}' | \
  column -t

echo ""
echo "=== Largest Directories ==="
git ls-tree -r -t --long HEAD | \
  awk '{print $4}' | \
  sed 's|/[^/]*$||' | \
  sort | uniq -c | sort -rn | \
  head -10

实际案例:将一个 2GB 的仓库缩减到 200MB

以下是一个真实案例的完整操作流程:

bash
# 初始状态
$ du -sh .git
2.0G    .git

# 步骤 1:找出问题所在
$ git verify-pack -v .git/objects/pack/*.idx | \
  sort -k 3 -n -r | head -10

# 发现几个大文件:
# - database-dump.sql (500MB)
# - video-demo.mp4 (800MB)
# - node_modules/ (多次提交,累计 400MB)

# 步骤 2:从历史中删除这些文件
$ git filter-repo \
  --path database-dump.sql \
  --path video-demo.mp4 \
  --path node_modules/ \
  --invert-paths \
  --force

# 步骤 3:删除旧标签
$ git tag -l 'v1.*' | xargs git tag -d
$ git push origin --delete $(git tag -l 'v1.*')

# 步骤 4:删除废弃分支
$ git branch -D old-experiment
$ git push origin --delete old-experiment

# 步骤 5:深度清理
$ git reflog expire --expire=now --all
$ git gc --prune=now --aggressive
$ git repack -Ad
$ git prune

# 最终结果
$ du -sh .git
200M    .git

# 步骤 6:推送到远程
$ git push origin --force --all
$ git push origin --force --tags

# 缩减比例:90% 🎉

总结

优化 Git 仓库大小是一个系统性的工作,需要根据具体情况选择合适的方法:

方法对比

方法适用场景风险等级效果
git filter-repo删除大文件、敏感信息高(重写历史)⭐⭐⭐⭐⭐
清理标签分支删除过时引用⭐⭐
垃圾回收日常维护极低⭐⭐⭐
Git LFS预防大文件⭐⭐⭐⭐
.gitignore预防不必要文件⭐⭐⭐⭐

最佳实践建议

  1. 预防为主:配置好 .gitignore,使用 Git LFS
  2. 定期维护:设置自动化清理任务
  3. 谨慎操作:重写历史前务必备份
  4. 团队协作:提前沟通,选择合适的时机
  5. 监控预警:设置仓库大小监控

下一步学习

通过合理运用这些技术,你可以保持 Git 仓库的健康状态,提高开发效率!