Git与大多数其他版本控制系统之间的主要区别之一是,其他人倾向于将提交存储为一系列增量 - 一次提交与下一次提交之间的更改集。这似乎是合乎逻辑的,因为它是存储提交的最小可能信息量。但是,提交历史记录越长,比较修订范围所需的计算就越多。
相比之下,Git在每个版本中存储了整个项目的完整快照。这不会使每个提交的repo大小显着增长的原因是项目中的每个文件都存储为Git子目录中的文件,以其内容的哈希命名。因此,如果内容未更改,则散列未更改,并且提交仅指向同一文件。还有其他优化。
所有这一切对我来说都是有意义的,直到我偶然发现this information about pack files,Git定期为此节省数据以节省空间:
为了节省这个空间,Git 利用packfile。这是一个 格式,Git只会保存 在第二部分发生了变化的部分 文件,指向文件的指针 类似于。
这基本上不会回到存储增量吗?如果没有,它有什么不同?这如何避免让Git遇到其他版本控制系统遇到的相同问题?
例如,Subversion使用增量,回滚50个版本意味着撤消50个差异,而使用Git,您可以获取适当的快照。除非git还在packfiles中存储50个差异...是否有一些机制说“在经过一些少量的增量之后,我们将存储一个全新的快照”,这样我们就不会堆积太大的变更集? Git还有什么可以避免增量的缺点?
答案 0 :(得分:66)
要点:
Git的包文件经过精心构建,可以有效地使用磁盘缓存和
为常用命令和最近引用的读取提供“好的”访问模式
对象。
Git的包文件 格式非常灵活(参见Documentation/technical/pack-format.txt, 或The Packfile中的The Git Community Book)。 pack文件以两个主要方式存储对象 方式:“不定型”(取原始对象数据和deflate-compress it),或“deltified”(然后形成一个与其他物体相对的三角洲) deflate-compress得到的delta数据)。存储的对象 一个包可以是任何顺序(它们不一定必须) 按对象类型,对象名称或任何其他属性排序)和 可以对任何其他相同类型的合适对象进行分层对象。
Git的pack-objects命令使用了几个heuristics 为普通人提供出色的locality of reference 命令。这些启发式控制了基础的选择 用于分层对象的对象和对象的顺序。每 机制大多是独立的,但它们有一些目标。
Git确实形成了delta压缩对象的长链,但是
启发式试图确保只有“旧”对象在最后
长链。增量基本缓存(谁的大小由
core.deltaBaseCacheLimit
配置变量)是自动的
使用并可以大大减少所需的“重建”次数
需要读取大量对象的命令(例如git log
-p
)。
典型的Git存储库存储了大量的对象,所以 它无法合理地比较它们以找到对(和 ()将产生最小的增量表示。
delta基础选择启发式基于以下思想 在具有相似文件名的对象中将找到良好的delta基础 和尺寸。每种类型的对象都是单独处理的(即 一种类型的对象永远不会被用作a的delta基础 另一种类型的对象)。
出于delta base selection的目的,对象按(主要)排序 文件名然后大小。进入此排序列表的窗口用于限制 被视为潜在delta基础的对象数。 如果没有找到对象的“足够好” 1 delta表示 在其窗口中的对象之间,该对象将不是delta 压缩。
窗口的大小由--window=
选项控制
git pack-objects
或pack.window
配置变量。该
delta链的最大深度由--depth=
控制
git pack-objects
或pack.depth
配置的选项
变量。 --aggressive
git gc
选项大大扩大了
窗口大小和尝试创建的最大深度
一个较小的包文件。
文件名排序将带有with的条目的对象聚集在一起
相同的名称(或至少相似的结尾(例如.c
))。尺寸
sort从最大到最小,以便删除数据的增量
首选添加数据的增量(因为删除增量更短
表示)以及更早,更大的对象(通常
更新的)倾向于用普通压缩来表示。
1 什么称为“足够好”取决于所讨论的对象的大小及其潜在的delta基数以及其产生的delta链的深度。
对象存储在“最近引用的”包文件中 订购。重建最近历史所需的对象是 放在包装的早期,他们将在一起。这个 通常适用于OS磁盘缓存。
所有提交对象都按提交日期排序(最近一次)
并存储在一起。此放置和排序可优化磁盘
访问历史图表并提取基本提交所需的访问权限
信息(例如git log
)。
树和blob对象从树中开始存储 首次存储(最近)提交。每棵树都在深度处理 第一种方式,存储任何尚未存在的物体 存储。这将重建所需的所有树和blob 最近的一次提交在一个地方。任何树木和斑点 尚未保存,但以后的提交是必需的 以排序的提交顺序存储在下一个。
最终对象排序受delta基础选择的轻微影响 如果为delta表示及其基础对象选择了一个对象 尚未存储,然后它的基础对象存储在之前 令人满意的对象本身。这可以防止由于此而导致的磁盘缓存未命中 读取“自然”的基础对象所需的非线性访问 稍后存储在包文件中。
答案 1 :(得分:7)
在包文件中使用增量存储只是一个实现细节。在那个级别,Git不知道为什么或如何从一个版本更改为下一个版本,而只是知道blob B非常类似于blob A,除了这些更改C.因此它只会存储blob A并更改C (如果它选择这样做 - 它也可以选择存储blob A和blob B)。
从包文件中检索对象时,增量存储不会向调用者公开。呼叫者仍然看到完整的blob。因此,Git的工作方式与没有增量存储优化的情况一样。
答案 2 :(得分:4)
正如我在“What are git's thin packs?”
中提到的那样Git仅在packfiles中进行解决
我在“Is the git binary diff algorithm (delta storage) standardized?”中详细说明了用于包文件的增量编码 另请参阅“When and how does git use deltas for storage?”。
请注意,对于Git 2.0.x / 2.1(2014年第3季度),控制包文件默认大小的 core.deltaBaseCacheLimit
配置很快就会从16MB提升到96MB。 / p>
请参阅David Kastrup撰写的commit 4874f54(2014年5月):
默认值为16m会导致大型delta链与大文件组合严重颠簸。
以下是一些基准测试(
git blame
的pu变体):
time git blame -C src/xdisp.c >/dev/null
用于在SSD驱动器上重新打包的
git gc --aggressive
(v1.9,窗口大小为250)的Emacs存储库。
有问题的文件大约有30000行,1Mb大小,以及大约有的历史记录 2500次提交。
16m (previous default):
real 3m33.936s
user 2m15.396s
sys 1m17.352s
96m:
real 2m5.668s
user 1m50.784s
sys 0m14.288s