前言
事情的起因是因为公司项目git仓库大小有 6
个多G,这导致在首次拉项目的时候用的时间非常久(通常需要一个小时左右),并且项目拉下来之后在本地使用git工具的时候会非常卡,这时通常电脑风扇都要转冒烟了,因为项目里面的.git文件非常大,有6
个多G,同事们深受其苦,于是我就想去解决掉这个问题。
原因
实际上项目体积不应该有这么大,项目正常仓库体积也就50M
以内,导致这个问题的原因主要是历史记录
里面存在大文件
。
为什么有些大文件已经删除掉了还会存在占用存储空间的情况呢?这是因为git提供了版本控制的能力,如果这些文件没有保存,那么回退到有这些大文件的提交的时候,这些大文件要从哪里获取呢?所以这些文件即使现在被删除了,但是还是会被保存在.git文件中,以便需要的时候恢复。
如何清理
首先需要明确一点,清理只是修改跟已清理文件相关的提交记录,不会删掉提交记录 ,所有的tag和分支的提交记录都能正常保留,只是无法回溯已经清理的文件(都已经清理了应该也不需要回溯了),所以基本可以放心清理。
前置操作
⚠️ 兼容处理【重要】
远程仓库通常是多人协同开发的,清理文件的时候不能让其他人的本地修改丢失,分下面两种情况操作:
- 如果是所有人都直接拉取原始仓库代码,后续直接提PR/MR到原始仓库的情况
需要所有人把本地需要的修改提交到原始仓库
上的临时个人分支,清理完成后重新拉取原始仓库
就可以把自身的代码拉下来,然后再删除个人临时分支,防止本地代码后续无法推送到远程。 - 如果是每个人都分别fork原始仓库,后续提PR/MR到原始仓库的情况的情况
可以在原始仓库
先针对每个人建一个临时分支,让所有人的代码先合到原始仓库
对应个人分支上,后续清理完成之后,所有人重新拉取原始仓库就可以把自身的代码拉下来,然后再删除临时个人分支。
总之,需要保证清理的仓库是原始仓库
且原始仓库中已经包含了所有需要的代码
(不能有代码存在本地未提交到远程原始仓库,否则这部分代码后续无法直接提交,只能通过复制等手段到清理后的仓库提交),也就是说清理的期间不宜大量改动代码,本地可以少量改动,后续可以复制代码到新仓库提交。
重新克隆原始仓库
等所有人需要保留的代码都提交到远程原始仓库
后,新建一个文件夹,在新的文件夹内重新克隆一下远程原始仓库
,这么做是因为后续会推送本地所有分支到远程,防止本地测试分支后续被推送到远程,保证仓库的干净。
ps:清理前可以先记录一下
.git
文件大小,以便清理后对比分析清理效果~清理完成之后,所有人需要重新克隆一下仓库(注意是
clone
不是fetch
)
本地清理
清理有两种方式,一种是使用原生git命令清理,这种方式清理比较慢,而且可能存在一些问题。另一种就是利用官方推荐的一款工具清理,这种方式非常高效,也是官方推荐的清理方式。
以下两种方式可任选一种方式进行清理。
⭐️ 工具清理(官方推荐)
使用工具 git-filter-repo
清理(该工具是官方推荐改写commit的工具,效率非常高,清理一个文件用时在秒级),也是我比较推荐的清理方式。
git官方文档:

清理步骤
-
首先在【项目根路径】打开命令行终端
-
安装 git-filter-repo(需提前安装 Python,安装非常简单)
执行命令:
pip install git-filter-repo
-
生成仓库文件大小分析报告
执行命令:
git filter-repo --analyze
报告会生成在
.git/filter-repo/analysis
目录下,查看path-all-sizes.txt
或path-sizes.txt
找到大文件生成文件内容大致如下:
列表按照文件大小从大到小排列,最上面的文件就是占用存储空间最大的文件
-
【关键】删除大文件
执行命令:
git filter-repo --path '你要删除的文件路径(路径最好从上面报告内容中复制)' --invert-paths
参数说明:
--path
:指定要删除的目录路径--invert-paths
:反转匹配,即删除匹配的路径,保留未匹配的路径
--path 参数说明:
-
比如从上述例子中可以看出占用空间最大的是
native/aaa-chrome-1.0.2.tgz
文件,但是这个文件现在不用了,所以我们需要删掉。删除历史中所有名为
native/aaa-chrome-1.0.2.tgz
的文件:
git filter-repo --path native/aaa-chrome-1.0.2.tgz --invert-paths
-
删除以后会发现可能还有
native/aaa-chrome-1.1.22.tgz
的文件也需要删除,这时候就可以 使用通配符删除,native/*
表示删除native/
下所有文件:
git filter-repo --path-glob 'native/*' --invert-paths
-
也可直接删除
native
文件夹及内部文件的资源和历史记录:
git filter-repo --path 'native/' --invert-paths
-
清理多个文件可多次执行此命令以后再执行后续命令
-
使所有引用日志(reflog)立即过期 (以便后续的垃圾回收可以清理这些日志)
执行命令:
git reflog expire --expire=now --all
参数说明:
--expire=now
:将所有引用日志标记为立即过期--all
:对所有引用(分支、标签等)生效
-
执行垃圾回收,清理未使用的对象并优化仓库
执行命令:
git gc --aggressive --prune=now
参数说明:
--aggressive
:执行更彻底的优化(耗时较长)--prune=now
:立即清理所有过期的对象
原生清理
使用git提供的原生方法清理,该方式效率较低,尝试过清理一个文件需要大概20+分钟,非常耗时。这里我还是列出来,方便不便安装工具的掘友使用。
清理步骤
-
首先在【项目根路径】打开命令行终端
-
使用git原生命令分析git中的前20大对象
执行命令:
git verify-pack -v .git/objects/pack/pack-*.idx | sort -k 3 -g | tail -20
查询结果大致如下:
对象hash值 | 对象类型 | 对象原始大小 | 对象在pack文件中的压缩后大小 | 对象在pack文件中的偏移量
f7c7cb9fe6dea5fa4b295aeae2b14f827798abd2 blob 278508674 278592188 28110370
9af39b00b682139afe4f2a139709f8f3997413a9 blob 304147466 304238811 2471683414
165e05b191e3c36fb5ba6fdfad5537f9187b005f blob 386827775 386943760 1108838686
50d86002dd0e931f881daa13580b29e81dbd1c3c blob 386827790 386943774 2084739640
8582ef8ccb42a9731d98d5029cd15c730ce89889 blob 386886524 387002668 574217690
其中对象类型包含:
- blob:文件内容
- tree:目录结构
- commit:提交记录
- tag:标签
-
使用git命令查看大文件的路径
执行命令:
git rev-list --objects --all | grep 对象哈希值
比如运行:
git rev-list --objects --all | grep 8582ef8ccb42a9731d98d5029cd15c730ce89889
查询结果大致如下:
对象hash值 | 对象文件路径
8582ef8ccb42a9731d98d5029cd15c730ce89889 native/aaa-chrome-1.1.22.tgz
-
【关键】删除文件并覆盖提交记录
执行命令:
git filter-branch --index-filter 'git rm --cached --ignore-unmatch 要删除的文件名'
参数说明:
- --cached:从索引中删除文件,但保留工作目录中的文件
- --ignore-unmatch:如果文件不存在,忽略错误(避免命令中断)
-
删除git在执行
filter-branch
时备份的原始引用(refs/original/),确保清理彻底执行命令:
rm -rf .git/refs/original/
-
使所有引用日志(reflog)立即过期,以便后续的垃圾回收可以清理这些日志
执行命令:
git reflog expire --expire=now --all
参数说明:
--expire=now
:将所有引用日志标记为立即过期--all
:对所有引用(分支、标签等)生效
-
检查git仓库的完整性,并列出所有不可达的对象(即不再被任何引用指向的对象)
执行命令:
git fsck --full --unreachable
参数说明:
--full
:执行完整的检查--unreachable
:只列出不可达的对象
-
重新打包git对象,优化存储并删除冗余对象
执行命令:
git repack -A -d
参数说明:
-A
:重新打包所有对象,包括未引用的对象-d
:删除冗余的包文件
-
【关键】执行垃圾回收(Garbage Collection),清理未使用的对象并优化仓库
执行命令:
git gc --aggressive --prune=now
参数说明:
--aggressive
:执行更彻底的优化(耗时较长)--prune=now
:立即清理所有过期的对象
检查清理效果
最简单的办法就是直接查看仓库文件大小是否较清理前有显著减小,或者对比.git
文件的大小。
清理前后commit的变化
清理前(有文件记录)

清理后(文件记录被改写)

查看提交记录是否完好
检查一下各个分支和tag的提交记录是否完好,可以尝试checkout某一个提交看看是否符合预期,但是本地最好不要有修改。
推送到远程
上述本地清理(任选一种方式)步骤完成之后,就需要把本地已经优化的仓库的所有修改(分支和tag中的commit被改写)推送到远程。
操作步骤
-
为了操作过程中出现问题可以回退,推送到远程之前,首先需要备份老仓库
以
GitLab
为例,点击fork:配置信息:
最后等待fork完成就好。
-
备份好仓库之后,就可以将本地优化后的仓库推送到【原始仓库】
因为有备份,所以可以直接覆盖原始仓库代码。
先执行命令 :
git push --force origin --all
(强制将本地仓库的分支推送到远程,覆盖远程仓库分支的历史记录)
再执行命令 :git push --force origin --tags
(强制将本地仓库的tag推送到远程,覆盖远程仓库tag的历史记录)这两条命令执行完成之后,清理就算全部完成了!
可能遇到的问题
在执行第2步git push --force origin --all
的时候,可能会遇到如下报错:
scss
! [remote rejected] xxx分支 -> xxx分支 (pre-receive hook declined)
! [remote rejected] xxx分支 -> xxx分支 (pre-receive hook declined)
! [remote rejected] xxx分支 -> xxx分支 (pre-receive hook declined)
这通常是由于某些分支开启了分支保护导致的,需要取消分支保护后再重新推送。
以GitLab
为例,取消分支保护的操作(如果没有权限,可以联系仓库管理员操作):

上图操作执行完成之后再重新执行git push --force origin --all
和git push --force origin --tags
即可。
推送到远程成功之后,记得恢复之前的GitLab
配置,如果只是打开了 Allowed to force push
的开关,则直接重新关闭就行,如果是删除了分支保护规则,则需要重新建立分支保护规则:
填写分支保护信息:

后置操作
清理完成之后需要重新再clone
一下新推送上去的仓库,如果是fork
的仓库也需要重新fork
,操作完成后就可以在崭新的仓库中开始开发了~
以前的本地仓库就不再使用啦,用新的本地仓库啦~
清理效果
最后给大家看看我们公司项目经过我的清理以后,效果怎样吧。
公司项目文件的大小主要来自于.git
文件, 除去.git
文件,项目文件的实际大小只有 22 M
。
.git 文件大小
清理前(6.04G)

清理后(36.5M)

首次clone项目时间
清理前(53min)
清理后(7S)
总结
- 体积缩小了169倍,从 6.04 GB 减少到 36.5 MB;
- 清理了.git文件99.4% 的存储占用;
- 项目拉取时间从1小时左右 缩减为10秒之内。
思考
如果项目中涉及到需要引用一些大文件
,比如一些压缩包或者二进制文件之类的,最好是单独抽成一个npm包 ,不要直接放在开发的主项目中,否则会导致git保存这些大文件到.git
目录,后面项目文件就会越来越大。
瘦身以后的仓库克隆对比以前真的是飞快,而且本地开发时git工具再也不卡了,非常流畅。如果你也有相同的苦恼,可以和我一样尝试清理一下~