简体中文 ▾ 主题 ▾ 最新版本 ▾ gitformat-pack 上次更新于 2.44.0

名称

gitformat-pack - Git 包格式

概要

$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 包格式是 Git 存储其大部分主要仓库数据的方式。在一个仓库的生命周期中,松散对象(如果有的话)和较小的包会被合并成更大的包。请参阅 git-gc[1]git-pack-objects[1]

包格式也用于网络传输,例如参阅 gitprotocol-v2[5],并且是 gitformat-bundle[5] 等其他容器格式的一部分。

校验和与对象 ID

在传统 SHA-1 仓库中,下述的包校验和、索引校验和以及对象 ID(对象名称)都使用 SHA-1 计算。类似地,在 SHA-256 仓库中,这些值使用 SHA-256 计算。

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.
  • 尾部记录了上述所有内容的包校验和。

对象类型

有效的对象类型有

  • OBJ_COMMIT (1)

  • OBJ_TREE (2)

  • OBJ_BLOB (3)

  • OBJ_TAG (4)

  • OBJ_OFS_DELTA (6)

  • OBJ_REF_DELTA (7)

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

大小编码

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

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

增量表示

概念上只有四种对象类型:提交 (commit)、树 (tree)、标签 (tag) 和 Blob。然而,为了节省空间,对象可以作为另一个“基准”对象的“增量”存储。这些表示被分配了新的类型 ofs-delta 和 ref-delta,它们仅在包文件中有效。

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

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

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

每条指令的长度是可变的。指令类型由第一个八位字节的第七位决定。以下图表遵循 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 个条目记录了对应包中对象数量,这些对象的对象名称的首字节小于或等于 N。这被称为一级扇出表

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

    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.

包索引文件

	--  +--------------------------------+
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 的包,并且

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

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

  • 一个 256 条目的扇出表,与 v1 相同。

  • 一个已排序的对象名称表。这些名称被打包在一起,不带偏移量,以减少对特定对象名称进行二分查找时的缓存占用。

  • 一个 4 字节的已打包对象数据 CRC32 值表。这是 v2 中的新特性,以便在重新打包时可以直接从一个包复制压缩数据到另一个包,而不会出现未检测到的数据损坏。

  • 一个 4 字节偏移量值表(网络字节序)。这些通常是 31 位包文件偏移量,但大偏移量则编码为指向下一个表的索引,并将最高有效位设置为 1。

  • 一个 8 字节偏移量条目表(对于小于 2 GiB 的包文件为空)。包文件将频繁使用的对象放在前面,因此大多数对象引用不应需要引用此表。

  • 与 v1 包文件相同的尾部

    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 字节的哈希函数标识符(SHA-1 为 1,SHA-256 为 2)。

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

  • 一个尾部,包含一个

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

所有 4 字节数字都是网络字节序。

pack-*.mtimes 文件的格式如下

所有 4 字节数字都是网络字节序。

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

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

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

  • 一个 4 字节无符号整数表。第 i 个值是对应包中第 i 个对象的修改时间 (mtime),按字典序(索引)排序。mtime 以标准纪元秒为单位。

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

多包索引 (MIDX) 文件的格式如下

多包索引文件引用多个包文件和松散对象。

为了允许向 MIDX 添加额外数据的扩展,我们将主体组织成“块”,并在主体开头提供一个查找表。头部包含某些长度值,例如包的数量、基准 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.

多包索引反向索引

与基于包的反向索引类似,多包索引也可以用于生成反向索引。

这个反向索引不再映射偏移量、包和索引位置,而是映射对象在 MIDX 中的位置以及该对象在 MIDX 描述的伪包中的位置(即,多包反向索引的第 i 个条目存储伪包顺序中第 i 个对象的 MIDX 位置)。

为了澄清这些排序之间的差异,考虑一个多包可达性位图(它尚未存在,但这是我们正在努力的方向)。每个位需要对应 MIDX 中的一个对象,因此我们需要一个从位位置到 MIDX 位置的高效映射。

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

因此,我们希望围绕包排序为整个 MIDX 定义一个排序,这种排序具有更好的局部性(因此压缩效率更高)。我们可以将 MIDX 中所有包的连接视为一个伪包。例如,如果我们有一个 MIDX 包含三个包 (a, b, c),分别有 10、15 和 20 个对象,我们可以想象对象的排序如下:

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

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

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

调用者可以通过按位位置顺序读取对象来自行处理重复项,但这在对象数量上是线性的,对于普通的位图查找来说开销太大。构建反向索引解决了这个问题,因为它是索引的逻辑逆操作,并且该索引已经去除了重复项。但是,动态构建反向索引可能会很昂贵。由于我们已经有了基于包的反向索引的磁盘格式,那么也将其重用于 MIDX 的伪包吧。

MIDX 中的对象按以下方式排序以串联伪包。令 pack(o) 返回 MIDX 选择 o 的包,并根据包的数字 ID(MIDX 存储的)定义包的排序。令 offset(o) 返回 opack(o) 中的对象偏移量。然后,按以下方式比较 o1o2

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

    (这是一个细节,允许 MIDX 位图确定包重用机制应使用哪个包,因为它可以通过 MIDX 查询包含位位置 0 处对象的包)。

  • 如果 pack(o1) ≠ pack(o2),则根据包 ID 将两个对象按降序排序。

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

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

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

BTMP

位图包文件 (BTMP) 块编码了多包索引可达性位图中对象的额外信息。回想一下,MIDX 中的对象为可达性位图按“伪包”顺序(见上文)排列。

以上述示例为例,假设我们有包“a”、“b”和“c”,分别包含 10、15 和 20 个对象。在伪包顺序中,它们将按以下方式排列:

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

在使用单包位图(或等效地,带有首选包的多包可达性位图)时,git-pack-objects[1] 执行“逐字”重用,尝试重用位图或首选包文件中的块,而不是将对象添加到打包列表。

当从现有包中重用字节块时,其中包含的任何对象都不需要添加到打包列表,从而节省内存和 CPU 时间。但是,只有满足以下条件时,才能重用现有包文件中的块:

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

  • 所有以偏移或引用增量形式存储在非瘦包中的对象,其基准对象也包含在结果包中。

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

bitmap_pos

MIDX 可达性位图中,由来自 p 的对象占用的第一个位位置(在伪包顺序中)。

bitmap_nr

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

例如,对应上述示例(包含包“a”、“b”和“c”)的 BTMP 块将如下所示:

bitmap_pos bitmap_nr

包文件“a”

0

10

包文件“b”

10

15

包文件“c”

25

20

有了这些信息,我们可以将每个包文件视为可单独重用,就像在 BTMP 块实现之前对单个包执行的逐字包重用一样。

碎屑包

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

背景

要从您的仓库中移除不可达对象,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.

不可达对象不会立即被移除,因为这样做可能与即将到来的推送发生竞态,该推送可能引用即将被删除的对象。相反,这些不可达对象以松散对象的形式存储,并保持这种状态直到它们超过过期窗口,此时它们将被 git-prune[1] 移除。

Git 必须将这些不可达对象松散存储,以便跟踪它们的对象修改时间 (mtime)。如果这些不可达对象被写入一个大包中,那么无论是刷新该包(因为其中包含的对象被重新写入)还是创建一个新的不可达对象包,都会导致该包的 mtime 更新,并且其中的对象将永远不会离开过期窗口。相反,对象以松散形式存储,以跟踪单个对象的 mtime,并避免所有碎屑对象一次性刷新的情况。

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

碎屑包

碎屑包通过将每个对象的 mtime 包含在一个单独的文件中,与包含所有松散对象的单个包一起,从而消除了将不可达对象存储为松散状态的需要。

在生成新包时,git repack --cruft 会写入一个碎屑包。git-pack-objects[1]--cruft 选项。请注意,git repack --cruft 是一个经典的“全部打包到一个包”的操作,这意味着结果包中的所有内容都是可达的,而其他所有内容都是不可达的。一旦写入,--cruft 选项会指示 git repack 生成另一个只包含上一步未打包的对象的包(这等同于将所有不可达对象打包在一起)。其过程如下:

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

  2. 根据上一步收集的起点执行可达性遍历,并将沿途的每个对象添加到包中。

  3. 将包写入,并附带一个记录每个对象时间戳的 .mtimes 文件。

当被指示写入碎屑包时,此模式由 git-repack[1] 内部调用。关键在于,内存中保留的包集合恰好是不会被重新打包删除的包集合;换句话说,它们包含仓库中所有可达的对象。

当一个仓库已经有碎屑包时,git repack --cruft 通常只向其中添加对象。一个例外是当 git repack 被赋予 --cruft-expiration 选项时,这允许生成的碎屑包省略已过期的对象,而不是等待 git-gc[1] 稍后使这些对象过期。

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

替代方案

该设计值得注意的替代方案包括:

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

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

GIT

Git[1] 套件的一部分

scroll-to-top