
# Objek Git

Semua hal dalam repositori git disimpan sebagai objek. Ada empat jenis: blob (isi file), tree (daftar direktori), commit (snapshot beserta metadata), dan tag (referensi beranotasi). Setiap objek diidentifikasi oleh hash SHA-1 dari isinya.

## Jenis objek

### Blob

Blob menyimpan isi file mentah tanpa metadata (tanpa nama file atau izin).

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

Tree adalah daftar direktori. Setiap entri memiliki mode, nama, dan hash yang merujuk ke blob atau subpohon.

```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()` menulis entri sesuai urutan yang diberikan dan string mode persis seperti yang diberikan, jadi urutkan terlebih dahulu dan tulis mode direktori sebagai `40000`. Tree yang ditulis dengan `040000` atau urutan berbeda akan mendapat hash yang berbeda dari git, dan `git fsck --strict` melaporkan mode dengan padding nol.

#### TreeEntry

`TreeEntry` adalah objek nilai readonly:

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

Nilai mode:
| Mode | Arti |
|------|---------|
| `100644` | File biasa |
| `100755` | File yang dapat dieksekusi |
| `40000` | Direktori (tree); ditampilkan sebagai `040000` oleh `git ls-tree` |
| `120000` | Tautan simbolis |
| `160000` | Gitlink (submodul) |

Entri tree diurutkan berdasarkan nama, dengan nama direktori diperlakukan seolah diakhiri `/` untuk keperluan perbandingan.

### Commit

Commit menghubungkan tree dengan riwayat dan metadatanya.

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

#### Menyusun isi 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",
);
```

Format commit mentah:

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

Tag beranotasi adalah objek yang memiliki metadata.

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

Format tag mentah:

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

## ObjectId

`ObjectId` adalah objek nilai readonly yang merepresentasikan hash SHA-1 (atau 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);
```

Hash dihitung dari data yang tidak dikompresi: `sha1("{type} {size}\0{content}")`, atau SHA-256 dari byte yang sama. `fromHex()` dan `fromBinary()` memilih algoritme berdasarkan panjangnya.

## Enum ObjectType

```php
use Pitmaster\Object\ObjectType;

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

## Format objek lepas

Objek lepas disimpan sebagai file terpisah dalam `.git/objects/`, dikelompokkan berdasarkan dua karakter heksadesimal pertama dari hash-nya.

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

Isi file dikompresi dengan zlib. Konten yang tidak dikompresi adalah:

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

Contohnya, blob yang berisi "Hello" disimpan sebagai:

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

### Membaca objek lepas

```php
use Pitmaster\Storage\LooseObjectStore;

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

`LooseObjectStore`:
1. Menghitung jalur file dari hash
2. Membaca dan mendekompresi file dengan zlib
3. Mengurai header untuk mengambil jenis dan ukuran
4. Meneruskan ke pengurai yang sesuai (`Blob`, `Tree`, `Commit`, `Tag`)

### Menulis objek lepas

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

Proses penulisan:
1. Serialisasikan: `"{type} {size}\0{content}"`
2. Gunakan `id` objek, yang dihitung saat objek dibuat (pembacaan memverifikasi hash)
3. Kompresikan: `zlib_encode(serialized)`
4. Tulis ke `.git/objects/{hash[0..1]}/{rest of hash}`
5. Penulisan atomik (tulis ke file sementara, lalu ganti nama)

## ObjectSerializer

`ObjectSerializer` menangani pengodean dan penguraian format wire objek.

```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` adalah penyimpanan komposit yang mencari objek lepas terlebih dahulu, lalu file 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
```

Penulisan langsung ke penyimpanan lepas memang disengaja. Biarkan `git gc` mengemas ulang ke dalam file pack.

## Dukungan SHA-256

Pitmaster mengabstraksikan algoritme hash melalui `ObjectId`. SHA-1 adalah default. SHA-256 dipilih untuk setiap repositori saat dibuat:

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

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

Ini menulis `extensions.objectformat = sha256` ke konfigurasi. Repositori yang dibuka kemudian membaca format dari entri tersebut, lalu setiap operasi objek, tree, indeks, dan ref menggunakan string heksadesimal 64 karakter dan hash biner 32 byte. Tidak ada sakelar global atau konstanta untuk algoritme tersebut.

Kelas objek menerima algoritme sebagai argumen saat Anda membuat objek sendiri: `Blob::fromContent($content, 'sha256')`, `Tree::fromEntries($entries, 'sha256')`, `ObjectId::compute($type, $content, 'sha256')`. `Pitmaster::clone()` membuat repositori SHA-1.

## Format biner entri tree

Di dalam konten objek tree, setiap entri dikodekan sebagai:

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

Catatan: hash-nya berupa biner mentah, bukan heksadesimal, berbeda dengan hash heksadesimal pada objek commit dan tag.

Entri diurutkan dengan membandingkan nama seolah direktori memiliki akhiran `/`. Urutan ini harus persis agar hash tree cocok dengan hasil perhitungan git.
