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:
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:
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
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:
- Convalida l'intestazione
PACKe il numero di versione - Trova l'offset tramite il file
.idxe analizza il varint di tipo e dimensione della voce - Decomprime i dati compressi con zlib
- Per gli oggetti base: restituisce direttamente il contenuto
- 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
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)
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:
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
use Pitmaster\Pack\DeltaApplier;
$result = DeltaApplier::apply($baseContent, $deltaInstructions);
Il componente DeltaApplier:
- Legge dal header delta la dimensione prevista della base e quella del risultato (codifica LEB128)
- 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
- 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:
- Legge la voce. Se è di un tipo base (commit, tree, blob, tag), la restituisce.
- Se è OFS_DELTA: calcola l'offset della base (offset corrente meno offset negativo codificato), legge la voce base e richiama se stesso.
- Se è REF_DELTA: cerca la base tramite hash nell'indice dello stesso pack e richiama se stesso.
- Applica le istruzioni delta alla base risolta.
- Impone
PITMASTER_MAX_DELTA_CHAIN(predefinito 50) per evitare catene troppo lunghe.
// 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).
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:
- Raccoglie gli oggetti da includere nel pack
- Serializza ogni oggetto con un'intestazione varint di tipo e dimensione
- Comprime il contenuto con zlib
- 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:
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.
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.
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:
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:
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:
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);