跳转到正文

写入仓库

Pitmaster 使用与标准 git 二进制程序相同的二进制格式写入 git 仓库。对象会写入松散对象存储(使用 zlib 压缩),索引会以 v2 格式写入(条目需要扩展标志时使用 v3;现有索引保留其版本),引用则写为松散文件。写入的数据可通过 git fsck --strict 验证。

初始化仓库

PHP
use Pitmaster\Pitmaster;

$repo = Pitmaster::init('/path/to/new-project');

这会创建:

  • .git/objects/(空对象存储)
  • .git/refs/heads/ 和 .git/refs/tags/(空引用目录)
  • .git/HEAD,其中包含 ref: refs/heads/main
  • .git/hooks/、.git/info/exclude 和 .git/description
  • .git/config,包含默认设置

将 'sha256' 作为第二个参数传入,以创建 SHA-256 仓库:Pitmaster::init($path, 'sha256')。

默认配置未设置 core.logallrefupdates,因此 Pitmaster 创建的仓库在你将该键设为 true 之前不会记录 reflog 条目(#8)。由 git init 创建的仓库已设置该项。

暂存文件

添加

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

每个参数都是相对于工作树根目录的文件路径。目录和 glob 不会展开,文件不存在时会抛出 RuntimeException。对于每个文件,Pitmaster 会:

  1. 从工作树读取文件内容
  2. 创建 Blob 对象并计算其哈希值
  3. 将 blob 写入 .git/objects/(使用 zlib 压缩)
  4. 使用新的哈希值和文件元数据更新该路径的索引条目

移除

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

从索引和工作树中移除已跟踪路径。使用 --cached 仅从索引中移除,或使用 -r / --recursive 移除已跟踪目录。

移动

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

在工作树中重命名文件,移除旧索引条目,并添加新条目。

创建提交

PHP
$commitId = $repo->commit('Fix null pointer bug');
echo $commitId->hex;

提交过程:

  1. 从索引构建树:将索引条目的扁平列表组织为树层级结构(目录变为树对象,文件变为 blob 引用)。每棵树都会写入对象存储。
  2. 确定父项:当前 HEAD 提交将成为父项。根提交(仓库中的第一次提交)没有父项。
  3. 确定作者:依次从环境变量 PITMASTER_AUTHOR_* 和 GIT_AUTHOR_*、常量 PITMASTER_AUTHOR_NAME/PITMASTER_AUTHOR_EMAIL、.git/config(user.name/user.email),最后从 Pitmaster <pitmaster@localhost> 获取。提交者也按相同方式确定。
  4. 写入提交对象:提交内容(tree、parent、author、committer、消息)会被序列化、计算哈希值并写入。
  5. 更新 HEAD:HEAD 指向的分支引用会更新为新的提交哈希值。

除非句柄是使用 ['hooks' => false] 打开的,否则这些步骤前后都会运行钩子(pre-commit、prepare-commit-msg、commit-msg、post-commit)。缺少消息、树与 HEAD 相比没有变化,或索引中存在未合并路径时,commit() 会抛出异常。

自定义作者

PHP
$author = 'Jane Smith <jane@example.com> 1700000000 +0000';
$commitId = $repo->commit('Add feature', $author);

使用 git fsck 验证

Pitmaster 的集成测试会将写入的对象与 git fsck --strict 进行核验,你也可以手动执行相同验证:

命令行
cd /path/to/repo
git fsck --strict

如果 Pitmaster 写入了无效对象,git fsck 会立即报告错误。

管理分支

创建分支

PHP
// Branch from HEAD
$repo->createBranch('feature/new-thing');

// Branch from a specific commit
$repo->createBranch('hotfix', $repo->resolve('v1.0.0'));

在 .git/refs/heads/<name> 创建一个包含目标提交哈希值的引用文件。

删除分支

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

切换分支

PHP
$repo->checkout('feature/login');

将 HEAD 更新为指向该分支的符号引用,将索引重置为与分支顶端的树一致,并更新工作树。

如果两个树之间存在差异的路径包含本地更改,检出操作会因 RuntimeException 而停止。其他所有已跟踪文件中的未提交更改都会被分支内容替换;git 会保留这些更改(#2)。切换之前请先提交或暂存这些更改。

检查是否已合并

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

管理标签

轻量标签

PHP
$repo->createLightweightTag('v1.0.0');                          // At HEAD
$repo->createLightweightTag('v0.9.0', $repo->resolve('HEAD~5'));

轻量标签只是直接指向某个提交的引用。

带注释的标签

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

带注释的标签会创建一个标签对象(包含标记者、消息和目标),以及一个指向它的引用。标记者是当前提交者身份,其解析方式与提交作者相同。

自定义标记者

PHP
$tagId = $repo->createTag(
    'v2.0.0',
    'Release version 2.0.0',
    $repo->resolve('HEAD'),
    'Jane Smith <jane@example.com> 1700000000 +0000',
);

直接更新引用

PHP
$repo->updateRef('refs/heads/main', $commitId);
$repo->updateRef('refs/tags/latest', $commitId);

直接写入对象

对于高级用例,你可以不通过高级别的 API,直接写入对象。

写入 blob

PHP
use Pitmaster\Object\Blob;

$blob = Blob::fromContent('Hello, world!');
$id = $repo->writeObject($blob);
echo $id->hex;  // SHA-1 of "blob 13\0Hello, world!"

在 SHA-256 仓库中,传入算法:Blob::fromContent('Hello, world!', 'sha256')。

写入树

PHP
use Pitmaster\Object\Tree;
use Pitmaster\Object\TreeEntry;
use Pitmaster\Object\ObjectId;

$entries = [
    new TreeEntry('100644', 'README.md', $blobId),
    new TreeEntry('40000', 'src', $subtreeId),
];

$tree = Tree::fromEntries($entries);
$id = $repo->writeObject($tree);

Tree::fromEntries() 会按给定顺序写入条目,并按给定方式写入模式字符串。请按 git 的树顺序传入(按名称排序,比较目录时视为其名称以 / 结尾),并将目录模式写作 40000:git 存储树模式时会去掉前导零,而 git fsck --strict 会拒绝 040000。

写入提交

PHP
use Pitmaster\Object\Commit;
use Pitmaster\Object\ObjectId;
use Pitmaster\Object\ObjectType;

$content = Commit::buildContent(
    tree: $treeId,
    parents: [$parentId],
    author: 'Name <email> 1700000000 +0000',
    committer: 'Name <email> 1700000000 +0000',
    message: "Commit message\n",
);

$commitId = ObjectId::compute(ObjectType::Commit, $content);
$commit = Commit::parse($content, $commitId);
$repo->writeObject($commit);

重置

PHP
// Soft: move HEAD only
$repo->reset('HEAD~1', 'soft');

// Mixed: move HEAD + reset index (default)
$repo->reset('HEAD~1');

// Hard: move HEAD + reset index + reset working tree
$repo->reset('HEAD~3', 'hard');

还原

PHP
// Restore a file from the index into the worktree
$repo->restore('src/File.php');

// Restore the index version from HEAD (the worktree is left alone)
$repo->restore('src/File.php', staged: true);

// Restore from a specific commit
$repo->restore('src/File.php', 'HEAD~1');

// Restore both index and worktree from a specific commit
$repo->restore('src/File.php', 'HEAD~1', staged: true, worktree: true);

Cherry-pick 和 revert

PHP
// Apply a commit on the current branch
$newId = $repo->cherryPick('feature~1');

// Undo a commit
$revertId = $repo->revert('HEAD~2');

两者都接受 resolve() 支持的任何修订版本。完整哈希值有效;缩写哈希值无效。