
# 인덱스 형식

인덱스 파일(`.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: 유효한 것으로 간주 플래그
- 비트 13-12: 병합 단계(0-3)
- 비트 11-0: 경로 이름 길이. 경로가 4095바이트 이상이면 0xFFF이며(이 경우 읽기 작업에서 NUL을 검색합니다)

### 버전별 차이

**버전 2**: 위에서 설명한 기본 형식입니다. 가장 일반적으로 사용됩니다.

**버전 3**: 확장 플래그(일반 플래그 뒤에 추가 바이트 2개)가 추가됩니다. `skip-worktree` 및 `intent-to-add` 비트를 지원합니다. 확장 플래그가 필요한 항목이 있을 때 사용됩니다.

**버전 4**: 경로 접두사 압축이 추가됩니다. 전체 경로를 저장하는 대신 각 항목은 이전 항목 경로에서 제거할 바이트 수를 저장한 다음 새 접미사를 저장합니다. 깊게 중첩된 트리에서 인덱스 크기를 크게 줄입니다.

## 충돌 단계

병합 충돌이 발생하면 하나의 경로에 서로 다른 단계의 인덱스 항목이 여러 개 있을 수 있습니다.

| 단계 | 의미 |
|-------|---------|
| 0 | 일반(충돌 없음) |
| 1 | 기본 버전(공통 조상) |
| 2 | ours(현재 브랜치) |
| 3 | theirs(병합 중인 브랜치) |

```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`)는 로드되지 않으므로 이를 사용하는 저장소는 지원되지 않습니다.

## 체크섬

인덱스 파일의 끝에는 저장소의 해시를 사용한 앞선 모든 바이트의 체크섬이 있습니다(SHA-1은 20바이트, SHA-256은 32바이트). Pitmaster는 저장할 때 체크섬을 쓰지만 읽을 때는 확인하지 않습니다.

`Index::open()`와 `Index::parse()`은 `DIRC` 서명이 없거나 지원되지 않는 버전이면 `IndexParseException`를 발생시킵니다.

## 인덱스와 상태

인덱스는 상태 계산의 핵심입니다. `WorkingTreeStatus`은 다음 항목을 비교합니다.
1. HEAD 트리와 인덱스: 스테이징된 변경 사항을 감지합니다.
2. 인덱스와 작업 트리: 스테이징되지 않은 변경 사항을 감지합니다.

stat 필드(ctime, mtime, dev, ino, size, mode, uid, gid)를 사용하면 파일 콘텐츠를 읽지 않고도 "파일이 변경되었는가?"를 빠르게 확인할 수 있습니다. stat 필드가 일치하고 상태 검사 시작 전에 파일이 마지막으로 수정되었다면 파일이 변경되지 않은 것으로 간주합니다. 크기가 다르면 즉시 수정된 것으로 표시됩니다. 그 밖의 경우 Pitmaster은 파일을 읽고 해시를 계산해 실제 콘텐츠 변경 여부를 확인합니다.

## 유효한 것으로 간주 플래그

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

이 플래그는 stat 필드가 변경되었더라도 상태 확인 중 파일 검사를 건너뛰도록 git에 알립니다(`git update-index --assume-unchanged`). Pitmaster은 항목을 다시 쓸 때 이 비트를 유지하지만, 상태 확인에서는 이를 적용하지 않고 파일을 계속 비교합니다.
