
# Формат індексу

Файл індексу (`.git/index`) — це область підготовки. У ньому записано файли, які буде включено до наступного коміту, а також метадані для ефективного обчислення статусу. Pitmaster читає індекси версій 2, 3 і 4 та записує версії 2 і 3. Запис індексу, прочитаного як версію 4, створює файл, який git не може прочитати ([#15](https://github.com/inline0/pitmaster/issues/15)), тому перед записом через Pitmaster конвертуйте такі репозиторії за допомогою `git update-index --index-version 2`.

## Читання індексу

```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()` повертає порожній індекс, якщо файлу немає. `entries()` повертає лише записи етапу 0; `allEntries()` включає етапи конфліктів, а `paths()` перелічує відстежувані шляхи.

## IndexEntry

Кожен запис індексу — це незмінний об'єкт-значення:

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

### Створення записів

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

## Зміна індексу

```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()` і `removeEntries()` обробляють кілька записів або шляхів за один прохід. Будь-яка зміна записів видаляє збережені розширення (див. нижче).

## Запис індексу

```php
use Pitmaster\Index\IndexWriter;

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

Або через репозиторій (який робить це автоматично):

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

## Двійковий формат

### Заголовок

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

### Формат запису (v2)

Кожен запис має вигляд:

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

Поле прапорців:
- Біт 15: прапорець assume-valid
- Біти 13-12: етап злиття (0-3)
- Біти 11-0: довжина назви шляху або 0xFFF, якщо шлях має 4095 байтів чи більше (тоді читач шукає NUL)

### Відмінності версій

**Версія 2**: базовий формат, описаний вище. Найпоширеніша.

**Версія 3**: додає розширені прапорці (2 додаткові байти після звичайних прапорців). Підтримує біти `skip-worktree` і `intent-to-add`. Використовується, коли будь-який запис потребує розширених прапорців.

**Версія 4**: додає стиснення префіксів шляхів. Замість повних шляхів у кожному записі зберігається кількість байтів, які потрібно вилучити зі шляху попереднього запису, а потім новий суфікс. Значно зменшує розмір індексу для глибоко вкладених дерев.

## Етапи конфлікту

Під час конфлікту злиття один шлях може мати кілька записів індексу на різних етапах:

| Етап | Значення |
|-------|---------|
| 0 | Звичайний (без конфлікту) |
| 1 | Базова версія (спільний предок) |
| 2 | Наша версія (поточна гілка) |
| 3 | Їхня версія (гілка, що зливається) |

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

## Розширення індексу

Розширення додаються після записів, перед контрольною сумою. Кожне розширення має 4-байтовий підпис, 4-байтовий розмір і дані змінної довжини.

Pitmaster зберігає блоки розширень як необроблені байти (`$index->extensions()` повертає пари `signature` і `data`) та записує їх назад без змін, якщо записи не змінено. Він не інтерпретує й не створює жодних розширень; будь-яка зміна записів видаляє їх усі, що git приймає та за потреби перебудовує. Розширення, які використовує git, описано нижче для довідки.

### Розширення TREE

Кешує попередньо обчислені об'єкти дерев для каталогів. Прискорює `git status` і `git commit`, уникаючи повторного обчислення дерева, коли змінився лише індекс.

Підпис: `TREE` (0x54524545)

### Розширення REUC

Записує інформацію для скасування розв'язання конфлікту. Після розв'язання конфлікту початкові записи етапів 1/2/3 переміщуються сюди, щоб їх можна було відновити за допомогою `git checkout -m`.

Підпис: `REUC` (0x52455543)

### Інші розширення

git також записує:
- `EOIE` (кінець запису індексу): позначає, де закінчуються записи й починаються розширення
- `IEOT` (таблиця зміщень записів індексу): дає змогу паралельно завантажувати індекс
- `link` (розділений індекс): для дуже великих репозиторіїв із розділеним індексом

У форматі git розширення, підпис яких починається з великої літери, є необов'язковими, а з малої — обов'язковими. Pitmaster приймає обидва типи без перевірки. Розділений індекс (`link`) не завантажується, тому репозиторії, що його використовують, не підтримуються.

## Контрольна сума

Файл індексу завершується контрольною сумою всіх попередніх байтів, обчисленою хеш-функцією репозиторію (20 байтів SHA-1, 32 байти SHA-256). Pitmaster записує її під час збереження, але не перевіряє під час читання.

`Index::open()` і `Index::parse()` генерують `IndexParseException`, якщо відсутній підпис `DIRC` або версія не підтримується.

## Індекс і статус

Індекс має ключове значення для обчислення статусу. `WorkingTreeStatus` порівнює:
1. Дерево HEAD та індекс: виявляє підготовлені зміни
2. Індекс і робоче дерево: виявляє непідготовлені зміни

Поля stat (ctime, mtime, dev, ino, size, mode, uid, gid) дають змогу швидко перевірити, чи змінився файл, не читаючи його вміст. Якщо поля stat збігаються, а останню зміну файлу було внесено до початку сканування статусу, файл вважається незміненим. Інший розмір одразу означає, що файл змінено. Інакше Pitmaster читає файл і обчислює його хеш, щоб перевірити, чи змінився вміст.

## Прапорець assume-valid

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

Цей прапорець наказує git пропускати перевірку файлу під час визначення статусу, навіть якщо поля stat змінилися (`git update-index --assume-unchanged`). Pitmaster зберігає цей біт під час перезапису записів, але його механізм визначення статусу не враховує прапорець і все одно порівнює файл.
