git-filter-repo 把 .git 从 112MB 砍到 1.4MB,但漏推 tag 让 clone 又胖回来

今天来从一个真实的项目案例聊聊 git 管理的仓库大小的问题,希望给大家一些借鉴和启发,经常使用 github 的人可能会出现偶尔推送代码到远端失败的案例,很可能是你的代码中包含太多的图片和视频资源,导致单次提交超过 github 单次提交的上限(单个普通 Git 文件/对象 最大 100 MiB),如果不小心提交了一些大文件我们应该如何处理?

我有个 Chrome 扩展项目,工作区总共才 2.5MB,可每次 git clone 下来,.git/objects 都有 112MB。我在很早前把演示视频从仓库里删了,改成 GitHub 外链,提交历史干干净净。但是当我重新 clone,眼看着进度条又爬过 100MB 时,第一反应是------这清理压根没生效?

清理是生效了的。只是删的不彻底。

一、重量不在工作区,在历史里

先做个体检。du -sh 一看,整个项目 115MB,其中 .git 自己就占了 112MB,工作区那点源码和截图加起来不到 3MB。也就是说,胖的是版本库本身,不是当前的文件

git 是内容寻址的------每一个被提交过的文件,都会作为一个 blob 永久存在 .git/objects 里,靠它的 SHA 寻址。git rm 做的事情,只是在「下一次提交」里不再跟踪这个文件,让它在工作区和索引里消失。它没有、也不可能回头去抹掉历史提交里已经固化下来的那个 blob。所以那个 48.5MB 的演示 gif、那几个几十 MB 的 mp4,自打被 commit 进去的那一刻起,就成了仓库的永久负担,安安静静躺在历史深处。

把所有历史里的大对象揪出来:

bash 复制代码
git rev-list --objects --all \
  | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
  | awk '$1=="blob" && $3 > 1048576 {print $4}' | sort -u

输出一目了然:四个大视频文件,最大一个 gif 48.5MB。这就是 112MB 的去向。

二、重写历史:git-filter-repo

要把这些 blob 从历史里真正挖掉,得重写历史。官方工具是 git-filter-repo,老牌的 git filter-branch 和 BFG 都已被它取代------更快、更安全、更少踩坑。

bash 复制代码
pip3 install git-filter-repo

# 重写前,先备份 .git(这步不可逆)
cp -R .git ~/youtube-live-translate.git-backup

git filter-repo --invert-paths \
  --path public/demo-video-with-music.gif \
  --path public/demo-video-with-music.mp4 \
  --path dist/demo-video-with-music.mp4 \
  --force

--invert-paths 是反向匹配:排除列出的这几个路径,保留其余一切。千万别把方向搞反------不加这个参数就是正向匹配,会变成「只保留这几个、删掉其余」,那是另一场灾难。

跑完,filter-repo 扫了全部 35 个提交,重写、repack,本地 .git 从 112MB 直降到 1.4MB。提交历史、commit message、顺序、文件内容,全都原样保留,变的只有 hash。历史里再搜大 blob,零个。

到这里,看起来是个干净利落的胜利。

三、然后,新 clone 又变回了 100MB

强推到远程,本地也确认干净了。过了一阵,他在另一台机器上重新 clone------.git/objects 又是 100MB。

第一次清理,难道白做了?

没有。但这次失败指向了一个比「历史里有大文件」更隐蔽的事实。

四、可达性:clone 体积的唯一真相

git clone 不会把仓库里所有 对象都拉下来,它只拉可达的对象------从分支、tag 这些引用出发,沿着 parent 链一路能走到的 commit 及其关联的 blob。一个对象哪怕还物理存在于服务端,只要没有任何引用能到达它,clone 时就不会传输它。

把这条规则套到现状上:本地 .git 确实干净了,远程 main 分支也确实指向了重写后的新历史。那为什么 clone 还在拉 100MB?只能是------远程上仍然有某个引用,能走到含视频的旧历史

候选只有两个:分支,或 tag。分支只有 main,已经更新。那就只剩 tag。

bash 复制代码
# 本地 tag(重写后指向新 hash)
git show-ref --tags
# 远程 tag
git ls-remote --tags origin | grep -v '\^{}'

一对比,破案了。远程有十来个版本 tag,其中从 v1.3.0 到 v1.6.1 这一批,hash 和本地对不上------远程的 tag 还指向重写前的旧 commitfilter-repo 在本地把分支和 tag 一起重写了,但我们强推的时候只推了 main 分支,忘了推 tag。于是远程那几个旧 tag 依然锚定在旧历史上,旧历史里的视频 blob 对它们完全可达,clone 时照单全收。

修复只需要一行:

bash 复制代码
git push origin --force --tags

推完再真实 clone 一次,.git/objects 终于落到 2.0MB,大 blob 一个都搜不到了。

五、为什么会踩这个坑

因为「重写历史」在我们的直觉里约等于「改 main」,而 tag 是那种提交完就再也没人管的东西。一旦打上,它就长在远程某个 commit 上,记录着「这是 v1.6.1 的样子」。filter-repo 在本地重写了 tag 指向,但本地和远程是两个世界------本地改了,远程不知道;推了 main,tag 还停在原地。

这个坑的可怕之处在于它的假性成功 :本地 .git 真的瘦了,git log 真的干净,远程 main 也真的更新了,所有自检命令都绿。唯有重新 clone 一次,才会被狠狠打脸。所以验证仓库瘦身,只有一个标准------真 clone 一遍

bash 复制代码
git clone <url> /tmp/check && du -sh /tmp/check/.git/objects

本地 du 会骗你,因为本地还残留着重写前的引用缓存(remote-tracking、reflog、备份);只有从一个对远程一无所知的全新 clone 出发,测出来的才是别人真正会下载的体积。

六、把大文件挡在门外

git 没有按文件大小忽略的能力,只能按扩展名。治本的办法是让大视频从一开始就进不来:

gitignore 复制代码
# 大体积媒体文件 ------ 用外链/Release 托管,不入库
*.mp4
*.mov
*.webm
*.avi
*.mkv
*.flv
*.gif
# 截图类 *.webp / *.png / *.svg 体积小且有用,保持入库

演示视频这类东西,正确归宿是 GitHub Release 的附件,或者外部图床/对象存储的链接,而不是 git 仓库本身。

七、复盘:一个清单

整个过程的教训,可以压缩成五句话:

  1. 删文件删不掉历史。 git rm 管未来,不管过去;blob 一旦 commit 就固化。
  2. 体积由可达性决定。 clone 只拉可达对象,分支和 tag 是唯二的入口。
  3. 重写历史后,分支和 tag 必须一起强推。 漏推 tag 是最隐蔽的坑,症状是「本地干净、远程 main 也干净、但 clone 还是胖」。
  4. 用真实 clone 验证,别信本地 du。
  5. 预防胜于治疗。 .gitignore 按扩展名把大文件挡在门外。

最后一个小细节:filter-repo 跑完会主动删掉 origin 远程 ,这是它防止你手滑误推的设计。记得 git remote add origin <url> 加回来。而强推的时候,用 --force-with-lease 而不是裸 --force------如果远程在你重写之后被别人推过新提交,它会拒绝,避免你一巴掌把别人的工作盖掉。

112MB 到 1.4MB,技术上一行 filter-repo 就够了。真正花时间的,是理解为什么删了它还在、为什么推了还胖、以及怎么确信自己真的搞定了。git 的体积问题,本质上是一道关于可达性的题。

相关推荐
峰向AI2 小时前
Block 放出大招!Buzz:一个中继统一代码、聊天、CI 全流程
github
dong_junshuai2 小时前
每天一个开源项目#47 4.4K Stars 的 LikeC4:让架构图随代码进化
github
独立开阀者_FwtCoder5 小时前
最近做了一个健身小程序:智形健身助手,健身的佬们来提点意见
前端·javascript·github
夕夕木各9 小时前
从第一个 PR 到 Vite 官方中文文档维护者
github·vite
AOwhisky9 小时前
云原生 DevOps 工具链从入门到实战(第一期)——DevOps概述与GitLab部署——从理念到工具落地
运维·ci/cd·云原生·gitlab·开发·devops
隔窗听雨眠10 小时前
GitHub Actions自动化运维实战:从零构建一体化CI/CD流水线
运维·自动化·github
dong_junshuai1 天前
每天一个开源项目#46 World Monitor:6.6万星、56层地图的全球情报中枢
github
Xu_youyaxianshen1 天前
Git 零基础常用指令手册(Gitee / GitHub 通用 )
git·gitee·github
阿里嘎多学长1 天前
2026-07-22 GitHub 热点项目精选
开发语言·程序员·github·代码托管