
# Pakettitiedostot

Pakettitiedostot ovat gitin tapa tallentaa ja siirtää dataa tehokkaasti. Sen sijaan että kukin objekti tallennettaisiin erillisenä zlib-pakattuna tiedostona, pakettitiedostoon yhdistetään useita objekteja ja ne voidaan pakata lisäksi delta-pakkauksella. Lähes kaikissa kloonatuissa tietovarastoissa käytetään pakettitiedostoja.

## Pakettitiedoston rakenne

Pakettitiedosto (`.git/objects/pack/pack-{hash}.pack`) sisältää:

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

### Objektityypin koodaus

Kukin merkintä alkaa vaihtuvamittaisella kokonaisluvulla, jossa tyyppi ja koko lomittuvat:

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

Tyyppiarvot:
| Arvo | Tyyppi |
|-------|------|
| 1 | Commit |
| 2 | Puu |
| 3 | Blob |
| 4 | Tag |
| 6 | OFS_DELTA (siirtymään perustuva delta) |
| 7 | REF_DELTA (tiivisteeseen perustuva delta) |

## Pakettitiedostojen lukeminen

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

`PackFile`-luokka:
1. Vahvistaa `PACK`-otsakkeen ja versionumeron
2. Etsii siirtymän `.idx`-tiedoston avulla ja jäsentää merkinnän tyyppiä/kokoa kuvaavan varint-arvon
3. Purkaa zlib-pakatun datan
4. Perusobjekteille: palauttaa sisällön suoraan
5. Delta-objekteille: ratkaisee kantaobjektin (seuraamalla ketjuja) ja soveltaa deltaa sekä tallentaa ratkaistut objektit välimuistiin

## Pakettihakemistotiedostot

Jokaisella `.pack`-tiedostolla on vastaava `.idx`-tiedosto nopeita hakuja varten. Ilman hakemistoa objektin löytäminen edellyttää koko paketin läpikäyntiä.

### v2-hakemistomuoto

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

Fanout-taulukko mahdollistaa O(log n)-binäärihaun: 256-merkinnät ilmaisevat, kuinka moni tiiviste alkaa kullakin mahdollisella ensimmäisellä tavulla.

### v1-hakemistomuoto

Vanhempi muoto ilman CRC32-tarkistussummia. Pitmaster tukee kumpaakin: `PackIndex::open()` lukee v1-tiedoston, jos v2-taika-arvo puuttuu, joten sama koodi toimii kummassakin.

## Deltan ratkaiseminen

Delta-pakatut objektit tallentavat ohjeet sisällön rekonstruoimiseksi kantaobjektista sen sijaan, että koko sisältö tallennettaisiin. Näin samankaltaiset objektit pakkautuvat huomattavasti pienempään tilaan (e.g. esimerkiksi saman tiedoston peräkkäiset versiot).

### Deltatyypit

**OFS_DELTA** (tyyppi 6): kantaobjekti tunnistetaan negatiivisella siirtymällä saman pakettitiedoston sisällä. Tämä on yleisempi, koska kantaobjektin tiivistettä ei tarvitse tietää.

**REF_DELTA** (tyyppi 7): kantaobjekti tunnistetaan tiivisteensä perusteella. Gitissä kantaobjekti voi olla paketin ulkopuolella (ohuet paketit). Pitmaster etsii REF_DELTA-kantaobjekteja vain samasta paketista, joten ohuita paketteja ei tueta ([#16](https://github.com/inline0/pitmaster/issues/16)).

### Deltaohjeet

Deltan sisältö on kopiointi- ja lisäysohjeiden jono:

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

Kopiointiohjeet: 7 X-bittiä ilmaisevat, mitkä siirtymä- ja kokotavut ovat mukana. Siirtymä on enintään 4 tavua ja koko enintään 3 tavua. Jos koko on nolla, se tarkoittaa 65536 tavua (0x10000).

Lisäysohjeet: 7 L-bittiä ilmaisevat literaalidatan pituuden (1–127 tavua). L=0 on varattu/virheellinen.

### DeltaApplier

```php
use Pitmaster\Pack\DeltaApplier;

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

`DeltaApplier`:
1. Lukee odotetun kantaobjektin koon ja tuloksen koon delta-otsakkeesta (LEB128-koodattu)
2. Käy ohjeet läpi:
   - Kopiointi: poimii siirtymän ja koon sekä kopioi tavut kantaobjektin sisällöstä
   - Lisäys: poimii pituuden ja kopioi literaalitavut delta-datasta
3. Varmistaa, että tuloksen koko vastaa odotettua kokoa

### Deltaketjut

Delta-pakatun objektin kantaobjekti voi itsekin olla delta-pakattu. `PackFile::read()` ja `readAtOffset()` seuraavat ketjua:

1. Lukee merkinnän. Jos se on perustyyppiä (commit, tree, blob, tag), palauttaa sen.
2. Jos kyseessä on OFS_DELTA: laskee kantaobjektin siirtymän (nykyinen siirtymä miinus koodattu negatiivinen siirtymä), lukee kantaobjektin merkinnän ja kutsuu itseään rekursiivisesti.
3. Jos kyseessä on REF_DELTA: etsii kantaobjektin tiivisteen perusteella saman paketin hakemistosta ja kutsuu itseään rekursiivisesti.
4. Soveltaa deltaohjeet ratkaistuun kantaobjektiin.
5. Asettaa rajan `PITMASTER_MAX_DELTA_CHAIN` (oletus 50) estääkseen ketjujen hallitsemattoman kasvun.

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

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

## PackWriter

Pitmaster voi kirjoittaa pakettitiedostoja verkkotoimintoja (push) varten.

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

`PackWriter`:
1. Kerää pakettiin sisällytettävät objektit
2. Sarjoittaa kunkin objektin tyyppiä/kokoa kuvaavalla varint-otsakkeella
3. Pakkaa sisällön zlibillä
4. Kirjoittaa PACK-otsakkeen, merkinnät ja SHA-1-lopukkeen

Kirjoittaa tällä hetkellä vain perusobjekteja (ei delta-pakkausta). Delta-pakkaus on myöhemmin lisättävä optimointi; anna `git gc`:n pakata tiedostot uudelleen tallennustilan säästämiseksi.

## PackEnumerator

Käy läpi kaikki paketin objektit hakemistojärjestyksessä:

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

Kukin kohde on ratkaistu `GitObject`.

## MultiPackIndex

Jos tietovarastossa on paljon pakettitiedostoja, monipaketti-indeksi (MIDX) mahdollistaa haut yhdestä indeksistä kaikkien pakettien yli.

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

MIDX-tiedosto (`.git/objects/pack/multi-pack-index`) sisältää:
- Fanout-taulukon (kuten paketti-indeksi v2)
- Kaikkien pakettien kesken lajitellut objektitunnukset
- Pakettitiedostojen nimet ja objektikohtaiset pakettimääritykset
- Siirtymät yksittäisiin pakettitiedostoihin

`MultiPackIndex::open($path)` jäsentää MIDX-tiedoston ja tarjoaa `findObject($hex)` ja `packNames()`. Tietovaraston objektihauissa sitä ei käytetä: objektitietokanta lukee aina yksittäiset `.idx`-tiedostot.

## CommitGraph

Commit-graafitiedosto nopeuttaa commit-historian läpikäyntiä ja yhdistämiskannan laskentaa.

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

Sijainti: `.git/objects/info/commit-graph`. Tiedosto sisältää:
- Järjestyksessä lajitellut commitien OID-tunnukset
- Ylätason tiedot (indeksoituina, ilman objektien lukemista)
- Sukupolvinumerot (nopeampiin saavutettavuuskyselyihin)
- Commitien aikaleimat

Kun tiedosto on käytettävissä, toimintojen `Repository::merge()`, `mergeBase()` ja `isBranchMerged()` taustalla oleva yhdistämiskannan laskenta sekä push-toiminnon pikasiirron tarkistus lukevat ylätasojen linkit commit-graafista yksittäisten commit-objektien sijaan. `CommitWalker` (log) ei käytä sitä.

## Muuttuvapituiset kokonaisluvut

Pakettitiedostot käyttävät useita muuttuvapituisia kokonaislukuesityksiä:

### Git-varint (tyyppi-/kokootsakkeet)

MSB-jatkoesitys, jossa merkitsevin bitti ilmaisee, seuraako tavussa lisää bittejä:

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

Käytetään deltan ohjeotsakkeissa kantaobjektin ja tuloksen koolle:

```php
use Pitmaster\Encoding\Leb128;

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

### BinaryReader

Tason alapuolinen tavujono, jossa sijaintia seurataan:

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