Vai al contenuto

File Pack

I file pack sono il meccanismo di git per archiviare e trasferire i dati in modo efficiente. Invece di memorizzare ogni oggetto come file separato compresso con zlib, i file pack riuniscono molti oggetti in un unico file, con compressione delta facoltativa. Quasi tutti i repository clonati usano file pack.

Struttura di un file pack

Un file pack (.git/objects/pack/pack-{hash}.pack) contiene:

Codice
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)

Codifica del tipo di oggetto

Ogni voce inizia con un intero a lunghezza variabile in cui tipo e dimensione sono intercalati:

Codice
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 dei tipi:

Valore Tipo
1 Commit
2 Tree
3 Blob
4 Tag
6 OFS_DELTA (delta basato sull'offset)
7 REF_DELTA (delta basato sull'hash)

Lettura dei file 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();

La classe PackFile:

  1. Convalida l'intestazione PACK e il numero di versione
  2. Trova l'offset tramite il file .idx e analizza il varint di tipo e dimensione della voce
  3. Decomprime i dati compressi con zlib
  4. Per gli oggetti base: restituisce direttamente il contenuto
  5. Per gli oggetti delta: risolve la base (seguendo le catene) e applica il delta, memorizzando nella cache gli oggetti risolti

File indice pack

Ogni file .pack ha un file .idx corrispondente per ricerche rapide. Senza l'indice, per trovare un oggetto occorre esaminare l'intero pack.

Formato dell'indice v2

Codice
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();

La tabella fanout consente una ricerca binaria O(log n): le voci 256 indicano quanti hash iniziano con ciascun possibile primo byte.

Formato dell'indice v1

Formato precedente, senza checksum CRC32. Pitmaster supporta entrambi i formati: PackIndex::open() legge un file v1 se manca la firma magica v2, quindi lo stesso codice funziona per entrambi.

Risoluzione dei delta

Gli oggetti rappresentati tramite delta memorizzano istruzioni per ricostruire il contenuto a partire da un oggetto base, invece di conservarlo per intero. Ciò consente una compressione notevole per oggetti simili (e.g., versioni consecutive dello stesso file).

Tipi di delta

OFS_DELTA (tipo 6): l'oggetto base è identificato da un offset negativo all'interno dello stesso file pack. È il tipo più comune perché non richiede di conoscere l'hash dell'oggetto base.

REF_DELTA (tipo 7): l'oggetto base è identificato dal suo hash. In git, la base può trovarsi fuori dal pack (pack sottili). Pitmaster cerca le basi REF_DELTA solo all'interno dello stesso pack; pertanto i pack sottili non sono supportati (#16).

Istruzioni delta

Il contenuto delta è una sequenza di istruzioni di copia e inserimento:

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

Istruzioni di copia: i 7 bit X indicano quali byte di offset e dimensione sono presenti. L'offset è lungo fino a 4 byte e la dimensione fino a 3 byte. Se la dimensione è zero, significa 0x10000 (65536) byte.

Istruzioni di inserimento: i 7 bit L indicano la lunghezza dei dati letterali (da 1 a 127 byte). L=0 è riservato/non valido.

DeltaApplier

PHP
use Pitmaster\Pack\DeltaApplier;

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

Il componente DeltaApplier:

  1. Legge dal header delta la dimensione prevista della base e quella del risultato (codifica LEB128)
  2. Scorre le istruzioni:
    • Copia: estrae offset e dimensione, quindi copia i byte dal contenuto base
    • Inserimento: estrae la lunghezza, quindi copia i byte letterali dai dati delta
  3. Verifica che la dimensione del risultato corrisponda a quella prevista

Catene di delta

Anche l'oggetto base di un oggetto rappresentato tramite delta può essere a sua volta rappresentato tramite delta. PackFile::read() e readAtOffset() seguono la catena:

  1. Legge la voce. Se è di un tipo base (commit, tree, blob, tag), la restituisce.
  2. Se è OFS_DELTA: calcola l'offset della base (offset corrente meno offset negativo codificato), legge la voce base e richiama se stesso.
  3. Se è REF_DELTA: cerca la base tramite hash nell'indice dello stesso pack e richiama se stesso.
  4. Applica le istruzioni delta alla base risolta.
  5. Impone PITMASTER_MAX_DELTA_CHAIN (predefinito 50) per evitare catene troppo lunghe.
PHP
// Configure max chain depth (integer)
define('PITMASTER_MAX_DELTA_CHAIN', 50);

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

PackWriter

Pitmaster può scrivere file pack per le operazioni di rete (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

Il componente PackWriter:

  1. Raccoglie gli oggetti da includere nel pack
  2. Serializza ogni oggetto con un'intestazione varint di tipo e dimensione
  3. Comprime il contenuto con zlib
  4. Scrive l'intestazione PACK, le voci e il trailer SHA-1

Al momento scrive solo oggetti base (senza compressione delta). La compressione delta è un'ottimizzazione che può essere aggiunta in seguito; lasciate che git gc ricompatti i dati per ottimizzare lo spazio di archiviazione.

PackEnumerator

Scorre tutti gli oggetti di un pack, nell'ordine dell'indice:

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";
}

Ogni elemento è un GitObject risolto.

MultiPackIndex

Nei repository con molti file pack, l'indice multipack (MIDX) consente di cercare in un'unica volta in tutti i pack.

PHP
use Pitmaster\Pack\MultiPackIndex;

Il file MIDX (.git/objects/pack/multi-pack-index) contiene:

  • Una tabella fanout (come l'indice pack v2)
  • ID degli oggetti ordinati tra tutti i pack
  • Nomi dei file pack e assegnazioni dei pack per ogni oggetto
  • Offset nei singoli file pack

MultiPackIndex::open($path) analizza un file MIDX e offre findObject($hex) e packNames(). Le ricerche degli oggetti nel repository non lo usano: il database degli oggetti legge sempre i singoli file .idx.

CommitGraph

Il file commit-graph accelera l'attraversamento della cronologia dei commit e il calcolo della base di merge.

PHP
use Pitmaster\Pack\CommitGraph;

Si trova in .git/objects/info/commit-graph e contiene:

  • OID dei commit in ordine
  • Informazioni sui genitori (indicizzate, senza dover leggere gli oggetti)
  • Numeri di generazione (per query di raggiungibilità più rapide)
  • Timestamp dei commit

Quando è presente, il calcolo della base di merge per Repository::merge(), mergeBase(), isBranchMerged() e il controllo di avanzamento rapido del push leggono i collegamenti ai genitori dal commit-graph invece di leggere i singoli oggetti commit. CommitWalker (log) non lo usa.

Interi a lunghezza variabile

I file pack usano diverse codifiche di interi a lunghezza variabile:

Varint Git (intestazioni di tipo/dimensione)

Codifica MSB-continue, in cui il bit più significativo indica che seguono altri byte:

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 (intestazioni delta)

Usati negli header delle istruzioni delta per la dimensione di base e quella del risultato:

PHP
use Pitmaster\Encoding\Leb128;

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

BinaryReader

Flusso di byte a basso livello con tracciamento della posizione:

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);