Packbestanden
Packbestanden zijn het mechanisme van git voor efficiënte opslag en overdracht. In plaats van elk object als een afzonderlijk, met zlib gecomprimeerd bestand op te slaan, combineren packbestanden veel objecten in één bestand, eventueel met deltacompressie. Bijna elke gekloonde repository gebruikt packbestanden.
Structuur van packbestanden
Een packbestand (.git/objects/pack/pack-{hash}.pack) bevat:
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)
Codering van objecttypen
Elke vermelding begint met een geheel getal met variabele lengte, waarin het type en de grootte door elkaar zijn gecodeerd:
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
Typewaarden:
| Waarde | Type |
|---|---|
| 1 | Commit |
| 2 | Tree |
| 3 | Blob |
| 4 | Tag |
| 6 | OFS_DELTA (deltaverwijzing op basis van offset) |
| 7 | REF_DELTA (deltaverwijzing op basis van hash) |
Packbestanden lezen
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();
De klasse PackFile:
- Valideert de koptekst en het versienummer van
PACK - Vindt de offset via het bestand
.idxen parseert de varint voor type en grootte van de vermelding - Decomprimeert de gegevens die met zlib zijn gecomprimeerd
- Voor basisobjecten: retourneert de inhoud rechtstreeks
- Voor delta-objecten: haalt het basisobject op (en volgt daarbij de keten) en past de delta toe; opgeloste objecten worden gecachet
Packindexbestanden
Bij elk bestand van .pack hoort een bestand van .idx voor snelle zoekacties. Zonder de index moet het hele packbestand worden doorzocht om een object te vinden.
Indeling van de v2-index
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();
De fan-outtabel maakt binair zoeken naar O(log n) mogelijk: de vermeldingen van 256 geven aan hoeveel hashes met elke mogelijke eerste byte beginnen.
Indeling van de v1-index
Oudere indeling zonder CRC32-controlesommen. Pitmaster ondersteunt beide: PackIndex::open() leest een bestand van v1 als de magische waarde v2 ontbreekt, zodat dezelfde code voor beide werkt.
Delta's verwerken
Objecten met deltacompressie bevatten instructies om de inhoud uit een basisobject te reconstrueren, in plaats van de volledige inhoud op te slaan. Dit zorgt voor aanzienlijke compressie bij vergelijkbare objecten (e.g., opeenvolgende versies van hetzelfde bestand).
Deltatypen
OFS_DELTA (type 6): het basisobject wordt aangeduid met een negatieve offset binnen hetzelfde packbestand. Dit komt vaker voor omdat de hash van het basisobject niet bekend hoeft te zijn.
REF_DELTA (type 7): het basisobject wordt aangeduid met zijn hash. In git kan het basisobject buiten het packbestand staan (thin packs). Pitmaster zoekt alleen binnen hetzelfde packbestand naar REF_DELTA-basisobjecten. Thin packs worden daarom niet ondersteund (#16).
Delta-instructies
Delta-inhoud is een reeks kopieer- en invoeginstructies:
Copy: 1XXXXXXX [offset bytes] [size bytes] (copy from base)
Insert: 0LLLLLLL <L bytes of literal data> (insert new data)
Kopieerinstructies: de 7 bits X geven aan welke bytes voor offset en grootte aanwezig zijn. De offset is maximaal 4 bytes en de grootte maximaal 3 bytes. Als de grootte nul is, betekent dit 0x10000 (65536) bytes.
Invoeginstructies: de 7 L-bits geven de lengte van de letterlijke gegevens aan (1-127 bytes). L=0 is gereserveerd en ongeldig.
DeltaApplier
use Pitmaster\Pack\DeltaApplier;
$result = DeltaApplier::apply($baseContent, $deltaInstructions);
De DeltaApplier:
- Leest de verwachte grootte van het basisobject en de resultaatgrootte uit de delta-koptekst (gecodeerd met LEB128)
- Doorloopt de instructies:
- Kopiëren: haalt de offset en grootte op en kopieert bytes uit de inhoud van het basisobject
- Invoegen: haalt de lengte op en kopieert de letterlijke bytes uit de deltagegevens
- Controleert of de resultaatgrootte overeenkomt met de verwachte grootte
Deltaketens
Het basisobject van een deltacompressie kan zelf ook deltacompressie gebruiken. PackFile::read() en readAtOffset() volgen de keten:
- Lees de vermelding. Als het een basistype is (commit, tree, blob, tag), retourneer deze.
- Bij OFS_DELTA: bereken de base-offset (huidige offset min de gecodeerde negatieve offset), lees de basisvermelding en herhaal.
- Bij REF_DELTA: zoek het basisobject op hash op in de index van hetzelfde packbestand en herhaal.
- Pas de delta-instructies toe op het opgeloste basisobject.
- Beperk de keten met
PITMASTER_MAX_DELTA_CHAIN(standaard 50) om eindeloze ketens te voorkomen.
// Configure max chain depth (integer)
define('PITMASTER_MAX_DELTA_CHAIN', 50);
// Read the effective limit
$max = \Pitmaster\Pack\DeltaResolver::maxChainDepth();
PackWriter
Pitmaster kan packbestanden schrijven voor netwerkbewerkingen (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
De PackWriter:
- Verzamelt objecten voor het packbestand
- Serialiseert elk object met een varint-koptekst voor type en grootte
- Comprimeert de inhoud met zlib
- Schrijft de PACK-koptekst, vermeldingen en de trailer SHA-1
Momenteel worden alleen basisobjecten geschreven (geen deltacompressie). Deltacompressie is een optimalisatie die later kan worden toegevoegd; laat git gc de objecten opnieuw inpakken voor efficiëntere opslag.
PackEnumerator
Doorloop alle objecten in een packbestand, in indexvolgorde:
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";
}
Elk item is een opgelost GitObject.
MultiPackIndex
Voor repositories met veel packbestanden biedt de multi-pack-index (MIDX) één zoekactie voor alle packbestanden.
use Pitmaster\Pack\MultiPackIndex;
Het MIDX-bestand (.git/objects/pack/multi-pack-index) bevat:
- Een fan-outtabel (zoals in packindex v2)
- Object-ID's, gesorteerd over alle packbestanden
- Bestandsnamen van packbestanden en de packtoewijzing per object
- Offsets in de afzonderlijke packbestanden
MultiPackIndex::open($path) parseert een MIDX-bestand en biedt findObject($hex) en packNames(). Zoekacties naar objecten in de repository gebruiken deze niet: de objectdatabase leest altijd de afzonderlijke bestanden van .idx.
CommitGraph
Het commit-graph-bestand versnelt het doorlopen van de commitgeschiedenis en het berekenen van de mergebasis.
use Pitmaster\Pack\CommitGraph;
Het staat in .git/objects/info/commit-graph en bevat:
- Commit-OID's in gesorteerde volgorde
- Informatie over ouders (geïndexeerd, zodat objecten niet hoeven te worden gelezen)
- Generatienummers (voor snellere bereikbaarheidscontroles)
- Tijdstempels van commits
Als dit bestand aanwezig is, leest de berekening van de mergebasis achter Repository::merge(), mergeBase(), isBranchMerged() en de controle op fast-forward bij push de ouderkoppelingen uit de commit-graph in plaats van afzonderlijke commitobjecten te lezen. CommitWalker (log) gebruikt dit bestand niet.
Gehele getallen met variabele lengte
Packbestanden gebruiken meerdere coderingen voor gehele getallen met variabele lengte:
Varint van Git (kopteksten voor type en grootte)
Codering waarbij de MSB aangeeft dat er meer bytes volgen:
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 (delta-kopteksten)
Gebruikt in delta-instructiekoppen voor de basisgrootte en resultaatgrootte:
use Pitmaster\Encoding\Leb128;
$value = Leb128::decodeUnsigned($reader); // a BinaryReader
$bytes = Leb128::encodeUnsigned($value);
BinaryReader
Laag-niveau byte stream met positietracking:
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);