Skip to content

GC 与清理

概述

Git 垃圾回收(Garbage Collection,GC)是清理无用对象、优化仓库性能的重要机制。定期运行 GC 可以保持仓库健康,减少磁盘占用。

Git 垃圾回收

什么是 Git GC

Git GC 会:

  • 清理不可达的对象
  • 压缩松散对象为包文件
  • 优化仓库存储结构
  • 提高访问效率

Git 对象存储

.git/
├── objects/
│   ├── pack/          # 包文件(压缩存储)
│   │   ├── pack-abc123.idx
│   │   └── pack-abc123.pack
│   ├── info/          # 对象信息
│   └── [0-9a-f]{2}/   # 松散对象目录
│       └── [0-9a-f]{38}
└── ...

对象类型

类型说明
blob文件内容
tree目录结构
commit提交信息
tag标签对象

git gc 命令

基本用法

bash
# 运行垃圾回收
git gc

# 自动运行(Git 会在需要时自动执行)
git gc --auto

# 激进模式(更彻底的优化)
git gc --aggressive

# 立即清理(不等待过期)
git gc --prune=now

# 组合使用
git gc --aggressive --prune=now

参数说明

参数说明
--auto只在需要时运行
--aggressive激进优化,耗时更长
--prune=<date>清理指定日期前的对象
--prune=now立即清理所有不可达对象
--quiet静默模式
--force强制运行

GC 工作流程

bash
# 1. 检查是否需要 GC
git gc --auto

# 2. 打包松散对象
git repack

# 3. 清理不可达对象
git prune

# 4. 清理 reflog
git reflog expire

# 5. 更新索引
git update-server-info

查看仓库状态

bash
# 查看对象统计
git count-objects -v

# 输出示例:
# count: 123      # 松散对象数量
# size: 456       # 松散对象大小(KB)
# in-pack: 7890   # 包文件中的对象数量
# packs: 3        # 包文件数量
# size-pack: 1234 # 包文件大小(KB)
# prune-packable: 0
# garbage: 0      # 损坏对象数量

# 查看磁盘使用
du -sh .git

# 查看包文件
ls -lh .git/objects/pack/

清理无用对象

git prune

bash
# 清理不可达对象
git prune

# 清理特定日期前的对象
git prune --expire=now

# 清理 30 天前的对象
git prune --expire=30.days.ago

# 查看将被清理的对象
git prune --dry-run

清理 reflog

bash
# 查看 reflog
git reflog

# 清理过期的 reflog
git reflog expire --expire=now --all

# 清理 30 天前的 reflog
git reflog expire --expire=30.days.ago --all

# 清理特定分支的 reflog
git reflog expire --expire=now refs/heads/main

清理远程分支

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

# 查看将被清理的分支
git remote prune origin --dry-run

# 清理所有远程
git remote update --prune

清理工作目录

bash
# 查看将被清理的文件
git clean -n

# 清理未跟踪的文件
git clean -f

# 清理未跟踪的文件和目录
git clean -fd

# 清理忽略的文件
git clean -fX

# 清理所有未跟踪文件(包括忽略的)
git clean -fx

# 清理所有(包括子模块)
git clean -fdx

仓库维护

定期维护脚本

bash
#!/bin/bash
# git-maintenance.sh

echo "=== Git 仓库维护 ==="

echo "1. 清理远程分支引用..."
git remote prune origin

echo "2. 清理 reflog..."
git reflog expire --expire=30.days.ago --all

echo "3. 运行垃圾回收..."
git gc --prune=now

echo "4. 检查仓库完整性..."
git fsck

echo "5. 统计仓库信息..."
git count-objects -v

echo "=== 维护完成 ==="

Git 维护命令(Git 2.30+)

bash
# 启动定期维护
git maintenance start

# 停止定期维护
git maintenance stop

# 手动运行维护任务
git maintenance run

# 运行特定任务
git maintenance run --task=gc
git maintenance run --task=commit-graph
git maintenance run --task=prefetch

# 查看维护状态
git maintenance register

配置自动维护

bash
# 启用自动 GC
git config gc.auto 256

# 启用自动打包
git config repack.usedeltabaseoffset true

# 配置 GC 频率
git config gc.autoPackLimit 50

# 配置 reflog 过期时间
git config gc.reflogExpire 90.days.ago
git config gc.reflogExpireUnreachable 30.days.ago

# 配置 prune 过期时间
git config gc.pruneExpire 2.weeks.ago

优化仓库性能

重新打包

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

# 使用增量打包
git repack -d

# 完全重新打包
git repack -a -d -f --depth=250 --window=250

优化提交图

bash
# 生成提交图文件
git commit-graph write

# 生成完整提交图
git commit-graph write --reachable

# 验证提交图
git commit-graph verify

# 清理提交图
rm .git/objects/info/commit-graph

优化索引

bash
# 重新生成索引
git read-tree HEAD

# 更新索引
git update-index --refresh

# 优化索引
git update-index --index-version 4

使用 fsck 检查

bash
# 检查仓库完整性
git fsck

# 检查所有对象
git fsck --full

# 检查并显示进度
git fsck --progress

# 检查连通性
git fsck --connectivity-only

# 检查并修复(谨慎使用)
git fsck --lost-found

清理大仓库

查找大对象

bash
# 查找最大的对象
git rev-list --objects --all | \
  git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
  sed -n 's/^blob //p' | \
  sort -rnk2 | \
  head -20

# 查找大文件
git ls-files | xargs -I {} du -h {} | sort -rh | head -20

移除大文件

bash
# 使用 filter-repo 移除
git filter-repo --path large-file.zip --invert-paths

# 使用 BFG 移除
bfg --delete-files large-file.zip

# 清理
git reflog expire --expire=now --all
git gc --prune=now --aggressive

清理历史中的敏感信息

bash
# 移除敏感文件
git filter-repo --path config/secrets.yml --invert-paths

# 替换敏感信息
git filter-repo --replace-text <(echo 'password==>REDACTED')

# 清理
git gc --prune=now --aggressive

恢复丢失的提交

使用 reflog 恢复

bash
# 查看 reflog
git reflog

# 恢复到特定提交
git reset --hard HEAD@{5}

# 创建分支保存丢失的提交
git branch recovered-branch HEAD@{5}

使用 fsck 恢复

bash
# 查找悬空提交
git fsck --lost-found

# 查看悬空提交
git show <dangling-commit-hash>

# 恢复悬空提交
git cherry-pick <dangling-commit-hash>

# 或创建分支
git branch recovered <dangling-commit-hash>

从 .git/objects 恢复

bash
# 查找悬空对象
find .git/objects -type f | head

# 查看对象内容
git cat-file -p <object-hash>

# 恢复对象
git hash-object -w <file>

GC 配置详解

常用配置

bash
# 自动 GC 阈值(松散对象数量)
git config gc.auto 256

# 自动打包阈值
git config gc.autoPackLimit 50

# reflog 过期时间
git config gc.reflogExpire 90.days.ago

# 不可达 reflog 过期时间
git config gc.reflogExpireUnreachable 30.days.ago

# prune 过期时间
git config gc.pruneExpire 2.weeks.ago

# 打包窗口大小
git config pack.windowMemory 256m

# 打包深度
git config pack.depth 50

# 并行压缩线程
git config pack.threads 4

性能相关配置

bash
# 增加缓存
git config core.packedGitLimit 512m
git config core.packedGitWindowSize 512m
git config core.deltaBaseCacheLimit 512m

# 启用预加载
git config core.preloadindex true

# 启用文件系统缓存
git config core.fscache true

# 优化 status
git config core.untrackedCache true

最佳实践

1. 定期维护

bash
# 每周运行一次
git gc

# 每月运行一次激进 GC
git gc --aggressive --prune=now

2. 大型仓库维护

bash
#!/bin/bash
# large-repo-maintenance.sh

echo "开始维护大型仓库..."

# 1. 清理远程引用
git remote prune origin

# 2. 清理 reflog
git reflog expire --expire=7.days.ago --all

# 3. 重新打包
git repack -a -d -f --depth=250 --window=250

# 4. 生成提交图
git commit-graph write --reachable

# 5. 清理
git prune --expire=now

# 6. 检查
git fsck

echo "维护完成!"
git count-objects -v

3. CI/CD 中的清理

yaml
# GitHub Actions
jobs:
  cleanup:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: 运行 GC
        run: |
          git gc --prune=now
          git count-objects -v

4. 预防措施

bash
# 使用 pre-commit 钩子检查文件大小
#!/bin/bash
# .git/hooks/pre-commit

MAX_SIZE=10485760  # 10MB

for file in $(git diff --cached --name-only); 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 超过 10MB,请使用 Git LFS"
            exit 1
        fi
    fi
done

常见问题

Q: GC 后仓库大小没变?

bash
# 可能原因:
# 1. reflog 保留了引用
git reflog expire --expire=now --all

# 2. 远程分支还有引用
git remote prune origin

# 3. 需要激进 GC
git gc --aggressive --prune=now

Q: GC 运行很慢?

bash
# 减少打包窗口
git config pack.window 10

# 减少深度
git config pack.depth 20

# 增加线程
git config pack.threads 8

# 分步运行
git repack -d
git prune

Q: 如何查看 GC 进度?

bash
# 使用 --verbose
git gc --verbose

# 或查看日志
GIT_TRACE=1 git gc

总结

操作命令说明
垃圾回收git gc运行 GC
激进 GCgit gc --aggressive --prune=now彻底清理
清理对象git prune清理不可达对象
清理 refloggit reflog expire --expire=now --all清理引用日志
清理远程git remote prune origin清理远程分支引用
检查仓库git fsck检查完整性
统计对象git count-objects -v查看对象统计

维护建议

  • 定期运行 git gc
  • 大型仓库使用 git gc --aggressive
  • 清理 reflog 释放空间
  • 使用 git fsck 检查完整性
  • 配置合适的 GC 参数