关于大型git仓库的大小的问题,可能是每一个开发者遇到的棘手的问题,尤其仓库中涉及很多文件以及包含二进制文件时候,虽然git对这类问题从根源上的解决方案,比如Git LFS, git稀疏索引等,但是这个问题在实际中也一直还存在。本文我们来学习微软一个开源Javascript 项目git仓库瘦身的实践,学习他们排查、解决的整个过程。
概述微软官方有个Javascript 仓库1JS。该仓库不光文件大,有上百GB ,而且在代码和贡献量方面也非常巨大,刚刚突破月度1000 个活跃,涉及大约2500 个软件包,以及大约2000万行的代码。 由于仓库巨大,使得其clone变得基本不可能,为了解决这个问题其内部对仓库涉及的问题进行了排查和优化。
目录不要存大量文件几年前,刚开始时这个库仅仅只有一两个G,但是随着时间增长这仓库也在成倍在增长,几个月后就达到了4G,翻了两翻。通过运行一个git-sizer工具,该工具会列出仓库中一下大blob对象(文件)。注意到当有人意外签入某些二进制文件时,就会出现大blob对象。为此,仓库中对此签入的文件做了强制大小限制(Azure DevOps的一项功能)。但是这个问题也一直没有解决,一旦签进二进制大文件,仓库就会一直存在,并且累积起来。
另外该仓库标记的beachball的变化文件。基于相同的方式使用变化集类似的目标,实现与语义发布,报告包如何自动改变其语义版本范围。
有时,可能会在一个文件夹中包含多达4万个文件,然后发现每次向该文件夹中添加新文件时,都会创建一个巨大的树对象。
因此,通过分析得到第一个教训就是:
不要将数千个东西保存在一个文件夹中
为了解决这个问题,最后采取了两个措施:一是对beachball 的拉取请求,它在单个更改文件中进行了多项更改,而不是每个包进行一次更改。
其次,编写了一个管道,该管道会定期运行并自动清理该更改文件夹,以防止其变大。
不同目录中同名CHANGELOG带来的麻烦解决了仓库中大文件和目录中大量文件的问题后,问题并没有多少改善,仓库还是没瘦身多少。
仓库的大规模版本控制流程维护着main被称为versioned 它存储包的实际版本,以便可以保留main 不会造成git 冲突,并且可以准确查看哪些git 提交对应于NPM包发布的semver版本。
但是因为仓库变大,版本控制分支似乎变得越来越难克隆。但是,既然已经处理了更改文件问题,唯一要做的就是版本提交方面的分支会被附加到CHANGELOG.md和CHANGELOG.json 文件。
优化后,存储库虽然增长速度虽然稍变缓,但还是不会不停增长。至于这些增长是由于规模扩大还是其他原因造成的则还没有定数。该仓库自2021年以来,每年都会有数百名开发人员都会添加数十万行代码,因此其规模增长显然是不可避免的。然而,对比其他同类的git仓库,比如Microsoft 最大的单一存储库之一Office 存储库的增长率,可以发现事情没有那么简单。
通过相关git专家的帮助分析仓库的增长速度明显是不自然的。当拉取版本化分支(那些仅更改CHANGELOG.md和CHANGELOG.json 的分支)时,竟然生成了125GB的额外的git数据。
经过一些超深入的git 源代码挖掘,结果发现Linux Torvalds签入的一些Git旧代码存在一些小Bug,在推送差异之前的文件实际上只在准备压缩文件名时检查文件名的最后16 个字符。
对于上下文,通常git只是推送已更改文件的差异,但是,由于这个问题,git 会错误的比较来自两个不同包的CHANGELOG.md 文件。
例如,如果更改了repo/packages/foo/CHANGELOG.json,当git 准备好要进行推送时,正生成一个包(目录)repo/packages/bar/CHANGELOG.json的差异。这样在很多情况会一次又一次地推送整个文件,在某些情况下每个文件可能有10 MB,可以想象在存储库中大小,这将是一个很大的问题。
然后可以尝试使用更大的窗口重新打包存储库git repack -adf --window=250 让git 更好地压缩的存储库的包文件以减小大小。这确实显着减少了存储库的大小,但是,可以做得更好!
通过PR提交的补丁(git-for-windows/git/pull/5171 )新添加了一种基于path-walk 路径(而不是默认的提交)来打包存储库的新方法,解决这个问题后,瘦身效果非常明显:
使用Microsoft git fork补丁新版本(git 版本 2.47.0.vfs.0.2... )后运行了一个新的git克隆,
运行新的后git repack -adf --path-walk ...
整个仓库一下子从178GB锐减到5GB。
另外,补丁中还添加了另一个新配置选项将进一步确保在以下位置生成正确类型的增量:git push 时间...
git config --global pack.usePathWalk true
这将确保git push 命令执行正确的压缩。
git 版本2.47.0.vfs.0.2 上的任何开发人员现在都可以在本地克隆后重新打包存储库,并使用新版本git path-walk算法来进行仓库瘦身。
在Github 上,重新打包和git垃圾收集会定期执行,但是同样的Github 执行的打包类型也无法正确计算这些CHANGELOG.md 和CHANGELOG.json 文件的增量,或者可能具有相同16+角色名的称任何文件的增量随着发生了很大的变化,比如想想i18n 类型的大字符串文件等。
总结如果在大型单一存储库的情况下,在仓库中有CHANGELOG.md 或其他任何具有相对较长名称(> 16 个字符)且反复更新的文件,可能需要密切关注路径。
还可以尝试git survey命令启发式查看发现这个,例如按磁盘大小排列的热门文件、按文件变化大小排列的热门目录或文件变化大小排列的热门文件。
如果在实际工作中也遇到git仓库中匪夷所思的大小乱长的问题,则可以用同样思路排查一下,如果遇到相同问题则可以直接升级git补丁包,就可以直接解决问题。