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:
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:
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
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:
- Validează antetul
PACKși numărul versiunii - Găsește decalajul prin fișierul
.idxși analizează varintul tipului/dimensiunii intrării - Decomprimă datele comprimate cu zlib
- Pentru obiectele de bază: returnează direct conținutul
- 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
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();
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:
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
use Pitmaster\Pack\DeltaApplier;
$result = DeltaApplier::apply($baseContent, $deltaInstructions);
DeltaApplier:
- Citește dimensiunea de bază așteptată și dimensiunea rezultatului din antetul delta (codificat LEB128)
- 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
- 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:
- Citește intrarea. Dacă este un tip de bază (commit, arbore, blob, tag), returneaz-o.
- Dacă OFS_DELTA: calculează decalajul bazei (decalajul curent minus decalajul negativ codificat), citește intrarea de bază, apoi apelează recursiv.
- Dacă REF_DELTA: caută baza după hash în indexul aceluiași fișier pack, apoi apelează recursiv.
- Aplică instrucțiunile delta asupra bazei rezolvate.
- Impune
PITMASTER_MAX_DELTA_CHAIN(implicit 50) pentru a preveni lanțuri excesiv de lungi.
// 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).
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:
- Colectează obiectele de inclus în fișierul pack
- Serializază fiecare obiect cu antetul varint pentru tip și dimensiune
- Comprimă conținutul cu zlib
- 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:
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.
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.
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:
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:
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:
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);