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

Git State Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. sebastian/git-state is not a Laravel package — there is no service provider, config file, facade, or published assets. It is a single-purpose, dependency-free PHP library with two entry points: Builder::build(), which returns either a state object (originUrl(), branch(), commit(), isClean(), status()) or false, and — new in 1.1.0 — Builder::commit(), which returns the checked-out commit hash (or false) without requiring an origin remote. Architecturally this is still a utility, not infrastructure: it fits cleanly as an injected collaborator behind a small application-owned adapter (interface + DTO), used by Artisan commands, deployment gates, and CI scripts. The new commit() entry point is meaningful for us because it decouples the most commonly needed datum (the SHA) from the heaviest and most environment-sensitive part of the library (remote resolution and working-tree status). It does not belong in the HTTP request path, in domain services, or anywhere a container may lack a .git directory.

Integration feasibility. Trivial mechanically and unchanged by this release: one composer require, one class, no transitive dependencies (composer show --tree will show a leaf), no autoload surprises. 1.1.0 is an additive minor with no declared breaking changes, so the upgrade from 1.0.0 is a caret-range no-op for any consumer already pinned at ^1.0. The real feasibility questions remain environmental, not code-level:

Check Why it matters Verdict
PHP floor Sebastian Bergmann packages track current PHP aggressively; must confirm 1.1.0 did not raise the floor Must verify against our runtime before adopting; a floor above our fleet is a hard stop
git binary present Library shells out; FPM/Alpine/Slim images frequently omit git Blocker for runtime use; fine for CI runners
.git directory present Most production artifacts are built from a tarball or docker build context excluding .git Both build() and commit() return false in the exact environments we care most about
exec/shell_exec enabled Common hardening in shared hosting / hardened containers Silent breakage risk
Git ≥ 2.35 "dubious ownership" Containers running as non-owner UID hit safe.directory errors Real operational gotcha
origin remote configured Previously required by build(); commit() removes this dependency Partially retired as a blocker — see below

Technical risk.

  • Ambiguous failure contract — materially improved, not eliminated. In 1.0.0, build() returning false conflated "not a Git repository" with "no origin remote." With Builder::commit() available independently of origin, the adapter can now disambiguate positively: if commit() yields a hash, the repository exists and only the remote is missing. The residual ambiguity is in the negative direction — commit() returning false still covers "no .git", "git binary missing", and "exec disabled" identically. Net: our adapter needs to call both methods to classify, which is one extra subprocess but removes the single worst debugging trap in the original assessment.
  • Open question on build() semantics. The release note says commit() works "without requiring an origin remote," which strongly implies build() still returns false when no origin exists. If so, existing call sites that use build() for branch/clean checks will still fail in remote-less checkouts (common in vendor forks, air-gapped mirrors, and some CI mirrors). This must be confirmed from source before we write the adapter, because it determines whether build() is safe as a general-purpose entry point.
  • Bus factor / dependency governance. 26 stars, 0 declared dependents, one maintainer. Counterweights unchanged: the maintainer authors PHPUnit, the surface is ~100 LOC and just grew by roughly one method, BSD-3-Clause permits forking/vendoring, and the exit cost is still roughly an afternoon. This remains a low-severity, high-recoverability risk — acceptable if we accept that we may own a fork.
  • No timeout control. Unchanged. commit() is a single rev-parse HEAD and is comparatively cheap, but status() on a large monorepo can still take seconds and the API exposes no timeout knob. That call remains unacceptable in any user-facing latency path; the argument for keeping commit() out of the request path is now weaker but not absent.
  • Parsing fragility. Unchanged for status() (raw porcelain, no --porcelain=v2, detached-HEAD branch semantics undocumented) and for originUrl() (single remote). commit() sidesteps parsing entirely for the SHA, which is the field we are most likely to expose.
  • Opportunity score 59.35 / 9.17 composite suggests a narrow, well-built tool — not a platform. 1.1.0 sharpens the tool rather than broadening it; scope it accordingly.

Key questions for the team.

  1. Does 1.1.0 change the PHP constraint? Confirm the floor is unchanged from 1.0.0 before we widen the caret range in production manifests.
  2. Does build() still return false when no origin remote exists, or did 1.1.0 relax that too? This determines whether build() is a safe general entry point or only a CI-with-remote entry point.
  3. What is the package's PHP constraint versus our minimum supported runtime across app, CI, and legacy services?
  4. Where does the artifact actually live — is .git present in production, or is this strictly a CI/deploy-time tool?
  5. Do we already have exec()-based Git calls or laravel/git in the codebase? If so, is standardizing worth a dependency?
  6. Is the business need "block bad deploys" (CI-time, high value) or "expose version metadata at runtime" (build-time injection is still cheaper and safer, but commit()-only stamping is now a viable middle path)?
  7. Who owns the adapter, and is the fork-and-vendor fallback documented and accepted?

Integration Approach

Stack fit. Works alongside Laravel 10/11/12 as a plain library. Recommended shape, revised for 1.1.0:

  • app/Git/GitStateProvider.php — interface (commit(): ?string, state(): GitState|Unavailable)
  • app/Git/PackageGitStateProvider.php — adapter wrapping Builder, using commit() as the cheap, remote-independent probe and build() only when branch/clean/remote are actually needed; translates false into a typed Unknown/NotAvailable result rather than nullable ambiguity
  • app/Git/GitState.php — readonly DTO (branch, commit, originUrl, clean, availability)
  • GitStateServiceProvider — singleton binding, config-driven enable/disable flag

This gives us one testable seam, keeps the third-party type out of application code, and makes the fork/vendor swap a one-file change. The adapter's commit() path should be the default resolution strategy; build() is the escalation when more context is required.

Migration path.

  1. Upgrade in place. Bump ^1.0 → ^1.1 for any existing consumer; additive minor, no code change expected. Verify in CI that no build() call site regressed.
  2. Dev dependency first. composer require --dev sebastian/git-state for CI helpers and test-time version stamping. Zero production blast radius.
  3. Replace ad-hoc shell calls. Any exec('git rev-parse HEAD') scattered in scripts or Artisan commands folds into the adapter — and now maps 1:1 onto Builder::commit(), which is a cleaner substitution than routing through build().
  4. Production only if genuinely required. If we need runtime version metadata, prefer a build-time injected APP_COMMIT/APP_VERSION env var; promote the package to a runtime dependency only where the SHA must reflect the actual checkout on disk. commit() makes this narrower runtime use case materially safer than it was at 1.0.0.
  5. Decommission check. After two release cycles, confirm no orphaned shell-based Git calls remain.

Compatibility.

  • License BSD-3-Clause — compatible with commercial/closed distribution; attribution only.
  • Semver 1.x; pin with caret and let Dependabot surface majors (which will be PHP-floor bumps). 1.1.0 confirms the maintainer is shipping additive minors rather than churning the API — a good signal for caret pinning.
  • Cross-platform: Linux/macOS CI and dev are safe; Windows developer machines are untested by us — validate on one Windows laptop if we have any.
  • CI: ensure actions/checkout runs with fetch-depth: 1 but the working tree intact. commit() reads HEAD only, so shallow clones are sufficient; repositories checked out without an origin remote (mirrors, vendored forks) are now usable for SHA extraction but still not for build()-derived branch/remote data unless question 2 resolves favourably.

Sequencing (4–6 weeks, low effort — largely unchanged, with phase 4 simplified).

Phase Deliverable Exit criteria
1 (wk 1) Spike: adapter + Artisan git:state command Works locally, on CI, and correctly reports unavailability when .git absent; correctly distinguishes "no origin" from "no repo" using commit()
2 (wk 1–2) CI guardrail: fail on dirty tree / wrong branch Enforced as a required status check on protected branches; team agrees on policy
3 (wk 3) Deployment gate in release pipeline Rehearsed rollback if check mis-fires; documented override
4 (wk 4–5) /version or health metadata endpoint (cached), backed by commit() only P95 latency impact < 2ms via cache; no status() call on the request path; endpoint documented
5 (wk 6) Runbook + ADR ("why this over laravel/git", "why commit() over build()") On-call can triage the common failures unaided

Operational Impact

Maintenance. Ongoing cost is dominated not by the package (frozen-size, infrequent releases, no transitive dependency surface) but by governance: a single-maintainer dependency in a production manifest. Controls: caret pin, automated dependency PRs, and a standing decision that if the maintainer archives or breaks us, we vendor the ~100 LOC under our namespace within a sprint. The artifact that will actually accrue maintenance is our adapter and its tests — budget a few hours per PHP minor upgrade. 1.1.0 adds no maintenance surface; it removes a workaround we would otherwise have written by hand.

Support. Support load remains concentrated in misdiagnosis of the ambiguous false return, but the surface has shrunk. Required runbook entries:

  • "Git not installed" → git --version in the runtime container; neither build() nor commit() gives a clear signal here.
  • "Not a repository" → both methods return false; the adapter must disambiguate before logging.
  • "No origin remote" → new guidance: commit() still succeeds, build() does not. Route to commit()-only extraction rather than treating this as a failure.
  • "Dubious ownership" → Docker UID mismatch; fix with ownership alignment, not safe.directory blanket trust.
  • "Dirty tree blocks deploy" → expected behavior; route to the developer, not on-call.

Instrumentation: log at info on success with commit/branch, warning on false with the reason (now including a distinct "remote absent, commit available" reason code), error only on unexpected exceptions. Dashboards should alert on repeated false at deploy time (indicates artifact/pipeline regression), not on individual occurrences.

Scaling. Every call is a blocking subprocess spawn. The two entry points now have materially different cost profiles:

  • commit() — single rev-parse, roughly 5–15 ms, no working-tree scan, no network-adjacent remote resolution. Affordable once per process; still not per-request by default.
  • build() / status() — can reach seconds on large trees. Never on a per-request path.
  • Cache the DTO in Redis/config cache with a TTL matched to deploy cadence (or invalidate on deploy) for any endpoint serving it.
  • For high-QPS services, inject at build time instead; treat the library as a CI-only tool.
  • On serverless/FaaS, assume git and .git are absent and design the "unknown" path as the default, not the exception.

Failure modes.

Mode Detection Impact Mitigation
git binary missing false from both methods Runtime endpoint degrades Fail closed at deploy gate; fail open ("unknown") at runtime
No .git in artifact false from both methods Silent loss of metadata Build-arg injection fallback
No origin remote commit() succeeds, build() returns false Previously indistinguishable from "no repo"; now diagnosable Use commit() for SHA; skip branch/remote claims
exec disabled PHP warning/exception Hard failure Detect at boot, disable feature flag
Repo ownership mismatch Git exit error false on an otherwise valid checkout Fix container UID; document
Large repo status() latency Latency spike Request timeout if mis-placed Never call in request path; cache
Ambiguous false (negative case) Log noise, wrong alerts Debugging time Adapter-level disambiguation; reason codes
Upstream PHP floor bump Composer resolution failure Blocked upgrade Dependabot + fork contingency
Maintainer abandonment Stale releases Low, given size Vendor/fork plan pre-approved

Ramp-up. A competent Laravel developer is productive in under an hour — the API is now five getters, a boolean, and a standalone commit(). What does need ramp-up is the judgment layer:

  • A 15-minute onboarding demo covering both entry points ((new Builder)->commit() and (new Builder)->build()->branch()), plus the adapter walkthrough.
  • Explicit guidance on which method to reach for: commit() for version stamping and existence checks, build() only when branch, remote, or cleanliness is genuinely required.
  • A copy-paste CI snippet in the platform template so teams don't each invent a guardrail.
  • Explicit guidance in the ADR on where not to use it: request lifecycle, domain logic, anywhere .git isn't guaranteed.
  • A single owner (platform/DevOps) for the CI gate policy so "which branches may deploy" is decided once, not per team.

Recommendation. Adopt narrowly and deliberately, and treat 1.1.0 as a modest strengthening of the case: as a dev/CI dependency for deployment guardrails and version stamping, fronted by a thin application adapter that uses commit() as its default probe, with a pre-approved fork path and a build-time metadata fallback for production. The new method removes the "no origin remote" ambiguity that was the single largest diagnostic liability in the 1.0.0 assessment, and it opens a defensible narrow runtime use case (internal admin/version endpoints where .git is guaranteed) without exposing us to status() latency. Do not adopt as a general runtime dependency in request-handling containers, and confirm the build()-without-origin semantics before treating it as a general-purpose entry point. On that basis the risk is low, the value (blocking dirty-tree and wrong-branch deploys, plus reliable SHA stamping) is concrete and immediately measurable, and the total cost of reversal remains trivial.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource