git-filter-repo

名称、版本与用途

  • 名称:git-filter-repo
  • 当前包版本:2.47.0
  • 当前命令构建标识:a40bce548d2c
  • 路径:~/.local/bin/git-filter-repo
  • 实际环境:~/.local/share/uv/tools/git-filter-repo/
  • 用途:快速、可编程地重写 Git 历史,是 git filter-branch 的推荐替代。

基本语法(Synopsis)

git filter-repo [OPTIONS]

[] 表示可选项,... 表示可重复;大写名称表示需要替换的参数。

当前安装与更新

当前由 uv tool 管理:

uv tool list
uv tool upgrade git-filter-repo

如需重装:

uv tool install --force git-filter-repo

使用约定

它会读取仓库自身的 Git 数据和部分 Git 配置。执行后通常会写入重写映射及相关元数据。

基本功能与推荐用法

git filter-repo --analyze
git filter-repo --path secret.txt --invert-paths
git filter-repo --sensitive-data-removal --path secret.txt --invert-paths
git filter-repo --path old/ --path-rename old/:new/
git filter-repo --replace-text replacements.txt
git filter-repo --mailmap ../mailmap
git filter-repo --help

推荐流程:

  1. 使用新的 --mirror 克隆或完整备份。
  2. 先运行 --analyze 查看历史结构。
  3. 在副本中执行过滤。
  4. 检查分支、标签、对象数量和敏感内容。
  5. 与所有协作者协调后再推送重写历史。

历史重写会改变 commit ID,并可能让其他克隆产生复杂冲突。不要在唯一副本上执行。不要用 --force 绕过“新克隆”检查,除非已经理解全部后果。

删除已经泄露的密钥后,还必须在服务端撤销或轮换密钥;仅重写 Git 历史不等于密钥重新安全。

示例中的选项、参数与子命令

本表解释本页示例实际出现的选项、参数、子命令及必要的辅助命令语法。

语法元素含义
--analyze分析仓库历史并生成报告,不重写历史。
--path PATH只选择指定路径,可重复使用。
--invert-paths反转路径选择,用于删除所选路径。
--sensitive-data-removal, --sdr针对敏感数据清理收集额外信息,并给出其他副本的后续清理提示;默认会从 origin 获取所有可获取引用。
--path-rename OLD:NEW在整个历史中重命名路径。
--replace-text FILE根据规则文件替换历史中的文本。
--mailmap FILE按 mailmap 重写作者和提交者身份。
--help显示完整帮助。
uv tool install --force TOOL强制重新安装已存在的 uv 工具。

清除历史提交中的敏感数据

假设 simulators/ 已写入 .gitignore,但历史中仍包含敏感文件。.gitignore 只能防止以后重新提交,不能清除已有提交。

先撤销凭据,再重写历史

API key、Token、密码或私钥一旦提交,就应视为已经泄露。第一步是在对应服务端撤销或轮换,而不是先运行 Git 命令。历史重写不能让旧凭据重新变得安全。

1. 协调维护窗口

重写开始前:

  1. 暂停向仓库推送。
  2. 记录受影响的路径、分支、标签和凭据。
  3. 确认谁有权限调整受保护分支。
  4. 通知协作者重写完成后必须重新 clone。

2. 使用 fresh mirror clone

在原仓库之外创建一次性镜像副本:

git clone --mirror <remote-url> repo-clean.git
cd repo-clean.git
git filter-repo --analyze

如果需要回滚,可在重写前创建访问受限的 bundle:

git bundle create ../repo-before-rewrite.bundle --all

备份也包含敏感数据

bundle、旧 clone、CI 缓存和本地副本仍保存泄露历史。备份必须限制访问,并在确认迁移完成后按安全策略销毁。

从本地路径 clone 时应加 --no-local,避免 Git 通过硬链接等本地优化导致 fresh clone 检查或隔离效果不符合预期:

git clone --mirror --no-local /path/to/repo repo-clean.git

3. 重写所有可获取引用

删除整个目录:

git filter-repo \
  --sensitive-data-removal \
  --invert-paths \
  --path simulators/

删除单个文件可将路径改为文件名;文件曾经移动或改名时,要为每个历史路径重复添加 --path:

git filter-repo \
  --sensitive-data-removal \
  --invert-paths \
  --path config/secret.env \
  --path old-config/secret.env

路径匹配不会自动跟随历史重命名。遗漏旧路径会让敏感 blob 继续留在其他提交中。

--sensitive-data-removal 默认从 origin 获取所有可获取引用,以减少遗漏远程分支或标签的风险。除非已经确认全部引用另有来源,不要使用 --no-fetch。

不要把 --force 当作常规参数

在 fresh clone 中通常不需要 --force。它会绕过安全检查,并立即清理 reflog 和旧对象;只应在已经理解仓库状态、拥有独立恢复副本时使用。

4. 验证本地结果

确认路径不再出现在任何本地引用:

git log --all --oneline -- simulators/
git rev-list --objects --all | rg '(^| )simulators/'

两条命令都应无输出。然后检查仓库结构和重写报告:

git show-ref
git fsck --full
ls -lah filter-repo/

还应使用原有的 secret scanner 重新扫描全部历史。不要把真实密钥直接写进命令行,因为它可能再次进入 Shell 历史、终端日志或自动化记录。

5. 更新远程历史

git-filter-repo 通常会移除 origin,避免误推送。核对地址后重新添加:

git remote add origin <remote-url>
git remote -v

先阅读命令输出中的敏感数据清理提示,再按代码托管平台的要求更新所有受影响分支和标签:

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

受保护分支可能需要临时调整规则。托管平台还可能保留 pull request 引用、fork、缓存或对象视图;这些内容无法只靠本地 force push 清除,需要按平台文档处理,必要时联系平台支持。

6. 从远程重新验证

不要只检查重写用的本地副本。推送完成后,再创建一个 fresh clone:

cd ..
git clone --mirror <remote-url> repo-verify.git
cd repo-verify.git
git log --all --oneline -- simulators/
git rev-list --objects --all | rg '(^| )simulators/'

重新运行 secret scanner,并确认默认分支、标签、CI、发布流程和部署仍然正常。

7. 协作者收尾

  • 所有协作者重新 clone,不要把旧分支 merge 或 push 回新历史。
  • 清理 fork、CI workspace、制品、缓存和其他镜像仓库。
  • 更新因凭据轮换而受影响的 CI/CD、部署平台和本地环境。
  • 保留事故记录,但不要在记录中复制真实秘密。
  • 确认无需回滚后,按安全策略销毁包含旧历史的备份。

历史重写后 commit hash 会改变。只要旧 clone 或旧引用还能被推回远程,敏感数据就可能重新进入可达历史。

官方资料