Git的包文件是否是快照而不是快照?

时间:2011-03-03 03:09:05

标签: git version-control internals

Git与大多数其他版本控制系统之间的主要区别之一是,其他人倾向于将提交存储为一系列增量 - 一次提交与下一次提交之间的更改集。这似乎是合乎逻辑的,因为它是存储提交的最小可能信息量。但是,提交历史记录越长,比较修订范围所需的计算就越多。

相比之下,Git在每个版本中存储了整个项目的完整快照。这不会使每个提交的repo大小显着增长的原因是项目中的每个文件都存储为Git子目录中的文件,以其内容的哈希命名。因此,如果内容未更改,则散列未更改,并且提交仅指向同一文件。还有其他优化。

所有这一切对我来说都是有意义的,直到我偶然发现this information about pack files,Git定期为此节省数据以节省空间:

  

为了节省这个空间,Git   利用packfile。这是一个   格式,Git只会保存   在第二部分发生了变化的部分   文件,指向文件的指针   类似于。

这基本上不会回到存储增量吗?如果没有,它有什么不同?这如何避免让Git遇到其他版本控制系统遇到的相同问题?

例如,Subversion使用增量,回滚50个版本意味着撤消50个差异,而使用Git,您可以获取适当的快照。除非git还在packfiles中存储50个差异...是否有一些机制说“在经过一些少量的增量之后,我们将存储一个全新的快照”,这样我们就不会堆积太大的变更集? Git还有什么可以避免增量的缺点?

3 个答案:

答案 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)。

Delta压缩启发式

典型的Git存储库存储了大量的对象,所以 它无法合理地比较它们以找到对(和 ()将产生最小的增量表示。

delta基础选择启发式基于以下思想 在具有相似文件名的对象中将找到良好的delta基础 和尺寸。每种类型的对象都是单独处理的(即 一种类型的对象永远不会被用作a的delta基础 另一种类型的对象)。

出于delta base selection的目的,对象按(主要)排序 文件名然后大小。进入此排序列表的窗口用于限制 被视为潜在delta基础的对象数。 如果没有找到对象的“足够好” 1 delta表示 在其窗口中的对象之间,该对象将不是delta 压缩。

窗口的大小由--window=选项控制 git pack-objectspack.window配置变量。该 delta链的最大深度由--depth=控制 git pack-objectspack.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月):

Bump core.deltaBaseCacheLimit到96m

  

默认值为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