Fichiers pack
Les fichiers pack sont le mécanisme utilisé par git pour stocker et transférer les données efficacement. Au lieu de stocker chaque objet dans un fichier distinct compressé avec zlib, ils regroupent de nombreux objets dans un seul fichier, avec une compression delta facultative. Presque tous les dépôts clonés utilisent des fichiers pack.
Structure d'un fichier pack
Un fichier pack (.git/objects/pack/pack-{hash}.pack) contient :
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)
Encodage du type d'objet
Chaque entrée commence par un entier de longueur variable où le type et la taille sont entrelacés :
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
Valeurs de type :
| Valeur | Type |
|---|---|
| 1 | Commit |
| 2 | Tree |
| 3 | Blob |
| 4 | Tag |
| 6 | OFS_DELTA (delta basé sur un décalage) |
| 7 | REF_DELTA (delta basé sur un hachage) |
Lire les fichiers 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 :
- Valide l'en-tête
PACKet le numéro de version - Trouve le décalage dans le fichier
.idxet analyse le varint de type et de taille de l'entrée - Décompresse les données compressées avec zlib
- Pour les objets de base : renvoie directement le contenu
- Pour les objets delta : résout la base (en suivant les chaînes) et applique le delta, tout en mettant en cache les objets résolus
Fichiers d'index pack
Chaque fichier .pack a un fichier .idx correspondant pour accélérer les recherches. Sans l'index, il faudrait parcourir tout le fichier pack pour trouver un objet.
Format d'index 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 table de répartition permet une recherche binaire O(log n) : les entrées 256 indiquent le nombre de hachages commençant par chacun des premiers octets possibles.
Format d'index v1
Ancien format sans sommes de contrôle CRC32. Pitmaster prend en charge les deux formats : PackIndex::open() lit un fichier v1 si la valeur magique v2 est absente, ce qui permet au même code de fonctionner dans les deux cas.
Résolution des deltas
Les objets delta stockent des instructions permettant de reconstruire le contenu à partir d'un objet de base, plutôt que le contenu complet. Cela permet une compression importante pour les objets similaires (e.g., les versions successives d'un même fichier).
Types de delta
OFS_DELTA (type 6) : l'objet de base est identifié par un décalage négatif dans le même fichier pack. Ce type est plus courant, car il n'est pas nécessaire de connaître le hachage de l'objet de base.
REF_DELTA (type 7) : l'objet de base est identifié par son hachage. Dans git, la base peut se trouver hors du pack (packs minces). Pitmaster recherche les bases REF_DELTA uniquement dans le même pack ; les packs minces ne sont donc pas pris en charge (#16).
Instructions delta
Le contenu delta est une séquence d'instructions de copie et d'insertion :
Copy: 1XXXXXXX [offset bytes] [size bytes] (copy from base)
Insert: 0LLLLLLL <L bytes of literal data> (insert new data)
Instructions de copie : les 7 bits X indiquent quels octets de décalage et de taille sont présents. Le décalage occupe jusqu'à 4 octets et la taille jusqu'à 3 octets. Si la taille est égale à zéro, elle correspond à 0x10000 (65536) octets.
Instructions d'insertion : les 7 bits L donnent la longueur des données littérales (de 1 à 127 octets). L=0 est réservé et invalide.
DeltaApplier
use Pitmaster\Pack\DeltaApplier;
$result = DeltaApplier::apply($baseContent, $deltaInstructions);
DeltaApplier :
- Lit la taille de base attendue et la taille du résultat dans l'en-tête delta (encodage LEB128)
- Parcourt les instructions :
- Copie : extrait le décalage et la taille, puis copie les octets du contenu de base
- Insertion : extrait la longueur, puis copie les octets littéraux des données delta
- Vérifie que la taille du résultat correspond à la taille attendue
Chaînes de deltas
La base d'un objet delta peut elle-même être un objet delta. PackFile::read() et readAtOffset() suivent la chaîne :
- Lire l'entrée. Si son type est un type de base (commit, tree, blob, tag), la renvoyer.
- Si c'est un OFS_DELTA : calculer le décalage de base (décalage actuel moins le décalage négatif encodé), lire l'entrée de base et recommencer récursivement.
- Si c'est un REF_DELTA : rechercher la base par hachage dans l'index du même pack et recommencer récursivement.
- Appliquer les instructions delta à la base résolue.
- Appliquer
PITMASTER_MAX_DELTA_CHAIN(50 par défaut) pour empêcher les chaînes trop longues.
// Configure max chain depth (integer)
define('PITMASTER_MAX_DELTA_CHAIN', 50);
// Read the effective limit
$max = \Pitmaster\Pack\DeltaResolver::maxChainDepth();
PackWriter
Pitmaster peut écrire des fichiers pack pour les opérations réseau (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 :
- Rassemble les objets à inclure dans le pack
- Sérialise chaque objet avec un en-tête varint de type et de taille
- Compresse le contenu avec zlib
- Écrit l'en-tête PACK, les entrées et la somme de contrôle finale SHA-1
Actuellement, seuls les objets de base sont écrits (sans compression delta). La compression delta est une optimisation qui pourra être ajoutée ultérieurement ; laissez git gc regrouper les objets pour optimiser le stockage.
PackEnumerator
Parcourir tous les objets d'un pack dans l'ordre de l'index :
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";
}
Chaque élément est un GitObject résolu.
MultiPackIndex
Pour les dépôts contenant de nombreux fichiers pack, l'index multipack (MIDX) permet d'effectuer une seule recherche dans tous les packs.
use Pitmaster\Pack\MultiPackIndex;
Le fichier MIDX (.git/objects/pack/multi-pack-index) contient :
- Une table de répartition (comme dans l'index pack v2)
- Les ID d'objet triés dans tous les packs
- Les noms des fichiers pack et l'affectation de chaque objet à un pack
- Les décalages dans les fichiers pack individuels
MultiPackIndex::open($path) analyse un fichier MIDX et fournit findObject($hex) et packNames(). Les recherches d'objets dans le dépôt ne l'utilisent pas : la base d'objets lit toujours les fichiers .idx individuels.
CommitGraph
Le fichier commit-graph accélère le parcours de l'historique des commits et le calcul des bases de fusion.
use Pitmaster\Pack\CommitGraph;
Situé dans .git/objects/info/commit-graph, il contient :
- Les OID des commits, triés
- Les informations sur les parents (indexées, sans nécessiter la lecture des objets)
- Les numéros de génération (pour accélérer les requêtes d'accessibilité)
- Les horodatages des commits
Lorsqu'il est présent, le calcul des bases de fusion utilisé par Repository::merge(), mergeBase(), isBranchMerged() et la vérification d'avance rapide lors du push lit les liens parentaux depuis le commit-graph au lieu de lire chaque objet commit. CommitWalker (log) ne l'utilise pas.
Entiers de longueur variable
Les fichiers pack utilisent plusieurs encodages d'entiers de longueur variable :
Varint Git (en-têtes de type et de taille)
Encodage avec continuation MSB, où le bit de poids fort indique que d'autres octets suivent :
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 (en-têtes delta)
Utilisé dans les en-têtes des instructions delta pour la taille de base et celle du résultat :
use Pitmaster\Encoding\Leb128;
$value = Leb128::decodeUnsigned($reader); // a BinaryReader
$bytes = Leb128::encodeUnsigned($value);
BinaryReader
Flux d'octets de bas niveau avec suivi de position :
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);