Soubory pack
Soubory pack jsou mechanismem git pro efektivní ukládání a přenos. Místo ukládání každého objektu jako samostatného souboru komprimovaného pomocí zlib kombinují mnoho objektů do jednoho souboru s volitelnou kompresí delt. Soubory pack používá téměř každé naklonované úložiště.
Struktura souboru pack
Soubor pack (.git/objects/pack/pack-{hash}.pack) obsahuje:
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)
Kódování typu objektu
Každá položka začíná celočíselnou hodnotou proměnné délky, v níž jsou typ a velikost proloženy:
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
Hodnoty typu:
| Hodnota | Typ |
|---|---|
| 1 | Commit |
| 2 | Tree |
| 3 | Blob |
| 4 | Tag |
| 6 | OFS_DELTA (delta založená na offsetu) |
| 7 | REF_DELTA (delta založená na hashi) |
Čtení souborů 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();
Třída PackFile:
- Ověří hlavičku
PACKa číslo verze - Vyhledá offset v souboru
.idxa načte varint typu a velikosti položky - Dekomprimuje data komprimovaná pomocí zlib
- U základních objektů vrátí obsah přímo
- U deltových objektů najde základ (postupuje řetězcem delt), použije deltu a objekty po vyřešení ukládá do mezipaměti
Soubory indexu pack
Ke každému souboru .pack patří soubor .idx pro rychlé vyhledávání. Bez indexu je nutné při hledání objektu prohledat celý soubor pack.
Formát indexu 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();
Tabulka rozptylu umožňuje binární vyhledávání O(log n): položky 256 udávají, kolik hashů začíná každým možným prvním bajtem.
Formát indexu v1
Starší formát bez kontrolních součtů CRC32. Pitmaster podporuje oba formáty: PackIndex::open() načte soubor v1, pokud chybí magická hodnota v2, takže stejný kód funguje pro oba.
Řešení delt
Objekty uložené pomocí delty obsahují pokyny k obnovení obsahu ze základního objektu místo celého obsahu. Tím se dosahuje výrazné komprese u podobných objektů (e.g např. po sobě jdoucích verzí stejného souboru).
Typy delt
OFS_DELTA (typ 6): základní objekt je určen záporným offsetem v rámci stejného souboru pack. Tento typ je běžnější, protože nevyžaduje znalost hashe základního objektu.
REF_DELTA (typ 7): základní objekt je určen svým hashem. V gitu může být základ mimo soubor pack (tenké soubory pack). Pitmaster hledá základy REF_DELTA pouze v rámci stejného souboru pack, takže tenké soubory pack nejsou podporovány (#16).
Pokyny delty
Obsah delty tvoří posloupnost pokynů ke kopírování a vkládání:
Copy: 1XXXXXXX [offset bytes] [size bytes] (copy from base)
Insert: 0LLLLLLL <L bytes of literal data> (insert new data)
Pokyny ke kopírování: 7 bitů X určuje, které bajty offsetu a velikosti jsou přítomné. Offset může mít až 4 bajty, velikost až 3 bajty. Pokud je velikost nula, znamená to 65536 bajtů.
Pokyny k vložení: 7 bitů L udává délku doslovných dat (1–127 bajtů). L=0 je vyhrazená/neplatná hodnota.
DeltaApplier
use Pitmaster\Pack\DeltaApplier;
$result = DeltaApplier::apply($baseContent, $deltaInstructions);
DeltaApplier:
- Načte očekávanou velikost základu a výslednou velikost z hlavičky delty (kódování LEB128)
- Prochází pokyny:
- Kopírování: načte offset a velikost a zkopíruje bajty z obsahu základu
- Vložení: načte délku a zkopíruje doslovné bajty z dat delty
- Ověří, že výsledná velikost odpovídá očekávané velikosti
Řetězce delt
Základ objektu uloženého pomocí delty může být sám uložen pomocí delty. PackFile::read() a readAtOffset() postupují řetězcem:
- Načte položku. Pokud jde o základní typ (commit, tree, blob, tag), vrátí ji.
- U OFS_DELTA vypočítá offset základu (aktuální offset minus zakódovaný záporný offset), načte základní položku a rekurzivně pokračuje.
- U REF_DELTA vyhledá základ podle hashe v indexu stejného souboru pack a rekurzivně pokračuje.
- Použije pokyny delty na vyřešený základ.
- Vynutí limit
PITMASTER_MAX_DELTA_CHAIN(výchozí hodnota 50), aby zabránil nekonečně dlouhým řetězcům.
// Configure max chain depth (integer)
define('PITMASTER_MAX_DELTA_CHAIN', 50);
// Read the effective limit
$max = \Pitmaster\Pack\DeltaResolver::maxChainDepth();
PackWriter
Pitmaster může zapisovat soubory pack pro síťové operace (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:
- Shromáždí objekty, které mají být zahrnuty do souboru pack
- Serializuje každý objekt s hlavičkou varint typu a velikosti
- Komprimuje obsah pomocí zlib
- Zapíše hlavičku PACK, položky a závěrečný SHA-1
V současnosti zapisuje pouze základní objekty (bez komprese delt). Komprese delt je optimalizace, kterou lze přidat později; přenechte přebalení kvůli efektivnějšímu ukládání nástroji git gc.
PackEnumerator
Procházet všechny objekty v souboru pack v pořadí podle indexu:
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";
}
Každá položka je vyřešený objekt GitObject.
MultiPackIndex
U úložišť s mnoha soubory pack poskytuje multi-pack-index (MIDX) jednotné vyhledávání napříč všemi soubory pack.
use Pitmaster\Pack\MultiPackIndex;
Soubor MIDX (.git/objects/pack/multi-pack-index) obsahuje:
- Tabulku rozptylu (jako index pack v2)
- ID objektů seřazená napříč všemi soubory pack
- Názvy souborů pack a přiřazení jednotlivých objektů k souborům pack
- Offsety v jednotlivých souborech pack
MultiPackIndex::open($path) analyzuje soubor MIDX a poskytuje findObject($hex) a packNames(). Vyhledávání objektů v úložišti jej nepoužívá: databáze objektů vždy čte jednotlivé soubory .idx.
CommitGraph
Soubor commit-graph urychluje procházení historie commitů a výpočet základů sloučení.
use Pitmaster\Pack\CommitGraph;
Nachází se v .git/objects/info/commit-graph a obsahuje:
- OID commitů v seřazeném pořadí
- Informace o rodičích (indexované, bez nutnosti číst objekty)
- Generační čísla (pro rychlejší dotazy na dosažitelnost)
- Časová razítka commitů
Pokud je k dispozici, výpočet základního sloučení pro Repository::merge(), mergeBase(), isBranchMerged() a kontrola rychlého posunu při odesílání čtou odkazy na rodiče z grafu commitů namísto jednotlivých objektů commitů. CommitWalker (log) tuto možnost nepoužívá.
Celá čísla proměnné délky
Soubory pack používají několik kódování celých čísel proměnné délky:
Git varint (hlavičky typu a velikosti)
Kódování MSB-continue, kde nejvýznamnější bit označuje, že následují další bajty:
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 (hlavičky rozdílů)
Používá se v hlavičkách instrukcí rozdílu pro velikost základu a výsledku:
use Pitmaster\Encoding\Leb128;
$value = Leb128::decodeUnsigned($reader); // a BinaryReader
$bytes = Leb128::encodeUnsigned($value);
BinaryReader
Nízkourovňový proud bajtů se sledováním pozice:
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);