
# 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>` 的文件）和裸仓库（其中 `$path` 本身就是 `.git` 目录）。

`$options` 接受两个布尔值，默认都为 `true`：

- `hooks`：运行 `.git/hooks` 脚本。传入 `false` 可让此句柄跳过这些脚本。
- `processes`：允许任何会启动进程或打开网络传输的操作。传入 `false` 可禁用钩子、fsmonitor 钩子、clone、fetch、push 和 SSH 进程传输。本地读写仍可正常使用。

如果路径不是 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
```

克隆远程仓库。初始化新仓库、发现远程引用、获取 pack 文件、设置远程跟踪分支，并生成工作树。SSH URL（`ssh://...` 或 `user@host:path`）使用 SSH 传输；其他 URL 使用 smart HTTP，如果 smart 交换失败则回退到 dumb 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/` 目录）、链接 worktree（`.git` 文件）和裸仓库（存在 `HEAD` 文件），均返回 `true`。

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

### `isWorktree`

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

检查路径是否为链接 worktree（而非主仓库）。仅当 `.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` 目录。对于链接 worktree，这是共享的 git 目录，其中包含对象、配置和 packed-refs。如果路径不是仓库，则返回 `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
```

返回每个 worktree 独有的 git 目录。对于常规仓库，这是 `.git/`。对于链接 worktree，这是 `.git/worktrees/<name>/`。

#### `commonGitDir`

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

返回共享的 git 目录。对于常规仓库，它与 `gitDir()` 相同。对于链接 worktree，这是主仓库的 `.git/` 目录（对象、配置和 packed-refs 所在的位置）。

#### `workDir`

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

返回工作树根路径。

#### `isLinkedWorktree`

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

如果此仓库句柄指向链接 worktree，则返回 `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
```

通过完整十六进制哈希读取任意对象（40 个字符，SHA-256 仓库中为 64 个字符）。不接受缩写哈希。返回 `Blob`、`Tree`、`Commit` 或 `Tag` 实例。先搜索松散对象，再搜索 pack 文件。

如果找不到哈希，则抛出 `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
```

列出仓库中的所有对象哈希。返回十六进制字符串数组。

---

### 引用

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

传入分支名称时，会将其解析为提交哈希（十六进制字符串）；如果分支不存在，则返回 `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`。支持：
- 完整十六进制哈希（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
```

以关联数组形式返回所有引用，键为引用名称，值为十六进制哈希。

```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` 文件，并清理相应的共享松散引用。对于链接 worktree，此操作会写入公共 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
```

从索引和工作树中移除已跟踪路径。参数列表支持 `--cached` 以及 `-r` / `--recursive`，以实现类似 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
```

在工作树和索引中移动或重命名文件。

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

---

### 提交

#### `commit`

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

根据当前索引创建提交。根据索引条目构建树层级，创建以 HEAD 为父项（如果存在）的提交对象，并更新 HEAD。返回新提交的 `ObjectId`。

只有在合并、cherry-pick、revert 或 rebase 因冲突而暂停时，`$message` 才可以为 `null`，此时会使用准备好的消息。否则，`null` 消息会抛出 `RuntimeException('Commit message required')`。如果索引中有未合并路径、无提交的仓库中索引为空，或树与 HEAD 相同，提交也会抛出异常。

`$author` 参数是完整的 git 作者字符串（`Name <email> timestamp timezone`）。如果为 null，则按 [入门](https://pitmaster.inline0.dev/zh/docs/getting-started) 中所述的顺序，从环境变量、常量和 `.git/config` 中解析。

```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 目前支持线性 rebase；冲突时会进入形如 Git 的暂停状态，并支持继续、终止和跳过。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` 用于按一个精确文件路径筛选。`$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` 比较两棵树。返回 `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
```

以 `git log --oneline` 形式返回日志行（`abc1234 Subject`），可从 HEAD、所有引用（`$all`）获取，也可按路径筛选。

#### `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()` 会将两个或更多分支合并为一个提交。如果任一分支发生冲突，则返回未清洁的结果，不会写入合并状态或创建提交。

合并一个已包含在 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
```

查找两个提交的合并基点（最近公共祖先）。如果不存在公共祖先，则返回 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`），然后检出并返回下一个候选版本。只剩一个可疑提交时，会返回该提交并将其保留为候选项：这就是第一个有问题的提交。`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` 可加深或限制浅历史记录。

在使用 `['processes' => false]` 打开的句柄上，`fetch()` 和 `push*()` 方法会抛出 `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` 的每一行返回一个条目：`['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 注释。`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'));
```
