git filter-repo 在生产机上跑完,nginx 开始 404——记一次仓库瘦身踩坑
某个部署在线上的项目,.git 目录膨胀到了 2.8 GB,原因是历史上有人把 public/assets/ 里的视频文件直接 git add 进了版本库,之后也没有从历史中移除。每次 push/clone 都要传几百兆,CI 拉代码慢得让人抓狂。
决定用 git filter-repo 重写历史、把大 blob 从所有 commit 里抹掉。照着网上的教程,SSH 登上生产机,在工作目录里直接执行——命令跑完,仓库从 2.8 GB 缩到了 500 MB,效果拔群。然后打开浏览器,静态资源全部 404。
现象
git filter-repo命令正常退出,无报错- 仓库体积大幅缩小,符合预期
- nginx 服务的静态文件(图片、视频、字体)全部返回 404
ls public/assets/发现目录为空——文件被物理删除了
根因
git filter-repo 默认会将工作树同步到重写后的新 HEAD。你从历史里删掉了那个 799 MB 的视频,新 HEAD 的 tree 里自然没有这个文件,filter-repo 执行完毕时就把工作树里对应的物理文件一并删掉了。
这和 git checkout / git reset --hard 的行为逻辑相同——只是教程里没人提,因为在本地开发机上根本不在乎工作树里的几个测试文件;而生产服务器上,工作树里跑着 nginx 实时服务的静态资源,删了就是线上事故。
git filter-repo 执行完成后,会把工作树重置为重写后的新 HEAD。如果你把某个目录从历史中剔除,该目录下的所有物理文件都会从工作树里消失——无论它们是否还在被 nginx/apache 服务。在生产服务器上照做,等于亲手删掉线上静态资源。
正解
核心思路:在一个 bare clone(无工作树)上跑 filter-repo,再 force-push 到远端;生产机的工作树和 .git 原样保留。
# 1. 在生产机本地 clone 一个 bare mirror(纯 .git,无工作树)
TS=$(date +%Y%m%d-%H%M)
git clone --mirror /www/wwwroot/myapp /root/myapp-cleanup-${TS}.git
# 2. 安装 git-filter-repo(若未安装)
pip3 install --user git-filter-repo
# 3. 在 bare mirror 上执行 filter-repo(没有工作树,不会删任何业务文件)
cd /root/myapp-cleanup-${TS}.git
~/.local/bin/git-filter-repo --force \
--path public/assets/ \
--path-glob '*.mp4' \
--invert-paths
# 4. GC 压缩,确认体积
git reflog expire --expire=now --all
git gc --prune=now --aggressive
du -sh .
# 5. 修正 remote(mirror clone 默认指向本地路径,不是远端)
git remote set-url origin git@github.com:your-org/your-repo.git
# 6. force-push 到远端(用显式分支名,不要 --mirror)
git push --force origin master几个细节需要注意:
- 不要用
git push --mirror origin:mirror push 会把refs/remotes/origin/*也推到远端,污染远端的 ref 命名空间。 - 生产机的
.git保留原样,当天然备份:不要在 force-push 完之后立刻在生产机git fetch && git reset --hard origin/master——这等于把大文件再从工作树里删掉一次,走回老路。生产本地 repo 与远端分叉是可以接受的,生产机本来就不应该 push。 - 事前查
.env历史:git log --all --oneline -- .env,如果曾经入库过凭据,要先走凭据轮换流程,不能只清历史了事。
git clone --mirror <prod-path> 得到的是纯 .git,没有工作树。在这个副本上跑 filter-repo,不管删多少历史 blob,nginx 正在服务的物理文件一个字节都不会少。清完再 git push --force origin <branch> 推远端即可。
顺手检查:.gitignore 盲区
.git 膨胀的根本原因往往是 .gitignore 漏写了某个目录。常见高危路径:
public/assets/
static/
uploads/
media/
dist/
清完历史记得补 .gitignore,否则下次有人 git add . 又会把大文件喂进去,历史再次膨胀。
一句话外卖
永远不在生产工作仓库直跑
git filter-repo——用--mirrorclone 出一个 bare 副本,那里没有工作树,filter-repo 没有文件可删。