
# PHP API

## `Pitmaster\Pitmaster`

Kelas `Pitmaster\Pitmaster` adalah titik masuk statis. Kelas ini menyediakan metode factory untuk membuka, membuat, dan mengkloning repositori, serta helper deteksi.

```php
use Pitmaster\Pitmaster;
```

### `open`

```php
public static function open(string $path, array $options = []): Repository
```

Buka repositori yang ada di `$path`. Jalur tersebut seharusnya merupakan akar tree kerja (berisi `.git/`). Juga mendukung worktree tertaut (dengan `.git` berupa file yang berisi `gitdir: <path>`), dan repositori bare (dengan `$path` berupa direktori `.git` itu sendiri).

`$options` menerima dua boolean, keduanya bernilai `true` secara default:

- `hooks`: jalankan skrip `.git/hooks`. Berikan `false` untuk melewatinya pada handle ini.
- `processes`: izinkan segala hal yang memulai proses atau membuka transport jaringan. Berikan `false` untuk menonaktifkan hook, hook fsmonitor, clone, fetch, push, dan transport proses SSH. Pembacaan dan penulisan lokal tetap berfungsi.

Melempar `InvalidArgumentException` jika jalurnya bukan repositori git.

```php
$repo = Pitmaster::open('/home/user/project');
$repo = Pitmaster::open('/home/user/project', ['hooks' => false]);
$repo = Pitmaster::open('/home/user/project', Pitmaster::processFreeOptions());
```

### `init`

```php
public static function init(string $path, string $objectFormat = 'sha1', array $options = []): Repository
```

Inisialisasi repositori baru di `$path`. Membuat struktur direktori `.git` dengan `objects/`, `refs/heads/`, `refs/tags/`, `hooks/`, `info/`, `HEAD` (menunjuk ke `refs/heads/main`), dan `config` default.

`$objectFormat` adalah `'sha1'` atau `'sha256'`. Repositori SHA-256 memperoleh `repositoryformatversion = 1` dan `extensions.objectformat = sha256` di konfigurasinya. Nilai lainnya melempar `InvalidArgumentException`. `$options` menerima kunci yang sama dengan `open()`.

Melempar `RuntimeException` jika repositori sudah ada di jalur tersebut.

```php
$repo = Pitmaster::init('/home/user/new-project');
$repo = Pitmaster::init('/home/user/sha256-project', 'sha256');
```

### `clone`

```php
public static function clone(string $url, string $path, ?int $depth = null, array $options = []): Repository
```

Klon repositori jarak jauh. Menginisialisasi repo baru, menemukan ref jarak jauh, mengambil file pack, menyiapkan cabang pelacakan jarak jauh, dan membuat tree kerja. URL SSH (`ssh://...` atau `user@host:path`) menggunakan transport SSH; URL lainnya menggunakan HTTP pintar dan beralih ke HTTP biasa jika pertukaran pintar gagal. Berikan `$depth` untuk clone dangkal.

Melempar `RuntimeException` saat `$options['processes']` adalah `false`. Jika clone gagal dan `$path` belum ada sebelumnya, direktori yang dibuat sebagian akan dihapus.

```php
$repo = Pitmaster::clone('https://github.com/user/repo.git', '/tmp/repo');
$repo = Pitmaster::clone('https://github.com/user/repo.git', '/tmp/shallow', 1);
$repo = Pitmaster::clone('https://github.com/user/repo.git', '/tmp/repo', null, ['hooks' => false]);
```

### `processFreeOptions`

```php
public static function processFreeOptions(): array
```

Mengembalikan `['processes' => false, 'hooks' => false]` untuk runtime yang tidak boleh memulai proses host. Berikan nilai ini ke `open()` atau `init()`.

### `isRepository`

```php
public static function isRepository(string $path): bool
```

Periksa apakah suatu jalur adalah repositori git. Mengembalikan `true` untuk repo biasa (direktori `.git/`), worktree tertaut (file `.git`), dan repo bare (file `HEAD` tersedia).

```php
if (Pitmaster::isRepository('/path/to/check')) {
    // It's a git repo
}
```

### `isWorktree`

```php
public static function isWorktree(string $path): bool
```

Periksa apakah suatu jalur adalah worktree tertaut (bukan repo utama). Mengembalikan `true` hanya jika `.git` adalah file yang berisi referensi `gitdir:`.

```php
if (Pitmaster::isWorktree('/path/to/worktree')) {
    // It's a linked worktree
}
```

### `commonGitDir`

```php
public static function commonGitDir(string $path): ?string
```

Selesaikan direktori git bersama dari jalur checkout mana pun. Untuk repo biasa, ini adalah direktori `.git`. Untuk worktree tertaut, ini adalah direktori git bersama yang berisi objek, konfigurasi, dan packed-refs. Mengembalikan `null` jika jalurnya bukan repositori.

```php
$common = Pitmaster::commonGitDir('/path/to/worktree');
// '/path/to/main-repo/.git'
```

---

## `Pitmaster\Repository`

Kelas `Pitmaster\Repository` adalah handle utama untuk semua operasi git. Dapatkan handle melalui `Pitmaster::open()`, `Pitmaster::init()`, atau `Pitmaster::clone()`.

```php
use Pitmaster\Repository;
```

### Pengakses jalur

#### `gitDir`

```php
public function gitDir(): string
```

Mengembalikan direktori git per-worktree. Untuk repo biasa, nilainya adalah `.git/`. Untuk worktree tertaut, nilainya adalah `.git/worktrees/<name>/`.

#### `commonGitDir`

```php
public function commonGitDir(): string
```

Mengembalikan direktori git bersama. Sama dengan `gitDir()` untuk repo biasa. Untuk worktree tertaut, ini adalah direktori `.git/` repo utama (tempat objek, konfigurasi, dan packed-refs berada).

#### `workDir`

```php
public function workDir(): string
```

Mengembalikan jalur akar tree kerja.

#### `isLinkedWorktree`

```php
public function isLinkedWorktree(): bool
```

Mengembalikan `true` jika handle repositori ini adalah worktree tertaut.

#### `isBare`, `hooksEnabled`, `processesEnabled`

```php
public function isBare(): bool
public function hooksEnabled(): bool
public function processesEnabled(): bool
```

Melaporkan apakah repositori tidak memiliki tree kerja, dan opsi `hooks` / `processes` mana yang berlaku untuk handle ini. `hooksEnabled()` bernilai `false` setiap kali proses dinonaktifkan.

---

### Objek

#### `readObject`

```php
public function readObject(string $hash): GitObject
```

Baca objek apa pun menggunakan hash hex lengkapnya (40 karakter, atau 64 dalam repositori SHA-256). Hash singkat tidak diterima. Mengembalikan instance `Blob`, `Tree`, `Commit`, atau `Tag`. Mencari objek lepas terlebih dahulu, lalu file pack.

Melempar `ObjectNotFoundException` jika hash tidak ditemukan.

```php
$object = $repo->readObject('a1b2c3d4e5f6...');
```

#### `writeObject`

```php
public function writeObject(GitObject $object): ObjectId
```

Tulis objek ke penyimpanan objek lepas. Mengembalikan `ObjectId` yang dihitung.

```php
use Pitmaster\Object\Blob;

$blob = Blob::fromContent('Hello, world!');
$id = $repo->writeObject($blob);
```

#### `catFile`

```php
public function catFile(string $hash): string
```

Mengembalikan konten mentah suatu objek, setara dengan `git cat-file -p`.

```php
echo $repo->catFile('a1b2c3d4...');
```

#### `objectExists`

```php
public function objectExists(string $hash): bool
```

Periksa apakah objek ada di repositori (lepas atau terkemas).

#### `listObjects`

```php
public function listObjects(): array
```

Cantumkan semua hash objek di repositori. Mengembalikan array string hex.

---

### Ref

#### `head`

```php
public function head(): Commit
```

Mengembalikan commit HEAD saat ini. Mengikuti ref simbolis (HEAD -> refs/heads/main -> hash commit).

Melempar `RuntimeException` jika HEAD tidak menunjuk ke commit yang valid (e.g., repositori kosong).

#### `branch`

```php
public function branch(?string $name = null): ?string
```

Tanpa argumen, mengembalikan nama cabang saat ini (e.g., `'main'`), atau `null` jika HEAD terlepas.

Dengan nama cabang, menyelesaikannya menjadi hash commit (string hex) atau mengembalikan `null` jika cabang tidak ada.

```php
$current = $repo->branch();        // 'main'
$hash = $repo->branch('feature');   // 'abc123...' or null
```

#### `branches`

```php
public function branches(): array
```

Mengembalikan array terurut dari semua nama cabang (tanpa awalan `refs/heads/`).

```php
$branches = $repo->branches();
// ['bugfix/typo', 'feature/login', 'main']
```

#### `tags`

```php
public function tags(): array
```

Mengembalikan array terurut dari semua nama tag (tanpa awalan `refs/tags/`).

#### `resolve`

```php
public function resolve(string $revision): ObjectId
```

Selesaikan ekspresi revisi menjadi `ObjectId`. Mendukung:
- Hash hex lengkap (40 karakter, atau 64 untuk SHA-256). Hash singkat seperti `abc1234` tidak dapat diselesaikan.
- `HEAD`
- Nama cabang (`main`, `feature/login`)
- Nama tag (`v1.0.0`)
- Jalur ref lengkap (`refs/heads/main`)
- Ekspresi revisi (`HEAD~3`, `main^2`)

Melempar `RuntimeException` jika revisi tidak dapat diselesaikan.

```php
$id = $repo->resolve('HEAD~3');
$id = $repo->resolve('v1.0.0');
$id = $repo->resolve('feature/login');
```

#### `allRefs`

```php
public function allRefs(): array
```

Mengembalikan semua ref sebagai array asosiatif dari nama ref ke hash hex.

```php
$refs = $repo->allRefs();
// ['refs/heads/main' => 'abc123...', 'refs/tags/v1.0.0' => 'def456...']
```

#### `updateRef`

```php
public function updateRef(string $name, ObjectId $target): void
```

Perbarui ref agar menunjuk ke target baru.

```php
$repo->updateRef('refs/heads/main', $commitId);
```

#### `createBranch`

```php
public function createBranch(string $name, ?ObjectId $from = null): void
```

Buat cabang baru. Jika `$from` bernilai null, cabang dibuat dari HEAD saat ini.

```php
$repo->createBranch('feature/new-thing');
$repo->createBranch('hotfix', $repo->resolve('v1.0.0'));
```

#### `deleteBranch`

```php
public function deleteBranch(string $name): void
```

Hapus cabang berdasarkan nama. Melempar `RuntimeException` jika cabang sedang di-checkout atau tidak ada. Tidak seperti `git branch -d`, metode ini tidak memeriksa apakah cabang sudah digabung.

```php
$repo->deleteBranch('feature/old');
```

#### `createLightweightTag`

```php
public function createLightweightTag(string $name, ?ObjectId $target = null): void
```

Buat tag ringan. Jika `$target` bernilai null, tag langsung diterapkan ke HEAD saat ini.

```php
$repo->createLightweightTag('v2.0.0');
$repo->createLightweightTag('hotfix-start', $repo->resolve('main~2'));
```

#### `createTag`

```php
public function createTag(string $name, string $message, ?ObjectId $target = null, ?string $tagger = null): ObjectId
```

Buat tag beranotasi. Jika `$target` bernilai null, tag HEAD saat ini. Mengembalikan `ObjectId` milik objek tag.

```php
$tagId = $repo->createTag('v2.0.0', 'Release version 2.0.0');
```

#### `deleteTag`

```php
public function deleteTag(string $name): void
```

Hapus tag berdasarkan nama.

```php
$repo->deleteTag('v1.0.0');
```

#### `packRefs`

```php
public function packRefs(): void
```

Tulis ulang file `packed-refs` bersama dari kumpulan ref lepas saat ini dan pangkas ref lepas bersama yang terkait. Untuk worktree tertaut, ini menulis ke direktori git umum.

```php
$repo->packRefs();
```

#### `defaultBranch`

```php
public function defaultBranch(): string
```

Tentukan branch default repositori. Periksa symref HEAD remote terlebih dahulu, lalu HEAD lokal, kemudian gunakan `main` atau `master` sebagai fallback.

#### `isBranchMerged`

```php
public function isBranchMerged(string $branch, ?string $target = null): bool
```

Periksa apakah sebuah branch telah digabungkan sepenuhnya ke branch lain (secara default, branch default).

```php
if ($repo->isBranchMerged('feature/done')) {
    $repo->deleteBranch('feature/done');
}
```

#### `checkout`

```php
public function checkout(string $target): void
```

Beralih branch atau lepaskan HEAD. Memperbarui HEAD, indeks, dan working tree. `$target` adalah nama branch, tag, hash lengkap, atau ekspresi revisi; apa pun yang bukan branch akan melepaskan HEAD.

Memunculkan `RuntimeException` jika ada perubahan lokal (sudah di-stage atau belum) atau file yang tidak dilacak pada jalur yang berbeda antara tree saat ini dan tree target.

Perubahan yang belum di-commit pada file terlacak lainnya tidak dibawa: indeks dan working tree dibangun ulang dari tree target, sehingga perubahan pada file yang identik di kedua sisi akan hilang, seperti yang akan dipertahankan oleh git ([#2](https://github.com/inline0/pitmaster/issues/2)). Commit atau stash perubahan tersebut terlebih dahulu. `merge()`, `cherryPick()`, `revert()`, `rebase()` dan metode bisect membangun ulang working tree dengan cara yang sama.

```php
$repo->checkout('feature/login');
$repo->checkout($repo->resolve('v1.0.0')->hex);  // Detached HEAD
```

---

### Indeks

#### `index`

```php
public function index(): Index
```

Baca dan kembalikan indeks (staging area) saat ini dari `.git/index`.

#### `add`

```php
public function add(string ...$paths): void
```

Stage file. Membaca setiap file dari working tree, membuat objek blob, dan memperbarui indeks. Setiap argumen harus berupa jalur file (atau symlink) relatif terhadap root working tree; direktori dan glob tidak diperluas, dan file yang tidak ditemukan akan memunculkan `RuntimeException`.

```php
$repo->add('src/Feature.php', 'tests/FeatureTest.php');
```

#### `remove`

```php
public function remove(string ...$paths): void
public function removeCached(string ...$paths): void
```

Hapus jalur terlacak dari indeks dan working tree. Mendukung `--cached` dan `-r` / `--recursive` dalam daftar argumen untuk perilaku berbentuk Git.

```php
$repo->remove('old-file.txt');
$repo->remove('-r', 'src');
$repo->remove('--cached', 'old-file.txt');
$repo->removeCached('old-file.txt');
```

#### `mv`

```php
public function mv(string $source, string $destination): void
```

Pindahkan/ganti nama file di working tree dan indeks.

```php
$repo->mv('old-name.php', 'new-name.php');
```

---

### Commit

#### `commit`

```php
public function commit(?string $message = null, ?string $author = null): ObjectId
```

Buat commit dari indeks saat ini. Bangun hierarki tree dari entri indeks, buat objek commit dengan HEAD sebagai parent (jika ada), lalu perbarui HEAD. Mengembalikan `ObjectId` commit baru.

`$message` hanya boleh berupa `null` saat merge, cherry-pick, revert, atau rebase berhenti karena konflik; pesan yang telah disiapkan akan digunakan saat itu. Selain itu, pesan `null` akan memunculkan `RuntimeException('Commit message required')`. Commit juga akan memunculkan error jika indeks memiliki jalur yang belum digabungkan, jika indeks kosong di repositori tanpa commit, atau jika tree tidak berubah dari HEAD.

Parameter `$author` adalah string author git lengkap (`Name <email> timestamp timezone`). Jika null, nilainya ditentukan dari variabel lingkungan, konstanta, dan `.git/config`, sesuai urutan yang dijelaskan di [Memulai](https://pitmaster.inline0.dev/id/docs/getting-started).

```php
$id = $repo->commit('Fix null pointer bug');
```

#### `show`

```php
public function show(string $revision): array
```

Tampilkan commit-ish dan diff-nya terhadap parent pertama. Jika revisi merujuk ke tag beranotasi, nilai yang dikembalikan juga mencakup `['tag' => Tag]` dan `commit` adalah commit target hasil peel.

```php
$result = $repo->show('HEAD');
echo $result['commit']->message;

foreach ($result['diff'] as $diff) {
    echo $diff->format();
}
```

#### `reset`

```php
public function reset(string $revision, string $mode = 'mixed'): void
```

Reset HEAD ke sebuah commit. Mode:
- `'soft'`: hanya pindahkan HEAD
- `'mixed'`: pindahkan HEAD + reset indeks (default)
- `'hard'`: pindahkan HEAD + reset indeks + reset working tree

```php
$repo->reset('HEAD~1', 'soft');
$repo->reset('main', 'hard');
```

#### `restore`

```php
public function restore(string $path, ?string $source = null, bool $staged = false, bool $worktree = false): void
```

Pulihkan jalur dari indeks atau revisi sumber tertentu. Jika kedua flag tidak disetel, Pitmaster memulihkan worktree dari indeks (atau dari `$source` jika diberikan). `staged: true` memulihkan indeks dari HEAD (atau `$source`) dan membiarkan worktree apa adanya; berikan `staged: true, worktree: true` untuk memulihkan keduanya.

```php
$repo->restore('src/File.php');                                        // Worktree from index
$repo->restore('src/File.php', staged: true);                          // Index from HEAD
$repo->restore('src/File.php', 'HEAD~1');                              // Worktree from another commit
$repo->restore('src/File.php', 'HEAD~1', staged: true, worktree: true); // Index + worktree from another commit
```

#### `cherryPick`

```php
public function cherryPick(string $revision): ObjectId
public function cherryPickContinue(): ObjectId
public function cherryPickAbort(): void
```

Terapkan commit sebagai commit baru pada branch saat ini, dengan mempertahankan author aslinya. Jika konflik menghentikan operasi, selesaikan konfliknya, stage jalurnya, lalu panggil `cherryPickContinue()` atau `cherryPickAbort()`.

```php
$newId = $repo->cherryPick('feature~2');

// After resolving conflicts:
$repo->add('src/File.php');
$repo->cherryPickContinue();
```

#### `revert`

```php
public function revert(string $revision): ObjectId
public function revertContinue(): ObjectId
public function revertAbort(): void
```

Buat commit yang membatalkan commit lain. Jika konflik menghentikan revert, selesaikan konfliknya, stage jalurnya, lalu panggil `revertContinue()` atau `revertAbort()`.

```php
$revertId = $repo->revert('HEAD~1');

// After resolving conflicts:
$repo->add('src/File.php');
$repo->revertContinue();
```

#### `rebase`

```php
public function rebase(string $onto): array
public function rebaseContinue(): array
public function rebaseAbort(): void
public function rebaseSkip(): array
```

Rebase branch saat ini ke revisi lain. Pitmaster saat ini mencakup rebase linear dengan status berhenti berbentuk Git untuk konflik, serta melanjutkan, membatalkan, dan melewati. HEAD harus menunjuk ke sebuah branch.

`rebase()`, `rebaseContinue()`, dan `rebaseSkip()` mengembalikan `['success' => bool, 'commits' => int, 'conflicts' => list<string>]`: apakah rebase selesai, jumlah commit yang diterapkan, dan jalur yang berkonflik saat proses berhenti.

```php
$result = $repo->rebase('main');

if (!$result['success']) {
    // Resolve conflicts, then continue or skip/abort.
    $repo->add('src/File.php');
    $repo->rebaseContinue();
}
```

---

### Status

#### `status`

```php
public function status(): array
```

Hitung status working tree. Mengembalikan array objek `StatusEntry`, yang masing-masing berisi:
- `path`: jalur file
- `index`: enum `FileStatus` untuk perubahan dari HEAD ke indeks
- `worktree`: enum `FileStatus` untuk perubahan dari indeks ke worktree
- `origPath`, `renameScore`: disetel untuk entri yang diganti nama, selain itu `null`

File yang tidak dilacak memiliki `FileStatus::Untracked` di kedua kolom. File yang diabaikan tidak dilaporkan.

```php
foreach ($repo->status() as $entry) {
    echo $entry->shortFormat() . "\n"; // "M  src/File.php", "?? notes.txt"
}
```

#### `statusPorcelainV2`

```php
public function statusPorcelainV2(): string
```

Output status yang dapat dibaca mesin dalam format porcelain v2 milik git.

```php
echo $repo->statusPorcelainV2();
// 1 M. N... 000000 000000 000000 0000...0000 0000...0000 modified-file.txt
// ? untracked-file.txt
```

---

### Diff

#### `diff`

```php
public function diff(?string $pathspec = null, string $algorithm = 'myers'): array
```

Diff working tree terhadap indeks (perubahan yang belum di-stage). Mengembalikan array objek `DiffResult`. `$pathspec` memfilter berdasarkan satu jalur file yang persis. `$algorithm` adalah `'myers'` (default), `'patience'`, `'histogram'`, atau `'minimal'`; nilai lain akan memunculkan `InvalidArgumentException`.

```php
$diffs = $repo->diff();

foreach ($diffs as $diff) {
    echo $diff->format();  // Unified diff output
}
```

#### `diffStaged`

```php
public function diffStaged(?string $pathspec = null, string $algorithm = 'myers'): array
```

Diff indeks terhadap HEAD (perubahan yang sudah di-stage). Mengembalikan `DiffResult[]`, dengan deteksi penggantian nama antara file yang dihapus dan ditambahkan.

Argumen `$pathspec` saat ini diabaikan, sehingga semua file yang sudah di-stage dikembalikan, dan metode ini mengembalikan array kosong sebelum commit pertama ([#5](https://github.com/inline0/pitmaster/issues/5)).

```php
$staged = $repo->diffStaged();
```

#### `diffTree`

```php
public function diffTree(ObjectId $a, ObjectId $b, string $algorithm = 'myers'): array
```

Bandingkan dua tree berdasarkan `ObjectId`-nya. Mengembalikan `DiffResult[]`. Berikan ID tree, bukan ID commit: ID commit tidak dibaca sebagai tree dan menghasilkan hasil kosong.

```php
$old = $repo->readObject($repo->resolve('HEAD~1')->hex);
$new = $repo->head();
$diffs = $repo->diffTree($old->tree, $new->tree);
```

---

### Log

#### `log`

```php
public function log(int $limit = 50, ?ObjectId $from = null): array
```

Telusuri riwayat commit, dari yang terbaru berdasarkan cap waktu committer. Mengembalikan `Commit[]`. Jika `$from` null, mulai dari HEAD. Mengembalikan array kosong jika HEAD belum memiliki commit.

```php
$commits = $repo->log(10);
```

#### `logAll`

```php
public function logAll(int $limit = 50): array
```

Telusuri commit yang dapat dijangkau dari setiap ref dan HEAD, dari yang terbaru. Mengembalikan `Commit[]`.

#### `logOneline`

```php
public function logOneline(int $limit = 50, bool $all = false, ?string $path = null): array
```

Kembalikan baris log dalam format `git log --oneline` (`abc1234 Subject`), dari HEAD, dari semua ref (`$all`), atau yang difilter berdasarkan jalur.

#### `logPath`

```php
public function logPath(string $path, int $limit = 50): array
```

Log yang difilter berdasarkan jalur file. Hanya mengembalikan commit yang diff-nya terhadap parent pertama menambahkan, mengubah, menghapus, atau mengganti nama file tersebut. Jalur direktori tidak cocok.

Hanya `5 * $limit` commit terbaru yang diperiksa, sehingga commit lama yang menyentuh file tersebut mungkin tidak ada dalam hasil ([#7](https://github.com/inline0/pitmaster/issues/7)).

```php
$commits = $repo->logPath('src/Repository.php', 20);
```

---

### Merge

#### `merge`

```php
public function merge(string $branch, string $strategy = 'recursive'): MergeResult
public function mergeOctopus(array $branches): MergeResult
public function mergeContinue(): ObjectId
public function mergeAbort(): void
```

Gabungkan branch (atau revisi apa pun) ke HEAD. Mendeteksi merge fast-forward secara otomatis. Untuk merge non-fast-forward, melakukan deteksi konflik tingkat tree dan membuat commit merge jika tidak ada konflik. Jika terjadi konflik, status merge berbentuk Git akan ditulis ke indeks dan worktree; selesaikan jalurnya, stage semuanya, lalu panggil `mergeContinue()` atau `mergeAbort()`.

`$strategy` `'ours'` mencatat commit merge yang mempertahankan tree HEAD; nilai lain menggunakan merge rekursif default. `mergeOctopus()` menggabungkan dua branch atau lebih menjadi satu commit. Jika ada branch yang berkonflik, metode ini mengembalikan hasil yang belum bersih tanpa menulis status merge atau membuat commit.

Menggabungkan branch yang sudah tercakup di HEAD membuat commit merge lain, alih-alih melaporkan "Sudah mutakhir" ([#6](https://github.com/inline0/pitmaster/issues/6)). Perubahan yang belum di-commit pada file terlacak akan ditimpa seperti yang dijelaskan di `checkout()`.

Mengembalikan `MergeResult` dengan:
- `clean`: apakah merge berhasil tanpa konflik
- `commitId`: ID commit merge (jika bersih)
- `conflictPaths`: array jalur file yang berkonflik (jika tidak bersih)
- `mergedContents`: jalur file berkonflik ke konten file dengan penanda konflik (jika tidak bersih)

```php
$result = $repo->merge('feature/login');

if ($result->clean) {
    echo "Merged: {$result->commitId->hex}\n";
} else {
    echo "Conflicts in: " . implode(', ', $result->conflictPaths) . "\n";
    $repo->add('src/File.php');
    $repo->mergeContinue();
}
```

#### `mergeBase`

```php
public function mergeBase(ObjectId $a, ObjectId $b): ?ObjectId
```

Temukan merge base (leluhur bersama terdekat) dari dua commit. Mengembalikan null jika tidak ada leluhur yang sama.

```php
$base = $repo->mergeBase(
    $repo->resolve('main'),
    $repo->resolve('feature/login'),
);
```

### Bisect

```php
public function bisectStart(string $bad, string $good): ObjectId
public function bisectGood(?string $revision = null): ?ObjectId
public function bisectBad(?string $revision = null): ?ObjectId
public function bisectReset(): void
```

Cari commit yang memperkenalkan perubahan dengan pencarian biner. `bisectStart()` mencatat revisi buruk dan baik, melakukan checkout pada kandidat pertama, lalu mengembalikannya. `bisectGood()` dan `bisectBad()` menandai HEAD saat ini (atau `$revision`), lalu melakukan checkout dan mengembalikan kandidat berikutnya. Jika hanya tersisa satu kandidat yang dicurigai, commit tersebut dikembalikan dan tetap menjadi kandidat: itulah commit buruk pertama. `bisectReset()` mengakhiri sesi dan kembali ke branch atau commit tempat sesi dimulai.

```php
$candidate = $repo->bisectStart('HEAD', 'v1.0.0');
// test $candidate ...
$candidate = $repo->bisectBad();
$candidate = $repo->bisectGood();
$repo->bisectReset();
```

---

### Jaringan

#### `fetch`

```php
public function fetch(string $remote = 'origin', ?int $depth = null): void
```

Ambil data dari remote. Unduh objek baru dan perbarui ref pelacakan remote. Berikan `$depth` untuk memperdalam atau membatasi riwayat dangkal.

`fetch()` dan metode `push*()` akan memunculkan `RuntimeException` pada handle yang dibuka dengan `['processes' => false]`.

```php
$repo->fetch();
$repo->fetch('upstream');
```

#### `push`

```php
public function push(string $remote = 'origin', ?string $branch = null): void
public function pushForceWithLease(string $remote = 'origin', ?string $branch = null, ?ObjectId $expected = null): void
public function pushAtomic(string $remote = 'origin', array $branches = []): void
public function pushMirror(string $remote = 'origin'): void
```

Push branch saat ini (atau branch yang ditentukan) ke remote. Varian force-with-lease, atomic, dan mirror tersedia untuk pembaruan ref yang lebih ketat atau lebih luas.

```php
$repo->push();
$repo->push('origin', 'feature/login');
$repo->pushForceWithLease('origin', 'main', $repo->resolve('refs/remotes/origin/main'));
$repo->pushAtomic('origin', ['main', 'release']);
$repo->pushMirror();
```

---

### Worktree

#### `addWorktree`

```php
public function addWorktree(string $path, string $branch, ?ObjectId $from = null, ?string $name = null): Worktree
```

Tambahkan worktree tertaut. Buat branch jika belum ada, siapkan metadata worktree, lalu wujudkan file working tree. Berikan `$name` jika Anda memerlukan slug metadata deterministik yang berbeda dari nama dasar direktori checkout.

```php
$wt = $repo->addWorktree('/tmp/review', 'feature/review');
$wt = $repo->addWorktree('/tmp/checkouts/review', 'feature/review', name: 'review-wt');
```

#### `removeWorktree`

```php
public function removeWorktree(string $pathOrName, bool $force = false): void
```

Hapus worktree tertaut. Gunakan `$force = true` untuk menghapusnya meskipun terkunci.

#### `worktrees`

```php
public function worktrees(): array
```

Daftarkan semua worktree (utama + tertaut). Mengembalikan `Worktree[]`.

```php
foreach ($repo->worktrees() as $wt) {
    echo "{$wt->path} [{$wt->branch}]\n";
}
```

---

### Konfigurasi

#### `config`

```php
public function config(): GitConfig
```

Akses konfigurasi git repositori. Baca dan tulis nilai konfigurasi.

```php
$config = $repo->config();
$name = $config->get('user.name');
$config->set('user.email', 'new@example.com');
```

---

### Akses internal

Metode ini mengekspos komponen internal untuk kasus penggunaan tingkat lanjut.

#### `objectDatabase`

```php
public function objectDatabase(): ObjectDatabase
```

Akses basis data objek mentah (penyimpanan loose + pack).

#### `refDatabase`

```php
public function refDatabase(): RefDatabase
```

Akses basis data ref mentah (ref loose + packed).

---

## Kelas tingkat rendah

Kelas ini tidak dibungkus oleh `Repository` dan dibuat dari komponen internalnya.

### `Pitmaster\Graph\Blame`

```php
public function blame(ObjectId $headId, string $path): array
```

Mengembalikan satu entri untuk setiap baris `$path` pada `$headId`: `['line' => int, 'hash' => string, 'author' => string, 'content' => string]`. `author` adalah string identitas lengkap (`Name <email> timestamp timezone`).

```php
use Pitmaster\Graph\Blame;

$blame = new Blame($repo->objectDatabase());

foreach ($blame->blame($repo->resolve('HEAD'), 'src/File.php') as $line) {
    echo substr($line['hash'], 0, 8) . " {$line['content']}\n";
}
```

### `Pitmaster\Graph\Grep`

```php
public function grep(ObjectId $treeId, string $pattern, string $prefix = '', array $options = []): array
```

Mencari di blob sebuah tree. Mengembalikan `['path' => string, 'line' => int, 'content' => string]` untuk setiap baris yang cocok. Opsi: `regex` (perlakukan pola sebagai pola PCRE) dan `ignore_case`. Regex yang tidak valid akan memunculkan `InvalidArgumentException`.

```php
use Pitmaster\Graph\Grep;

$grep = new Grep($repo->objectDatabase());
$matches = $grep->grep($repo->head()->tree, 'TODO', '', ['ignore_case' => true]);
```

### `Pitmaster\Ref\Notes`

```php
public function get(ObjectId $commitId, string $notesRef = 'refs/notes/commits'): ?string
public function set(ObjectId $commitId, string $noteContent, string $notesRef = 'refs/notes/commits'): void
public function remove(ObjectId $commitId, string $notesRef = 'refs/notes/commits'): void
public function listAll(string $notesRef = 'refs/notes/commits'): array
public function merge(string $sourceRef, string $targetRef = 'refs/notes/commits'): void
```

Membaca dan menulis catatan git yang disimpan di bawah ref catatan. `listAll()` mengembalikan hash commit ke konten catatan.

```php
use Pitmaster\Ref\Notes;

$notes = new Notes($repo->objectDatabase(), $repo->refDatabase());
$notes->set($repo->resolve('HEAD'), "Reviewed\n");
echo $notes->get($repo->resolve('HEAD'));
```
