真正清除Git中敏感文件需重写历史,而非仅删除文件或加.gitignore;可用git filter-branch(精准控制)或BFG(批量文本替换更快),之后必须强制推送并通知协作者重新克隆。

直接删掉 .env 或 config/database.yml 不够,Git 历史里还留着
你执行了 git rm .env 并提交,甚至删了文件、加了 .gitignore,但只要它曾经被 commit 过,就还在 Git 对象库里。任何人用 git log -p 或 git show <old-commit>:./.env 都能原样扒出来——这不是“删除”,只是“藏起来了”。真正的清除必须重写历史,让那些包含敏感内容的提交彻底消失。
git filter-branch 是最可控的手动方案,但参数错一个就白干
它适合明确知道要删哪个文件、且仓库不大的场景。命令长,但每个参数都有不可替代的作用:
-
--force:强制覆盖已有重写记录(否则中途失败再跑会报错) -
--index-filter:只操作暂存区,比--tree-filter快 10 倍以上(别用后者) -
git rm --cached --ignore-unmatch config/database.yml:关键!--ignore-unmatch防止某次提交没这个文件时报错中断 -
--prune-empty:自动剔除因删文件而变成空的提交(否则一堆无意义空提交) -
--tag-name-filter cat:保留原有 tag 名称,否则 tag 会丢失或重命名 -
-- --all:作用于所有分支和 tag,漏掉--all就只清当前分支
完整命令示例:git filter-branch --force --index-filter "git rm --cached --ignore-unmatch .env" --prune-empty --tag-name-filter cat -- --all
BFG 更快更安全,但只适合批量删文件或文本
如果你要删的是密码明文(比如 API_KEY=abc123),BFG 比 filter-branch 简单得多,也快得多。但它不支持复杂逻辑(比如按路径条件过滤),且必须用裸仓库(--mirror 克隆):
- 先生成裸库:
git clone --mirror https://github.com/user/repo.git - 准备替换规则(如
passwords.txt写API_KEY=.*):bfg --replace-text passwords.txt repo.git - 清理引用:
cd repo.git && git reflog expire --expire=now --all && git gc --prune=now --aggressive
BFG 不会碰 commit message 或 author 信息,这点比 filter-branch 的 --msg-filter 更省心。
推完强制推送后,所有人必须重新克隆
这不是“同步更新”,而是历史已变。旧 clone 的本地分支指向旧 commit SHA,git pull 会失败或产生冲突;git push 会被拒绝。必须通知所有协作者:
- 备份自己改过的未提交代码(
git stash或复制到临时目录) - 删掉本地工作目录,重新
git clone远程仓库 - 把之前 stash 的改动
git stash pop回去(注意可能有冲突)
如果项目已被 fork 或公开过,仅删历史没用——得立刻轮换所有密钥、重置 token、吊销证书。Git 历史一旦泄露,就永远无法真正“收回”。


















