gitonomy/gitlib
Gitonomy Gitlib is a PHP library for interacting with Git repositories programmatically. Read commits, trees, branches, tags and diffs; run Git commands via a clean API and work with local repos from your apps, tools, or CI scripts.
Install via Composer (PHP 8.1+ required as of 1.7, which also pulls in symfony/deprecation-contracts):
composer require gitonomy/gitlib
Point a Repository at an existing local checkout. The 1.7 constructor accepts a working directory and a CliDriver as second/third arguments:
use Gitonomy\Git\Repository;
use Gitonomy\Git\Driver\Cli\CliDriver;
$driver = new CliDriver(bin: '/usr/bin/git', timeout: 60);
$repository = new Repository('/path/to/repo', null, $driver);
$log = $repository->getLog(); // history
$diff = $repository->getDiff('main', 'HEAD'); // two-revision diff
First use case: read-only repository introspection for CI dashboards, deployment gates, or release-notes generators — commit history, branches, tags, structured diffs and blame. Prefer the 1.7 API surface from day one; code that is deprecation-clean on 1.7 runs unchanged on 2.0, so a library or internal package can declare ^1.7 || ^2.0 with one code path.
Bind the driver in a service provider. Configure CliDriver once (binary path, env vars, timeout, logger) from config/git.php and resolve it as a singleton. This replaces the old options array, the debug flag, and setLogger()/getLogger() — all deprecated in 1.7.
$this->app->singleton(CliDriver::class, fn () => new CliDriver(
bin: config('git.bin'),
env: ['LC_ALL' => 'C'],
timeout: config('git.timeout'),
logger: logger()->channel('git'),
));
Create and validate repositories via static factories. Repository::init(), initBare(), cloneFrom(), cloneBareFrom(), mirrorFrom(), isValidRepository() and isValidRepositoryAndBranch() all take the URL before the path and a CliDriver last. These replace the deprecated Admin class and Repository::cloneTo(). Note isValidRepositoryAndBranch() now matches the branch name exactly, and relative paths are always treated as paths, never as remote names.
Use the two-argument diff and merge base. getDiff($from, $to) compares revisions, getMergeBase() returns their best common ancestor, and WorkingCopy::getDiff() compares the working tree against a revision. All throw ReferenceNotFoundException on an unknown revision — great for validating user input before running expensive work.
Render diffs from typed objects. FileChange::getDiffLines() returns DiffLine objects with getType() (DiffLineType enum: add/remove/context) and getContent(). This is far more reliable than parsing the deprecated getLines() arrays or the LINE_* constants when building colored diff UIs or annotation tooling.
Paginate logs with immutable builders. Log::withOffset() and withLimit() return a new log rather than mutating in place:
$page = $repository->getLog()->withOffset($offset)->withLimit(50);
foreach ($page as $commit) { /* ... */ }
Walk trees and submodules structurally. Iterating a Tree yields TreeEntry objects (getName(), getMode(), getObject()) keyed by name; getBlobs(), getTrees() and getSubmodules() filter by type. Use Submodule::getHash() for pinned-commit checks instead of the deprecated array-returning getEntries() family.
Blame by integer ranges. Repository::getBlame($file, $from, $to) plus Blame::getHunks() and Blame\Line::getNumber()/getOriginalNumber() — the string range form and getGroupedLines() are deprecated. Hunk numbering follows the file, which maps cleanly onto editor gutter rendering.
Queue the heavy operations. Large diffs, getSizeInBytes(), git gc, and full-history blame still belong in queued jobs; the driver's timeout is your safety valve.
Deprecation noise will land in your logs. Every deprecated call emits through symfony/deprecation-contracts, which Laravel surfaces as a logged deprecation and PHPUnit can turn into a failure. Audit with a fresh install after the upgrade and fix calls before they become 2.0 breakages. Where a third-party subclass triggers one, it's usually the library calling a method you override — see the release note about func_get_arg()/@psalm-variadic.
Log::count() changed meaning. If an offset or limit excludes commits, 1.7's count() still counts all commits while 2.0 counts only iterated ones. Use countCommits() explicitly wherever pagination is involved.
checkout() and local Branch objects. Passing a Branch object detaches HEAD on 1.7 but attaches on 2.0. Pass the branch name (or commit) and use WorkingCopy::checkoutNewBranch() instead of checkout()'s second argument.
Re-read references by opening a new Repository. Passing an argument to getReferences() is deprecated, as are ReferenceBag::update(), Branch::isMergedTo() (now isMergedInto()), and re-creating an existing branch/tag.
Hash, not revision name, for trees and blobs. getTree()/getBlob() accept a name today but that's deprecated — resolve through Revision first, and only if you need the object.
getSize() → getSizeInBytes(). The new call does not follow symlinks, so reported sizes can differ from the old value for repos with links.
Don't build on parser internals. All parser classes except DiffParser and ParserBase move into the CliDriver in 2.0. If you've subclassed or type-hinted a parser, that's the biggest hidden migration cost.
final markers are coming. Classes annotated @final since gitonomy/gitlib 1.7 (and constructors of Commit, Tree, Blob, Revision, Reference, ReferenceBag) will be closed in 2.0 — stop extending them now, prefer composition.
Still validate and normalize paths before instantiation, and stream large outputs with execute() callbacks rather than buffering. Catch Gitonomy\Git\Exception\ProcessException for exit code, stdout and stderr; it remains the fastest route to diagnosing a bad binary path or permission problem.
Set locale through the driver. LC_ALL=C and friends now belong in CliDriver's env array rather than ad-hoc putenv() calls, which keeps behavior identical between web requests and queue workers.
How can I help you explore Laravel packages today?