Treci la conținut

Fișiere Pack

Fișierele pack sunt mecanismul git pentru stocare și transfer eficiente. În loc să stocheze fiecare obiect într-un fișier separat, comprimat cu zlib, fișierele pack combină multe obiecte într-un singur fișier, cu compresie delta opțională. Aproape toate depozitele clonate folosesc fișiere pack.

Structura fișierelor pack

Un fișier pack (.git/objects/pack/pack-{hash}.pack) conține:

Cod
Header:   PACK (4 bytes) + version (4 bytes, big-endian) + count (4 bytes, big-endian)
Entries:  [type+size varint] [delta-base] [zlib-compressed data] ...
Trailer:  checksum of the pack (20 bytes SHA-1, 32 bytes SHA-256)

Codificarea tipului de obiect

Fiecare intrare începe cu un număr întreg de lungime variabilă, în care tipul și dimensiunea sunt intercalate:

Cod
First byte: 1TTTSSSS
  TTT  = object type (1-4 for base objects, 6 for OFS_DELTA, 7 for REF_DELTA)
  SSSS = first 4 bits of uncompressed size
  MSB  = 1 means more size bytes follow

Valori pentru tipuri:

Valoare Tip
1 Commit
2 Arbore
3 Blob
4 Tag
6 OFS_DELTA (delta bazat pe decalaj)
7 REF_DELTA (delta bazat pe hash)

Citirea fișierelor pack

PHP
use Pitmaster\Pack\PackFile;

$pack = PackFile::open(
    '/path/to/.git/objects/pack/pack-abc123.pack',
    '/path/to/.git/objects/pack/pack-abc123.idx',
);

$pack->has($hex);               // Is the object in this pack?
$object = $pack->read($hex);    // Resolved GitObject, or null
$object = $pack->readAtOffset($offset);
$hashes = $pack->allHashes();

Clasa PackFile:

  1. Validează antetul PACK și numărul versiunii
  2. Găsește decalajul prin fișierul .idx și analizează varintul tipului/dimensiunii intrării
  3. Decomprimă datele comprimate cu zlib
  4. Pentru obiectele de bază: returnează direct conținutul
  5. Pentru obiectele delta: rezolvă obiectul de bază (urmărind lanțurile) și aplică delta, memorând în cache obiectele rezolvate

Fișiere index pack

Fiecare fișier .pack are un fișier .idx corespunzător pentru căutări rapide. Fără index, găsirea unui obiect necesită scanarea întregului fișier pack.

Formatul indexului v2

Cod
Magic:    FF 74 4F 63 (identifies v2)
Version:  00 00 00 02
Fanout:   256 x 4-byte big-endian counts (cumulative)
Names:    N x 20-byte SHA-1 hashes, sorted (32 bytes for SHA-256)
CRC32s:   N x 4-byte CRC32 checksums
Offsets:  N x 4-byte pack offsets (MSB=1 means large offset table)
Large:    M x 8-byte offsets (for packs > 2GB)
Checksums: pack checksum + index checksum (20 bytes each for SHA-1)
PHP
use Pitmaster\Pack\PackIndex;

$index = PackIndex::open('/path/to/pack-abc123.idx');

// Look up an object
$offset = $index->findOffset($hex);  // Pack file offset, or null

// List all objects in the pack
$hashes = $index->allHashes();
$count = $index->objectCount();

Tabelul fanout permite căutarea binară O(log n): intrările 256 indică numărul de hashuri care încep cu fiecare octet inițial posibil.

Formatul indexului v1

Format mai vechi, fără sume de control CRC32. Pitmaster le acceptă pe ambele: PackIndex::open() citește un fișier v1 când lipsește semnătura magică v2, astfel încât același cod funcționează pentru ambele.

Rezolvarea delta

Obiectele delta stochează instrucțiuni pentru reconstruirea conținutului pornind de la un obiect de bază, în loc să stocheze conținutul complet. Astfel se obține o compresie semnificativă pentru obiecte similare (e.g, versiuni consecutive ale aceluiași fișier).

Tipuri delta

OFS_DELTA (tipul 6): obiectul de bază este identificat printr-un decalaj negativ în același fișier pack. Este mai frecvent, deoarece nu necesită cunoașterea hashului obiectului de bază.

REF_DELTA (tipul 7): obiectul de bază este identificat prin hashul său. În git, baza poate fi în afara fișierului pack (packuri subțiri). Pitmaster caută bazele REF_DELTA numai în același fișier pack, așa că packurile subțiri nu sunt acceptate (#16).

Instrucțiuni delta

Conținutul delta este o secvență de instrucțiuni de copiere și inserare:

Cod
Copy:   1XXXXXXX [offset bytes] [size bytes]   (copy from base)
Insert: 0LLLLLLL <L bytes of literal data>       (insert new data)

Instrucțiuni de copiere: cei 7 biți X indică octeții de decalaj și dimensiune prezenți. Decalajul poate avea până la 4 octeți, iar dimensiunea până la 3 octeți. Dacă dimensiunea este zero, înseamnă 0x10000 (65536) octeți.

Instrucțiuni de inserare: cei 7 biți L indică lungimea datelor literale (1-127 octeți). L=0 este rezervat/nevalid.

DeltaApplier

PHP
use Pitmaster\Pack\DeltaApplier;

$result = DeltaApplier::apply($baseContent, $deltaInstructions);

DeltaApplier:

  1. Citește dimensiunea de bază așteptată și dimensiunea rezultatului din antetul delta (codificat LEB128)
  2. Parcurge instrucțiunile:
    • Copiere: extrage decalajul și dimensiunea, copiază octeții din conținutul de bază
    • Inserare: extrage lungimea, copiază octeții literali din datele delta
  3. Verifică dacă dimensiunea rezultatului corespunde celei așteptate

Lanțuri delta

Baza unui obiect delta poate fi ea însăși delta. PackFile::read() și readAtOffset() urmăresc lanțul:

  1. Citește intrarea. Dacă este un tip de bază (commit, arbore, blob, tag), returneaz-o.
  2. Dacă OFS_DELTA: calculează decalajul bazei (decalajul curent minus decalajul negativ codificat), citește intrarea de bază, apoi apelează recursiv.
  3. Dacă REF_DELTA: caută baza după hash în indexul aceluiași fișier pack, apoi apelează recursiv.
  4. Aplică instrucțiunile delta asupra bazei rezolvate.
  5. Impune PITMASTER_MAX_DELTA_CHAIN (implicit 50) pentru a preveni lanțuri excesiv de lungi.
PHP
// Configure max chain depth (integer)
define('PITMASTER_MAX_DELTA_CHAIN', 50);

// Read the effective limit
$max = \Pitmaster\Pack\DeltaResolver::maxChainDepth();

PackWriter

Pitmaster poate scrie fișiere pack pentru operațiuni de rețea (push).

PHP
use Pitmaster\Pack\PackWriter;
use Pitmaster\Pack\PackIndexer;

$packData = PackWriter::encode($objects);           // GitObject[] to pack bytes
$paths = PackWriter::write($packDir, $objects);     // ['packPath' => ..., 'idxPath' => ..., 'hash' => ...]

$idxPath = PackIndexer::writeIndex($packPath);      // Build the .idx for a received pack

PackWriter:

  1. Colectează obiectele de inclus în fișierul pack
  2. Serializază fiecare obiect cu antetul varint pentru tip și dimensiune
  3. Comprimă conținutul cu zlib
  4. Scrie antetul PACK, intrările și trailerul SHA-1

În prezent scrie numai obiecte de bază (fără compresie delta). Compresia delta este o optimizare care poate fi adăugată ulterior; lăsați git gc să reîmpacheteze obiectele pentru eficiența stocării.

PackEnumerator

Parcurge toate obiectele dintr-un fișier pack, în ordinea indexului:

PHP
use Pitmaster\Pack\PackEnumerator;

$enumerator = new PackEnumerator($packFile); // a PackFile opened with its .idx

foreach ($enumerator->enumerate() as $object) {
    echo "{$object->type->value} {$object->id->hex} {$object->size()}\n";
}

Fiecare element este un GitObject rezolvat.

MultiPackIndex

Pentru depozitele cu multe fișiere pack, indexul multi-pack (MIDX) oferă o singură căutare în toate fișierele pack.

PHP
use Pitmaster\Pack\MultiPackIndex;

Fișierul MIDX (.git/objects/pack/multi-pack-index) conține:

  • Un tabel fanout (ca în indexul pack v2)
  • ID-uri de obiecte sortate în toate fișierele pack
  • Nume de fișiere pack și atribuiri de fișiere pack pentru fiecare obiect
  • Decalaje în fișierele pack individuale

MultiPackIndex::open($path) analizează un fișier MIDX și oferă findObject($hex) și packNames(). Căutările de obiecte din depozit nu îl folosesc: baza de date a obiectelor citește întotdeauna fișierele .idx individuale.

CommitGraph

Fișierul commit-graph accelerează parcurgerea istoricului commiturilor și calculul bazei comune de îmbinare.

PHP
use Pitmaster\Pack\CommitGraph;

Se află în .git/objects/info/commit-graph și conține:

  • OID-urile commiturilor în ordine sortată
  • Informații despre părinți (indexate, fără a necesita citirea obiectelor)
  • Numere de generație (pentru interogări de accesibilitate mai rapide)
  • Marcaje temporale ale commiturilor

Când este prezent, calculul bazei comune de îmbinare pentru Repository::merge(), mergeBase(), isBranchMerged() și verificarea push-ului fast-forward citesc legăturile către părinți din commit-graph, în loc să citească obiectele commit individuale. CommitWalker (log) nu îl folosește.

Numere întregi de lungime variabilă

Fișierele pack folosesc mai multe codificări pentru numere întregi de lungime variabilă:

Varint Git (antete pentru tip/dimensiune)

Codificare cu continuare MSB, în care bitul cel mai semnificativ indică prezența unor octeți suplimentari:

PHP
use Pitmaster\Encoding\VarInt;

$size = VarInt::decodePackSize($reader, $initialBits);  // continues the size from the first header byte
$offset = VarInt::decodeOfsOffset($reader);             // OFS_DELTA base distance
$header = VarInt::encodePackSize($type, $size);

LEB128 (antete delta)

Folosit în antetele instrucțiunilor delta pentru dimensiunea de bază și dimensiunea rezultatului:

PHP
use Pitmaster\Encoding\Leb128;

$value = Leb128::decodeUnsigned($reader);   // a BinaryReader
$bytes = Leb128::encodeUnsigned($value);

BinaryReader

Flux de octeți de nivel scăzut cu urmărirea poziției:

PHP
use Pitmaster\Encoding\BinaryReader;

$reader = new BinaryReader($data);
$byte = $reader->readByte();
$int = $reader->readUint32();   // big-endian
$bytes = $reader->read(20);
$hash = $reader->readHash(20);
$reader->position();  // Current offset
$reader->seek(0);