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-json for operation state files and Git LFS API payloads
- ext-mbstring, declared in
composer.json, so Composer requires it at install time - ext-ctype for hash validation (enabled in most PHP builds, but not declared in
composer.json)
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 uses ext-ssh2 when it is loaded and otherwise starts the system ssh binary through proc_open().
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.
open(), init(), and clone() accept an options array as their last argument:
// Do not run .git/hooks scripts for this handle
$repo = Pitmaster::open('/path/to/project', ['hooks' => false]);
// Do not start any process: no hooks, no fsmonitor hook, no clone/fetch/push, no SSH
$repo = Pitmaster::open('/path/to/project', Pitmaster::processFreeOptions());
Hooks run by default. Pitmaster::processFreeOptions() returns ['processes' => false, 'hooks' => false]; local reads and writes keep working on such a handle.
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.
To create a SHA-256 repository, pass the object format as the second argument:
$repo = Pitmaster::init('/path/to/new-project', 'sha256');
Object IDs in that repository are 64 hex characters, and the config records extensions.objectformat = sha256. Repositories opened later pick the format up from that config entry.
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 author name and email are resolved in this order: the PITMASTER_AUTHOR_NAME / PITMASTER_AUTHOR_EMAIL environment variables, the GIT_AUTHOR_NAME / GIT_AUTHOR_EMAIL environment variables, the PITMASTER_AUTHOR_NAME / PITMASTER_AUTHOR_EMAIL constants, the repository's own .git/config (user.name and user.email), and finally Pitmaster <pitmaster@localhost>. Only the repository config file (and files it includes) is read, not ~/.gitconfig. The committer is resolved the same way from the *_COMMITTER_* names and falls back to the author constants.
Compute diffs
// Unstaged changes (worktree vs index)
$diffs = $repo->diff();
foreach ($diffs as $diff) {
echo $diff->format(); // Unified diff output
}
// 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 discovers refs, fetches the pack file, sets up remote tracking branches, and checks out the default branch. HTTP(S) URLs use smart HTTP and fall back to dumb HTTP; SSH URLs use the SSH transport. Pass a depth as the third argument for a shallow clone: Pitmaster::clone($url, $path, 1).
Read any object by hash
$object = $repo->readObject($fullHash); // full 40-character (or 64 for SHA-256) hex hash
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 with PHP constants, defined before Pitmaster runs:
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_OBJECT_CACHE_BYTES', '64M');
| Constant | Default | Effect |
|---|---|---|
PITMASTER_AUTHOR_NAME, PITMASTER_AUTHOR_EMAIL |
none | Author identity (see the order above) |
PITMASTER_COMMITTER_NAME, PITMASTER_COMMITTER_EMAIL |
author values | Committer identity |
PITMASTER_HTTP_TIMEOUT |
30 |
Smart HTTP timeout in seconds (integer) |
PITMASTER_MAX_DELTA_CHAIN |
50 |
Maximum delta chain depth when reading packs (integer) |
PITMASTER_OBJECT_CACHE_ENTRIES |
1024 |
Objects kept in the in-memory object cache |
PITMASTER_OBJECT_CACHE_BYTES |
16M |
Byte budget of the object cache |
PITMASTER_PACK_RESOLVED_CACHE_ENTRIES |
4096 |
Resolved delta objects cached per pack |
PITMASTER_PACK_RESOLVED_CACHE_BYTES |
32M |
Byte budget of that pack cache |
PITMASTER_MAX_PACK_MEMORY |
none | Older name for the pack cache byte budget, used only when PITMASTER_PACK_RESOLVED_CACHE_BYTES is not defined |
Byte values accept an integer or a string with a k, m, or g suffix ('256M'). Invalid values fall back to the default.
Environment variables override the identity constants: PITMASTER_AUTHOR_NAME, PITMASTER_AUTHOR_EMAIL, PITMASTER_AUTHOR_DATE, the PITMASTER_COMMITTER_* equivalents, and git's own GIT_AUTHOR_* and GIT_COMMITTER_* variables. The SSH transport reads PITMASTER_SSH_* variables, described in Protocols.
There is no constant for the hash algorithm. The object format is chosen when the repository is created (Pitmaster::init($path, 'sha256') or init --object-format=sha256 on the CLI) and read from the repository config afterwards.