
# File Pack

I file pack sono il meccanismo di git per archiviare e trasferire i dati in modo efficiente. Invece di memorizzare ogni oggetto come file separato compresso con zlib, i file pack riuniscono molti oggetti in un unico file, con compressione delta facoltativa. Quasi tutti i repository clonati usano file pack.

## Struttura di un file pack

Un file 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)
```

### Codifica del tipo di oggetto

Ogni voce inizia con un intero a lunghezza variabile in cui tipo e dimensione sono intercalati:

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

Valori dei tipi:
| Valore | Tipo |
|-------|------|
| 1 | Commit |
| 2 | Tree |
| 3 | Blob |
| 4 | Tag |
| 6 | OFS_DELTA (delta basato sull'offset) |
| 7 | REF_DELTA (delta basato sull'hash) |

## Lettura dei file 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 classe `PackFile`:
1. Convalida l'intestazione `PACK` e il numero di versione
2. Trova l'offset tramite il file `.idx` e analizza il varint di tipo e dimensione della voce
3. Decomprime i dati compressi con zlib
4. Per gli oggetti base: restituisce direttamente il contenuto
5. Per gli oggetti delta: risolve la base (seguendo le catene) e applica il delta, memorizzando nella cache gli oggetti risolti

## File indice pack

Ogni file `.pack` ha un file `.idx` corrispondente per ricerche rapide. Senza l'indice, per trovare un oggetto occorre esaminare l'intero pack.

### Formato dell'indice 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 tabella fanout consente una ricerca binaria O(log n): le voci 256 indicano quanti hash iniziano con ciascun possibile primo byte.

### Formato dell'indice v1

Formato precedente, senza checksum CRC32. Pitmaster supporta entrambi i formati: `PackIndex::open()` legge un file v1 se manca la firma magica v2, quindi lo stesso codice funziona per entrambi.

## Risoluzione dei delta

Gli oggetti rappresentati tramite delta memorizzano istruzioni per ricostruire il contenuto a partire da un oggetto base, invece di conservarlo per intero. Ciò consente una compressione notevole per oggetti simili (e.g., versioni consecutive dello stesso file).

### Tipi di delta

**OFS_DELTA** (tipo 6): l'oggetto base è identificato da un offset negativo all'interno dello stesso file pack. È il tipo più comune perché non richiede di conoscere l'hash dell'oggetto base.

**REF_DELTA** (tipo 7): l'oggetto base è identificato dal suo hash. In git, la base può trovarsi fuori dal pack (pack sottili). Pitmaster cerca le basi REF_DELTA solo all'interno dello stesso pack; pertanto i pack sottili non sono supportati ([#16](https://github.com/inline0/pitmaster/issues/16)).

### Istruzioni delta

Il contenuto delta è una sequenza di istruzioni di copia e inserimento:

```
Copy:   1XXXXXXX [offset bytes] [size bytes]   (copy from base)
Insert: 0LLLLLLL <L bytes of literal data>       (insert new data)
```

Istruzioni di copia: i 7 bit X indicano quali byte di offset e dimensione sono presenti. L'offset è lungo fino a 4 byte e la dimensione fino a 3 byte. Se la dimensione è zero, significa 0x10000 (65536) byte.

Istruzioni di inserimento: i 7 bit L indicano la lunghezza dei dati letterali (da 1 a 127 byte). L=0 è riservato/non valido.

### DeltaApplier

```php
use Pitmaster\Pack\DeltaApplier;

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

Il componente `DeltaApplier`:
1. Legge dal header delta la dimensione prevista della base e quella del risultato (codifica LEB128)
2. Scorre le istruzioni:
   - Copia: estrae offset e dimensione, quindi copia i byte dal contenuto base
   - Inserimento: estrae la lunghezza, quindi copia i byte letterali dai dati delta
3. Verifica che la dimensione del risultato corrisponda a quella prevista

### Catene di delta

Anche l'oggetto base di un oggetto rappresentato tramite delta può essere a sua volta rappresentato tramite delta. `PackFile::read()` e `readAtOffset()` seguono la catena:

1. Legge la voce. Se è di un tipo base (commit, tree, blob, tag), la restituisce.
2. Se è OFS_DELTA: calcola l'offset della base (offset corrente meno offset negativo codificato), legge la voce base e richiama se stesso.
3. Se è REF_DELTA: cerca la base tramite hash nell'indice dello stesso pack e richiama se stesso.
4. Applica le istruzioni delta alla base risolta.
5. Impone `PITMASTER_MAX_DELTA_CHAIN` (predefinito 50) per evitare catene troppo lunghe.

```php
// Configure max chain depth (integer)
define('PITMASTER_MAX_DELTA_CHAIN', 50);

// Read the effective limit
$max = \Pitmaster\Pack\DeltaResolver::maxChainDepth();
```

## PackWriter

Pitmaster può scrivere file pack per le operazioni di rete (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
```

Il componente `PackWriter`:
1. Raccoglie gli oggetti da includere nel pack
2. Serializza ogni oggetto con un'intestazione varint di tipo e dimensione
3. Comprime il contenuto con zlib
4. Scrive l'intestazione PACK, le voci e il trailer SHA-1

Al momento scrive solo oggetti base (senza compressione delta). La compressione delta è un'ottimizzazione che può essere aggiunta in seguito; lasciate che `git gc` ricompatti i dati per ottimizzare lo spazio di archiviazione.

## PackEnumerator

Scorre tutti gli oggetti di un pack, nell'ordine dell'indice:

```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";
}
```

Ogni elemento è un `GitObject` risolto.

## MultiPackIndex

Nei repository con molti file pack, l'indice multipack (MIDX) consente di cercare in un'unica volta in tutti i pack.

```php
use Pitmaster\Pack\MultiPackIndex;
```

Il file MIDX (`.git/objects/pack/multi-pack-index`) contiene:
- Una tabella fanout (come l'indice pack v2)
- ID degli oggetti ordinati tra tutti i pack
- Nomi dei file pack e assegnazioni dei pack per ogni oggetto
- Offset nei singoli file pack

`MultiPackIndex::open($path)` analizza un file MIDX e offre `findObject($hex)` e `packNames()`. Le ricerche degli oggetti nel repository non lo usano: il database degli oggetti legge sempre i singoli file `.idx`.

## CommitGraph

Il file commit-graph accelera l'attraversamento della cronologia dei commit e il calcolo della base di merge.

```php
use Pitmaster\Pack\CommitGraph;
```

Si trova in `.git/objects/info/commit-graph` e contiene:
- OID dei commit in ordine
- Informazioni sui genitori (indicizzate, senza dover leggere gli oggetti)
- Numeri di generazione (per query di raggiungibilità più rapide)
- Timestamp dei commit

Quando è presente, il calcolo della base di merge per `Repository::merge()`, `mergeBase()`, `isBranchMerged()` e il controllo di avanzamento rapido del push leggono i collegamenti ai genitori dal commit-graph invece di leggere i singoli oggetti commit. `CommitWalker` (log) non lo usa.

## Interi a lunghezza variabile

I file pack usano diverse codifiche di interi a lunghezza variabile:

### Varint Git (intestazioni di tipo/dimensione)

Codifica MSB-continue, in cui il bit più significativo indica che seguono altri byte:

```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 (intestazioni delta)

Usati negli header delle istruzioni delta per la dimensione di base e quella del risultato:

```php
use Pitmaster\Encoding\Leb128;

$value = Leb128::decodeUnsigned($reader);   // a BinaryReader
$bytes = Leb128::encodeUnsigned($value);
```

### BinaryReader

Flusso di byte a basso livello con tracciamento della posizione:

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