
# Git 객체

git 저장소의 모든 항목은 객체로 저장됩니다. 객체 유형은 네 가지입니다. 블롭(파일 콘텐츠), 트리(디렉터리 목록), 커밋(메타데이터가 포함된 스냅샷), 태그(주석이 달린 참조)입니다. 각 객체는 콘텐츠의 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를 쓰거나 순서를 다르게 하면 git의 해시와 달라지며, `git fsck --strict`은 앞에 0을 채운 모드를 보고합니다.

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

#### 커밋 콘텐츠 만들기

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

## Loose 객체 형식

Loose 객체는 `.git/objects/`에 개별 파일로 저장되며, 해시의 처음 두 16진수 문자를 기준으로 구성됩니다.

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

파일 콘텐츠는 zlib으로 압축됩니다. 압축을 푼 콘텐츠는 다음과 같습니다.

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

예를 들어 "Hello"가 들어 있는 blob은 다음과 같이 저장됩니다.

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

### Loose 객체 읽기

```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`)에 처리를 위임합니다.

### Loose 객체 쓰기

```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`은 먼저 loose 객체를 검색한 뒤 pack 파일을 검색하는 복합 저장소입니다.

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

loose 저장소에 쓰는 것은 의도된 동작입니다. `git gc`이 pack 파일로 다시 패킹하도록 하세요.

## 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자 16진수 문자열과 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 객체의 16진수 해시와 달리, 해시는 16진수가 아닌 원시 바이너리입니다.

디렉터리 이름 뒤에 `/`이 붙은 것으로 간주해 비교한 순서대로 항목을 정렬합니다. tree의 해시가 git의 계산값과 일치하려면 이 정렬 순서가 정확해야 합니다.
