如果对象已经被压缩,为什么 git pack-objects 会“压缩对象”?

iAd*_*nct 2 git

据我了解,存储在 [bare] git 存储库中的松散对象是经过压缩的...

...那么为什么git pack-objects(以及所有相关repackgc命令)有一个非常长的 Compressing objects阶段?不应该只是复制它们吗?

例如:

objects/75/f0debd8e421ab3f9cc8b6aeb539796ae86b705已经压缩了。在包文件中,这个文件应该在它的头之后立即按字节复制到该位置,因为包文件格式指定压缩数据去那里......那么如果它已经被压缩,为什么需要重新压缩?

如果它可能试图使用不同的压缩...我怎么能告诉它不要,而只是按原样使用文件?

更新说明:

  • 我已经设置了设置和选项,以便有效地不发生增量压缩。Delta 压缩对于存储 2 TB 的.NEF图像没有用。

tor*_*rek 6

据我了解,存储在 [bare] git 存储库中的松散对象是经过压缩的...

他们是。但它们是zlib-deflate压缩的。

...那么为什么 git pack-objects(以及所有相关的 repack 和 gc 命令)有一个非常长的 Compressing objects 阶段?

这些命令——git pack-objects而且git repack,无论如何;git gcgit repack为您运行——将许多对象组合成一个包文件

包文件是压缩对象的一种方式。松散对象是独立的:Git 只需要读取松散对象并对其运行 zlib inflate 传递以获取该对象的未压缩数据。相比之下,包文件包含许多对象,这些对象中的一些对象首先是delta-compressed

Delta 压缩实际上是这样说的:要产生这个对象,首先要产生那个其他对象。然后在此处添加这些字节和/或在此处删除 N 个字节。重复此添加和/或删除操作,直到我完成了增量列表。 (然后 delta 指令本身也可以被 zlib 压缩。)您可能认为这是一种差异,实际上,一些非 Git 版本控制系统确实使用 diff 或它们自己的内部差异引擎来生成它们的delta 压缩文件。

传统上,这使用观察到某个文件(例如,foo.ccfoo.py)倾向于通过在文件中的某处添加和/或删除几行而随着时间的推移而改变,但保持其中的大部分不变。如果我们可以说:获取所有以前的版本,然后添加和/或删除这些行,我们可以在比存储其中一个版本更少的空间内存储两个版本。

当然,我们可以在先前的 delta 压缩文件之上构建一个 delta 压缩的文件:获取扩展先前的 delta 压缩文件的结果,并应用这些 deltas。 这些构成了delta 链,它可以是你喜欢的长度,也许可以一直回到文件第一次创建的那一点。

一些(非 Git)系统到此为止:每个文件都存储为对先前版本的更改,或者,每次存储文件时,系统都会存储最新的文件,并将之前的完整副本(曾经是最新的,因此完整的副本)到将“最新”转换为“以前”所需的增量中。第一种方法称为正向增量存储,而第二种方法当然是反向增量存储。前向增量往往处于一个可怕的劣势,因为它提取最新的文件的版本需要提取第一个版本,然后应用非常长的增量序列,这需要很长时间。因此 RCS 使用反向增量,这意味着获取最新版本的速度很快;它得到了一个很慢的非常旧的版本。(但是,出于技术原因,这只适用于 RCS 称为主干的内容。RCS 的“分支”使用前向增量代替。) Mercurial 使用前向增量,但偶尔会存储文件的新完整副本,以保留增量链长短。一个系统 SCCS 使用一种 SCCS 称为interleaved deltas的技术,它为提取任何文件提供线性时间(但更难生成)。

但是,Git 不会将文件存储为files。您已经知道文件数据存储为blob object,它最初只是 zlib-deflated,否则完好无损。给定一组对象,其中一些是文件数据,而另一些不是(提交、树或带注释的标签对象),哪些数据属于哪个文件并不明显。因此,Git 所做的是找到一个可能的候选对象某个对象似乎与某个其他对象很相似,最好的表达方式可能是说从另一个对象开始,然后进行这些增量更改。

用于压缩的大部分 CPU 时间在于寻找好的链。如果版本控制系统选择的文件(或对象)很差,那么压缩将不会很好。Git 使用了一系列启发式方法,包括查看树对象来重建文件名(仅基本名称 - 不是完整路径名),否则时间复杂度会变得非常疯狂。但即使使用启发式方法,找到好的 delta 链也很昂贵。通过“窗口”和“深度”设置,可以调整成本究竟有多高。

有关随时间经过多次修订的包文件的(更多)更多信息,请参阅Git 中文档/技术目录