Reading Repositories
Pitmaster reads git repositories by parsing the on-disk formats directly: loose objects (zlib-compressed files in .git/objects/), pack files (.git/objects/pack/), refs (files in .git/refs/ and packed-refs), and the index (.git/index).
Opening a repository
use Pitmaster\Pitmaster;
$repo = Pitmaster::open('/path/to/project');Pitmaster supports three repository layouts:
- Regular repositories:
.gitis a directory at the project root. - Linked worktrees:
.gitis a file containinggitdir: <path>pointing to.git/worktrees/<name>/. - Bare repositories: the path itself contains
HEAD,objects/, etc.
// Detection helpers
Pitmaster::isRepository('/path'); // true for any of the above
Pitmaster::isWorktree('/path'); // true only for linked worktreesReading objects
Every piece of data in a git repository is stored as an object with a SHA-1 hash. There are four object types: blob, tree, commit, and tag.
readObject
use Pitmaster\Object\Blob;
use Pitmaster\Object\Tree;
use Pitmaster\Object\Commit;
use Pitmaster\Object\Tag;
$object = $repo->readObject('a1b2c3d4e5f6...');
match (true) {
$object instanceof Blob => handleBlob($object),
$object instanceof Tree => handleTree($object),
$object instanceof Commit => handleCommit($object),
$object instanceof Tag => handleTag($object),
};Objects are searched in loose storage first, then in pack files. Pack file lookups use the .idx fanout table for O(log n) binary search.
catFile
For simple content retrieval without type checking:
$content = $repo->catFile('a1b2c3d4e5f6...');
echo $content;objectExists
if ($repo->objectExists('a1b2c3d4e5f6...')) {
// Object is in the repository
}listObjects
$hashes = $repo->listObjects();
// ['a1b2c3d4...', 'f6e5d4c3...', ...]Returns hex hashes for all objects (loose and packed).
Working with blobs
A blob holds raw file content.
$blob = $repo->readObject($hash);
echo $blob->content; // Raw file bytes
echo $blob->size; // Content length
echo $blob->id->hex; // SHA-1 hashWorking with trees
A tree is a directory listing. Each entry has a mode, name, and hash.
$tree = $repo->readObject($hash);
foreach ($tree->entries as $entry) {
echo "{$entry->mode} {$entry->name} {$entry->hash->hex}\n";
// 100644 README.md abc123...
// 040000 src def456...
// 100755 deploy.sh 789012...
if ($entry->isTree()) {
// Recurse into subdirectory
} elseif ($entry->isBlob()) {
// Read file content
} elseif ($entry->isExecutable()) {
// Executable file
} elseif ($entry->isSymlink()) {
// Symbolic link
} elseif ($entry->isGitlink()) {
// Submodule (mode 160000)
}
}Working with commits
A commit links a tree snapshot to its parents and metadata.
$commit = $repo->head();
echo $commit->id->hex; // Full SHA-1 hash
echo $commit->tree->hex; // Tree object hash
echo $commit->author; // "Name <email> timestamp tz"
echo $commit->committer; // "Name <email> timestamp tz"
echo $commit->message; // Full commit message
// Parent commits
foreach ($commit->parents as $parentId) {
$parent = $repo->readObject($parentId->hex);
}
// Merge detection
if ($commit->isMerge()) {
// Two or more parents
}Working with tags
Annotated tags are objects with a target, type, name, tagger, and message.
$tag = $repo->readObject($hash);
echo $tag->objectId->hex; // Target object hash
echo $tag->objectType; // 'commit', 'tree', 'blob', or 'tag'
echo $tag->name; // Tag name
echo $tag->tagger; // "Name <email> timestamp tz"
echo $tag->message; // Tag messageReading refs
Current HEAD
$commit = $repo->head();Resolves HEAD (following symbolic refs like HEAD -> refs/heads/main -> commit hash) and returns the commit object.
Current branch
$branch = $repo->branch(); // 'main' or null if detachedListing branches and tags
$branches = $repo->branches(); // ['bugfix/typo', 'feature/login', 'main']
$tags = $repo->tags(); // ['v1.0.0', 'v1.1.0']All refs
$refs = $repo->allRefs();
// [
// 'refs/heads/main' => 'abc123...',
// 'refs/heads/feature' => 'def456...',
// 'refs/tags/v1.0.0' => '789012...',
// 'refs/remotes/origin/main' => 'abc123...',
// ]Resolving revisions
$id = $repo->resolve('HEAD');
$id = $repo->resolve('main');
$id = $repo->resolve('v1.0.0');
$id = $repo->resolve('HEAD~3');
$id = $repo->resolve('main^2');
$id = $repo->resolve('abc123def456...');The revision parser supports ~N (ancestor) and ^N (parent) operators, chained arbitrarily.
Walking commit history
log
// Last 20 commits from HEAD
$commits = $repo->log(20);
foreach ($commits as $commit) {
$short = substr($commit->id->hex, 0, 7);
echo "{$short} {$commit->message}\n";
}log from a specific commit
$from = $repo->resolve('feature/login');
$commits = $repo->log(10, $from);log filtered by file path
$commits = $repo->logPath('src/Repository.php', 20);Returns only commits where the specified file was changed (added, modified, or deleted).
show
$result = $repo->show('HEAD');
$commit = $result['commit'];
$diffs = $result['diff'];
echo "Author: {$commit->author}\n";
echo "Message: {$commit->message}\n\n";
foreach ($diffs as $diff) {
echo $diff->format();
}Default branch detection
$default = $repo->defaultBranch();
// Checks: remote HEAD symref -> local HEAD -> main/master fallbackMerge ancestry
// Is feature fully merged into main?
$merged = $repo->isBranchMerged('feature/done', 'main');
// Find the merge base of two commits
$base = $repo->mergeBase(
$repo->resolve('main'),
$repo->resolve('feature/login'),
);