adachsoft / gitlib
Lightweight, framework-agnostic Git library with flat facade and action dispatcher for PHP.
Requires
- php: ^8.3
- adachsoft/command-executor-lib: ^2.0
Requires (Dev)
- adachsoft/php-code-style: ^0.4
- friendsofphp/php-cs-fixer: ^3.94
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^13.0
- rector/rector: ^2.3
README
Lightweight, framework-agnostic Git library with a flat facade and a name-based action dispatcher. Built for PHP 8.3+, PSR-compliant, easily extensible with plugin-like operation handlers.
Requirements
- PHP 8.3+
- Composer
Installation
composer require adachsoft/gitlib
Quick start
<?php
use AdachSoft\CommandExecutorLib\SimpleCommandExecutor;
use Adachsoft\GitLab\Internal\Adapter\CommandExecutorProcessRunner;
use Adachsoft\GitLab\Internal\Operation\HandlerLocator;
use Adachsoft\GitLab\Internal\Operation\OperationRegistry;
use Adachsoft\GitLab\Internal\GitRepository;
use Adachsoft\GitLab\DTO\Options\PullOptions;
use Adachsoft\GitLab\DTO\Options\PushOptions;
use Adachsoft\GitLab\Exception\ProcessFailedException;
use Adachsoft\GitLab\Exception\ValidationException;
$executor = new SimpleCommandExecutor();
$runner = new CommandExecutorProcessRunner($executor);
$locator = new HandlerLocator();
$registry = (new OperationRegistry())
->discoverAndRegisterHandlers($locator, $runner, __DIR__);
$repo = new GitRepository($runner, $registry, __DIR__); // working directory
try {
// 1) Using the facade methods
$status = $repo->status();
$repo->add(['file1.php', 'file2.php']);
$repo->commit('Initial commit');
$repo->pull(new PullOptions(remote: 'origin', branch: 'main', rebase: true));
$repo->push(new PushOptions(remote: 'origin', branch: 'main', forceWithLease: true));
// 2) Using the action dispatcher
$repo->execute('getRemotes', ['verbose' => true]);
$repo->execute('rebase', ['branch' => 'feature/xyz']);
$repo->execute('push', [
'remote' => 'origin',
'branch' => 'main',
'setUpstream' => true,
]);
} catch (ValidationException|ProcessFailedException $e) {
// Handle invalid arguments or failed git processes
echo $e->getMessage();
}
Client-level operations
In addition to working inside an existing repository, you can use GitClientInterface to create/clone repositories and check if a path is a Git repository.
<?php
use AdachSoft\CommandExecutorLib\SimpleCommandExecutor;
use Adachsoft\GitLab\Internal\Adapter\CommandExecutorProcessRunner;
use Adachsoft\GitLab\Internal\GitClient;
use Adachsoft\GitLab\DTO\Options\InitOptions;
$executor = new SimpleCommandExecutor();
$runner = new CommandExecutorProcessRunner($executor);
$client = new GitClient($runner);
// git clone git@gitlab.com:a.adach/embedding-contracts.git
$client->clone('git@gitlab.com:a.adach/embedding-contracts.git');
// git init --initial-branch=main --object-format=sha1
$client->init(new InitOptions(initialBranch: 'main', objectFormat: 'sha1'));
// check if directory is a Git repository
if ($client->isRepository('/path/to/repo')) {
// ...
}
Mapping common Git commands
The facade API is designed to closely match common Git CLI commands. Examples:
<?php
use Adachsoft\GitLab\DTO\Options\InitOptions;
use Adachsoft\GitLab\DTO\Options\PushOptions;
// git config --local user.name "Arek"
$repo->setConfig('user.name', 'Arek', 'local');
// git config --local user.email "adachsoft@gmail.com"
$repo->setConfig('user.email', 'adachsoft@gmail.com', 'local');
// git switch --create main (equivalent)
$repo->createBranch('main');
// git push --set-upstream origin main
$repo->push(new PushOptions(remote: 'origin', branch: 'main', setUpstream: true));
// git init --initial-branch=main --object-format=sha1
$repo->init(new InitOptions(initialBranch: 'main', objectFormat: 'sha1'));
// git remote add origin git@gitlab.com:a.adach/embedding-contracts.git
$repo->addRemote('origin', 'git@gitlab.com:a.adach/embedding-contracts.git');
Concepts
- Facade:
GitRepositoryInterfaceexposes a full set of explicit Git operations (no magicexecute()here). - Dispatcher:
GitActionExecutorInterface::execute(string $action, array $args)allows dynamic invocation by action name. - Handlers: Each action is handled by a class implementing
OperationHandlerInterfaceand marked with#[GitOperation('actionName')]. - Process execution: Abstracted via
ProcessRunnerInterfacewith the default adapterCommandExecutorProcessRunner(wrapsadachsoft/command-executor-lib). - DTOs: Simple
final readonlyobjects (no getters) for structured results.
Auto-discovery of handlers
HandlerLocator discovers and instantiates handlers marked with #[GitOperation] in the Adachsoft\GitLab\Extensions namespace (filesystem defaults to src/Extensions). You can customize the directory and namespace if needed.
$registry->discoverAndRegisterHandlers($locator, $runner, $workingDirectory);
Adding a custom action
1) Create a handler in src/Extensions:
<?php
namespace Adachsoft\GitLab\Extensions;
use Adachsoft\GitLab\Attributes\GitOperation;
use Adachsoft\GitLab\Contracts\ProcessRunnerInterface;
use Adachsoft\GitLab\Contracts\OperationHandlerInterface;
#[GitOperation('hello')]
final class HelloHandler extends AbstractOperationHandler implements OperationHandlerInterface
{
public function name(): string { return 'hello'; }
public function handle(array $arguments)
{
$this->processRunner->run('git status', $this->workingDirectory);
return 'world';
}
}
2) Let the locator discover it (nothing else to change).
Exceptions
ValidationException– invalid arguments/options passed to operations/handlers.ProcessFailedException– underlying git command failed (non-zero exit, etc.).
Notes
- Framework-agnostic: no container required; wire objects manually.
- PSR style; PHPDoc/comments in English.
- See
docs/tasks/task001-plan-v1.mdfor the original design plan and acceptance criteria.