簡體中文 ▾ 主題 ▾ 最新版本 ▾ 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