
# PHP API

## `Pitmaster\Pitmaster`

`Pitmaster\Pitmaster`クラスは静的エントリーポイントです。リポジトリを開く、作成する、クローンするためのファクトリメソッドと、検出用のヘルパーを提供します。

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

### `open`

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

`$path`にある既存のリポジトリを開きます。パスには作業ツリーのルート（`.git/`を含むディレクトリ）を指定してください。リンクされたworktree（`.git`が`gitdir: <path>`を含むファイルの場合）と、bareリポジトリ（`$path`が`.git`ディレクトリそのものの場合）にも対応しています。

`$options`は2つのブール値を受け取り、どちらもデフォルトは`true`です。

- `hooks`: `.git/hooks`スクリプトを実行します。このハンドルで実行しない場合は`false`を渡します。
- `processes`: プロセスを開始する操作やネットワークトランスポートを開く操作をすべて許可します。フック、fsmonitorフック、clone、fetch、push、SSHプロセストランスポートを無効にするには`false`を渡します。ローカルの読み書きは引き続き行えます。

パスがgitリポジトリでない場合、`InvalidArgumentException`をスローします。

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

`$path`に新しいリポジトリを初期化します。`.git`ディレクトリ構造を作成し、`objects/`、`refs/heads/`、`refs/tags/`、`hooks/`、`info/`、`HEAD`（`refs/heads/main`を指す）、およびデフォルトの`config`を作成します。

`$objectFormat`は`'sha1'`または`'sha256'`です。SHA-256リポジトリでは、設定に`repositoryformatversion = 1`と`extensions.objectformat = sha256`が追加されます。それ以外の値を指定すると`InvalidArgumentException`がスローされます。`$options`は`open()`と同じキーを受け取ります。

指定したパスにリポジトリがすでに存在する場合、`RuntimeException`をスローします。

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

リモートリポジトリをクローンします。新しいリポジトリを初期化し、リモート参照を検出してパックファイルを取得し、リモート追跡ブランチを設定して、作業ツリーを展開します。SSH URL（`ssh://...`または`user@host:path`）にはSSHトランスポートを使用します。それ以外のURLにはスマートHTTPを使用し、スマート交換に失敗した場合はダムHTTPにフォールバックします。浅いクローンには`$depth`を渡します。

`$options['processes']`が`false`の場合に`RuntimeException`をスローします。クローンに失敗し、`$path`が事前に存在しなかった場合は、作成途中のディレクトリを削除します。

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

ホストプロセスを起動してはならないランタイム向けに`['processes' => false, 'hooks' => false]`を返します。`open()`または`init()`に渡してください。

### `isRepository`

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

パスがgitリポジトリかどうかを確認します。通常のリポジトリ（`.git/`ディレクトリ）、リンクされたワークツリー（`.git`ファイル）、ベアリポジトリ（`HEAD`ファイルが存在）では`true`を返します。

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

### `isWorktree`

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

パスがリンクされたワークツリー（メインリポジトリではない）かどうかを確認します。`.git`が`gitdir:`参照を含むファイルの場合に限り、`true`を返します。

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

### `commonGitDir`

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

任意のチェックアウトパスから共通のgitディレクトリを解決します。通常のリポジトリでは`.git`ディレクトリです。リンクされたワークツリーでは、オブジェクト、設定、packed-refsを含む共有gitディレクトリです。パスがリポジトリでない場合は`null`を返します。

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

---

## `Pitmaster\Repository`

`Pitmaster\Repository`クラスは、すべてのgit操作を行うための主要なハンドルです。`Pitmaster::open()`、`Pitmaster::init()`、または`Pitmaster::clone()`で取得できます。

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

### パスアクセサー

#### `gitDir`

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

ワークツリーごとのgitディレクトリを返します。通常のリポジトリでは`.git/`、リンクされたワークツリーでは`.git/worktrees/<name>/`です。

#### `commonGitDir`

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

共有gitディレクトリを返します。通常のリポジトリでは`gitDir()`と同じです。リンクされたワークツリーでは、メインリポジトリの`.git/`ディレクトリ（オブジェクト、設定、packed-refsが格納される場所）です。

#### `workDir`

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

作業ツリーのルートパスを返します。

#### `isLinkedWorktree`

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

このリポジトリハンドルがリンクされたワークツリーの場合、`true`を返します。

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

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

リポジトリに作業ツリーがないかどうか、およびこのハンドルで`hooks` / `processes`のどちらのオプションが有効かを報告します。プロセスが無効な場合、`hooksEnabled()`は常に`false`です。

---

### オブジェクト

#### `readObject`

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

完全な16進ハッシュ（40文字、SHA-256リポジトリでは64文字）で任意のオブジェクトを読み込みます。省略形のハッシュは受け付けません。`Blob`、`Tree`、`Commit`、または`Tag`のインスタンスを返します。ルーズオブジェクトを先に検索し、その後パックファイルを検索します。

ハッシュが見つからない場合、`ObjectNotFoundException`をスローします。

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

#### `writeObject`

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

ルーズオブジェクトストアにオブジェクトを書き込みます。計算された`ObjectId`を返します。

```php
use Pitmaster\Object\Blob;

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

#### `catFile`

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

`git cat-file -p`と同等の、オブジェクトの生コンテンツを返します。

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

#### `objectExists`

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

リポジトリ内にオブジェクトが存在するか（ルーズまたはパック済み）を確認します。

#### `listObjects`

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

リポジトリ内のすべてのオブジェクトハッシュを一覧表示します。16進文字列の配列を返します。

---

### 参照

#### `head`

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

現在のHEADコミットを返します。シンボリック参照（HEAD -> refs/heads/main -> コミットハッシュ）をたどります。

HEADが有効なコミットを指していない場合（e.g、空のリポジトリ）、`RuntimeException`をスローします。

#### `branch`

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

引数がない場合、現在のブランチ名（e.g、`'main'`）を返します。HEADがデタッチ状態の場合は`null`を返します。

ブランチ名を指定すると、そのコミットハッシュ（16進文字列）を解決して返します。ブランチが存在しない場合は`null`を返します。

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

#### `branches`

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

すべてのブランチ名を含むソート済み配列を返します（`refs/heads/`プレフィックスなし）。

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

#### `tags`

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

すべてのタグ名を含むソート済み配列を返します（`refs/tags/`プレフィックスなし）。

#### `resolve`

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

リビジョン式を`ObjectId`に解決します。対応形式：
- 完全な16進ハッシュ（40文字、SHA-256では64文字）。`abc1234`のような省略形のハッシュは解決されません。
- `HEAD`
- ブランチ名（`main`、`feature/login`）
- タグ名（`v1.0.0`）
- 完全な参照パス（`refs/heads/main`）
- リビジョン式（`HEAD~3`、`main^2`）

リビジョンを解決できない場合、`RuntimeException`をスローします。

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

#### `allRefs`

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

すべての参照を、参照名をキー、16進ハッシュを値とする連想配列で返します。

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

#### `updateRef`

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

参照が新しい対象を指すように更新します。

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

#### `createBranch`

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

新しいブランチを作成します。`$from`がnullの場合、現在のHEADから分岐します。

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

#### `deleteBranch`

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

名前を指定してブランチを削除します。ブランチがチェックアウト中、または存在しない場合は`RuntimeException`をスローします。`git branch -d`とは異なり、ブランチがマージ済みかどうかは確認しません。

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

#### `createLightweightTag`

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

軽量タグを作成します。`$target`がnullの場合、現在のHEADに直接タグを付けます。

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

注釈付きタグを作成します。`$target`がnullの場合、現在のHEADにタグを付けます。タグオブジェクトの`ObjectId`を返します。

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

#### `deleteTag`

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

名前を指定してタグを削除します。

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

#### `packRefs`

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

現在のルーズ参照セットから共有`packed-refs`ファイルを書き換え、対応する共有ルーズ参照を削除します。リンクされたワークツリーでは、共通のgitディレクトリに書き込みます。

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

#### `defaultBranch`

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

リポジトリのデフォルトブランチを解決します。まずリモートHEADのシンボリック参照、次にローカルHEADを確認し、その後`main`または`master`にフォールバックします。

#### `isBranchMerged`

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

あるブランチが別のブランチ（デフォルトではデフォルトブランチ）に完全にマージ済みかどうかを確認します。

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

#### `checkout`

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

ブランチを切り替えるか、HEADをデタッチします。HEAD、インデックス、作業ツリーを更新します。`$target`にはブランチ名、タグ、完全なハッシュ、またはリビジョン式を指定します。ブランチ以外を指定するとHEADがデタッチされます。

現在のツリーと対象ツリーで異なるパスに、ローカル変更（ステージ済みかどうかを問わない）または未追跡ファイルがある場合、`RuntimeException`をスローします。

その他の追跡対象ファイルにある未コミットの変更は引き継がれません。インデックスと作業ツリーは対象ツリーから再構築されるため、両側で同一のファイルに対する編集は失われます。gitなら保持される編集です（[#2](https://github.com/inline0/pitmaster/issues/2)）。先にコミットするか、スタッシュしてください。`merge()`、`cherryPick()`、`revert()`、`rebase()`およびbisectメソッドも同じ方法で作業ツリーを再構築します。

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

---

### インデックス

#### `index`

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

`.git/index`から現在のインデックス（ステージング領域）を読み込み、返します。

#### `add`

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

ファイルをステージします。各ファイルを作業ツリーから読み込み、blobオブジェクトを作成して、インデックスを更新します。各引数には作業ツリーのルートからの相対パスでファイル（またはシンボリックリンク）を指定してください。ディレクトリとglobは展開されず、ファイルが見つからない場合は`RuntimeException`をスローします。

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

#### `remove`

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

追跡対象のパスをインデックスと作業ツリーの両方から削除します。Gitと同様の動作にするため、引数リストでは`--cached`および`-r` / `--recursive`をサポートします。

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

作業ツリーとインデックスの両方でファイルを移動または名前変更します。

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

---

### コミット

#### `commit`

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

現在のインデックスからコミットを作成します。インデックスのエントリーからツリー階層を構築し、HEADがあれば親としてコミットオブジェクトを作成して、HEADを更新します。新しいコミットの`ObjectId`を返します。

`$message`を`null`にできるのは、マージ、cherry-pick、revert、またはrebaseが競合で停止している間だけです。その場合は準備済みメッセージが使われます。それ以外の場合、`null`メッセージは`RuntimeException('Commit message required')`をスローします。インデックスに未マージのパスがある場合、コミットのないリポジトリでインデックスが空の場合、およびツリーがHEADから変わっていない場合も、コミットはエラーになります。

`$author`パラメーターには完全なgit作成者文字列（`Name <email> timestamp timezone`）を指定します。nullの場合、環境変数、定数、`.git/config`から、[Getting Started](https://pitmaster.inline0.dev/ja/docs/getting-started)で説明されている順に解決されます。

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

#### `show`

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

コミット相当のリビジョンと最初の親との差分を表示します。リビジョンが注釈付きタグに解決される場合、戻り値には`['tag' => Tag]`も含まれ、`commit`はタグを剥がした後の対象コミットになります。

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

HEADをコミットにリセットします。モード：
- `'soft'`：HEADのみ移動
- `'mixed'`：HEADを移動し、インデックスをリセット（デフォルト）
- `'hard'`：HEADを移動し、インデックスと作業ツリーをリセット

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

インデックスまたは指定したソースリビジョンからパスを復元します。どちらのフラグも指定しない場合、Pitmasterはインデックスから作業ツリーを復元します（指定があれば`$source`から復元）。`staged: true`はHEAD（または`$source`）からインデックスを復元し、作業ツリーには触れません。両方を復元するには`staged: true, worktree: true`を渡します。

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

現在のブランチに新しいコミットとしてコミットを適用し、元の作成者を保持します。競合で処理が停止した場合は、競合を解決してパスをステージし、`cherryPickContinue()`または`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
```

別のコミットを取り消すコミットを作成します。revertが競合で停止した場合は、競合を解決してパスをステージし、`revertContinue()`または`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します。Pitmasterは現在、競合時にGitと同様の停止状態になるリニアrebaseに加え、続行、中止、スキップに対応しています。HEADはブランチを指している必要があります。

`rebase()`、`rebaseContinue()`、`rebaseSkip()`は`['success' => bool, 'commits' => int, 'conflicts' => list<string>]`を返します。rebaseが完了したかどうか、適用されたコミット数、停止時に競合したパスが含まれます。

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

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

---

### ステータス

#### `status`

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

作業ツリーのステータスを計算します。各`StatusEntry`オブジェクトを含む配列を返します。各オブジェクトには次の情報が含まれます。
- `path`：ファイルパス
- `index`：HEADからインデックスへの変更を示す`FileStatus`列挙値
- `worktree`：インデックスから作業ツリーへの変更を示す`FileStatus`列挙値
- `origPath`、`renameScore`：名前変更されたエントリーの場合に設定。それ以外は`null`

未追跡ファイルの両方の列には`FileStatus::Untracked`が設定されます。無視対象のファイルは報告されません。

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

#### `statusPorcelainV2`

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

gitのporcelain v2形式による機械可読なステータス出力です。

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

---

### 差分

#### `diff`

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

作業ツリーとインデックスの差分（未ステージの変更）を表示します。`DiffResult`オブジェクトの配列を返します。`$pathspec`を指定すると、完全一致する1つのファイルパスで絞り込みます。`$algorithm`は`'myers'`（デフォルト）、`'patience'`、`'histogram'`、または`'minimal'`です。それ以外の値を指定すると`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
```

インデックスとHEADの差分（ステージ済みの変更）を表示します。名前変更の検出を含め、削除されたファイルと追加されたファイルを照合して`DiffResult[]`を返します。

現在、`$pathspec`引数は無視されるため、ステージ済みのファイルがすべて返されます。また、最初のコミット前は空の配列を返します（[#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
```

`ObjectId`を使って2つのツリーを比較します。`DiffResult[]`を返します。コミットIDではなくツリーIDを渡してください。コミットIDはツリーとして読み込まれず、結果は空になります。

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

---

### ログ

#### `log`

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

コミッターのタイムスタンプが新しい順にコミット履歴をたどります。`Commit[]`を返します。`$from`がnullの場合はHEADから開始します。HEADにコミットがない場合は空の配列を返します。

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

#### `logAll`

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

すべての参照とHEADから到達可能なコミットを新しい順にたどります。`Commit[]`を返します。

#### `logOneline`

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

HEADから、すべての参照から（`$all`）、またはパスで絞り込んだ`git log --oneline`形式（`abc1234 Subject`）のログ行を返します。

#### `logPath`

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

ファイルパスで絞り込んだログです。最初の親との差分で、指定したファイルそのものを追加、変更、削除、または名前変更したコミットだけを返します。ディレクトリパスは一致しません。

最新の`5 * $limit`件のコミットだけが調べられるため、その範囲より古くにファイルに変更を加えたコミットが結果に含まれないことがあります（[#7](https://github.com/inline0/pitmaster/issues/7)）。

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

---

### マージ

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

ブランチ（または任意のリビジョン）をHEADにマージします。早送りマージは自動検出します。早送りでないマージでは、ツリー単位で競合を検出し、競合がなければマージコミットを作成します。競合で停止した場合は、Gitと同様のマージ状態をインデックスと作業ツリーに書き込みます。パスの競合を解決してステージし、`mergeContinue()`または`mergeAbort()`を呼び出します。

`$strategy` `'ours'`はHEADのツリーを保持するマージコミットを記録し、それ以外の値ではデフォルトの再帰マージを使用します。`mergeOctopus()`は2つ以上のブランチを1つのコミットにマージします。いずれかのブランチで競合が発生した場合は、マージ状態を書き込まず、コミットも作成せずに、クリーンでない結果を返します。

すでにHEADに含まれているブランチをマージすると、「Already up to date」と報告する代わりに、別のマージコミットが作成されます（[#6](https://github.com/inline0/pitmaster/issues/6)）。追跡対象ファイルの未コミット変更は、`checkout()`で説明したように上書きされます。

次の情報を含む`MergeResult`を返します。
- `clean`：競合なくマージが成功したかどうか
- `commitId`：マージコミットID（クリーンな場合）
- `conflictPaths`：競合したファイルパスの配列（クリーンでない場合）
- `mergedContents`：競合マーカー付きのファイル内容。競合パスをキーとします（クリーンでない場合）

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

2つのコミットのマージベース（最も近い共通祖先）を検索します。共通祖先がない場合はnullを返します。

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

### 二分探索

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

変更を導入したコミットを二分探索します。`bisectStart()`は不良と良好のリビジョンを記録し、最初の候補をチェックアウトして返します。`bisectGood()`と`bisectBad()`は現在のHEAD（または`$revision`）をマークし、次の候補をチェックアウトして返します。疑わしいコミットが1つだけになると、そのコミットが返され、候補として維持されます。これが最初の不良コミットです。`bisectReset()`はセッションを終了し、開始時のブランチまたはコミットに戻ります。

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

---

### ネットワーク

#### `fetch`

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

リモートからフェッチします。新しいオブジェクトをダウンロードし、リモート追跡参照を更新します。浅い履歴を深くするか制限するには`$depth`を渡します。

`fetch()`および`push*()`メソッドは、`['processes' => false]`を指定して開いたハンドルでは`RuntimeException`をスローします。

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

現在のブランチ（または指定したブランチ）をリモートにプッシュします。参照の更新をより厳密に、または広範囲に行うためのforce-with-lease、atomic、mirrorの各バリアントを利用できます。

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

---

### ワークツリー

#### `addWorktree`

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

リンクされたワークツリーを追加します。ブランチが存在しない場合は作成し、ワークツリーのメタデータを設定して、作業ツリーのファイルを展開します。チェックアウトディレクトリのベース名とは異なる、決定的なメタデータ用slugが必要な場合は`$name`を渡します。

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

リンクされたワークツリーを削除します。ロックされていても削除するには`$force = true`を使います。

#### `worktrees`

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

すべてのワークツリー（メインとリンク済み）を一覧表示します。`Worktree[]`を返します。

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

---

### 設定

#### `config`

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

リポジトリのgit設定にアクセスします。設定値の読み取りと書き込みができます。

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

---

### 内部アクセス

これらのメソッドでは、高度な用途向けに内部コンポーネントを公開します。

#### `objectDatabase`

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

生のオブジェクトデータベース（ルーズストアとパックストア）にアクセスします。

#### `refDatabase`

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

生の参照データベース（ルーズ参照とパック済み参照）にアクセスします。

---

## 低レベルクラス

これらのクラスは`Repository`でラップされず、その内部コンポーネントから構築されます。

### `Pitmaster\Graph\Blame`

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

`$headId`にある`$path`の各行について1つのエントリーを返します：`['line' => int, 'hash' => string, 'author' => string, 'content' => string]`。`author`は完全な識別文字列（`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
```

ツリー内のblobを検索します。一致した行ごとに`['path' => string, 'line' => int, 'content' => string]`を返します。オプション：`regex`（パターンをPCREパターンとして扱う）と`ignore_case`。正規表現が無効な場合は`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
```

notes参照の下に保存されたgit notesを読み書きします。`listAll()`はコミットハッシュからノート内容への対応を返します。

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