简体中文 ▾ 主题 ▾ 最新版本 ▾ gitformat-pack 最后更新于 2.52.0

名称

gitformat-pack - Git pack 格式

概要

$GIT_DIR/objects/pack/pack-.{pack,idx}
$GIT_DIR/objects/pack/pack-.rev
$GIT_DIR/objects/pack/pack-*.mtimes
$GIT_DIR/objects/pack/multi-pack-index

描述

Git pack 格式是 Git 存储其大部分原始仓库数据的方式。在仓库的生命周期中,散乱对象(如果有)和较小的 pack 会被合并成一个或多个较大的 pack。请参阅 git-gc[1]git-pack-objects[1]

pack 格式也用于网络传输,请参见例如 gitprotocol-v2[5],同时在 gitformat-bundle[5] 的情况下,它也是其他容器格式的一部分。

校验和与对象 ID

在使用传统 SHA-1 的仓库中,下面提到的 pack 校验和、index 校验和和对象 ID(对象名称)都是使用 SHA-1 计算的。同样,在 SHA-256 仓库中,这些值是使用 SHA-256 计算的。

CRC32 校验和总是对整个打包的对象进行计算,包括头部(n 字节类型和长度);如果存在,则包括基本对象名称或偏移量;以及整个压缩对象。使用的 CRC32 算法是 zlib 的算法。

pack-*.pack 文件具有以下格式

  • 文件开头有一个头部,包含以下内容

    4-byte signature:
        The signature is: {'P', 'A', 'C', 'K'}
       4-byte version number (network byte order):
    Git currently accepts version number 2 or 3 but
           generates version 2 only.
    4-byte number of objects contained in the pack (network byte order)
    Observation: we cannot have more than 4G versions ;-) and
    more than 4G objects in a pack.
  • 头部后面是多个对象条目,每个条目如下所示

    (undeltified representation)
    n-byte type and length (3-bit type, (n-1)*7+4-bit length)
    compressed data
       (deltified representation)
       n-byte type and length (3-bit type, (n-1)*7+4-bit length)
       base object name if OBJ_REF_DELTA or a negative relative
    offset from the delta object's position in the pack if this
    is an OBJ_OFS_DELTA object
       compressed delta data
    Observation: the length of each object is encoded in a variable
    length format and is not constrained to 32-bit or anything.
  • 尾部记录了所有上述内容的 pack 校验和。

对象类型

有效对象类型为

  • OBJ_COMMIT (1)

  • OBJ_TREE (2)

  • OBJ_BLOB (3)

  • OBJ_TAG (4)

  • OBJ_OFS_DELTA (6)

  • OBJ_REF_DELTA (7)

类型 5 用于未来扩展。类型 0 无效。

对象编码

与散乱对象不同,打包对象没有包含类型、大小和 NUL 字节的头部。这些不是必需的,因为它们可以从数据前的 n 字节类型和长度确定,因此它们被从压缩和增量数据中省略了。

对象 ID 的计算仍然通过根据需要从类型和长度重构此头部来使用。

大小编码

本文档使用以下“大小编码”表示非负整数:对于每个字节,使用七个最低有效位构成结果整数。只要最高有效位是 1,此过程就继续;MSB 为 0 的字节提供最后七位。七位块被连接起来。后面的值更具代表性。

此大小编码不应与本文档中也使用的“偏移量编码”混淆。

在 pack 中对未增量对象的大小进行编码时,大小是未压缩的原始对象的大小。对于增量对象,它是未压缩的增量的大小。基本对象名称或偏移量不包含在大小计算中。

增量表示

概念上只有四种对象类型:commit、tree、tag 和 blob。但是为了节省空间,对象可以存储为另一个“基本”对象的“增量”。这些表示被分配了新的类型 ofs-delta 和 ref-delta,它们仅在 pack 文件中有效。

ofs-delta 和 ref-delta 都存储要应用于另一个对象(称为基本对象)以重建对象的“增量”。它们之间的区别在于,ref-delta 直接编码基本对象名称。如果基本对象在同一个 pack 中,ofs-delta 则编码该对象在 pack 中的偏移量。

基本对象也可以被增量化,如果它在同一个 pack 中。Ref-delta 也可以引用 pack 外的对象(即所谓的“thin pack”)。然而,当存储在磁盘上时,pack 应该是自包含的,以避免循环依赖。

增量数据以基本对象的大小和要重建的对象的大小开始。这些大小使用上面的大小编码进行编码。增量数据的其余部分是一系列指令,用于从基本对象重建对象。如果基本对象被增量化,则必须先将其转换为规范形式。每条指令都会向目标对象添加越来越多的数据,直到完成为止。到目前为止支持两种指令:一种用于从源对象复制字节范围,另一种用于插入嵌入在指令本身中的新数据。

每条指令的长度可变。指令类型由第一个字节的第七位确定。下面的图表遵循 RFC 1951(Deflate 压缩数据格式)的约定。

从基本对象复制的指令

+----------+---------+---------+---------+---------+-------+-------+-------+
| 1xxxxxxx | offset1 | offset2 | offset3 | offset4 | size1 | size2 | size3 |
+----------+---------+---------+---------+---------+-------+-------+-------+

这是从源对象复制字节范围的指令格式。它编码了要复制的偏移量和要复制的字节数。偏移量和大小以小端顺序表示。

所有偏移量和大小字节都是可选的。这是为了减少编码小偏移量或大小时的指令大小。第一个字节的第七位决定了接下来的七个字节中的哪个字节存在。如果设置了第零位,则存在 offset1。如果设置了第一位,则存在 offset2,依此类推。

请注意,更紧凑的指令不会改变偏移量和大小的编码。例如,如果只省略了 offset2,如下所示,offset3 仍然包含 16-23 位。即使它紧邻 offset1,它也不会变成 offset2 并包含 8-15 位。

+----------+---------+---------+
| 10000101 | offset1 | offset3 |
+----------+---------+---------+

在其最紧凑的形式中,此指令只占用一个字节 (0x80),其中偏移量和大小都被省略,它们将具有默认值零。还有另一个例外:大小零会自动转换为 0x10000。

添加新数据的指令

+----------+============+
| 0xxxxxxx |    data    |
+----------+============+

这是在没有基本对象的情况下构造目标对象的指令。以下数据将被附加到目标对象。第一个字节的前七位确定数据的字节大小。大小必须非零。

保留指令

+----------+============
| 00000000 |
+----------+============

此指令保留供将来扩展使用。

原始 (版本 1) pack-*.idx 文件具有以下格式

  • 头部由 256 个 4 字节网络字节序整数组成。此表的第 N 个条目记录了对应 pack 中对象的数量,其对象名称的第一个字节小于或等于 N。这被称为第一级散布表。

  • 头部后面是已排序的 24 字节条目,每个 pack 中的对象一个条目。每个条目是

    4-byte network byte order integer, recording where the
    object is stored in the packfile as the offset from the
    beginning.
    one object name of the appropriate size.
  • 文件以尾部结束

    A copy of the pack checksum at the end of the corresponding
    packfile.
    Index checksum of all of the above.

Pack Idx 文件

	--  +--------------------------------+
fanout	    | fanout[0] = 2 (for example)    |-.
table	    +--------------------------------+ |
	    | fanout[1]                      | |
	    +--------------------------------+ |
	    | fanout[2]                      | |
	    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
	    | fanout[255] = total objects    |---.
	--  +--------------------------------+ | |
main	    | offset                         | | |
index	    | object name 00XXXXXXXXXXXXXXXX | | |
table	    +--------------------------------+ | |
	    | offset                         | | |
	    | object name 00XXXXXXXXXXXXXXXX | | |
	    +--------------------------------+<+ |
	  .-| offset                         |   |
	  | | object name 01XXXXXXXXXXXXXXXX |   |
	  | +--------------------------------+   |
	  | | offset                         |   |
	  | | object name 01XXXXXXXXXXXXXXXX |   |
	  | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~   |
	  | | offset                         |   |
	  | | object name FFXXXXXXXXXXXXXXXX |   |
	--| +--------------------------------+<--+
trailer	  | | packfile checksum              |
	  | +--------------------------------+
	  | | idxfile checksum               |
	  | +--------------------------------+
          .-------.
                  |
Pack file entry: <+
    packed object header:
1-byte size extension bit (MSB)
       type (next 3 bit)
       size0 (lower 4-bit)
       n-byte sizeN (as long as MSB is set, each 7-bit)
	size0..sizeN form 4+7+7+..+7 bit integer, size0
	is the least significant part, and sizeN is the
	most significant part.
    packed object data:
       If it is not DELTA, then deflated bytes (the size above
	is the size before compression).
If it is REF_DELTA, then
  base object name (the size above is the
	size of the delta data that follows).
         delta data, deflated.
If it is OFS_DELTA, then
  n-byte offset (see below) interpreted as a negative
	offset from the type-byte of the header of the
	ofs-delta entry (the size above is the size of
	the delta data that follows).
  delta data, deflated.
  offset encoding:
n bytes with MSB set in all but the last one.
The offset is then the number constructed by
concatenating the lower 7 bit of each byte, and
for n >= 2 adding 2^7 + 2^14 + ... + 2^(7*(n-1))
to the result.

版本 2 pack-*.idx 文件支持大于 4 GiB 的 pack,并且

have some other reorganizations.  They have the format:
  • 一个 4 字节的魔术数字 \377tOc,这是一个不合理的散布[0]值。

  • 一个 4 字节的版本号(= 2)

  • 一个 256 个条目的散布表,就像 v1 一样。

  • 一个已排序的对象名称表。这些对象被打包在一起,没有偏移量值,以减少二进制搜索特定对象名称的缓存占用空间。

  • 一个 4 字节的打包对象数据 CRC32 值表。这是 v2 中的新功能,因此在重新打包过程中,可以不受干扰地直接从 pack 复制压缩数据,以防止数据损坏。

  • 一个 4 字节的偏移量表(网络字节序)。这些通常是 31 位 pack 文件偏移量,但大偏移量被编码为指向下一个表的索引,并设置了 msbit。

  • 一个 8 字节偏移量条目表(对于小于 2 GiB 的 pack 文件为空)。Pack 文件被组织成将常用的对象放在前面,因此大多数对象引用不需要指向此表。

  • 与 v1 pack 文件相同的尾部

    A copy of the pack checksum at the end of the
    corresponding packfile.
    Index checksum of all of the above.

pack-*.rev 文件具有以下格式

  • 一个 4 字节的魔术数字 0x52494458 (RIDX)。

  • 一个 4 字节的版本标识符(= 1)。

  • 一个 4 字节的哈希函数标识符(= 1 表示 SHA-1,2 表示 SHA-256)。

  • 一个索引位置表(每个打包对象一个,总共 num_objects 个,每个是 4 字节无符号整数,网络序),按其在 packfile 中的相应偏移量排序。

  • 一个尾部,包含一个

    checksum of the corresponding packfile, and
    a checksum of all of the above.

所有 4 字节数字都采用网络字节序。

pack-*.mtimes 文件具有以下格式

所有 4 字节数字都采用网络字节序。

  • 一个 4 字节的魔术数字 0x4d544d45 (MTME)。

  • 一个 4 字节的版本标识符(= 1)。

  • 一个 4 字节的哈希函数标识符(= 1 表示 SHA-1,2 表示 SHA-256)。

  • 一个 4 字节无符号整数表。第 i 个值是对应 pack 中第 i 个对象的修改时间 (mtime),按字典序(索引)排序。mtimes 记录标准 epoch 秒。

  • 一个尾部,包含对应 packfile 的校验和,以及所有上述内容的校验和(每个校验和的长度根据指定的哈希函数确定)。

multi-pack-index (MIDX) 文件具有以下格式

multi-pack-index 文件引用多个 pack 文件和散乱对象。

为了允许扩展添加额外数据到 MIDX,我们将主体组织成“块”,并在主体开头提供一个查找表。头部包含一些长度值,例如 pack 的数量、base MIDX 文件的数量、哈希长度和类型。

所有 4 字节数字都采用网络字节序。

头部 (HEADER)

4-byte signature:
    The signature is: {'M', 'I', 'D', 'X'}
1-byte version number:
    Git only writes or recognizes version 1.
1-byte Object Id Version
    We infer the length of object IDs (OIDs) from this value:
	1 => SHA-1
	2 => SHA-256
    If the hash type does not match the repository's hash algorithm,
    the multi-pack-index file should be ignored with a warning
    presented to the user.
1-byte number of "chunks"
1-byte number of base multi-pack-index files:
    This value is currently always zero.
4-byte number of pack files

块查找表 (CHUNK LOOKUP)

(C + 1) * 12 bytes providing the chunk offsets:
    First 4 bytes describe chunk id. Value 0 is a terminating label.
    Other 8 bytes provide offset in current file for chunk to start.
    (Chunks are provided in file-order, so you can infer the length
    using the next chunk position if necessary.)
The CHUNK LOOKUP matches the table of contents from
the chunk-based file format, see gitformat-chunk[5].
The remaining data in the body is described one chunk at a time, and
these chunks may be given in any order. Chunks are required unless
otherwise specified.

块数据 (CHUNK DATA)

Packfile Names (ID: {'P', 'N', 'A', 'M'})
    Store the names of packfiles as a sequence of NUL-terminated
    strings. There is no extra padding between the filenames,
    and they are listed in lexicographic order. The chunk itself
    is padded at the end with between 0 and 3 NUL bytes to make the
    chunk size a multiple of 4 bytes.
Bitmapped Packfiles (ID: {'B', 'T', 'M', 'P'})
    Stores a table of two 4-byte unsigned integers in network order.
    Each table entry corresponds to a single pack (in the order that
    they appear above in the `PNAM` chunk). The values for each table
    entry are as follows:
    - The first bit position (in pseudo-pack order, see below) to
      contain an object from that pack.
    - The number of bits whose objects are selected from that pack.
OID Fanout (ID: {'O', 'I', 'D', 'F'})
    The ith entry, F[i], stores the number of OIDs with first
    byte at most i. Thus F[255] stores the total
    number of objects.
OID Lookup (ID: {'O', 'I', 'D', 'L'})
    The OIDs for all objects in the MIDX are stored in lexicographic
    order in this chunk.
Object Offsets (ID: {'O', 'O', 'F', 'F'})
    Stores two 4-byte values for every object.
    1: The pack-int-id for the pack storing this object.
    2: The offset within the pack.
	If all offsets are less than 2^32, then the large offset chunk
	will not exist and offsets are stored as in IDX v1.
	If there is at least one offset value larger than 2^32-1, then
	the large offset chunk must exist, and offsets larger than
	2^31-1 must be stored in it instead. If the large offset chunk
	exists and the 31st bit is on, then removing that bit reveals
	the row in the large offsets containing the 8-byte offset of
	this object.
[Optional] Object Large Offsets (ID: {'L', 'O', 'F', 'F'})
    8-byte offsets into large packfiles.
[Optional] Bitmap pack order (ID: {'R', 'I', 'D', 'X'})
    A list of MIDX positions (one per object in the MIDX, num_objects in
    total, each a 4-byte unsigned integer in network byte order), sorted
    according to their relative bitmap/pseudo-pack positions.

尾部 (TRAILER)

Index checksum of the above contents.

multi-pack-index 反向索引

与基于 pack 的反向索引类似,multi-pack 索引也可用于生成反向索引。

此反向索引不映射偏移量、pack 和索引位置之间的关系,而是映射对象在 MIDX 中的位置与其在 MIDX 所描述的伪 pack 中的位置之间的关系(即,multi-pack 反向索引的第 i 个条目保存第 i 个对象在伪 pack 顺序中的 MIDX 位置)。

为了阐明这些顺序之间的区别,可以考虑一个 multi-pack 可达性位图(它尚不存在,但这是我们在此构建的目标)。每个位都需要对应 MIDX 中的一个对象,因此我们需要一个从位位置到 MIDX 位置的高效映射。

一种解决方案是让位在 MIDX 存储的 oid 排序索引中占用相同的位置。但是由于 oid 实际上是随机的,因此它们的可达性位图将没有局部性,从而压缩效果不佳。(这就是为什么单 pack 位图为此目的使用 pack 顺序,而不是 .idx 顺序的原因。)

因此,我们希望为整个 MIDX 定义一个基于 pack 顺序的排序,该排序具有更好的局部性(因此压缩效率更高)。我们可以想象一个由 MIDX 中所有 pack 连接而成的伪 pack。例如,如果我们有一个包含三个 pack(a、b、c)的 MIDX,分别有 10、15 和 20 个对象,我们可以设想一个对象顺序

|a,0|a,1|...|a,9|b,0|b,1|...|b,14|c,0|c,1|...|c,19|

其中 pack 的顺序由 MIDX 的 pack 列表定义,然后每个 pack 内对象的顺序与实际 pack 文件中的顺序相同。

给定 pack 列表及其对象计数,您可以粗略地重建该伪 pack 顺序(例如,位置 27 的对象必须是 (c,1),因为 pack "a" 和 "b" 占用了 25 个槽)。但有一个问题。对象可能在 pack 之间重复,在这种情况下,MIDX 只存储一个指向对象的指针(因此我们希望位图中只有一个槽)。

调用者可以通过按位位置顺序读取对象来自己处理重复项,但这与对象数量成线性关系,对于普通的位图查找来说过于昂贵。构建反向索引可以解决这个问题,因为它是索引的逻辑逆运算,而该索引已经删除了重复项。但是,即时构建反向索引可能会很昂贵。由于我们已经有了基于 pack 的反向索引的磁盘格式,让我们也将其用于 MIDX 的伪 pack。

MIDX 中的对象按以下方式排序以串联伪 pack。令 pack(o) 返回 MIDX 选择 o 的 pack,并定义一个基于 pack ID(由 MIDX 存储)的 pack 顺序。令 offset(o) 返回 opack(o) 中的对象偏移量。然后,按如下方式比较 o1o2

  • 如果 pack(o1)pack(o2) 中有一个是首选的而另一个不是,则首选的排在前面。

    (这是一个允许 MIDX 位图确定哪个 pack 应该被 pack-reuse 机制使用的细节,因为它可以在位位置 0 处询问 MIDX 包含该对象的 pack)。

  • 如果 pack(o1) ≠ pack(o2),则根据 pack ID 以降序对两个对象进行排序。

  • 否则,pack(o1) = pack(o2),并且对象按照 pack 顺序排序(即,o1 排在 o2 前当且仅当 offset(o1) < offset(o2))。

简而言之,MIDX 的伪 pack 是 MIDX 存储的 pack 中对象的去重连接,按 pack 顺序排列,pack 按 MIDX 顺序排列(首选 pack 在前)。

MIDX 的反向索引存储在 MIDX 本身的可选 RIDX 块中。

BTMP

位图 Packfiles (BTMP) 块编码了 multi-pack index 可达性位图中对象的附加信息。回想一下,MIDX 中的对象按“伪 pack”顺序(见上文)排列以用于可达性位图。

根据上面的例子,假设我们有 pack "a"、"b" 和 "c",分别有 10、15 和 20 个对象。在伪 pack 顺序中,它们将按如下方式排列

|a,0|a,1|...|a,9|b,0|b,1|...|b,14|c,0|c,1|...|c,19|

在处理单 pack 位图(或等效地,具有首选 pack 的 multi-pack 可达性位图)时,git-pack-objects[1] 执行“逐字”重用,尝试重用位图或首选 packfile 的块,而不是将对象添加到 packing list 中。

当从现有 pack 中重用字节块时,其中包含的任何对象都无需添加到 packing list 中,从而节省内存和 CPU 时间。但是,来自现有 packfile 的块只有在满足以下条件时才能重用

  • 该块仅包含调用者请求的对象(即,不包含任何调用者未明确或隐式请求的对象)。

  • 在非 thin pack 中作为偏移量或引用增量存储的所有对象也包括其基本对象在生成的 pack 中。

BTMP 块编码了实现上述 multi-pack 重用所需的信息。具体来说,BTMP 块为 MIDX 中存储的每个 packfile p 编码三条信息(均为 32 位无符号整数,网络字节序),如下所示

bitmap_pos

multi-pack index 可达性位图中来自 p 的对象占用的第一个位位置(按伪 pack 顺序)。

bitmap_nr

编码来自该 pack p 的对象的位数(包括 bitmap_pos 处的位数)。

例如,与上述示例(包含 pack "a"、"b" 和 "c")对应的 BTMP 块将如下所示

bitmap_pos (位图位置) bitmap_nr (位图数量)

packfile "a"

0

10

packfile "b"

10

15

packfile "c"

25

20

有了这些信息,我们可以将每个 packfile 视为可单独重用,方式与在 BTMP 块实现之前对单个 pack 执行的逐字 pack 重用方式相同。

cruft packs (垃圾包)

cruft packs 功能提供了一种替代 Git 传统的移除不可达对象机制的方法。本文档概述了 Git 的修剪机制,以及如何使用 cruft pack 来实现相同的目的。

背景

要从仓库中删除不可达对象,Git 提供了 git repack -Ad(请参阅 git-repack[1])。引用文档内容

[...] unreachable objects in a previous pack become loose, unpacked objects,
instead of being left in the old pack. [...] loose unreachable objects will be
pruned according to normal expiry rules with the next 'git gc' invocation.

不可达对象不会立即删除,因为这样做可能会与传入的 push 发生竞争,而该 push 可能引用即将被删除的对象。相反,这些不可达对象被存储为散乱对象,并保持该状态,直到它们超过过期窗口,此时它们将被 git-prune[1] 删除。

Git 必须将这些不可达对象存储为散乱对象,以便跟踪它们的每个对象 mtime。如果这些不可达对象被写入一个大的 pack 中,那么无论是因为其中一个对象被重写而刷新该 pack( freshening),还是创建了一个新的不可达对象 pack,都会导致 pack 的 mtime 被更新,并且其中的对象将永远不会离开过期窗口。相反,对象以散乱状态存储是为了跟踪单个对象 mtime,并避免所有 cruft 对象同时被刷新的情况。

当仓库包含许多尚未过宽限期的不可达对象时,这可能导致不良情况。在 .git/objects 的分片中拥有大型目录可能导致仓库性能下降。但如果不可达对象足够多,这可能导致 inode 耗尽并降低整个系统的性能。由于我们永远无法打包这些对象,因此这些仓库通常占用大量磁盘空间,因为我们只能进行 zlib 压缩,但不能将它们存储在 delta 链中。

Cruft packs (垃圾包)

cruft pack 通过将每个对象的 mtime 存储在一个单独的文件中,并结合一个包含所有散乱对象的单个 pack,消除了将不可达对象存储为散乱状态的需要。

cruft pack 由 git repack --cruft 在生成新 pack 时写入。git-pack-objects[1]--cruft 选项。请注意,git repack --cruft 是经典的“全合一”repack,意味着生成 pack 中的所有内容都是可达的,而其他所有内容都是不可达的。写入后,--cruft 选项指示 git repack 生成另一个只包含上一步未打包的对象(相当于将所有不可达对象打包在一起)的 pack。这按以下步骤进行

  1. 枚举每个对象,标记任何(a)不包含在保留 pack 中,并且(b)其 mtime 在宽限期内的对象作为遍历的起点。

  2. 基于上一步收集的起点执行可达性遍历,并将沿途的所有对象添加到 pack 中。

  3. 将 pack 写出,以及一个 .mtimes 文件,该文件记录了每个对象的修改时间戳。

此模式由 git-repack[1] 在被指示写入 cruft pack 时内部调用。至关重要的是,核心保留的 pack 集就是不会被 repack 删除的 pack 集;换句话说,它们包含仓库中所有可达的对象。

当仓库已包含 cruft pack 时,git repack --cruft 通常只向其中添加对象。一个例外是当 git repack 提供了 --cruft-expiration 选项时,该选项允许生成的 cruft pack 排除过期的对象,而不是等待 git-gc[1] 稍后过期这些对象。

通常由 git-gc[1] 负责删除过期的不可达对象。

替代方案

此设计的显著替代方案包括

  • 每个对象 mtime 数据的位置。

关于 mtime 数据的位置,选择了一个与 pack 相关联的新辅助文件,以避免使 .idx 格式复杂化。如果 .idx 格式将来支持可选数据块,那么将 .mtimes 格式合并到 .idx 本身中可能会有意义。

GIT

Git[1] 套件的一部分