简体中文 ▾ 主题 ▾ 最新版本 ▾ gitformat-pack 上次更新于 2.44.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 校验和、索引校验和和下面提到的对象 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.
  • 尾部记录上述所有内容的 pack 校验和。

对象类型

有效的对象类型有

  • OBJ_COMMIT (1)

  • OBJ_TREE (2)

  • OBJ_BLOB (3)

  • OBJ_TAG (4)

  • OBJ_OFS_DELTA (6)

  • OBJ_REF_DELTA (7)

类型 5 保留供将来扩展。 类型 0 无效。

大小编码

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

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

增量表示

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

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

如果基础对象在同一个 pack 中,它也可以被增量化。 Ref-delta 也可以引用 pack 之外的对象(即所谓的“瘦 pack”)。 但是,在磁盘上存储时,pack 应该是独立的,以避免循环依赖。

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

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

从基础对象复制的指令

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

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

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

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

+----------+---------+---------+
| 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,这是一个不合理的 fanout[0] 值。

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

  • 一个 256 条目的扇出表,就像 v1 一样。

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

  • 一个 4 字节的 CRC32 值表,包含打包对象的数据。 这是 v2 中的新功能,因此压缩数据可以直接从一个 pack 复制到另一个 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),按词典排序(索引)。 mtime 计数标准 epoch 秒。

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

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

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

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

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

标头

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

块查找

(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.

块数据

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.

尾部

Index checksum of the above contents.

多包索引反向索引

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

此反向索引不是在偏移量、包和索引位置之间进行映射,而是在 MIDX 中的对象位置与 MIDX 描述的伪包中的对象位置之间进行映射(即,多包反向索引的第 i 个条目保存伪包顺序中第 i 个对象的 MIDX 位置)。

为了阐明这些排序之间的区别,请考虑一个多包可达性位图(它还不存在,但我们正在朝着这个方向努力)。每个位都需要对应于 MIDX 中的一个对象,因此我们需要一种从位位置到 MIDX 位置的有效映射。

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

因此,我们希望为整个 MIDX 定义一个基于包排序的排序,它具有更好的局部性(因此压缩效率更高)。我们可以将伪包视为通过连接 MIDX 中的所有包而创建的。例如,如果我们有一个包含三个包(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|

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

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

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

MIDX 中的对象按以下方式排序,以将伪包串联在一起。让 pack(o) 返回 MIDX 从中选择 o 的包,并根据它们的数字 ID(由 MIDX 存储)定义包的排序。让 offset(o) 返回 pack(o)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

多包索引的可达性位图中 p 中的对象占用的第一个位位置(按伪包顺序)。

bitmap_nr

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

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

bitmap_pos bitmap_nr

包文件“a”

0

10

包文件“b”

10

15

包文件“c”

25

20

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

cruft 包

cruft 包功能提供了 Git 移除无法访问对象的传统机制的替代方案。本文档概述了 Git 的修剪机制,以及如何使用 cruft 包来完成相同的操作。

背景

要从您的存储库中删除无法访问的对象,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,并避免所有 cruft 对象一次性刷新的情况。

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

Cruft 包

cruft 包通过将每个对象的 mtime 包含在与包含所有松散对象的单个包并排的单独文件中,从而消除了以松散状态存储无法访问的对象的需要。

当生成新包时,git repack --cruftgit-pack-objects[1]--cruft 选项会写入 cruft 包。请注意,git repack --cruft 是一个经典的 all-into-one 重新打包,这意味着生成的包中的所有内容都是可访问的,而其他所有内容都是无法访问的。写入后,--cruft 选项指示 git repack 生成另一个仅包含未在上一步中打包的对象的包(相当于将所有无法访问的对象打包在一起)。这按以下方式进行:

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

  2. 基于上一步中收集的提示执行可达性遍历,并将沿途的每个对象添加到包中。

  3. 写出包,以及记录每个对象的时间戳的 .mtimes 文件。

当指示写入 cruft 包时,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