Pitmaster

PHP API

Pitmaster\Pitmaster

The Pitmaster\Pitmaster class is the static entry point. It provides factory methods for opening, creating, and cloning repositories, plus detection helpers.

use Pitmaster\Pitmaster;

open

public static function open(string $path): Repository

Open an existing repository at $path. The path should be the working tree root (containing .git/). Also supports linked worktrees (where .git is a file containing gitdir: <path>), and bare repositories (where $path is the .git directory itself).

Throws InvalidArgumentException if the path is not a git repository.

$repo = Pitmaster::open('/home/user/project');

init

public static function init(string $path): Repository

Initialize a new repository at $path. Creates the .git directory structure with objects/, refs/heads/, refs/tags/, HEAD (pointing to refs/heads/main), and a default config.

Throws RuntimeException if a repository already exists at the path.

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

clone

public static function clone(string $url, string $path): Repository

Clone a remote repository via smart HTTP. Initializes a new repo, discovers remote refs, fetches the pack file, sets up remote tracking branches, and materializes the working tree.

$repo = Pitmaster::clone('https://github.com/user/repo.git', '/tmp/repo');

isRepository

public static function isRepository(string $path): bool

Check if a path is a git repository. Returns true for regular repos (.git/ directory), linked worktrees (.git file), and bare repos (HEAD file present).

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

isWorktree

public static function isWorktree(string $path): bool

Check if a path is a linked worktree (not the main repo). Returns true only if .git is a file containing a gitdir: reference.

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

commonGitDir

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

Resolve the common git directory from any checkout path. For regular repos, this is the .git directory. For linked worktrees, this is the shared git directory that contains objects, config, and packed-refs. Returns null if the path is not a repository.

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

Pitmaster\Repository

The Pitmaster\Repository class is the main handle for all git operations. Obtain one via Pitmaster::open(), Pitmaster::init(), or Pitmaster::clone().

use Pitmaster\Repository;

Path accessors

gitDir

public function gitDir(): string

Returns the per-worktree git directory. For regular repos this is .git/. For linked worktrees, this is .git/worktrees/<name>/.

commonGitDir

public function commonGitDir(): string

Returns the shared git directory. Same as gitDir() for regular repos. For linked worktrees, this is the main repo's .git/ directory (where objects, config, and packed-refs live).

workDir

public function workDir(): string

Returns the working tree root path.

isLinkedWorktree

public function isLinkedWorktree(): bool

Returns true if this repository handle is a linked worktree.


Objects

readObject

public function readObject(string $hash): GitObject

Read any object by its full 40-character hex hash. Returns a Blob, Tree, Commit, or Tag instance. Searches loose objects first, then pack files.

Throws ObjectNotFoundException if the hash is not found.

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

writeObject

public function writeObject(GitObject $object): ObjectId

Write an object to the loose object store. Returns the computed ObjectId.

use Pitmaster\Object\Blob;

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

catFile

public function catFile(string $hash): string

Return the raw content of an object, equivalent to git cat-file -p.

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

objectExists

public function objectExists(string $hash): bool

Check if an object exists in the repository (loose or packed).

listObjects

public function listObjects(): array

List all object hashes in the repository. Returns an array of hex strings.


Refs

public function head(): Commit

Returns the current HEAD commit. Follows symbolic refs (HEAD -> refs/heads/main -> commit hash).

Throws RuntimeException if HEAD does not point to a valid commit (e.g., empty repository).

branch

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

Without arguments, returns the current branch name (e.g., 'main'), or null if HEAD is detached.

With a branch name, resolves it to its commit hash (hex string) or returns null if the branch does not exist.

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

branches

public function branches(): array

Returns a sorted array of all branch names (without the refs/heads/ prefix).

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

tags

public function tags(): array

Returns a sorted array of all tag names (without the refs/tags/ prefix).

resolve

public function resolve(string $revision): ObjectId

Resolve a revision expression to an ObjectId. Supports:

  • Full 40-character hex hashes
  • HEAD
  • Branch names (main, feature/login)
  • Tag names (v1.0.0)
  • Full ref paths (refs/heads/main)
  • Revision expressions (HEAD~3, main^2)

Throws RuntimeException if the revision cannot be resolved.

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

allRefs

public function allRefs(): array

Returns all refs as an associative array of ref name to hex hash.

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

updateRef

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

Update a ref to point to a new target.

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

createBranch

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

Create a new branch. If $from is null, branches from the current HEAD.

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

deleteBranch

public function deleteBranch(string $name): void

Delete a branch by name.

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

createLightweightTag

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

Create a lightweight tag. If $target is null, tags the current HEAD directly.

$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

Create an annotated tag. If $target is null, tags the current HEAD. Returns the tag object's ObjectId.

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

deleteTag

public function deleteTag(string $name): void

Delete a tag by name.

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

packRefs

public function packRefs(): void

Rewrite the shared packed-refs file from the current loose ref set and prune the corresponding shared loose refs. For linked worktrees, this writes to the common git directory.

$repo->packRefs();

defaultBranch

public function defaultBranch(): string

Resolve the repository's default branch. Checks the remote HEAD symref first, then the local HEAD, then falls back to main or master.

isBranchMerged

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

Check if a branch is fully merged into another branch (defaults to the default branch).

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

checkout

public function checkout(string $target): void

Switch branches or detach HEAD. Updates HEAD, the index, and the working tree.

$repo->checkout('feature/login');
$repo->checkout('abc123def456...');  // Detached HEAD

Index

index

public function index(): Index

Read and return the current index (staging area) from .git/index.

add

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

Stage files. Reads each file from the working tree, creates a blob object, and updates the index.

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

remove

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

Remove tracked paths from both the index and the working tree. Supports --cached and -r / --recursive in the argument list for Git-shaped behavior.

$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

Move/rename a file in both the working tree and the index.

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

Commits

commit

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

Create a commit from the current index. Builds a tree hierarchy from the index entries, creates the commit object with the HEAD as parent (if any), and updates HEAD. Returns the new commit's ObjectId.

The $author parameter is a full git author string (Name <email> timestamp timezone). If null, it is derived from .git/config or the PITMASTER_AUTHOR_NAME/PITMASTER_AUTHOR_EMAIL constants.

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

show

public function show(string $revision): array

Show a commit-ish and its diff against the first parent. When the revision resolves to an annotated tag, the return value also includes ['tag' => Tag] and commit is the peeled target 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

Reset HEAD to a commit. Modes:

  • 'soft': move HEAD only
  • 'mixed': move HEAD + reset index (default)
  • 'hard': move HEAD + reset index + reset working tree
$repo->reset('HEAD~1', 'soft');
$repo->reset('main', 'hard');

restore

public function restore(string $path, ?string $source = null): void

Restore a path from the index or a specific source tree. By default Pitmaster restores the worktree from the index. Pass staged: true to restore the index, and worktree: true to restore both.

$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

Apply a commit as a new commit on the current branch, preserving the original author. If conflicts stop the operation, resolve them, stage the paths, then call cherryPickContinue() or cherryPickAbort().

$newId = $repo->cherryPick('abc123');

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

Create a commit that undoes another commit. If conflicts stop the revert, resolve them, stage the paths, then call revertContinue() or revertAbort().

$revertId = $repo->revert('abc123');

// 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 the current branch onto another revision. Pitmaster currently covers linear rebases with Git-shaped stop state for conflicts, plus --continue, --abort, and --skip.

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

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

Status

status

public function status(): array

Compute working tree status. Returns an array of StatusEntry objects, each containing:

  • path: the file path
  • index: FileStatus enum for HEAD-to-index changes
  • worktree: FileStatus enum for index-to-worktree changes
foreach ($repo->status() as $entry) {
    echo "{$entry->index->value}{$entry->worktree->value} {$entry->path}\n";
}

statusPorcelainV2

public function statusPorcelainV2(): string

Machine-readable status output in git's porcelain v2 format.

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

Diff

diff

public function diff(?string $pathspec = null): array

Diff working tree against the index (unstaged changes). Returns an array of DiffResult objects. Optionally filter by a single path.

$diffs = $repo->diff();

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

diffStaged

public function diffStaged(?string $pathspec = null): array

Diff index against HEAD (staged changes). Returns DiffResult[].

$staged = $repo->diffStaged();

diffTree

public function diffTree(ObjectId $a, ObjectId $b): array

Diff two trees by their ObjectId. Returns DiffResult[].

$commit1 = $repo->resolve('HEAD~1');
$commit2 = $repo->resolve('HEAD');
$diffs = $repo->diffTree($commit1, $commit2);

Log

log

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

Walk commit history in topological order. Returns Commit[]. If $from is null, starts from HEAD.

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

logPath

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

Log filtered by a file path. Returns only commits that touch the given file.

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

Merge

merge

public function merge(string $branch): MergeResult
public function mergeContinue(): ObjectId
public function mergeAbort(): void

Merge a branch into HEAD. Detects fast-forward merges automatically. For non-fast-forward merges, performs tree-level conflict detection and creates a merge commit if clean. Conflict stops write Git-shaped merge state to the index and worktree; resolve the paths, stage them, then call mergeContinue() or mergeAbort().

Returns a MergeResult with:

  • clean: whether the merge succeeded without conflicts
  • commitId: the merge commit ID (if clean)
  • conflictPaths: array of conflicting file paths (if not clean)
$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

Find the merge base (lowest common ancestor) of two commits. Returns null if no common ancestor exists.

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

Network

fetch

public function fetch(string $remote = 'origin'): void

Fetch from a remote. Downloads new objects and updates remote tracking refs.

$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

Push the current branch (or a specified branch) to a remote. Force-with-lease, atomic, and mirror variants are available for stricter or broader ref updates.

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

Worktrees

addWorktree

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

Add a linked worktree. Creates the branch if it does not exist, sets up the worktree metadata, and materializes the working tree files. Pass $name when you need a deterministic metadata slug that differs from the checkout directory basename.

$wt = $repo->addWorktree('/tmp/review', 'feature/review');
$wt = $repo->addWorktree('/tmp/a/divine-child', 'feature/review', name: 'app-theme');

removeWorktree

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

Remove a linked worktree. Use $force = true to remove even if locked.

worktrees

public function worktrees(): array

List all worktrees (main + linked). Returns Worktree[].

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

Config

config

public function config(): GitConfig

Access the repository's git config. Read and write config values.

$config = $repo->config();
$name = $config->get('user.name');
$config->set('user.email', '[email protected]');

Internal access

These methods expose internal components for advanced use cases.

objectDatabase

public function objectDatabase(): ObjectDatabase

Access the raw object database (loose + pack stores).

refDatabase

public function refDatabase(): RefDatabase

Access the raw ref database (loose + packed refs).

On this page