git 是追踪代码库演进的***选择 ,并且它能让你与你的同事间高效协作。当你想要追踪的库非常巨大时会发生什么?
在这篇文章里,我会尝试着给你一些想法和技巧来恰当地处理不同种类的大仓库。
两种大代码库
如果仔细想想,大概会有两种导致仓库大规模增长的原因:
-
项目累积了非常长的历史(项目成长了很长一段时间并且积累了包袱)。
-
项目包括了巨大的二进制资产,需要与代码一起跟踪配对。
-
两者皆有。
因此,仓库的增长有两个维度的方向:工作目录的尺寸——例如:最近一次提交,和整个累积历史的尺寸。
有时第二种问题会与老的过时的二进制生成的东西(artifact)混合,它们都被放在仓库中,不过这类问题是比较容易处理的——如果它们很讨厌,就覆盖它们,见下文。
上述两种场景需要的技巧和解决方案是不同的——尽管有时候需要互补——让我们分别来处理它们吧。
处理拥有大量历史记录的库
将一个库视为 大规模 库的界线非常高 - 比如 Linux 内核的***一个版本记录了超过 1500 万行代码,但人们仍然愿意完整阅读 - 由于监管/规定方面的原因,某些很老的项目仍然需要保持完整,克隆它们是件痛苦的事情(现在通过拆分 Linux 库的方式使其结构清晰,它被拆分为历史库和最近时期的库,需要通过嫁接设置来访问完整的历史记录)。
浅克隆是简单的的解决办法
为了更快、更节省开发者和系统时间也更节约磁盘空间,***个解决办法是使用 git 进行浅克隆。通过浅克隆可以只克隆某个库***的历史记录。
怎么做到?只需要使用 --depth 选项,比如:
git clone --depth depth remote-url
想像一下,如果你的项目库中积累了 10 年甚至更长时间的历史记录 - 比如 JIRA 是我们往 git 迁移的一个 11 年的老库 - 累积节约的时间非常显著。
完整的克隆 JIRA 有 677 MB,如果包含工作目录还有另外的 320+ MB,总共超过 47,000 多次提交。通过浅克隆的方式检出 JIRE 需要 29.5 秒,而检出完整的历史记录则需要 4 分 24 秒。随着时间地推移及项目二进制资产的增长,这个差距也会成比例的增长。任何情况下,构建系统都会大大受益于这种技术(指浅克隆)。
最近 git 改善了对浅克隆的支持
过去浅克隆就像 git 世界里的残障人士一样,某些操作并未得到支持。不过最近的版本 (1.9+) 对此有着显著的改善,现在甚至可以适当的对浅克隆库使用 pull 和 push 操作。
另一个解决办法是 filter-branch (过滤分支)
巨大的库往往存在着大量错误的提交或无用的资源,对此,使用 filter-branch 是个很好的解决办法。这个命令可以根据预先定义的模式对项目历史进行过滤、 整理 、修改,甚至跳过一些文件。它是 git 工具集中的一个非常强大的工具。目前已经有脚本可以用于识别 git 库中的大型对象,所以它使用起来非常容易。
使用 filter-branch 的示例:
git filter-branch --tree-filter 'rm -rf /path/to/spurious/asset/folder' HEAD
filter-branch 有一个小小的缺点:一旦使用了 filter-branch,实际上已经重写了整个项目历史,因此每次提交的 ID 都会发生变化。这要求每个开发者都要重新克隆更新后的库。
所以,如果你打算使用 filter-branch 来进行一次清理行动,应该警告你的团队,计划一个短期的冻结来进行操作,然后通知大家重新克隆库。
浅克隆的替代者:只克隆一个分支
从 2012 年 4 月发布的 git 1.7.10 开始,你可以通过只克隆某一个分支来限制历史记录的数量,就像这样:
git clone URL --branch branch_name --single-branch [folder]
对于长期运行分发的分支,或者你在有很多分支的情况下,这个特殊的技巧都非常有用。如果你只有极少数分支,那这个办法不会带来显著的效果。
处理拥有巨大二进制资产的库
第二类大型仓库中的代码含有 巨大的二进制资产 。游戏团队要处理巨大的 3D 模型,Web 开发团队需要跟踪图像资产,CAD 团队可能需要操作和跟踪二进制交付物的状态。所以有各种不同的软件团队在使用 git 的过程中会遇到这样的问题。
git 在处理二进制资产的时候并不是特别差劲,但它也不会干得特别好。默认情况下,git 会完整压缩存储二进制资产的所有后续版本,如果你有很多二进制资产的情况下,这显然不是***方案。
可以通过一些 基本的调整来改善情况 ,比如运行垃圾回收 git gc,或者在 .gitattributes 中对部分二进制类型进行调整,以使用 delta 方式的提交。
不过有一点很重要,对项目中不同性质的二进制资产可能需要不同的方法。例如,这里需要检查三个方面(感谢 Stefan Saasen 的评论):
-
对于变化显著的二进制文件 - 这是指不仅只有元数据头变化 - 这时增量压缩可能没什么作用,建议对这些文件关闭 delta 选项,以避免不必要的增量压缩并重新打包
-
对于上述情形,就像某些文件通过 zlib 压缩并不会有多好的效果,你使用 core.compression 0 或 core.loosecompression 0 来关闭压缩功能一样;这是一个全局设置,它会对其它压缩效果不错的非二进制文件带来负面影响。因此建议你把二进制资产放在单独的库中。
-
一定要记住 git gc 将“重复的”松散的对象变成一个单独的包文件,除非以任何方式压缩文件都不会使生成的包文件有显著差异。
-
探索调整 core.bigFileThreshold 带来的效果。任何大于 512 MiB 都不会采用 delta 压缩 - 如果没有设置 .gitattributes 的话 - 所以这样的调整值得一试。
技巧1: 稀疏检出
一个温和的管理二进制资产问题的方法是 稀疏检出 (从 Git 1.7.0 之后可用)。我们可以通过显式地详细说明要填充的文件夹来保持工作目录的清洁。 不幸的是,它并不能影响整个本地存储库的大小,但如果你有一个巨大的树形文件夹,这可能是有用的。
涉及到哪些命令呢? 示例如下( credit ):
-
仅克隆全部存储库一次::git clone <repository-address>
-
激活以下功能:git config core.sparsecheckout true
-
添加那些需要显式依赖的文件夹,忽略 assets 文件夹:
echo src/ ? .git/info/sparse-checkout
-
读取指定的树目录:git read-tree -m -u HEAD
之后,你可以使用正常的 git 命令了,但你的工作目录将只包含你指定的文件夹。
技巧2:使用子模块
还有另一种处理二进制资产目录的的方法,就是把它们拆分到一个单独的库,然后在主项目是通过把它拉取为 子模块 。使用这种方法你可以控制资产的更新。需要了解子模块,可以看看: 核心概念与技巧 和 另一个选择 。
如果你想继续使用子模块的方法,你可能需要检查 项目依赖 的复杂性。我提到的方法对解决大型二进制文件问题会有所帮助。
技巧3:使用 git-annex 或 git-bigfiles
git 中处理二进制资产的第3个选择依靠第三方扩展。
我要说的***个扩展是 git-annex ,它可以使用 git 管理二进制文件,但不需要把文件内容检入库中。git-annex 使用一个特殊的键值库来保存文件,然后将符号链接像普通文件一样检入 git 库中进行版本管理。这种用法非常直接,还有 一看就能明白的例子 。
第二个扩展是 git-bigfiles ,一个 git 分支, 适合于使用 git 分享项目大文件的人 。
不要因为你的库有着巨大的历史记录或巨大的资产就放弃 git。这两个问题都可以得到解决。