
# Git 对象

git 仓库中的所有内容都以对象形式存储。对象有四种类型：blob（文件内容）、tree（目录列表）、commit（带元数据的快照）和 tag（带注释的引用）。每个对象都由其内容的 SHA-1 哈希标识。

## 对象类型

### Blob

blob 保存原始文件内容，不包含元数据（如文件名和权限）。

```php
use Pitmaster\Object\Blob;

// Create from content
$blob = Blob::fromContent('Hello, world!');
echo $blob->content;    // 'Hello, world!'
echo $blob->size();     // 13
echo $blob->id->hex;    // SHA-1 hash
echo $blob->type->value; // 'blob' (the property is ObjectType::Blob)
```

### 树

tree 是目录列表。每个条目都包含模式、名称和一个指向 blob 或子 tree 的哈希。

```php
use Pitmaster\Object\Tree;
use Pitmaster\Object\TreeEntry;

// Read a tree
$tree = $repo->readObject($treeHash);

foreach ($tree->entries as $entry) {
    echo "{$entry->mode} {$entry->name} {$entry->hash->hex}\n";
}

// Look up a specific entry
$entry = $tree->entry('README.md');

// Create a tree (entries already in git's sort order)
$entries = [
    new TreeEntry('100644', 'README.md', $blobId),
    new TreeEntry('100755', 'deploy.sh', $execBlobId),
    new TreeEntry('40000', 'src', $subtreeId),
];

$tree = Tree::fromEntries($entries);
```

`Tree::fromEntries()` 会按给定顺序写入条目，并原样写入给定的模式字符串，因此请先排序，并将目录模式写为 `40000`。使用 `040000` 写入的 tree 或按其他顺序写入的 tree，其哈希会与 git 的不同，而 `git fsck --strict` 会报告补零后的模式值。

#### TreeEntry

`TreeEntry` 是一个只读值对象：

```php
use Pitmaster\Object\TreeEntry;

$entry->mode;   // '100644', '100755', '40000', '120000', '160000'
$entry->name;   // Filename
$entry->hash;   // ObjectId

// Type checks
$entry->isBlob();        // regular or executable file (mode 10xxxx)
$entry->isExecutable();  // mode 100755
$entry->isTree();        // mode 40000 (also accepts 040000)
$entry->isSymlink();     // mode 120000
$entry->isGitlink();     // mode 160000 (submodule)
```

模式值：
| 模式 | 含义 |
|------|---------|
| `100644` | 普通文件 |
| `100755` | 可执行文件 |
| `40000` | 目录（tree）；`git ls-tree` 将其显示为 `040000` |
| `120000` | 符号链接 |
| `160000` | Gitlink（子模块）|

Tree 条目按名称排序；比较时，目录名会被视为以 `/` 结尾。

### 提交

commit 将 tree 与其历史记录和元数据关联起来。

```php
use Pitmaster\Object\Commit;

$commit = $repo->head();

$commit->id;         // ObjectId
$commit->tree;       // ObjectId of the tree
$commit->parents;    // array of ObjectId (zero for root, one for normal, two+ for merge)
$commit->author;     // 'Name <email> timestamp timezone'
$commit->committer;  // 'Name <email> timestamp timezone'
$commit->message;    // Full commit message

$commit->isMerge();  // true if two or more parents
```

#### 构建 commit 内容

```php
$content = Commit::buildContent(
    tree: $treeId,
    parents: [$parentId],
    author: 'Jane <jane@example.com> 1700000000 +0000',
    committer: 'Jane <jane@example.com> 1700000000 +0000',
    message: "Add feature\n",
);
```

原始 commit 格式：

```
tree <hex hash>\n
parent <hex hash>\n       (zero or more)
author <name> <<email>> <timestamp> <tz>\n
committer <name> <<email>> <timestamp> <tz>\n
\n
<message>
```

### 标签

带注释的 tag 是包含元数据的对象。

```php
use Pitmaster\Object\Tag;

$tag = $repo->readObject($tagHash);

$tag->object;        // ObjectId of the target
$tag->objectType;    // ObjectType enum (ObjectType::Commit, ...)
$tag->name;          // Tag name
$tag->tagger;        // 'Name <email> timestamp timezone'
$tag->message;       // Tag message
```

原始 tag 格式：

```
object <hex hash>\n
type commit\n
tag <name>\n
tagger <name> <<email>> <timestamp> <tz>\n
\n
<message>
```

## ObjectId

`ObjectId` 是一个只读值对象，表示 SHA-1（或 SHA-256）哈希。

```php
use Pitmaster\Object\ObjectId;
use Pitmaster\Object\ObjectType;

// From hex string
$id = ObjectId::fromHex('a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2');

// From binary (20 bytes for SHA-1, 32 for SHA-256)
$id = ObjectId::fromBinary($rawBytes);

// Compute from content
$id = ObjectId::compute(ObjectType::Blob, 'Hello, world!');
$id = ObjectId::compute(ObjectType::Blob, 'Hello, world!', 'sha256');

// Zero hash
$id = ObjectId::zero();
$id = ObjectId::zero('sha256');

// Properties
$id->hex;      // 40-character hex string (64 for SHA-256)
$id->binary;   // 20-byte raw binary (32 for SHA-256)
$id->algo;     // 'sha1' or 'sha256'
$id->isSha256();
$id->isZero();

// Comparison
$id->equals($otherId);
```

哈希根据未压缩数据计算：`sha1("{type} {size}\0{content}")`，或对相同字节进行 SHA-256。`fromHex()` 和 `fromBinary()` 根据长度选择算法。

## ObjectType 枚举

```php
use Pitmaster\Object\ObjectType;

ObjectType::Blob;    // 'blob'
ObjectType::Tree;    // 'tree'
ObjectType::Commit;  // 'commit'
ObjectType::Tag;     // 'tag'
```

## 松散对象格式

松散对象以独立文件的形式存储在 `.git/objects/` 中，并按哈希的前两个十六进制字符组织。

```
.git/objects/a1/b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
```

文件内容经过 zlib 压缩。解压后的内容为：

```
<type> <decimal size>\0<raw content>
```

例如，包含 "Hello" 的 blob 存储为：

```
zlib_encode("blob 5\0Hello")
```

### 读取松散对象

```php
use Pitmaster\Storage\LooseObjectStore;

$store = new LooseObjectStore('/path/to/.git/objects');
$object = $store->read($objectId);
```

`LooseObjectStore`：
1. 根据哈希计算文件路径
2. 读取文件并使用 zlib 解压
3. 解析标头以提取类型和大小
4. 委托相应的解析器处理（`Blob`、`Tree`、`Commit`、`Tag`）

### 写入松散对象

```php
$store->write($object);
```

写入过程：
1. 序列化：`"{type} {size}\0{content}"`
2. 使用对象的 `id`（对象构建时计算；读取时会验证哈希）
3. 压缩：`zlib_encode(serialized)`
4. 写入 `.git/objects/{hash[0..1]}/{rest of hash}`
5. 原子写入（先写入临时文件，再重命名）

## ObjectSerializer

`ObjectSerializer` 负责对对象线格式进行编码和解码。

```php
use Pitmaster\Storage\ObjectSerializer;

// Encode
$raw = ObjectSerializer::encodeRaw($object);
// "blob 5\0Hello"
$compressed = ObjectSerializer::encode($object);
// zlib-compressed form, as stored in a loose object file

// Decode
$object = ObjectSerializer::decodeRaw($raw);
$object = ObjectSerializer::decode($compressed, $expectedHex); // throws CorruptObjectException on a hash mismatch
```

## ObjectDatabase

`ObjectDatabase` 是一个组合存储，先搜索松散对象，再搜索包文件。

```php
use Pitmaster\Storage\ObjectDatabase;

$db = new ObjectDatabase('/path/to/.git/objects');

$object = $db->read($objectId);    // Search loose, then packs
$db->write($object);               // Always writes to loose
$exists = $db->exists($objectId);  // Check loose, then packs
$all = $db->listAll();             // All hashes from loose + packs
```

有意将写入操作直接写入松散存储。让 `git gc` 将其重新打包为包文件。

## SHA-256 支持

Pitmaster 通过 `ObjectId` 抽象哈希算法。SHA-1 是默认算法。创建仓库时会为每个仓库选择 SHA-256：

```php
$repo = Pitmaster::init('/path/to/project', 'sha256');
```

```bash
./vendor/bin/pitmaster init --object-format=sha256
```

这会将 `extensions.objectformat = sha256` 写入配置。之后打开的仓库会从该配置项读取格式，随后所有对象、tree、索引和 ref 操作都会使用 64 个字符的十六进制字符串和 32 字节的二进制哈希。没有全局开关或算法常量。

自行构建对象时，对象类会将算法作为参数：`Blob::fromContent($content, 'sha256')`、`Tree::fromEntries($entries, 'sha256')`、`ObjectId::compute($type, $content, 'sha256')`。`Pitmaster::clone()` 会创建 SHA-1 仓库。

## Tree 条目二进制格式

在 tree 对象的内容中，每个条目都编码为：

```
<octal mode as ASCII> <space> <filename> \0 <binary hash: 20 bytes SHA-1, 32 bytes SHA-256>
```

注意：哈希是原始二进制数据，而不是十六进制；这与 commit 和 tag 对象中的十六进制哈希不同。

条目按名称排序，比较时将目录视为末尾带有 `/`。要使 tree 的哈希与 git 的计算结果一致，排序顺序必须完全准确。
