Archivos Pack
Los archivos pack son el mecanismo de git para almacenar y transferir datos de forma eficiente. En lugar de guardar cada objeto como un archivo comprimido por separado con zlib, los archivos pack combinan muchos objetos en un solo archivo con compresión delta opcional. Casi todos los repositorios clonados usan archivos pack.
Estructura de los archivos pack
Un archivo 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)
Codificación del tipo de objeto
Cada entrada comienza con un entero de longitud variable en el que se intercalan el tipo y el tamaño:
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
Valores de tipo:
| Valor | Tipo |
|---|---|
| 1 | Commit |
| 2 | Árbol |
| 3 | Blob |
| 4 | Etiqueta |
| 6 | OFS_DELTA (delta basado en desplazamiento) |
| 7 | REF_DELTA (delta basado en hash) |
Leer archivos 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 clase PackFile:
- Valida el encabezado
PACKy el número de versión - Busca el desplazamiento mediante el archivo
.idxy analiza el varint de tipo/tamaño de la entrada - Descomprime los datos comprimidos con zlib
- Para objetos base: devuelve el contenido directamente
- Para objetos delta: resuelve la base (siguiendo las cadenas) y aplica el delta, almacenando en caché los objetos resueltos
Archivos de índice pack
Cada archivo .pack tiene un archivo .idx correspondiente para búsquedas rápidas. Sin el índice, encontrar un objeto requiere recorrer todo el pack.
Formato de índice 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 tabla fanout permite una búsqueda binaria O(log n): las entradas 256 indican cuántos hashes comienzan con cada primer byte posible.
Formato de índice v1
Formato anterior sin sumas de verificación CRC32. Pitmaster admite ambos: PackIndex::open() lee un archivo v1 cuando falta el valor mágico v2, así que el mismo código sirve para cualquiera de los dos.
Resolución de deltas
Los objetos con delta almacenan instrucciones para reconstruir el contenido a partir de un objeto base, en lugar de guardar el contenido completo. Esto permite una compresión significativa en objetos similares (e.g., versiones consecutivas del mismo archivo).
Tipos de delta
OFS_DELTA (tipo 6): el objeto base se identifica mediante un desplazamiento negativo dentro del mismo archivo pack. Es más común porque no requiere conocer el hash del objeto base.
REF_DELTA (tipo 7): el objeto base se identifica por su hash. En git, la base puede estar fuera del pack (packs delgados). Pitmaster busca las bases REF_DELTA solo dentro del mismo pack, por lo que los packs delgados no son compatibles (#16).
Instrucciones delta
El contenido delta es una secuencia de instrucciones de copia e inserción:
Copy: 1XXXXXXX [offset bytes] [size bytes] (copy from base)
Insert: 0LLLLLLL <L bytes of literal data> (insert new data)
Instrucciones de copia: los 7 bits X indican qué bytes de desplazamiento y tamaño están presentes. El desplazamiento puede ocupar hasta 4 bytes y el tamaño hasta 3. Si el tamaño es cero, significa 0x10000 (65536) bytes.
Instrucciones de inserción: los 7 bits L indican la longitud de los datos literales (de 1 a 127 bytes). L=0 está reservado y no es válido.
DeltaApplier
use Pitmaster\Pack\DeltaApplier;
$result = DeltaApplier::apply($baseContent, $deltaInstructions);
El DeltaApplier:
- Lee el tamaño base esperado y el tamaño del resultado del encabezado delta (codificado en LEB128)
- Recorre las instrucciones:
- Copiar: extrae el desplazamiento y el tamaño y copia bytes del contenido base
- Insertar: extrae la longitud y copia bytes literales de los datos delta
- Verifica que el tamaño del resultado coincida con el tamaño esperado
Cadenas delta
La base de un objeto con delta también puede estar representada como delta. PackFile::read() y readAtOffset() siguen la cadena:
- Leer la entrada. Si es de tipo base (commit, árbol, blob, etiqueta), devolverla.
- Si es OFS_DELTA: calcular el desplazamiento de la base (desplazamiento actual menos el desplazamiento negativo codificado), leer la entrada base y volver a llamarse de forma recursiva.
- Si es REF_DELTA: buscar la base por hash en el índice del mismo pack y volver a llamarse de forma recursiva.
- Aplicar las instrucciones delta a la base resuelta.
- Aplicar
PITMASTER_MAX_DELTA_CHAIN(50 de forma predeterminada) para evitar cadenas interminables.
// Configure max chain depth (integer)
define('PITMASTER_MAX_DELTA_CHAIN', 50);
// Read the effective limit
$max = \Pitmaster\Pack\DeltaResolver::maxChainDepth();
PackWriter
Pitmaster puede escribir archivos pack para operaciones de red (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
El PackWriter:
- Recopila los objetos que incluirá en el pack
- Serializa cada objeto con un encabezado varint de tipo/tamaño
- Comprime el contenido con zlib
- Escribe el encabezado PACK, las entradas y la suma de verificación SHA-1
Actualmente solo escribe objetos base (sin compresión delta). La compresión delta es una optimización que se puede añadir más adelante; deje que git gc reempaquete los objetos para optimizar el almacenamiento.
PackEnumerator
Recorrer todos los objetos de un pack, en el orden del índice:
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";
}
Cada elemento es un GitObject resuelto.
MultiPackIndex
En repositorios con muchos archivos pack, el índice multipack (MIDX) ofrece una sola búsqueda en todos los packs.
use Pitmaster\Pack\MultiPackIndex;
El archivo MIDX (.git/objects/pack/multi-pack-index) contiene:
- Una tabla fanout (como en el índice pack v2)
- ID de objeto ordenados entre todos los packs
- Nombres de archivos pack y asignaciones de cada objeto a un pack
- Desplazamientos en los archivos pack individuales
MultiPackIndex::open($path) analiza un archivo MIDX y ofrece findObject($hex) y packNames(). Las búsquedas de objetos del repositorio no lo usan: la base de datos de objetos siempre lee los archivos .idx individuales.
CommitGraph
El archivo commit-graph acelera el recorrido del historial de commits y el cálculo de la base de una fusión.
use Pitmaster\Pack\CommitGraph;
Se encuentra en .git/objects/info/commit-graph y contiene:
- OID de commits en orden
- Información de los padres (indexada, sin necesidad de leer objetos)
- Números de generación (para consultas de alcanzabilidad más rápidas)
- Marcas de tiempo de los commits
Cuando existe, el cálculo de la base de fusión que se realiza detrás de Repository::merge(), mergeBase(), isBranchMerged() y la comprobación de avance rápido de push lee los vínculos con los padres del commit-graph en lugar de leer objetos commit individuales. CommitWalker (log) no lo usa.
Enteros de longitud variable
Los archivos pack usan varias codificaciones de enteros de longitud variable:
Varint Git (encabezados de tipo/tamaño)
Codificación con continuación MSB, donde el bit más significativo indica que siguen más bytes:
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 (encabezados delta)
Se usa en los encabezados de instrucciones delta para el tamaño base y el tamaño del resultado:
use Pitmaster\Encoding\Leb128;
$value = Leb128::decodeUnsigned($reader); // a BinaryReader
$bytes = Leb128::encodeUnsigned($value);
BinaryReader
Flujo de bytes de bajo nivel con seguimiento de posición:
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);