Pitmaster

Getting Started

Installation

Install Pitmaster via Composer:

composer require pitmaster/pitmaster

Requirements

  • PHP 8.2+ with readonly classes, enums, and constructor promotion
  • ext-zlib for object compression/decompression (built-in on most installs)
  • ext-mbstring for UTF-8 path handling
  • ext-json for config and protocol payload handling

Core repository operations do not require the git binary, FFI, or external helpers. Optional features have additional runtime requirements: hook execution uses proc_open(), and SSH transport requires ext-ssh2.

Open an existing repository

use Pitmaster\Pitmaster;

$repo = Pitmaster::open('/path/to/project');

This opens the repository at the given path. Pitmaster locates the .git directory (or resolves a .git file for linked worktrees) and initializes the object database, ref database, and config.

Check if a path is a repository

if (Pitmaster::isRepository('/path/to/project')) {
    $repo = Pitmaster::open('/path/to/project');
}

Initialize a new repository

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

This creates the .git directory structure with objects, refs, HEAD (pointing to refs/heads/main), and a default config.

Read the HEAD commit

$head = $repo->head();

echo "Commit: {$head->id->hex}\n";
echo "Author: {$head->author}\n";
echo "Message: {$head->message}\n";

The head() method resolves HEAD (following symbolic refs) and returns a Commit object.

List branches and tags

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

// Current branch name (null if detached)
$current = $repo->branch();
// 'main'

// All tag names
$tags = $repo->tags();
// ['v1.0.0', 'v1.1.0']

Walk commit history

// Last 20 commits from HEAD
$commits = $repo->log(20);

foreach ($commits as $commit) {
    echo substr($commit->id->hex, 0, 7) . " {$commit->message}\n";
}

You can also start from a specific commit:

$from = $repo->resolve('feature/login');
$commits = $repo->log(10, $from);

Check working tree status

use Pitmaster\Status\FileStatus;

$entries = $repo->status();

foreach ($entries as $entry) {
    if ($entry->index === FileStatus::Untracked) {
        echo "?? {$entry->path}\n";
    } elseif ($entry->index === FileStatus::Modified) {
        echo "M  {$entry->path}\n";
    } elseif ($entry->worktree === FileStatus::Modified) {
        echo " M {$entry->path}\n";
    }
}

Stage files and commit

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

// Create a commit
$commitId = $repo->commit('Add new feature with tests');

echo "Created commit: {$commitId->hex}\n";

The commit uses the author name and email from the repository's .git/config (user.name and user.email). If those are not set, it falls back to the PITMASTER_AUTHOR_NAME and PITMASTER_AUTHOR_EMAIL constants, then to defaults.

Compute diffs

// Unstaged changes (worktree vs index)
$diffs = $repo->diff();

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

// Staged changes (index vs HEAD)
$staged = $repo->diffStaged();

Clone a remote repository

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

This performs a full clone over smart HTTP: discovers refs, fetches the pack file, sets up remote tracking branches, and checks out the default branch.

Read any object by hash

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

if ($object instanceof \Pitmaster\Object\Blob) {
    echo $object->content;
} elseif ($object instanceof \Pitmaster\Object\Commit) {
    echo $object->message;
}

Configuration

Pitmaster respects the repository's .git/config for author information and remote URLs. You can also set global defaults via PHP constants:

define('PITMASTER_AUTHOR_NAME', 'Your Name');
define('PITMASTER_AUTHOR_EMAIL', 'you@example.com');
define('PITMASTER_HTTP_TIMEOUT', 60);        // Seconds
define('PITMASTER_MAX_DELTA_CHAIN', 50);     // Max delta chain depth
define('PITMASTER_MAX_PACK_MEMORY', '256M'); // Memory limit for pack ops
define('PITMASTER_HASH_ALGO', 'sha1');       // sha1 or sha256

On this page