
# 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

```php
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`:
1. Valida el encabezado `PACK` y el número de versión
2. Busca el desplazamiento mediante el archivo `.idx` y analiza el varint de tipo/tamaño de la entrada
3. Descomprime los datos comprimidos con zlib
4. Para objetos base: devuelve el contenido directamente
5. 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)
```

```php
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](https://github.com/inline0/pitmaster/issues/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

```php
use Pitmaster\Pack\DeltaApplier;

$result = DeltaApplier::apply($baseContent, $deltaInstructions);
```

El `DeltaApplier`:
1. Lee el tamaño base esperado y el tamaño del resultado del encabezado delta (codificado en LEB128)
2. 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
3. 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:

1. Leer la entrada. Si es de tipo base (commit, árbol, blob, etiqueta), devolverla.
2. 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.
3. Si es REF_DELTA: buscar la base por hash en el índice del mismo pack y volver a llamarse de forma recursiva.
4. Aplicar las instrucciones delta a la base resuelta.
5. Aplicar `PITMASTER_MAX_DELTA_CHAIN` (50 de forma predeterminada) para evitar cadenas interminables.

```php
// 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).

```php
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`:
1. Recopila los objetos que incluirá en el pack
2. Serializa cada objeto con un encabezado varint de tipo/tamaño
3. Comprime el contenido con zlib
4. 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:

```php
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.

```php
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.

```php
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:

```php
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:

```php
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:

```php
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);
```
