把 1.8GB 的 data.tar.gz 误提交进 Git 仓库,git rm 再提交一次根本没用——那个大文件还躺在历史里,别人一克隆照样把 1.8GB 全拉下来。要彻底清除,得用 git filter-repo 重写提交历史。下面是一套我实测能跑通的流程,照着做能避开绝大多数坑。
为什么 git rm 清不干净
Git 把每个文件版本存成不可变的 blob,靠 SHA 寻址。git rm 只是让最新提交不再引用那个 blob,可旧提交里还指着它,.git/objects 里的数据块原封不动。克隆时 Git 会把整条历史的所有 blob 全部拉回本地,所以仓库体积一点没少,克隆也照样慢。
动手前先做两件事
第一,整库备份。重写历史是不可逆操作,先 git clone --mirror 仓库地址 backup.git 留一份底,万一改坏还能还原。第二,在群里吼一声。强制推送之后,所有同事都得重新 git clone,否则他们本地的旧历史会和远端对不上,推送时直接冲突。
三步重写历史
关键原则:在全新的镜像克隆里操作,不要碰正在用的仓库。
# 1. 拉一份镜像克隆(含全部分支与标签)
git clone --mirror git@github.com:you/repo.git repo-rewrite.git
cd repo-rewrite.git
# 2. 删掉指定文件或目录
git filter-repo --path data.tar.gz --invert-paths
# 2b. 不想逐个列文件名,按体积一刀切:
git filter-repo --strip-blobs-bigger-than 50M
# 3. 强制推送覆盖远端
git push --force --all
git push --force --tags
--invert-paths 的意思是「保留除此之外的所有东西」,把目标路径从每个提交里抠掉。--strip-blobs-bigger-than 50M 则按大小无差别清理,适合「这几年攒了一堆二进制」的场景。两步选其一即可。
踩坑实录
我第一次直接在天天用的仓库里跑 git filter-repo --path data.tar.gz --invert-paths,当场报错:Aborting: Refusing to destructively overwrite repo history since this is not a fresh clone. 翻了文档才懂,filter-repo 默认只认全新镜像克隆,怕你手滑覆盖正在工作的仓库;而且我上周误跑过一次,.git/filter-repo 目录还在,它直接拒绝执行。最后老老实实 git clone --mirror 重新拉一份,在镜像克隆里重跑,才把那个 1.8GB 文件从全部 312 个提交里抹掉。教训:别省镜像克隆这一步,把它当沙箱。
三种清理工具怎么选
别再照着十年前的 StackOverflow 用 git filter-branch 了,Git 官方自己都不推荐——又慢又容易出隐蔽错误。
| 工具 | 适用场景 | 结论 |
|---|---|---|
git filter-repo |
绝大多数重写历史场景 | 首选 |
BFG Repo-Cleaner |
只按大小批量删、不想写规则 | 备选 |
git filter-branch |
几乎不再需要 | 弃用 |
常见问题
清理完远程仓库怎么还是那么大?
强制推送只是改了引用,对象还在服务端等垃圾回收。GitHub 或 GitLab 要在设置里点一次「Repository cleanup / 仓库清理」,等 GC 跑完体积才真正降下来,通常几分钟到几小时。
同事的本地仓库怎么办?
让他们删掉旧 clone 重新拉。如果手头有未推送的本地提交,先把改动 git cherry-pick 到新克隆的分支上,再推送,避免改动丢在强制推送里。
Windows 上装不上 filter-repo?
一条命令:pip install git-filter-repo。装完执行 git filter-repo --version,能看到版本号就说明环境好了。Python 没装的话先去装 Python 3。
如果你也在折腾仓库瘦身、迁移,或者 CI 卡在克隆慢,欢迎在 fenij.com 留言,我把踩过的坑继续整理出来。