Git
English ▾ 主題 ▾ 最新版本 ▾ 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 儲存其大多數主要儲存庫資料的方式。在儲存庫的生命週期中,鬆散物件(如果有的話)和較小的 packs 會被合併為較大的 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。但是為了節省空間,一個物件可以儲存為另一個「基本」物件的「差異」。這些表示法被分配了新的 ofs-delta 和 ref-delta 類型,這僅在 pack 檔案中有效。

ofs-delta 和 ref-delta 都儲存要應用於另一個物件(稱為基本物件)的「差異」,以重建該物件。它們之間的區別在於,ref-delta 直接編碼基本物件名稱。如果基本物件在同一個 pack 中,ofs-delta 會改為編碼基本物件在 pack 中的偏移量。

如果基本物件在同一個 pack 中,它也可以進行差異化處理。ref-delta 也可以引用 pack 之外的物件(即所謂的「精簡 pack」)。但是,當儲存在磁碟上時,pack 應該是獨立的,以避免循環相依性。

差異資料以基本物件的大小和要重建的物件的大小開始。這些大小使用上述的大小編碼進行編碼。差異資料的其餘部分是一系列指令,用於從基本物件重建物件。如果基本物件已差異化,則必須先將其轉換為標準形式。每個指令都會向目標物件附加越來越多的資料,直到完成為止。目前支援兩種指令:一種用於從來源物件複製位元組範圍,另一種用於插入嵌入在指令本身中的新資料。

每個指令的長度都是可變的。指令類型由第一個八位元組的第七位元決定。以下圖表遵循 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)

  • 一個與 v1 相同的 256 條目扇出表。

  • 一個排序的物件名稱表。這些物件名稱被打包在一起,沒有偏移量值,以減少特定物件名稱二進位搜尋的快取空間。

  • 一個打包物件資料的 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 位元組的雜湊函數識別碼(對於 SHA-1 為 = 1,對於 SHA-256 為 = 2)。

  • 一個索引位置表(每個打包物件一個,總共 num_objects 個,每個位置為網路順序的 4 位元組無符號整數),按其在 pack 檔案中對應的偏移量排序。

  • 一個結尾,包含一個

    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 個值是相應 pack 中第 i 個物件的修改時間 (mtime),按字典順序(索引)排序。mtime 計算標準 epoch 秒。

  • 一個結尾,其中包含相應 pack 檔案的校驗碼,以及上述所有內容的校驗碼(每個校驗碼的長度均根據指定的雜湊函數而定)。

多重 pack 索引 (MIDX) 檔案具有以下格式

多重 pack 索引檔案引用多個 pack 檔案和鬆散物件。

為了允許擴展向 MIDX 添加額外資料,我們將主體組織成「區塊」,並在主體開頭提供一個查找表。標頭包含某些長度值,例如 pack 的數量、基本 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.

多重 pack 索引反向索引

與基於 pack 的反向索引類似,多重 pack 索引也可用於產生反向索引。

這個反向索引不是映射偏移量、pack 和索引位置之間的關係,而是映射物件在 MIDX 內的相對位置,以及該物件在 MIDX 所描述的虛擬 pack 內的相對位置(也就是說,多重 pack 反向索引的第 i 個條目保存著虛擬 pack 順序中第 i 個物件的 MIDX 位置)。

為了釐清這些排序之間的差異,請考慮一個多重 pack 可達性位元圖(目前還不存在,但這是我們正在努力實現的目標)。每個位元都需要對應到 MIDX 中的一個物件,因此我們需要一個從位元位置到 MIDX 位置的有效映射。

一個解決方案是讓位元佔據 MIDX 儲存的 oid 排序索引中的相同位置。但由於 oid 實際上是隨機的,它們產生的可達性位元圖將沒有局部性,因此壓縮效果會很差。(這就是單一 pack 位元圖使用 pack 排序,而不是 .idx 排序的原因,目的是相同。)

因此,我們希望為整個 MIDX 定義一個基於 pack 排序的排序,這樣有更好的局部性(因此壓縮效率更高)。我們可以想像一個虛擬 pack,由 MIDX 中所有 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 內物件的排序與實際 packfile 中的排序相同。

給定 pack 列表及其物件計數,您可以天真地重建虛擬 pack 的排序(例如,位置 27 的物件必須是 (c,1),因為 pack "a" 和 "b" 用掉了 25 個位置)。但這裡有個陷阱。物件可能會在 pack 之間重複,在這種情況下,MIDX 只會儲存一個指向該物件的指標(因此我們只需要在位元圖中保留一個位置)。

呼叫者可以自行處理重複物件,按照它們的位元位置順序讀取物件,但這與物件數量成線性關係,對於一般的位元圖查找來說太過昂貴。建構反向索引可以解決這個問題,因為它是索引的邏輯逆向,而該索引已經刪除了重複物件。但是,即時建構反向索引可能會很耗費效能。由於我們已經有基於 pack 的反向索引的磁碟格式,我們也可以將其用於 MIDX 的虛擬 pack。

MIDX 中的物件按以下順序排列,以串連虛擬 pack。讓 pack(o) 返回 MIDX 從中選擇 o 的 pack,並根據其數值 ID(如 MIDX 儲存)定義 pack 的排序。讓 offset(o) 返回 opack(o) 內的物件偏移量。然後,比較 o1o2 如下:

  • 如果 pack(o1)pack(o2) 中有一個是首選,而另一個不是,則首選的那個會先排序。

    (這是一個細節,允許 MIDX 位元圖確定 pack 重用機制應使用哪個 pack,因為它可以向 MIDX 查詢位元位置 0 的物件所在的 pack)。

  • 如果 pack(o1) ≠ pack(o2),則根據 pack ID 以遞減順序對兩個物件進行排序。

  • 否則,pack(o1) = pack(o2),並且物件按照 pack 順序排序(也就是說,只有當 offset(o1) < offset(o2) 時,o1 才在 o2 之前排序)。

簡而言之,MIDX 的虛擬 pack 是 MIDX 儲存的 pack 中物件的去重複串聯,以 pack 順序排列,並且 pack 以 MIDX 順序排列(首選 pack 排在最前面)。

MIDX 的反向索引儲存在 MIDX 本身內的可選 *RIDX* 區塊中。

BTMP 區塊

位元圖 Packfile (BTMP) 區塊編碼了多重 pack 索引的可達性位元圖中關於物件的額外資訊。回想一下,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 的多重 pack 可達性位元圖)時,git-pack-objects[1] 會執行「逐字」重複使用,嘗試重複使用位元圖或首選 packfile 的區塊,而不是將物件新增到打包列表。

當重複使用現有 pack 中的位元組區塊時,其中包含的任何物件都不需要新增到打包列表,這樣可以節省記憶體和 CPU 時間。但是,只有在滿足以下條件時,才能重複使用現有 packfile 中的區塊:

  • 該區塊僅包含呼叫者請求的物件(也就是說,不包含任何呼叫者沒有明確或隱含要求提供的物件)。

  • 以偏移量或參考 delta 形式儲存在非 thin pack 中的所有物件,也會在其產生的 pack 中包含其基礎物件。

BTMP 區塊編碼了實現在一組 packfile 上進行多重 pack 重複使用所必需的資訊,如上所述。具體而言,BTMP 區塊為儲存在 MIDX 中的每個 packfile p 編碼三條資訊(所有 32 位元無符號整數,以網路位元組順序表示),如下所示:

bitmap_pos

多重 pack 索引的可達性位元圖中,由 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 pack

cruft pack 功能提供了 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.

無法訪問的物件不會立即移除,因為這樣做可能會與傳入的推送產生競爭,該推送可能會引用即將刪除的物件。相反,這些無法訪問的物件會以鬆散物件的形式儲存,並保持這種狀態,直到它們超過到期窗口期,屆時它們會被 git-prune[1] 移除。

Git 必須將這些無法訪問的物件鬆散地儲存,以便追蹤它們的每個物件的 mtime。如果這些無法訪問的物件被寫入一個大型 pack 中,那麼刷新該 pack(因為其中包含的物件被重新寫入)或建立一個新的無法訪問物件的 pack 將會導致該 pack 的 mtime 被更新,並且其中的物件將永遠不會離開到期窗口期。相反,物件會鬆散地儲存,以便追蹤每個物件的 mtime,並避免所有 cruft 物件一次刷新的情況。

當儲存庫包含許多尚未離開寬限期的無法訪問物件時,這可能會導致不理想的情況。在 .git/objects 的碎片中擁有大型目錄可能會導致儲存庫效能下降。但是,如果無法訪問的物件足夠多,則可能會導致 inode 耗盡,並降低整個系統的效能。由於我們永遠無法打包這些物件,因此這些儲存庫通常會佔用大量的磁碟空間,因為我們只能使用 zlib 壓縮它們,而不能將它們儲存在 delta 鏈中。

Cruft pack

cruft pack 消除了將無法訪問的物件以鬆散狀態儲存的需要,它會在單一包含所有鬆散物件的 pack 旁邊,包含一個單獨的檔案,其中包含每個物件的 mtime。

cruft pack 由 git repack --cruft 在產生新 pack 時寫入。git-pack-objects[1]--cruft 選項。請注意,git repack --cruft 是一種經典的 all-into-one 重新打包,這意味著產生的 pack 中的所有內容都是可訪問的,而其他所有內容都是無法訪問的。寫入後,--cruft 選項會指示 git repack 產生另一個 pack,其中僅包含上一步中未打包的物件(相當於將所有無法訪問的物件打包在一起)。此過程如下:

  1. 列舉每個物件,將 (a) 未包含在保留 pack 中,且 (b) 其 mtime 位於寬限期內的任何物件標記為遍歷提示。

  2. 根據上一步中收集的提示執行可達性遍歷,將沿途的每個物件新增到 pack 中。

  3. 將 pack 寫出,同時寫出一個 .mtimes 檔案,其中記錄每個物件的時間戳記。

當指示寫入 cruft pack 時,git-repack[1] 會在內部呼叫此模式。至關重要的是,核心中保留的 pack 的集合,正是重新打包將不會刪除的 pack 的集合;換句話說,它們包含儲存庫的所有可訪問物件。

當儲存庫已經有 cruft pack 時,git repack --cruft 通常只會向其中新增物件。例外情況是,當 git repack 收到 --cruft-expiration 選項時,它允許產生的 cruft pack 省略過期的物件,而不是等待 git-gc[1] 稍後使這些物件過期。

通常由 git-gc[1] 負責移除過期的無法訪問物件。

替代方案

此設計的值得注意的替代方案包括:

  • 每個物件的修改時間 (mtime) 資料存放位置。

關於修改時間資料的存放位置,我們選擇建立一個與 pack 檔相關聯的新輔助檔案,以避免使 .idx 格式變得複雜。如果 .idx 格式未來能夠支援可選的資料區塊,那麼將 .mtimes 格式整合到 .idx 本身可能會是合理的做法。

GIT

屬於 git[1] 工具套件的一部分

scroll-to-top