Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Gitelephant Laravel Package

cypresslab/gitelephant

GitElephant is a PHP abstraction layer for managing Git repositories. It wraps the git binary in an object-oriented API, letting you inspect history, branches, tags, commits, and files without handling raw command output. Requires PHP 7.2+ and Git on a Unix-like system.

View on GitHub
Deep Wiki
Context7

Getting Started

What changed in v5.0.0 (read this first)

GitElephant v5 is a maintenance/modernization release with breaking platform requirements — no new Git features, but your Laravel app must satisfy:

Requirement v4.6 v5.0.0
PHP ^7.4 || ^8.0 >=8.1
Symfony (Process) <6.x range ^6.4 || ^7.0 || ^8.0
Autoloading psr-0 (deprecated) psr-4
PHPUnit (contributors) 9.x ^10.5 || ^11.0
Tooling — Rector ^2.0, PHPStan ^2.0, ECS ^12 || ^13; annotations → PHP 8 attributes

Practical impact for Laravel teams: Laravel 10/11/12 apps are fine. Laravel 9 apps pinned to PHP 8.0 or Symfony 6.0–6.3 will fail composer update — stay on matteosister/git-elephant:^4.6 or bump PHP first.

First Steps

  1. Installation (no Laravel auto-discovery — this is a framework-agnostic library):

    composer require matteosister/git-elephant:^5.0
    

    Namespace is GitElephant\ (unchanged by the PSR-4 switch — class names are stable, only the file layout inside vendor/ moved).

  2. Wire it into the container yourself. Repository is stateful (it holds a working directory and a Caller), so bind it explicitly:

    // app/Providers/GitServiceProvider.php
    use GitElephant\Repository;
    
    public function register(): void
    {
        $this->app->scoped(Repository::class, fn () => Repository::open(base_path()));
    }
    

    Use scoped() rather than singleton() if you run Octane — a singleton would leak the repo path and last command output between requests.

  3. First use case — a "repo health" panel:

    use GitElephant\Repository;
    
    class RepoStatusController
    {
        public function __invoke(Repository $repo)
        {
            $status   = $repo->getStatus();
            $head     = $repo->getCommit();
            $branch   = $repo->getBranch();
    
            return view('git.status', [
                'branch'   => $branch->getName(),
                'modified' => $status->modified(),   // array of StatusFile
                'untracked'=> $status->untracked(),
                'lastMsg'  => $head->getMessage(),
                'lastDate' => $head->getDatetimeAuthor(),
            ]);
        }
    }
    

Implementation Patterns

Encapsulate in a service, don't sprinkle Repository around

Because every method shells out to the git binary, one service class gives you a single place for binary path, timeouts, and error handling:

namespace App\Services;

use GitElephant\Repository;

class GitService
{
    public function __construct(private Repository $repo) {}

    public function sync(string $remote = 'origin', string $branch = 'main'): void
    {
        $this->repo->fetch($remote);
        $this->repo->checkout($branch);
        $this->repo->pull($remote, $branch);
    }
}

Common day-to-day operations

$repo = Repository::open('/var/www/app');

// Status / staging / commit
$repo->getStatus()->all();                 // StatusFile[]
$repo->stage('app/Models/User.php');       // or stage(['a.php', 'b.php'])
$repo->unstage('app/Models/User.php');
$repo->commit('Update user model');

// Branches
$branches = $repo->getBranches();          // Branch[]
$repo->createBranch('feature/x', 'main');
$repo->checkout('feature/x');
$repo->merge('main');

// History
foreach ($repo->getLog('main', null, 20) as $commit) {
    $commit->getSha();
    $commit->getMessage();
    $commit->getDiff();                    // Diff object
}

// Tags
$repo->createTag('v1.2.3', 'Release 1.2.3');
$repo->getTags();
$repo->deleteTag('v1.2.3');

// Remotes
$repo->addRemote('origin', '[email protected]:acme/app.git');
$repo->fetch('origin');
$repo->push('origin', 'main');

Cloning into storage_path()

use GitElephant\Repository;

$path = storage__path('repos/'.$id);
Repository::cloneFrom('https://github.com/acme/app.git', $path);
$repo = Repository::open($path);

Artisan command for deployments

class DeployCommand extends Command
{
    protected $signature = 'deploy:pull {branch=main}';

    public function handle(GitService $git): int
    {
        try {
            $git->sync('origin', $this->argument('branch'));
            $this->info('Synced.');
            return self::SUCCESS;
        } catch (\Throwable $e) {
            $this->error($e->getMessage());
            return self::FAILURE;
        }
    }
}

Queued jobs for slow operations

Clones and fetches over the network belong on a queue with an explicit timeout — Symfony Process defaults to 60s and will kill large clones:

class SyncRepository implements ShouldQueue
{
    public $timeout = 900;
    public $tries   = 3;
    public $backoff = [30, 120, 300];

    public function __construct(public string $path, public string $branch = 'main') {}

    public function handle(): void
    {
        Repository::open($this->path)->pull('origin', $this->branch);
    }

    public function middleware(): array
    {
        return [(new WithoutOverlapping($this->path))->releaseAfter(60)];
    }
}

Escaping the wrapper

For anything not covered (worktree, submodule, reset --hard), drop to the Caller:

$caller = $repo->getCaller();
$caller->execute('reset --hard origin/main');
$caller->getOutput();

Gotchas and Tips

v5 upgrade pitfalls

  1. Composer conflicts are the #1 upgrade blocker. Run composer why-not matteosister/git-elephant 5.0 first. If it reports symfony/process at 6.0–6.3, you're blocked until you upgrade the framework. symfony/*:^8.0 additionally requires PHP 8.4+ — don't add it explicitly, let Laravel's constraint resolve.
  2. PSR-0 → PSR-4. Class names are unchanged, so normal usage is transparent. It does break you if you (a) had a custom psr-0 autoload block mapping GitElephant\ in your own composer.json, (b) class_alias'd internal classes, or (c) monkey-patched by requiring files from the vendor tree by path. Move to psr-4 mapping or drop it — Composer handles it now.
  3. Annotations → PHP 8 attributes. If you subclass internal classes and copied their docblock annotations (or wrote annotations expecting them to be read), those annotations are now attributes. Re-check your subclass metadata.
  4. PHPUnit 10/11 only matters if you maintain a fork or run the package's suite in CI. Don't import its test config into your own — the schema changed.
  5. Run php artisan optimize:clear after upgrading so no cached composer classmap from the old PSR-0 layout survives.

Common operational pitfalls

  1. Binary path. The git binary must exist in $PATH for the PHP-FPM/queue user (usually not your shell user). Override it:

    $repo->getCaller()->setBinaryPath('/usr/bin/git');
    

    Debug with which git as the same user, e.g. sudo -u www-data which git.

  2. Symfony Process 7/8 timeout behaviour. Long clones/fetches exceed the 60s default. Set a job $timeout and a process timeout; otherwise you get a ProcessTimedOutException mid-clone and a half-populated working directory.

  3. Paths must be absolute and writable. Repository::open() on a relative path silently resolves against the process CWD, which differs between web and queue contexts. Always use base_path() / storage_path().

  4. Credentials. HTTPS: use a token in the URL or a credential helper — never interactive prompts (they hang until timeout). SSH: the key must be readable by the runtime user:

    sudo -u www-data ssh -T [email protected]
    
  5. Detached HEAD. Checking out a SHA leaves you detached; subsequent pull() calls fail confusingly. Always follow with a branch checkout, or prefer fetch + reset --hard.

  6. Shallow clones. Repository::cloneFrom() doesn't expose --depth. If you need it, use the caller: $repo->getCaller()->execute("clone --depth=1 {$url} {$dir}").

  7. Don't put Repository in a singleton — see the Octane note above. Also don't inject it into queued job constructors (the object isn't serializable across the wire); resolve it inside handle().

  8. Validate branch/tag names coming from request input. These end up in a shell command string; a ; or $(...) in a branch name is a command injection vector.

Debugging tips

  • Inspect the raw command and output via the Caller — most failures are visible here before you touch anything else:
    try {
        $repo->pull('origin', 'main');
    } catch (\Throwable $e) {
        \Log::error('git failed', [
            'error'  => method_exists($repo->getCaller(), 'getError') ? $repo->getCaller()->getError() : null,
            'output' => $repo->getCaller()->getOutput(),
        ]);
        throw $e;
    }
    
  • Wrap every write operation in try/catch. Failures surface as exceptions from the command layer (RuntimeException and package-specific exceptions), not boolean returns.
  • Prove the repo is valid before doing anything else — $repo->getStatus() is the cheapest smoke test; if it throws, your path or binary is wrong.

Extension points

  1. Custom commands — implement GitElephant\Command\Command / extend the base command classes and register it on the Caller. Keeps escaping consistent instead of hand-built strings.
  2. Caller for one-offs — $repo->getCaller()->execute(...) with getOutput() / getError() is the intended escape hatch.
  3. Compose Repository objects — build a RepositoryFactory that caches one instance per path within a request/job, rather than binding a single global.
  4. Environment-driven config — read config('git.binary') and config('git.timeout') into the Caller in a service provider, so deploys can tune without code changes.

Testing your integration

Use a throwaway directory per test (sys_get_temp_dir()), git init it via the library, and assert on getStatus()/getLog(). Avoid sharing one fixture repo across parallel test processes — GitElephant has no locking, and index.lock errors will make your suite flaky.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator