sebastian/git-state
sebastian/git-state is a small PHP library for describing the state of a Git checkout. Its Builder returns the origin remote URL, current branch, commit hash, and working directory status, or false when the directory is not a repository.
commit() addition doesn't change the calculus — it's still a utility, not a platform./health endpoints, admin footers, and error reports with "what code is actually running." Previously the SHA came bundled with origin resolution; Builder::commit() now returns the checked-out hash without requiring an origin remote. That matters in exactly the environments where traceability is hardest: CI checkouts with remotes stripped, detached-HEAD deploys, vendored/mirrored repos, and repos where origin was renamed. Product implication: commit stamping is now a viable slice even for teams who can't guarantee a remote is configured — scope it as a small, standalone item rather than a platform initiative.APP_ENV. Useful for staging/canary verification, but should stay out of customer-facing logic.commit() over build() for stamping: The new API is narrower and has fewer environmental preconditions (no remote needed). Decision: make commit() the default call for build-info banners and health fields; reserve the full build() snapshot for the deploy preflight where branch/dirty state is actually needed. This reduces the blast radius of the "no .git" failure mode for the most common use case.GitStateProvider interface is now demonstrably cheap insurance — 1.1.0 is additive, so the wrapper absorbs it without touching call sites. Benefit: swapping to symfony/process calls or a different library later is a config change, not a refactor. Cost: a few hours.Adopt when:
git status) inside PHP — e.g., an Artisan command, a health endpoint, a deploy preflight, or a build-info banner.Builder::commit() covers this without an origin remote — the best fit for build stamping in CI checkouts, detached-HEAD deploys, or renamed/mirrored remotes. This is now the lowest-precondition entry point into the package.require --dev), where a hard failure just fails a build, not a customer request.Look elsewhere or skip when:
symfony/process, Laravel's Process facade, or a dedicated Git wrapper.git status / build() are blocking subprocess I/O; on a large repo this can take hundreds of milliseconds. commit() is lighter but still shells out — don't put either in a hot path, and caching "is the tree dirty" is often worse than the cost..git and sometimes without the git binary. build()/commit() returning false is graceful, but the feature silently disappears; confirm that's acceptable, and note that commit() narrows but does not remove this precondition.false/failures rather than typed exceptions. If your environments are heterogeneous, wrap symfony/process yourself.Adoption gate to set before committing: confirm git is present and .git exists in every environment where the check must run (prod included). If you only need the SHA, validate with commit() alone — that removes the origin-remote requirement from the gate but keeps the git-binary and .git requirements. Also confirm worst-case subprocess latency on the largest repo you'd run it against.
To executives (30 seconds):
"We have a recurring class of incident: something ships that wasn't supposed to — wrong branch, uncommitted work, or a build we can't identify after the fact. This is a tiny, free, permissively-licensed library that lets our PHP app report exactly which branch and commit it's running and whether the code is in a clean state. The new release makes the commit stamp work even where we don't control the Git remote configuration, which covers our container and CI deploy paths. It closes a real reliability gap for roughly a day of engineering, adds no ongoing vendor cost, and carries no meaningful platform lock-in because we'll isolate it behind our own interface. The risk is low and the downside is contained."
To engineering leads and DevOps:
"It's a ~100-line, zero-dependency Composer package that returns origin URL, branch, commit SHA, and clean/dirty state. 1.1.0 adds
Builder::commit()— the checked-out SHA without needing anoriginremote — which is the API we'll use for build stamping since it survives CI checkouts with stripped remotes and detached-HEAD deploys. Two concrete uses: (1) a deploy preflight that hard-fails on a dirty tree or non-release branch, and (2) a/healthor admin field that reports the deployed commit, which will cut time-to-diagnosis on every future incident. Caveats to plan for: it shells out togit, sogitand.gitmust exist in the target environment (the new method drops the remote requirement but not these); it's blocking, so it does not go in hot request paths; and it has one maintainer and no dependents, so we wrap it in an internalGitStateProviderand keep our call sites to a handful. The version bump is additive, so the wrapper absorbs it with no call-site changes. Primary enforcement for branch/dirty policy still belongs in CI — this is the in-app complement, not a replacement."
To the team (what we're actually shipping):
"One small service class, one Artisan command, one health field. We'll call
commit()for stamping andbuild()only for the preflight that needs branch/dirty state. Isolated behind an interface so it's replaceable. Explicit failure behavior: if it's not a repo, orgitisn't installed, log and degrade — don't crash the app. Not in the request hot path. Bump to 1.1.0 is additive, no migration. Ship it this sprint; revisit only if we outgrow read-only inspection."
How can I help you explore Laravel packages today?