PHP API
Pitmaster\Pitmaster
Pitmaster\Pitmaster 类是静态入口点。它提供用于打开、创建和克隆仓库的工厂方法,以及检测辅助方法。
use Pitmaster\Pitmaster;
open
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。
$repo = Pitmaster::open('/home/user/project');
$repo = Pitmaster::open('/home/user/project', ['hooks' => false]);
$repo = Pitmaster::open('/home/user/project', Pitmaster::processFreeOptions());
init
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。
$repo = Pitmaster::init('/home/user/new-project');
$repo = Pitmaster::init('/home/user/sha256-project', 'sha256');
clone
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 之前不存在,则会移除部分创建的目录。
$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
public static function processFreeOptions(): array
返回 ['processes' => false, 'hooks' => false],供不得启动宿主进程的运行时使用。将其传给 open() 或 init()。
isRepository
public static function isRepository(string $path): bool
检查路径是否为 git 仓库。对于常规仓库(.git/ 目录)、链接 worktree(.git 文件)和裸仓库(存在 HEAD 文件),均返回 true。
if (Pitmaster::isRepository('/path/to/check')) {
// It's a git repo
}
isWorktree
public static function isWorktree(string $path): bool
检查路径是否为链接 worktree(而非主仓库)。仅当 .git 是包含 gitdir: 引用的文件时才返回 true。
if (Pitmaster::isWorktree('/path/to/worktree')) {
// It's a linked worktree
}
commonGitDir
public static function commonGitDir(string $path): ?string
从任意检出路径解析公共 git 目录。对于常规仓库,这是 .git 目录。对于链接 worktree,这是共享的 git 目录,其中包含对象、配置和 packed-refs。如果路径不是仓库,则返回 null。
$common = Pitmaster::commonGitDir('/path/to/worktree');
// '/path/to/main-repo/.git'
Pitmaster\Repository
Pitmaster\Repository 类是所有 git 操作的主要句柄。通过 Pitmaster::open()、Pitmaster::init() 或 Pitmaster::clone() 获取句柄。
use Pitmaster\Repository;
路径访问器
gitDir
public function gitDir(): string
返回每个 worktree 独有的 git 目录。对于常规仓库,这是 .git/。对于链接 worktree,这是 .git/worktrees/<name>/。
commonGitDir
public function commonGitDir(): string
返回共享的 git 目录。对于常规仓库,它与 gitDir() 相同。对于链接 worktree,这是主仓库的 .git/ 目录(对象、配置和 packed-refs 所在的位置)。
workDir
public function workDir(): string
返回工作树根路径。
isLinkedWorktree
public function isLinkedWorktree(): bool
如果此仓库句柄指向链接 worktree,则返回 true。
isBare, hooksEnabled, processesEnabled
public function isBare(): bool
public function hooksEnabled(): bool
public function processesEnabled(): bool
报告仓库是否没有工作树,以及此句柄启用了哪些 hooks / processes 选项。禁用进程时,hooksEnabled() 始终为 false。
对象
readObject
public function readObject(string $hash): GitObject
通过完整十六进制哈希读取任意对象(40 个字符,SHA-256 仓库中为 64 个字符)。不接受缩写哈希。返回 Blob、Tree、Commit 或 Tag 实例。先搜索松散对象,再搜索 pack 文件。
如果找不到哈希,则抛出 ObjectNotFoundException。
$object = $repo->readObject('a1b2c3d4e5f6...');
writeObject
public function writeObject(GitObject $object): ObjectId
将对象写入松散对象存储。返回计算出的 ObjectId。
use Pitmaster\Object\Blob;
$blob = Blob::fromContent('Hello, world!');
$id = $repo->writeObject($blob);
catFile
public function catFile(string $hash): string
返回对象的原始内容,等同于 git cat-file -p。
echo $repo->catFile('a1b2c3d4...');
objectExists
public function objectExists(string $hash): bool
检查仓库中是否存在某个对象(松散对象或已打包对象)。
listObjects
public function listObjects(): array
列出仓库中的所有对象哈希。返回十六进制字符串数组。
引用
head
public function head(): Commit
返回当前 HEAD 提交。会跟随符号引用(HEAD -> refs/heads/main -> 提交哈希)。
如果 HEAD 未指向有效提交(例如 e.g,空仓库),则抛出 RuntimeException。
branch
public function branch(?string $name = null): ?string
不带参数时,返回当前分支名称(例如 e.g、'main');如果 HEAD 已分离,则返回 null。
传入分支名称时,会将其解析为提交哈希(十六进制字符串);如果分支不存在,则返回 null。
$current = $repo->branch(); // 'main'
$hash = $repo->branch('feature'); // 'abc123...' or null
branches
public function branches(): array
返回按排序排列的所有分支名称数组(不含 refs/heads/ 前缀)。
$branches = $repo->branches();
// ['bugfix/typo', 'feature/login', 'main']
tags
public function tags(): array
返回按排序排列的所有标签名称数组(不含 refs/tags/ 前缀)。
resolve
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。
$id = $repo->resolve('HEAD~3');
$id = $repo->resolve('v1.0.0');
$id = $repo->resolve('feature/login');
allRefs
public function allRefs(): array
以关联数组形式返回所有引用,键为引用名称,值为十六进制哈希。
$refs = $repo->allRefs();
// ['refs/heads/main' => 'abc123...', 'refs/tags/v1.0.0' => 'def456...']
updateRef
public function updateRef(string $name, ObjectId $target): void
更新引用,使其指向新的目标。
$repo->updateRef('refs/heads/main', $commitId);
createBranch
public function createBranch(string $name, ?ObjectId $from = null): void
创建新分支。如果 $from 为 null,则从当前 HEAD 创建分支。
$repo->createBranch('feature/new-thing');
$repo->createBranch('hotfix', $repo->resolve('v1.0.0'));
deleteBranch
public function deleteBranch(string $name): void
按名称删除分支。如果该分支已检出或不存在,则抛出 RuntimeException。与 git branch -d 不同,此操作不会检查该分支是否已合并。
$repo->deleteBranch('feature/old');
createLightweightTag
public function createLightweightTag(string $name, ?ObjectId $target = null): void
创建轻量标签。如果 $target 为 null,则直接为当前 HEAD 创建标签。
$repo->createLightweightTag('v2.0.0');
$repo->createLightweightTag('hotfix-start', $repo->resolve('main~2'));
createTag
public function createTag(string $name, string $message, ?ObjectId $target = null, ?string $tagger = null): ObjectId
创建附注标签。如果 $target 为 null,则为当前 HEAD 创建标签。返回标签对象的 ObjectId。
$tagId = $repo->createTag('v2.0.0', 'Release version 2.0.0');
deleteTag
public function deleteTag(string $name): void
按名称删除标签。
$repo->deleteTag('v1.0.0');
packRefs
public function packRefs(): void
根据当前松散引用集合重写共享的 packed-refs 文件,并清理相应的共享松散引用。对于链接 worktree,此操作会写入公共 git 目录。
$repo->packRefs();
defaultBranch
public function defaultBranch(): string
解析仓库的默认分支。先检查远程 HEAD 符号引用,再检查本地 HEAD,最后回退到 main 或 master。
isBranchMerged
public function isBranchMerged(string $branch, ?string $target = null): bool
检查一个分支是否已完全合并到另一个分支(默认为默认分支)。
if ($repo->isBranchMerged('feature/done')) {
$repo->deleteBranch('feature/done');
}
checkout
public function checkout(string $target): void
切换分支或分离 HEAD。更新 HEAD、索引和工作树。$target 可以是分支名称、标签、完整哈希或修订表达式;如果目标不是分支,HEAD 就会分离。
如果某个路径在当前树和目标树之间不同,且该路径上存在本地更改(已暂存或未暂存)或未跟踪文件,则抛出 RuntimeException。
其他已跟踪文件中的未提交更改不会保留:索引和工作树会根据目标树重建,因此两边相同的文件中所做的编辑会丢失,而 git 会保留这些编辑(#2)。请先提交或暂存这些更改。merge()、cherryPick()、revert()、rebase() 和 bisect 方法也会以同样方式重建工作树。
$repo->checkout('feature/login');
$repo->checkout($repo->resolve('v1.0.0')->hex); // Detached HEAD
索引
index
public function index(): Index
从 .git/index 读取并返回当前索引(暂存区)。
add
public function add(string ...$paths): void
暂存文件。从工作树读取每个文件、创建 blob 对象并更新索引。每个参数都必须是相对于工作树根目录的文件(或符号链接)路径;不会展开目录和 glob,缺少文件时会抛出 RuntimeException。
$repo->add('src/Feature.php', 'tests/FeatureTest.php');
remove
public function remove(string ...$paths): void
public function removeCached(string ...$paths): void
从索引和工作树中移除已跟踪路径。参数列表支持 --cached 以及 -r / --recursive,以实现类似 Git 的行为。
$repo->remove('old-file.txt');
$repo->remove('-r', 'src');
$repo->remove('--cached', 'old-file.txt');
$repo->removeCached('old-file.txt');
mv
public function mv(string $source, string $destination): void
在工作树和索引中移动或重命名文件。
$repo->mv('old-name.php', 'new-name.php');
提交
commit
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,则按 入门 中所述的顺序,从环境变量、常量和 .git/config 中解析。
$id = $repo->commit('Fix null pointer bug');
show
public function show(string $revision): array
显示一个提交及其与第一个父提交之间的差异。当修订解析为附注标签时,返回值还会包含 ['tag' => Tag],且 commit 是剥离标签后的目标提交。
$result = $repo->show('HEAD');
echo $result['commit']->message;
foreach ($result['diff'] as $diff) {
echo $diff->format();
}
reset
public function reset(string $revision, string $mode = 'mixed'): void
将 HEAD 重置为某个提交。模式:
'soft':仅移动 HEAD'mixed':移动 HEAD 并重置索引(默认)'hard':移动 HEAD、重置索引并重置工作树
$repo->reset('HEAD~1', 'soft');
$repo->reset('main', 'hard');
restore
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 可同时还原两者。
$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
public function cherryPick(string $revision): ObjectId
public function cherryPickContinue(): ObjectId
public function cherryPickAbort(): void
将一个提交应用为当前分支上的新提交,并保留原作者。如果操作因冲突暂停,请解决冲突、暂存相关路径,然后调用 cherryPickContinue() 或 cherryPickAbort()。
$newId = $repo->cherryPick('feature~2');
// After resolving conflicts:
$repo->add('src/File.php');
$repo->cherryPickContinue();
revert
public function revert(string $revision): ObjectId
public function revertContinue(): ObjectId
public function revertAbort(): void
创建一个撤销另一提交的提交。如果 revert 因冲突暂停,请解决冲突、暂存相关路径,然后调用 revertContinue() 或 revertAbort()。
$revertId = $repo->revert('HEAD~1');
// After resolving conflicts:
$repo->add('src/File.php');
$repo->revertContinue();
rebase
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 是否完成、已应用的提交数,以及暂停时发生冲突的路径。
$result = $repo->rebase('main');
if (!$result['success']) {
// Resolve conflicts, then continue or skip/abort.
$repo->add('src/File.php');
$repo->rebaseContinue();
}
状态
status
public function status(): array
计算工作树状态。返回 StatusEntry 对象数组,每个对象包含:
path:文件路径index:用于 HEAD 到索引更改的FileStatus枚举worktree:用于索引到工作树更改的FileStatus枚举origPath、renameScore:重命名条目时设置,否则为null
未跟踪文件在两列中均为 FileStatus::Untracked。不会报告已忽略的文件。
foreach ($repo->status() as $entry) {
echo $entry->shortFormat() . "\n"; // "M src/File.php", "?? notes.txt"
}
statusPorcelainV2
public function statusPorcelainV2(): string
以 git 的 porcelain v2 格式输出机器可读的状态信息。
echo $repo->statusPorcelainV2();
// 1 M. N... 000000 000000 000000 0000...0000 0000...0000 modified-file.txt
// ? untracked-file.txt
差异
diff
public function diff(?string $pathspec = null, string $algorithm = 'myers'): array
比较工作树与索引(未暂存的更改)。返回 DiffResult 对象数组。$pathspec 用于按一个精确文件路径筛选。$algorithm 为 'myers'(默认值)、'patience'、'histogram' 或 'minimal';其他任何值都会抛出 InvalidArgumentException。
$diffs = $repo->diff();
foreach ($diffs as $diff) {
echo $diff->format(); // Unified diff output
}
diffStaged
public function diffStaged(?string $pathspec = null, string $algorithm = 'myers'): array
比较索引与 HEAD(已暂存的更改)。返回 DiffResult[],并检测已删除和新增文件之间的重命名。
目前会忽略 $pathspec 参数,因此会返回所有已暂存文件;首次提交前,该方法会返回空数组(#5)。
$staged = $repo->diffStaged();
diffTree
public function diffTree(ObjectId $a, ObjectId $b, string $algorithm = 'myers'): array
按 ObjectId 比较两棵树。返回 DiffResult[]。传入树 ID,而不是提交 ID:提交 ID 不会被读取为树,因此会产生空结果。
$old = $repo->readObject($repo->resolve('HEAD~1')->hex);
$new = $repo->head();
$diffs = $repo->diffTree($old->tree, $new->tree);
日志
log
public function log(int $limit = 50, ?ObjectId $from = null): array
遍历提交历史,按提交者时间戳从新到旧排列。返回 Commit[]。如果 $from 为 null,则从 HEAD 开始。HEAD 尚无提交时返回空数组。
$commits = $repo->log(10);
logAll
public function logAll(int $limit = 50): array
遍历所有引用和 HEAD 可到达的提交,按时间从新到旧排列。返回 Commit[]。
logOneline
public function logOneline(int $limit = 50, bool $all = false, ?string $path = null): array
以 git log --oneline 形式返回日志行(abc1234 Subject),可从 HEAD、所有引用($all)获取,也可按路径筛选。
logPath
public function logPath(string $path, int $limit = 50): array
按文件路径筛选日志。仅返回其与第一个父提交之间的差异会添加、修改、删除或重命名该精确文件的提交。目录路径不匹配。
仅检查最新的 5 * $limit 个提交,因此较早的、涉及该文件的提交可能不会出现在结果中(#7)。
$commits = $repo->logPath('src/Repository.php', 20);
合并
merge
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)。已跟踪文件中的未提交更改会被覆盖,详见 checkout()。
返回一个 MergeResult,其中包含:
clean:合并是否成功且没有冲突commitId:合并提交 ID(如果没有冲突)conflictPaths:冲突文件路径数组(如果存在冲突)mergedContents:冲突路径到带冲突标记的文件内容的映射(如果存在冲突)
$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
public function mergeBase(ObjectId $a, ObjectId $b): ?ObjectId
查找两个提交的合并基点(最近公共祖先)。如果不存在公共祖先,则返回 null。
$base = $repo->mergeBase(
$repo->resolve('main'),
$repo->resolve('feature/login'),
);
二分查找
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()结束会话,并返回到开始时所在的分支或提交。
$candidate = $repo->bisectStart('HEAD', 'v1.0.0');
// test $candidate ...
$candidate = $repo->bisectBad();
$candidate = $repo->bisectGood();
$repo->bisectReset();
网络
fetch
public function fetch(string $remote = 'origin', ?int $depth = null): void
从远程仓库获取数据。下载新对象并更新远程跟踪引用。传入 $depth 可加深或限制浅历史记录。
在使用 ['processes' => false] 打开的句柄上,fetch() 和 push*() 方法会抛出 RuntimeException。
$repo->fetch();
$repo->fetch('upstream');
push
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 变体,以更严格或更广泛地更新引用。
$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
public function addWorktree(string $path, string $branch, ?ObjectId $from = null, ?string $name = null): Worktree
添加关联工作树。如果分支不存在,则创建该分支;设置工作树元数据,并生成工作树文件。需要一个不同于检出目录基本名称的确定性元数据 slug 时,请传入 $name。
$wt = $repo->addWorktree('/tmp/review', 'feature/review');
$wt = $repo->addWorktree('/tmp/checkouts/review', 'feature/review', name: 'review-wt');
removeWorktree
public function removeWorktree(string $pathOrName, bool $force = false): void
移除关联工作树。即使工作树已锁定,也可使用 $force = true 强制移除。
worktrees
public function worktrees(): array
列出所有工作树(主工作树和关联工作树)。返回 Worktree[]。
foreach ($repo->worktrees() as $wt) {
echo "{$wt->path} [{$wt->branch}]\n";
}
配置
config
public function config(): GitConfig
访问仓库的 git 配置。读取和写入配置值。
$config = $repo->config();
$name = $config->get('user.name');
$config->set('user.email', 'new@example.com');
内部访问
这些方法公开内部组件,供高级用例使用。
objectDatabase
public function objectDatabase(): ObjectDatabase
访问原始对象数据库(松散对象存储和包存储)。
refDatabase
public function refDatabase(): RefDatabase
访问原始引用数据库(松散引用和打包引用)。
较低层级的类
这些类未由 Repository 封装,而是由其内部组件构造。
Pitmaster\Graph\Blame
public function blame(ObjectId $headId, string $path): array
为 $headId 处 $path 的每一行返回一个条目:['line' => int, 'hash' => string, 'author' => string, 'content' => string]。author 是完整身份字符串(Name <email> timestamp timezone)。
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
public function grep(ObjectId $treeId, string $pattern, string $prefix = '', array $options = []): array
搜索树中的 blob。每个匹配行返回一个 ['path' => string, 'line' => int, 'content' => string]。选项:regex(将模式视为 PCRE 模式)和 ignore_case。正则表达式无效时会抛出 InvalidArgumentException。
use Pitmaster\Graph\Grep;
$grep = new Grep($repo->objectDatabase());
$matches = $grep->grep($repo->head()->tree, 'TODO', '', ['ignore_case' => true]);
Pitmaster\Ref\Notes
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() 返回从提交哈希到注释内容的映射。
use Pitmaster\Ref\Notes;
$notes = new Notes($repo->objectDatabase(), $repo->refDatabase());
$notes->set($repo->resolve('HEAD'), "Reviewed\n");
echo $notes->get($repo->resolve('HEAD'));