搜索 K
主题
git filter-repo 是 Git 官方推荐的历史重写工具,用于替代已弃用的 Git filter-branch 详解 和 BFG Repo Cleaner。它基于 git fast-export / git fast-import 管道工作,速度极快,且在设计上规避了 filter-branch 的众多陷阱。
Git 项目官方在 git filter-branch 文档中明确推荐使用 git filter-repo 作为替代方案。
# pip / pipx(推荐)
pip install git-filter-repo
# 或
pipx install git-filter-repo
# macOS(Homebrew)
brew install git-filter-repo
# Windows(Scoop)
scoop install git-filter-repo
# 手动安装:下载单文件脚本放入 $PATH
# https://github.com/newren/git-filter-repo/releasesgit-filter-repo 本质是一个单独的 Python 脚本文件,你也可以直接下载放到 PATH 中即可使用。
与 Git filter-branch 详解 逐个检出提交不同,filter-repo 通过 fast-export 将整个仓库历史序列化为流,在内存中高效处理后,再通过 fast-import 写入新历史。这使得它的速度比 filter-branch 快 10-100 倍。
# 只保留特定文件和目录
git filter-repo --path README.md --path guides/ --path tools/releases
# 保留匹配 glob 模式的路径
git filter-repo --path-glob '*.py'
# 保留匹配正则表达式的路径
git filter-repo --path-regex '^src/.*\.ts$'# 删除指定文件(反转路径选择)
git filter-repo --path config/secrets.yml --invert-paths
# 删除匹配模式的文件
git filter-repo --path-glob '*.log' --invert-paths# paths.txt 每行一个路径
git filter-repo --paths-from-file paths.txt等效于 git filter-branch --index-filter 'git rm --cached ...',但语法更简洁、速度更快、无需担心边界情况。
# 将所有文件中的旧文本替换为新文本
git filter-repo --replace-text expressions.txtexpressions.txt 格式:
# 字面替换
literal:old_password==>***REMOVED***
# 正则替换
regex:password\s*=\s*"[^"]*"==>password="***REMOVED***"
# 删除匹配行(替换为空)
literal:SECRET_KEY==># 删除所有大于 100MB 的文件
git filter-repo --strip-blobs-bigger-than 100M
# 删除所有大于 10MB 的文件
git filter-repo --strip-blobs-bigger-than 10M# 应用 mailmap 文件重写作者信息
git filter-repo --mailmap my-mailmap
# 使用仓库中已有的 .mailmap
git filter-repo --use-mailmapmy-mailmap 文件格式:
# 新名字 <新邮箱> <旧邮箱>
Zhang San <zhangsan@newmail.com> <root@localhost>
# 只改名字
Zhang San <zhangsan@newmail.com> Old Name <zhangsan@newmail.com>
# 只改邮箱
<zhangsan@newmail.com> <old@email.com>等效于 git filter-branch --env-filter '...',但只需一个简单的 mailmap 文件,无需编写复杂的 shell 脚本。
filter-repo 提供了 Python 回调函数,可以对 Git 对象进行任意处理。
# 删除超过 1MB 的 blob
git filter-repo --blob-callback '
if len(blob.data) > 1024*1024:
blob.skip()
'
# 替换文件中的文本
git filter-repo --blob-callback '
blob.data = blob.data.replace(b"old_text", b"new_text")
'# 跳过特定作者的提交
git filter-repo --commit-callback '
if commit.author_name == b"Bad Author":
commit.skip()
'
# 修改提交信息
git filter-repo --commit-callback '
commit.message = commit.message.replace(b"typo", b"fixed")
'# 将 src/ 目录移到 lib/ 下
git filter-repo --filename-callback '
if filename.startswith(b"src/"):
return b"lib/" + filename
return filename
'| 回调 | 说明 |
|---|---|
--tag-callback | 处理标签对象 |
--message-callback | 处理提交/标签信息 |
--name-callback | 处理作者/提交者名字 |
--email-callback | 处理作者/提交者邮箱 |
--refname-callback | 处理分支/标签引用名 |
git filter-repo --invert-paths --path config/secrets.yml仅需一行命令,而 Git filter-branch 详解#示例 1:从历史中彻底删除敏感文件(filter-branch + 清理备份 + 强制 GC)。filter-repo 会自动处理清理工作。
git filter-repo --subdirectory-filter src/frontendgit filter-repo --path-rename src/:lib/git filter-repo --strip-blobs-bigger-than 50M# 分析仓库中最大的文件、最大的目录等
git filter-repo --analyze--analyze 模式在 .git/filter-repo/analysis/ 下生成报告文件,帮助你了解仓库的文件大小分布、扩展名统计等信息,方便决定清理策略。
以下是 Git filter-branch 详解 常见操作与 filter-repo 等效命令的对照表:
| 目标 | filter-branch | filter-repo |
|---|---|---|
| 删除文件 | --index-filter 'git rm --cached --ignore-unmatch <file>' HEAD | --invert-paths --path <file> |
| 删除文件(glob) | --tree-filter 'rm -f *.log' HEAD | --path-glob '*.log' --invert-paths |
| 修改作者邮箱 | --env-filter 'if ...; then export GIT_AUTHOR_EMAIL=...; fi' -- --all | --mailmap my-mailmap |
| 修改提交信息 | --msg-filter 'sed ...' -- --all | --message-callback 'return re.sub(...)' |
| 提取子目录 | --subdirectory-filter <dir> -- --all | --subdirectory-filter <dir> |
| 重命名目录 | --tree-filter 'mv old new' HEAD | --path-rename old/:new/ |
| 删除大文件 | 无内置支持,需配合脚本 | --strip-blobs-bigger-than 50M |
| 跳过空提交 | --prune-empty | 默认行为(自动跳过) |
| 清理备份引用 | 需手动清理 refs/original/ | 自动完成,无备份引用 |
| 强制 GC | 需手动 git gc --prune=now | 自动完成 |
git filter-repo --analyze 分析仓库,了解文件分布git clone 的副本上执行操作filter-branch 场景只需一行 filter-repo 命令即可完成--analyze 详解 --analyze 是 filter-repo 独有的分析模式,不修改任何数据,只生成仓库统计报告。
git filter-repo --analyze执行后在 .git/filter-repo/analysis/ 目录下生成以下报告文件:
| 文件 | 内容 |
|---|---|
blob-shas-and-paths.txt | 每个 blob 的 SHA、大小和路径 |
path-all-sizes.txt | 所有路径的各种大小统计(压缩前后) |
path-deleted-sizes.txt | 已删除文件的大小统计 |
directories-all-sizes.txt | 目录级别的大小统计 |
directories-deleted-sizes.txt | 已删除目录的大小统计 |
extensions-all-sizes.txt | 按扩展名分类的大小统计 |
extensions-deleted-sizes.txt | 已删除文件按扩展名的大小统计 |
renames.txt | 文件重命名历史 |
# 1. 分析仓库
git filter-repo --analyze
# 2. 查看最大的文件
cat .git/filter-repo/analysis/path-all-sizes.txt | head -20
# 3. 查看哪些扩展名占用空间最多
cat .git/filter-repo/analysis/extensions-all-sizes.txt
# 4. 根据分析结果决定清理策略
git filter-repo --strip-blobs-bigger-than 50M
# 或
git filter-repo --path-glob '*.zip' --invert-pathspath-all-sizes.txt 的输出格式:
=== All paths by reverse accumulated size ===
Format: unpacked size, packed size, date, path name(s)
132589348 45678901 2024-06-15 data/large-dataset.csv
52428800 12345678 2024-03-20 assets/video.mp4
10485760 5242880 2024-01-10 build/output.tar.gz据此可以快速定位需要清理的大文件。
Aborting: Refusing to destructively overwrite repo historyfilter-repo 要求在全新 clone 的仓库上操作,以防止意外数据丢失。解决方式:
# 方式一:重新 clone(推荐)
git clone original-repo clean-repo
cd clean-repo
git filter-repo ...
# 方式二:强制执行(确认已备份后)
git filter-repo --force ...origin remote 消失了?这是设计行为。filter-repo 会自动删除 origin remote,防止你不小心将重写后的历史推送到原始远程仓库。需要手动重新设置:
git remote add origin git@github.com:user/repo.git
git push --force --all
git push --force --tagsfilter-repo 不支持增量执行。如果中断了,需要重新 clone 并从头执行。但由于 filter-repo 速度极快(基于 fast-export/import),即使重新执行也通常只需几秒到几分钟。
# 只保留 main 和 develop 分支
git filter-repo --refs main develop
# 排除某些分支
git filter-repo --refs main --refs develop --invert-refs refs/heads/feature/*filter-repo 在设计上比 filter-branch 更安全:
--force 覆盖)filter-branch 在 refs/original/ 留下大量备份引用| 对比项 | Git filter-branch 详解 | filter-repo |
|---|---|---|
| 速度 | 慢(逐个检出提交) | 快 10-100 倍(fast-export/import) |
| 安全性 | 多种已知陷阱 | 设计上规避常见问题 |
| 易用性 | 复杂的 shell 命令 | 简洁的 CLI + Python 回调 |
| 清理工作 | 需手动清理备份引用和 GC | 自动完成 |
| 依赖 | Git 内置 | 需单独安装(Python 脚本) |
| 官方态度 | 已弃用 | 推荐使用 |
| 回调语言 | Shell | Python |
| 分析功能 | 无 | --analyze 生成详细报告 |
| 目标 | 命令 |
|---|---|
| 删除文件 | git filter-repo --invert-paths --path <file> |
| 删除 glob 匹配 | git filter-repo --path-glob '*.log' --invert-paths |
| 保留文件/目录 | git filter-repo --path <file> --path <dir>/ |
| 重命名目录 | git filter-repo --path-rename old/:new/ |
| 提取子目录 | git filter-repo --subdirectory-filter <dir> |
| 删除大文件 | git filter-repo --strip-blobs-bigger-than 50M |
| 替换文本 | git filter-repo --replace-text expressions.txt |
| 修改作者信息 | git filter-repo --mailmap my-mailmap |
| 分析仓库 | git filter-repo --analyze |
| 只处理特定分支 | git filter-repo --refs main |
| 强制执行 | 加 --force |
filter-repo 不仅是 CLI 工具,还可以作为 Python 库导入使用,适合编写复杂的自定义过滤脚本。
import git_filter_repo as fr
# 定义过滤函数
def my_blob_callback(blob, callback_metadata):
# 处理每个 blob(文件内容)
if len(blob.data) > 10 * 1024 * 1024: # 大于 10MB
blob.skip()
def my_commit_callback(commit, callback_metadata):
# 处理每个提交
# 在提交信息前加上前缀
commit.message = b"[migrated] " + commit.message
# 创建过滤器并运行
args = fr.FilteringOptions.default_options()
args.force = True
repo_filter = fr.RepoFilter(
args,
blob_callback=my_blob_callback,
commit_callback=my_commit_callback,
)
repo_filter.run()| 对象 | 常用属性 |
|---|---|
Blob | data(文件内容,bytes)、original_id(原始 SHA) |
Commit | message、author_name、author_email、author_date、committer_name、committer_email、file_changes、parents |
FileChange | filename、mode、blob_id、type(b'M' 修改 / b'D' 删除) |
Tag | name、tagger_name、tagger_email、message |
import git_filter_repo as fr
def remove_large_binaries(commit, callback_metadata):
"""删除所有提交中大于 5MB 的二进制文件"""
new_file_changes = []
for change in commit.file_changes:
# 跳过删除操作(保留删除记录)
if change.type == b'D':
new_file_changes.append(change)
continue
# 检查文件扩展名
filename = change.filename.decode()
binary_exts = {'.zip', '.tar', '.gz', '.exe', '.dll', '.so', '.bin'}
ext = '.' + filename.rsplit('.', 1)[-1] if '.' in filename else ''
if ext.lower() not in binary_exts:
new_file_changes.append(change)
commit.file_changes = new_file_changes
args = fr.FilteringOptions.default_options()
args.force = True
fr.RepoFilter(args, commit_callback=remove_large_binaries).run()在 Windows 上使用 filter-repo 有一些平台特有的注意事项。
# 推荐使用 Scoop 安装(自动处理 PATH)
scoop install git-filter-repo
# 验证安装
git filter-repo --versionfilter-repo 内部统一使用 正斜杠 / 作为路径分隔符,即使在 Windows 上也是如此:
# 正确 ✅
git filter-repo --path src/utils/helper.py --invert-paths
# 错误 ❌(Windows 反斜杠不被识别)
git filter-repo --path src\utils\helper.py --invert-pathsPowerShell 和 CMD 的引号规则与 Bash 不同:
# PowerShell:使用双引号
git filter-repo --path-glob "*.log" --invert-paths
# PowerShell:回调中使用单引号包裹 Python 代码
git filter-repo --blob-callback '
blob.data = blob.data.replace(b"old", b"new")
'
# 如果遇到引号转义问题,建议使用 Git Bash# 如果提交信息含中文,确保 UTF-8 编码
git filter-repo --message-callback '
return message.decode("utf-8").replace("旧文本", "新文本").encode("utf-8")
'在 Windows 上遇到复杂的回调操作时,建议将 Python 脚本写入 .py 文件,然后用 Python 库方式调用(参见 Python 库用法),避免 shell 引号转义问题。
从分析到清理的端到端流程:
# ===== 1. 克隆全新副本(必须) =====
git clone --no-local original-repo clean-repo
cd clean-repo
# ===== 2. 分析仓库 =====
git filter-repo --analyze
# 查看报告
cat .git/filter-repo/analysis/path-all-sizes.txt | head -30
cat .git/filter-repo/analysis/extensions-all-sizes.txt
# ===== 3. 确定策略后执行(示例:删除大文件 + 敏感文件) =====
git filter-repo \
--invert-paths \
--path config/secrets.yml \
--path-glob '*.sql.gz' \
--strip-blobs-bigger-than 50M
# ===== 4. 验证结果 =====
# 检查文件是否已删除
git log --all --full-history -- config/secrets.yml
# 应该无输出
# 检查仓库大小
git count-objects -vH
# 检查提交历史是否完整
git log --oneline | wc -l
# ===== 5. 重设 remote 并推送 =====
git remote add origin git@github.com:user/repo.git
git push --force --all
git push --force --tags
# ===== 6. 通知所有协作者 =====
# 协作者需要重新 clone,不能 pull!历史重写后,所有协作者必须:
rm -rf repo && git clone ... 重新克隆绝对不要 git pull,否则会将新旧历史合并,产生混乱的重复提交。
# 企业安全审计发现仓库历史中存在 API Key、密码等敏感信息
# 1. 分析仓库
git filter-repo --analyze
cat .git/filter-repo/analysis/path-all-sizes.txt
# 2. 创建替换规则文件
cat > expressions.txt << 'EOF'
# API Keys(正则匹配常见格式)
regex:(?i)(api[_-]?key|apikey)\s*[=:]\s*["']?[A-Za-z0-9_\-]{20,}["']?==>\1="***REMOVED***"
# AWS 凭证
regex:AKIA[0-9A-Z]{16}==>***AWS_KEY_REMOVED***
regex:(?i)aws[_-]?secret[_-]?access[_-]?key\s*[=:]\s*["']?[A-Za-z0-9/+=]{40}["']?==>aws_secret_access_key="***REMOVED***"
# 私钥内容
regex:-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----[\s\S]*?-----END (RSA |EC |DSA )?PRIVATE KEY-----==>***PRIVATE_KEY_REMOVED***
# 数据库连接字符串
regex:(?i)(mysql|postgres|mongodb)://[^\s"']+:[^\s"']+@[^\s"']+==>\1://***REMOVED***
EOF
# 3. 执行替换
git filter-repo --replace-text expressions.txt
# 4. 同时删除敏感文件
git filter-repo --invert-paths \
--path-glob '*.pem' \
--path-glob '*.key' \
--path-glob '*.p12' \
--path .env \
--path-glob '.env.*' \
--force
# 5. 验证
git log -p --all -S "AKIA" | head -20
# 应无输出# monorepo 结构:
# packages/
# frontend/
# backend/
# shared/
# tools/
# docs/
# === 拆分 frontend ===
git clone --no-local monorepo frontend-repo
cd frontend-repo
# 保留 frontend 和 shared(共享代码)
git filter-repo \
--path packages/frontend/ \
--path packages/shared/
# 重命名目录结构(去掉 packages/ 前缀)
git filter-repo \
--path-rename packages/frontend/:src/ \
--path-rename packages/shared/:shared/ \
--force
# === 拆分 backend ===
cd ..
git clone --no-local monorepo backend-repo
cd backend-repo
git filter-repo \
--path packages/backend/ \
--path packages/shared/
git filter-repo \
--path-rename packages/backend/:src/ \
--path-rename packages/shared/:shared/ \
--force# 将内部项目开源前,清理私有信息
# 1. 克隆副本
git clone --no-local internal-repo opensource-repo
cd opensource-repo
# 2. 删除内部文档和配置
git filter-repo --invert-paths \
--path docs/internal/ \
--path configs/production/ \
--path-glob '**/internal-*' \
--path INTERNAL_README.md
# 3. 替换内部 URL 和邮箱
cat > replacements.txt << 'EOF'
literal:internal.company.com==>example.com
literal:@company-internal.com==>@example.com
regex:https://jira\.company\.com/browse/([A-Z]+-\d+)==>Issue #\1
EOF
git filter-repo --replace-text replacements.txt --force
# 4. 修正作者信息(使用公开邮箱)
cat > mailmap.txt << 'EOF'
Alice <alice@opensource.org> <alice@company-internal.com>
Bob <bob@opensource.org> <bob@company-internal.com>
EOF
git filter-repo --mailmap mailmap.txt --force
# 5. 验证无内部信息残留
git log --all -p | grep -i "company-internal\|internal\.company" | head -20# 将旧仓库的代码迁移到新的目录结构
git clone --no-local old-repo new-repo
cd new-repo
# 批量重命名
git filter-repo \
--path-rename app/:src/app/ \
--path-rename lib/:src/lib/ \
--path-rename test/:tests/ \
--path-rename config/:configs/ \
--path-rename Gruntfile.js:legacy/Gruntfile.js
# 使用 filename-callback 处理更复杂的规则
git filter-repo --filename-callback '
# 将所有 .jsx 文件重命名为 .tsx
if filename.endswith(b".jsx"):
return filename[:-4] + b".tsx"
# 将 styles/ 下的 .css 移到 src/styles/
if filename.startswith(b"styles/"):
return b"src/" + filename
return filename
' --force#!/usr/bin/env python3
"""repo-sanitizer.py — 仓库清理自动化脚本"""
import git_filter_repo as fr
import re
import sys
from pathlib import Path
# 敏感模式列表
SENSITIVE_PATTERNS = [
rb'AKIA[0-9A-Z]{16}', # AWS Access Key
rb'(?i)password\s*[=:]\s*["\'][^"\']+', # 密码赋值
rb'-----BEGIN.*PRIVATE KEY-----', # 私钥
rb'ghp_[A-Za-z0-9]{36}', # GitHub Token
rb'sk-[A-Za-z0-9]{48}', # OpenAI API Key
]
# 统计
stats = {'blobs_scanned': 0, 'blobs_modified': 0, 'secrets_found': 0}
def sanitize_blob(blob, callback_metadata):
"""扫描并替换 blob 中的敏感信息"""
stats['blobs_scanned'] += 1
original = blob.data
for pattern in SENSITIVE_PATTERNS:
matches = re.findall(pattern, blob.data)
if matches:
stats['secrets_found'] += len(matches)
blob.data = re.sub(pattern, b'***REDACTED***', blob.data)
if blob.data != original:
stats['blobs_modified'] += 1
def main():
args = fr.FilteringOptions.default_options()
args.force = True
repo_filter = fr.RepoFilter(
args,
blob_callback=sanitize_blob,
)
repo_filter.run()
print(f"\n{'='*50}")
print(f"扫描 blob 数:{stats['blobs_scanned']}")
print(f"修改 blob 数:{stats['blobs_modified']}")
print(f"发现敏感信息:{stats['secrets_found']}")
print(f"{'='*50}")
if __name__ == '__main__':
main()# 使用方式
git clone --no-local original-repo clean-repo
cd clean-repo
python ../repo-sanitizer.py# .github/workflows/repo-check.yml
name: Repository Health Check
on:
push:
branches: [main]
schedule:
- cron: '0 0 * * 1' # 每周一检查
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 需要完整历史
- name: Install git-filter-repo
run: pip install git-filter-repo
- name: Analyze repository
run: |
git filter-repo --analyze
echo "## 仓库分析报告" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
head -20 .git/filter-repo/analysis/path-all-sizes.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
- name: Check for large files
run: |
# 检查是否有超过 50MB 的文件
LARGE_FILES=$(git rev-list --objects --all | \
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
awk '/^blob/ && $3 > 52428800 {print $4, $3/1048576 "MB"}')
if [ -n "$LARGE_FILES" ]; then
echo "::warning::发现大文件,建议使用 Git LFS:"
echo "$LARGE_FILES"
fi#!/bin/bash
# pre-commit hook: 阻止提交大文件
# 安装:cp pre-commit-size-check.sh .git/hooks/pre-commit
MAX_SIZE=10485760 # 10MB
LARGE_FILES=$(git diff --cached --name-only --diff-filter=ACM | while read file; do
size=$(wc -c < "$file" 2>/dev/null || echo 0)
if [ "$size" -gt "$MAX_SIZE" ]; then
echo " $file ($(echo "scale=1; $size/1048576" | bc)MB)"
fi
done)
if [ -n "$LARGE_FILES" ]; then
echo "❌ 以下文件超过 $(echo "scale=0; $MAX_SIZE/1048576" | bc)MB 限制:"
echo "$LARGE_FILES"
echo ""
echo "建议使用 Git LFS: git lfs track \"*.ext\""
echo "跳过检查: git commit --no-verify"
exit 1
fi详见 Git hooks
# ❌ 直接在工作仓库上执行
cd my-project
git filter-repo --strip-blobs-bigger-than 50M
# Aborting: Refusing to destructively overwrite repo history
# ✅ 先克隆全新副本
git clone --no-local my-project my-project-clean
cd my-project-clean
git filter-repo --strip-blobs-bigger-than 50M
# 或使用 --force(确认已备份后)
git filter-repo --strip-blobs-bigger-than 50M --force# ❌ 连续执行多次 filter-repo(第二次在第一次结果上操作)
git filter-repo --invert-paths --path secret.txt
git filter-repo --path-rename old/:new/ --force
# 第二次的 --force 可能导致意外结果
# ✅ 在单次命令中完成所有操作
git filter-repo \
--invert-paths --path secret.txt \
--path-rename old/:new/
# 如果必须分步执行,每次都从全新 clone 开始# ❌ 重命名链可能产生意外结果
git filter-repo \
--path-rename src/:lib/ \
--path-rename lib/:modules/
# src/foo.js → lib/foo.js → modules/foo.js ?
# 实际上 rename 只应用一次,不会链式处理
# ✅ 直接指定最终目标
git filter-repo --path-rename src/:modules/# ❌ Python 回调中忘记使用 bytes
git filter-repo --commit-callback '
commit.message = commit.message.replace("旧文本", "新文本")
'
# TypeError: a bytes-like object is required
# ✅ 所有字符串必须是 bytes
git filter-repo --commit-callback '
commit.message = commit.message.replace(b"\xe6\x97\xa7", b"\xe6\x96\xb0")
'
# ✅ 更好:encode/decode 处理
git filter-repo --commit-callback '
msg = commit.message.decode("utf-8")
msg = msg.replace("旧文本", "新文本")
commit.message = msg.encode("utf-8")
'# filter-repo 执行后 origin 会被删除
git remote -v
# (empty)
# 这是设计行为——防止误推到原始仓库
# ✅ 重新添加 remote
git remote add origin git@github.com:team/repo.git
git push --force --all
git push --force --tags# 如果仓库使用 Git LFS,filter-repo 可能将 LFS 指针文件
# 当作普通文件处理,导致 LFS 关联断裂
# ✅ filter-repo 后重新配置 LFS
git lfs install
git lfs pull
# 或者在 filter-repo 时排除 LFS 跟踪的文件
git filter-repo --invert-paths --path secret.txt
# LFS 文件不受影响(因为它们本身就是指针文件)# filter-repo 已经很快了,但大型仓库(100K+ 提交)可以进一步优化
# 1. 限制处理范围
git filter-repo --refs main --refs develop
# 只处理指定分支,跳过不需要的分支
# 2. 使用 --partial 避免完全重写
git filter-repo --partial --invert-paths --path large-file.bin
# --partial 不会删除 origin remote,适合增量操作
# 3. 并行处理(Python API)
# 将大仓库按时间范围分批处理
# 然后用 git replace 或 grafts 拼接
# 4. 避免不必要的回调
# blob-callback 会对每个 blob 调用,如果只需修改文件名,用 --filename-callback
# commit-callback 比 blob-callback 调用次数少得多| 工具 | 10K 提交 (删文件) | 100K 提交 (删文件) | 优势 |
|---|---|---|---|
| filter-branch | ~45 秒 | ~8 分钟 | Git 内置 |
| BFG Repo Cleaner | ~3 秒 | ~20 秒 | 删大文件极快 |
| filter-repo | ~5 秒 | ~30 秒 | 功能最全、最灵活 |
gc --aggressive 进一步压缩