
# Format indeksu

Plik indeksu (`.git/index`) jest obszarem indeksu. Zapisuje pliki, które zostaną uwzględnione w następnym commicie, wraz z metadanymi ułatwiającymi szybkie obliczanie statusu. Pitmaster odczytuje wersje indeksu 2, 3 i 4, a zapisuje wersje 2 i 3. Zapisanie indeksu odczytanego jako wersja 4 tworzy plik, którego git nie potrafi odczytać ([#15](https://github.com/inline0/pitmaster/issues/15)), dlatego przed zapisem za pomocą Pitmaster przekonwertuj takie repozytoria przy użyciu `git update-index --index-version 2`.

## Odczytywanie indeksu

```php
use Pitmaster\Index\Index;

$index = Index::open('/path/to/.git/index');   // pass 32 as the second argument for SHA-256

// Or through the repository
$index = $repo->index();

// Entry count (all stages)
echo $index->count();

// Iterate stage 0 entries, keyed by path
foreach ($index->entries() as $path => $entry) {
    echo "{$entry->path} {$entry->hash->hex}\n";
}

// Look up a specific entry
$entry = $index->entry('src/File.php');
```

`Index::open()` zwraca pusty indeks, jeśli plik nie istnieje. `entries()` zwraca tylko wpisy etapu 0; `allEntries()` uwzględnia etapy konfliktu, a `paths()` wyświetla śledzone ścieżki.

## IndexEntry

Każdy wpis indeksu jest niezmiennym obiektem wartości:

```php
use Pitmaster\Index\IndexEntry;

$entry->path;          // File path relative to repo root
$entry->hash;          // ObjectId of the blob
$entry->mode;          // File mode as an int (0100644, 0100755, 0120000, 0160000)
$entry->fileSize;      // File size in bytes
$entry->ctimeSec;      // Metadata change time (seconds)
$entry->ctimeNsec;     // Metadata change time nanoseconds
$entry->mtimeSec;      // Modification time (seconds)
$entry->mtimeNsec;     // Modification time nanoseconds
$entry->dev;           // Device number
$entry->ino;           // Inode number
$entry->uid;           // User ID
$entry->gid;           // Group ID
$entry->flags;         // Raw flags field
$entry->extendedFlags; // v3 extended flags (skip-worktree, intent-to-add)
$entry->stage();       // Conflict stage (0 = normal, 1 = base, 2 = ours, 3 = theirs)
$entry->assumeValid(); // Assume-valid bit
$entry->skipWorktree(); // Skip-worktree bit
```

### Tworzenie wpisów

```php
// From file stat
$entry = IndexEntry::fromStat('src/File.php', $blobId, '/full/path/to/src/File.php');

// Minimal (no stat info)
$entry = IndexEntry::create('src/File.php', $blobId);

// With explicit mode
$entry = IndexEntry::create('deploy.sh', $blobId, 0100755);
```

## Modyfikowanie indeksu

```php
// Add or replace an entry
$index->addEntry($entry);

// Remove an entry
$index->removeEntry('src/OldFile.php');

// Replace all stages of a conflicted path with one resolved entry
$index->resolveConflict('src/File.php', $entry);
```

`addEntries()` i `removeEntries()` przetwarzają wiele wpisów lub ścieżek w jednym przebiegu. Każda zmiana wpisów usuwa zapisane rozszerzenia (zob. poniżej).

## Zapisywanie indeksu

```php
use Pitmaster\Index\IndexWriter;

IndexWriter::write($index, '/path/to/.git/index');
```

Lub za pośrednictwem repozytorium (które obsługuje to automatycznie):

```php
$repo->add('src/File.php');    // Reads file, creates blob, updates index, writes
$repo->remove('--cached', 'old-file.txt'); // Removes from index only, writes
```

## Format binarny

### Nagłówek

```
Signature:   DIRC (4 bytes, ASCII)
Version:     4 bytes, big-endian (2, 3, or 4)
Entry count: 4 bytes, big-endian
```

### Format wpisu (v2)

Każdy wpis ma postać:

```
ctime seconds:     4 bytes, big-endian
ctime nanoseconds: 4 bytes, big-endian
mtime seconds:     4 bytes, big-endian
mtime nanoseconds: 4 bytes, big-endian
dev:               4 bytes, big-endian
ino:               4 bytes, big-endian
mode:              4 bytes, big-endian
uid:               4 bytes, big-endian
gid:               4 bytes, big-endian
file size:         4 bytes, big-endian
object hash:       20 bytes SHA-1 or 32 bytes SHA-256 (raw binary)
flags:             2 bytes, big-endian
path:              variable length, NUL-terminated
padding:           1-8 NUL bytes to align to 8-byte boundary
```

Pole flag:
- Bit 15: flaga assume-valid
- Bity 13-12: etap scalania (0-3)
- Bity 11-0: długość nazwy ścieżki lub 0xFFF, gdy ścieżka ma co najmniej 4095 bajtów (czytnik szuka wtedy znaku NUL)

### Różnice między wersjami

**Wersja 2**: podstawowy format opisany powyżej. Najczęściej używany.

**Wersja 3**: dodaje rozszerzone flagi (2 dodatkowe bajty po zwykłych flagach). Obsługuje bity `skip-worktree` i `intent-to-add`. Używana, gdy dowolny wpis wymaga rozszerzonych flag.

**Wersja 4**: dodaje kompresję prefiksów ścieżek. Zamiast zapisywać pełne ścieżki, każdy wpis zawiera liczbę bajtów do usunięcia ze ścieżki poprzedniego wpisu, a następnie nowy sufiks. Znacznie zmniejsza rozmiar indeksu w przypadku głęboko zagnieżdżonych drzew.

## Etapy konfliktu

Podczas konfliktu scalania jedna ścieżka może mieć wiele wpisów indeksu na różnych etapach:

| Etap | Znaczenie |
|-------|---------|
| 0 | Normalny (brak konfliktu) |
| 1 | Wersja bazowa (wspólny przodek) |
| 2 | Nasza wersja (bieżąca gałąź) |
| 3 | Ich wersja (scalana gałąź) |

```php
// During a conflict, entries have non-zero stages
foreach ($index->allEntries() as $entry) {
    if ($entry->stage() > 0) {
        echo "Conflict: {$entry->path} (stage {$entry->stage()})\n";
    }
}

$index->hasUnmerged();            // true while conflicts remain
$index->unmergedPaths();          // conflicted paths
$index->stageEntries('a.txt');    // [1 => base, 2 => ours, 3 => theirs]
```

## Rozszerzenia indeksu

Rozszerzenia są dopisywane po wpisach, przed sumą kontrolną. Każde rozszerzenie ma 4-bajtowy podpis, 4-bajtowy rozmiar i dane o zmiennej długości.

Pitmaster zachowuje bloki rozszerzeń jako surowe bajty (`$index->extensions()` zwraca pary `signature` i `data`) i zapisuje je bez zmian, jeśli wpisy nie zostały zmodyfikowane. Nie interpretuje ani nie generuje żadnych rozszerzeń, a każda zmiana wpisów usuwa je wszystkie; git akceptuje takie działanie i w razie potrzeby odbudowuje rozszerzenia. Opis używanych przez git rozszerzeń znajduje się poniżej.

### Rozszerzenie TREE

Buforuje wstępnie obliczone obiekty drzewa dla katalogów. Przyspiesza `git status` i `git commit`, unikając ponownego obliczania drzewa, gdy zmienił się tylko indeks.

Podpis: `TREE` (0x54524545)

### Rozszerzenie REUC

Zapisuje informacje potrzebne do cofnięcia rozwiązania konfliktu. Po rozwiązaniu konfliktu pierwotne wpisy etapów 1/2/3 są przenoszone tutaj, skąd można je przywrócić za pomocą `git checkout -m`.

Podpis: `REUC` (0x52455543)

### Inne rozszerzenia

git zapisuje również:
- `EOIE` (koniec wpisu indeksu): oznacza koniec wpisów i początek rozszerzeń
- `IEOT` (tabela przesunięć wpisów indeksu): umożliwia równoległe wczytywanie indeksu
- `link` (indeks dzielony): do bardzo dużych repozytoriów korzystających z indeksu dzielonego

W formacie git rozszerzenia, których podpis zaczyna się wielką literą, są opcjonalne, a te zaczynające się małą literą — wymagane. Pitmaster akceptuje oba rodzaje bez sprawdzania. Indeks dzielony (`link`) nie jest wczytywany, więc repozytoria, które go używają, nie są obsługiwane.

## Suma kontrolna

Plik indeksu kończy się sumą kontrolną wszystkich wcześniejszych bajtów, obliczaną przy użyciu skrótu repozytorium (20 bajtów SHA-1, 32 bajty SHA-256). Pitmaster zapisuje ją podczas zapisu, ale nie weryfikuje jej podczas odczytu.

`Index::open()` i `Index::parse()` zgłaszają `IndexParseException` w przypadku braku podpisu `DIRC` lub nieobsługiwanej wersji.

## Indeks i status

Indeks ma kluczowe znaczenie dla obliczania statusu. `WorkingTreeStatus` porównuje:
1. Drzewo HEAD z indeksem: wykrywa zmiany dodane do indeksu
2. Indeks z drzewem roboczym: wykrywa zmiany niezindeksowane

Pola stat (ctime, mtime, dev, ino, size, mode, uid, gid) umożliwiają szybkie sprawdzenie „czy ten plik się zmienił?” bez odczytywania jego zawartości. Jeśli pola stat są zgodne, a plik zmodyfikowano przed rozpoczęciem skanowania statusu, uznaje się, że plik się nie zmienił. Inny rozmiar od razu oznacza modyfikację. W przeciwnym razie Pitmaster odczytuje plik i oblicza jego skrót, aby sprawdzić, czy zawartość rzeczywiście się zmieniła.

## Flaga assume-valid

```php
if ($entry->assumeValid()) {
    // Entry has assume-valid set (flags & 0x8000)
}
```

Ta flaga informuje git, aby pomijał sprawdzanie pliku podczas ustalania statusu, nawet jeśli zmieniły się pola stat (`git update-index --assume-unchanged`). Pitmaster zachowuje ten bit podczas ponownego zapisywania wpisów, ale jego mechanizm ustalania statusu go nie uwzględnia i nadal porównuje plik.
