Appearance
仓库瘦身
随着时间推移,Git 仓库可能因为误提交大文件、大量历史等问题变得庞大。本节介绍如何找到并清理这些"历史负担"。
查找大文件:git rev-list --objects --all
找出仓库历史中哪些文件占用空间最大:
bash
# 列出所有对象,并按大小排序(找出前 20 个)
git rev-list --objects --all | \
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
grep '^blob' | \
sort -k3 -n -r | \
head -20 | \
awk '{print $3, $4}'
# 更友好的输出格式
git rev-list --objects --all | \
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
sed -n 's/^blob //p' | \
sort -k2 -n -r | \
awk '{printf "%.2f MB\t%s\n", $2/1048576, $3}' | \
head -20找到文件在哪次提交中引入:
bash
# 找到大文件的哈希后,查看它属于哪个文件
git log --all --full-history -- "**/<filename>"
# 或者通过哈希查找路径
git log --all --find-object=<blob-hash>BFG Repo-Cleaner:清理历史中的大文件
BFG Repo-Cleaner 是专门用于清理 Git 历史的工具,比 git filter-branch 更快速易用。
安装
bash
# macOS
brew install bfg
# 或者下载 JAR 文件
wget https://repo1.maven.org/maven2/com/madgag/bfg/1.14.0/bfg-1.14.0.jar
alias bfg="java -jar bfg-1.14.0.jar"使用
bash
# 1. 克隆裸仓库(BFG 要求使用裸仓库)
git clone --mirror https://github.com/user/repo.git
# 2. 删除所有历史中的大文件(>10MB)
bfg --strip-blobs-bigger-than 10M repo.git
# 3. 删除特定文件
bfg --delete-files '*.zip' repo.git
bfg --delete-files 'secret.key' repo.git
# 4. 删除特定文件夹
bfg --delete-folders .git repo.git
# 5. 替换敏感信息(密码、密钥)
echo "my-secret-password" > passwords.txt
bfg --replace-text passwords.txt repo.git
# 6. 清理和推送
cd repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --forcegit filter-branch:重写历史
git filter-branch 是 Git 内置的历史重写工具(较慢,不推荐用于大型仓库):
bash
# 从所有历史中删除某个文件
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch path/to/large-file.zip' \
--prune-empty --tag-name-filter cat -- --all
# 清理
git reflog expire --expire=now --all
git gc --prune=now --aggressive
# 强制推送(会改变历史!)
git push origin --force --all
git push origin --force --tagsgit filter-repo(推荐替代 filter-branch)
git-filter-repo 是官方推荐的替代品,速度更快,功能更强:
安装
bash
pip install git-filter-repo
# 或
brew install git-filter-repo使用
bash
# 删除指定文件的所有历史
git filter-repo --path path/to/large-file.zip --invert-paths
# 删除指定目录的历史
git filter-repo --path assets/videos/ --invert-paths
# 使用通配符
git filter-repo --path-glob '*.mp4' --invert-paths
git filter-repo --path-glob '*.zip' --invert-paths
# 替换敏感字符串
git filter-repo --replace-text replacements.txt
# replacements.txt 格式:
# 旧字符串==>新字符串
# my_password==>REDACTED
# api_key_12345==>REDACTED_API_KEY
# 只保留某个目录的历史(提取子目录为独立仓库)
git filter-repo --subdirectory-filter src/
# 重命名路径
git filter-repo --path-rename old/path:new/path清理后的推送与团队同步
清理历史后,所有提交的 SHA-1 都会改变,需要团队配合:
bash
# 清理完成后,强制推送所有分支和标签
git push origin --force --all
git push origin --force --tags
# 通知所有团队成员!
echo "
⚠️ 重要通知:仓库历史已被重写!
所有团队成员需要执行以下操作:
rm -rf local-repo/ # 删除本地仓库
git clone <url> # 重新克隆
或者(如果有未推送的本地工作):
git fetch origin
git rebase origin/main # 在每个分支上执行
"协作者的恢复操作:
bash
# 方案1:直接重新克隆(最安全)
cd ..
rm -rf my-project
git clone https://github.com/user/repo.git
# 方案2:如果有未提交的本地工作
git stash
git fetch origin
git reset --hard origin/main # 或对应分支
git stash pop防止历史污染的最佳实践
bash
# 1. 配置大文件预防性钩子
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/sh
MAX_SIZE=$((10 * 1024 * 1024)) # 10MB
git diff --staged --name-only | while read file; do
size=$(git cat-file -s "$(git hash-object "$file")" 2>/dev/null || echo 0)
if [ "$size" -gt "$MAX_SIZE" ]; then
echo "❌ 文件 '$file' ($(($size/1024/1024))MB) 超过 10MB 限制"
echo " 请使用 Git LFS 管理大文件:git lfs track '$file'"
exit 1
fi
done
EOF
chmod +x .git/hooks/pre-commit
# 2. 使用 Git LFS 管理大文件(见性能优化章节)
git lfs install
git lfs track "*.mp4"
git lfs track "*.zip"
git lfs track "*.pdf"总结
| 操作 | 工具/命令 | 适用场景 |
|---|---|---|
| 查找大文件 | git rev-list --objects | 诊断阶段 |
| 清理大文件历史 | BFG Repo-Cleaner | 简单快速(推荐) |
| 复杂历史重写 | git-filter-repo | 复杂场景(官方推荐) |
| 内置历史重写 | git filter-branch | 内置但较慢(不推荐) |
清理历史是破坏性操作,执行前务必:
- 通知所有团队成员
- 在测试克隆上先验证效果
- 备份原始仓库
- 推送后所有人重新克隆