
# 索引格式

索引文件（`.git/index`）是暂存区。它记录将纳入下次提交的文件，以及用于高效计算状态的元数据。Pitmaster 可读取索引版本 2、3 和 4，并写入版本 2 和 3。将以版本 4 读取的索引写入后，会生成 git 无法读取的文件（[#15](https://github.com/inline0/pitmaster/issues/15)），因此请先用 `git update-index --index-version 2` 转换此类仓库，再通过 Pitmaster 写入。

## 读取索引

```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 | 我方（当前分支） |
| 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`）不会加载，因此不支持使用拆分索引的仓库。

## 校验和

索引文件以仓库哈希算法对之前所有字节计算出的校验和结尾（SHA-1 为 20 字节，SHA-256 为 32 字节）。Pitmaster 会在保存时写入校验和，但读取时不会验证。

缺少 `DIRC` 签名或遇到不支持的版本时，`Index::open()` 和 `Index::parse()` 会抛出 `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)
}
```

此标志告知 git 在计算状态时跳过对文件的检查，即使 stat 字段已更改也是如此（`git update-index --assume-unchanged`）。Pitmaster 重写条目时会保留此位，但其状态检查不会遵循此标志，仍会比较文件。
