sebastian/git-state
Describe the state of a Git checkout from PHP. Detect origin URL, current branch and commit hash, and whether the working directory is clean; otherwise return the git status output. Useful for build metadata and diagnostics.
Installation: Add the package via Composer:
composer require sebastian/git-state
For development-only use (e.g., testing):
composer require --dev sebastian/git-state
First Use Case: Check Git state in a Laravel controller or command:
use SebastianBergmann\GitState\Builder;
$gitState = (new Builder)->build();
if (!$gitState) {
abort(500, 'Git repository not found or missing origin.');
}
// Log or display Git metadata
\Log::info('Git State', [
'branch' => $gitState->branch(),
'commit' => $gitState->commit(),
'is_clean' => $gitState->isClean(),
]);
Where to Look First:
Builder class: Core entry point for constructing Git state.GitState object: Contains methods like branch(), commit(), isClean(), and status().false returns (e.g., missing .git or origin).Artisan Command Integration: Create a custom command to display Git state:
php artisan make:command GitStatusCommand
// app/Console/Commands/GitStatusCommand.php
use SebastianBergmann\GitState\Builder;
public function handle()
{
$state = (new Builder)->build();
if (!$state) {
$this->error('Not a Git repository or missing origin.');
return;
}
$this->info("Branch: {$state->branch()}");
$this->info("Commit: {$state->commit()}");
$this->line($state->isClean() ? 'Clean' : 'Dirty: ' . $state->status());
}
Run with:
php artisan git:status
Exception Handler Integration:
Enhance Laravel’s App\Exceptions\Handler to include Git metadata:
use SebastianBergmann\GitState\Builder;
use Illuminate\Support\Facades\Log;
public function report(Throwable $exception)
{
$gitState = (new Builder)->build();
Log::error('Exception occurred', [
'exception' => $exception->getMessage(),
'git' => $gitState ? [
'branch' => $gitState->branch(),
'commit' => $gitState->commit(),
] : null,
]);
parent::report($exception);
}
Middleware for Git-Aware Logging: Attach Git state to every request’s log context:
// app/Http/Middleware/LogGitState.php
use Closure;
use SebastianBergmann\GitState\Builder;
public function handle($request, Closure $next)
{
$gitState = (new Builder)->build();
if ($gitState) {
$request->merge(['git_state' => $gitState]);
}
return $next($request);
}
Register in app/Http/Kernel.php:
protected $middleware = [
// ...
\App\Http\Middleware\LogGitState::class,
];
Use in a service:
$gitState = request()->git_state;
CI/CD Guardrails: Fail builds if the working directory is dirty (e.g., in a Laravel Forge hook or GitHub Actions):
// Example: Laravel Forge deploy hook
$state = (new \SebastianBergmann\GitState\Builder)->build();
if (!$state || !$state->isClean()) {
throw new \RuntimeException('Dirty working directory. Commit changes before deploying.');
}
Dynamic Feature Flags: Enable features based on Git tags or branches:
$state = (new Builder)->build();
if ($state && $state->branch() === 'feature/new-ui') {
Feature::enable('new_ui');
}
Caching Git State: Avoid repeated Git process calls by caching results:
$gitState = Cache::remember('git.state', now()->addHours(1), function () {
return (new Builder)->build();
});
Fallback for Missing Git: Provide a graceful fallback (e.g., for serverless environments):
$gitState = (new Builder)->build();
$state = $gitState ?: new class {
public function branch() { return 'unknown'; }
public function commit() { return '0000000'; }
public function isClean() { return true; }
};
Testing Git State: Mock Git state in tests using Laravel’s service container:
$this->app->instance(
\SebastianBergmann\GitState\Builder::class,
\Mockery::mock(\SebastianBergmann\GitState\Builder::class)
->shouldReceive('build')
->andReturn(new \SebastianBergmann\GitState\GitState(
'origin-url',
'main',
'abc123',
true
))
);
Laravel Service Container:
Bind the Builder to the container for dependency injection:
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->singleton(\SebastianBergmann\GitState\Builder::class);
}
Then inject it into classes:
use SebastianBergmann\GitState\Builder;
public function __construct(private Builder $gitState) {}
Event Listeners: Trigger actions based on Git state changes (e.g., deploy events):
// app/Listeners/ValidateGitState.php
use SebastianBergmann\GitState\Builder;
public function handle()
{
$state = (new Builder)->build();
if (!$state || !$state->isClean()) {
throw new \RuntimeException('Git validation failed.');
}
}
Environment-Specific Config: Disable Git checks in non-Git environments (e.g., Docker builds):
if (app()->environment('docker') && !file_exists('.git')) {
return false;
}
false Returns:
The package returns false for:
.git).origin remote.
Always validate the return value:$state = (new Builder)->build();
if (!$state) {
// Handle error (e.g., log, throw exception, or provide fallback).
}
Git Process Failures:
Underlying shell_exec calls may fail silently. Wrap in error handling:
try {
$state = (new Builder)->build();
} catch (\RuntimeException $e) {
Log::error('Git state check failed: ' . $e->getMessage());
}
Cross-Platform Path Issues:
Git paths (e.g., .git/config) may differ on Windows/Linux. Test on both platforms:
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
// Windows-specific Git path handling.
}
Detached HEAD States:
The branch() method may return null or an unexpected value in detached HEAD states. Validate:
$branch = $state->branch() ?: 'detached HEAD';
Submodules or Sparse Checkouts: The package may not handle complex Git configurations (e.g., submodules). Test thoroughly in your environment.
Git Version Incompatibilities:
Older Git versions (<2.0) may cause parsing issues. Document requirements in your README.
Performance Overhead:
Each build() call spawns a Git process. Cache results aggressively:
Cache::remember('git.state', now()->addMinutes(5), function () {
return (new Builder)->build();
});
Enable Git Debugging:
Temporarily enable verbose Git output by extending the Builder:
class DebugBuilder extends \SebastianBergmann\GitState\Builder
{
protected function executeGitCommand($command)
{
return shell_exec('echo "Running: ' . $command . '"; ' . $command);
}
}
Log Raw Git Output: Log the raw Git commands and outputs for debugging:
$state = (new Builder)->build();
if (!$state) {
Log::debug('Git state build failed. Check logs for raw output.');
// Log the last Git command executed (if possible).
}
Test Edge Cases: Manually test these scenarios:
How can I help you explore Laravel packages today?