spiriitlabs/commit-history
Laravel package that brings Git commit history into your application, with a simple built-in interface to browse recent commits, inspect changes, and keep project history accessible to your team without leaving the admin area.
composer require spiriitlabs/commit-history
The package is auto-discovered by Laravel. On a fresh install, publish the config so you can see the provider/credential keys the library expects:
php artisan vendor:publish --tag=commit-history-config
Add credentials to .env (the library reads GitHub/GitLab tokens from config):
COMMIT_HISTORY_GITHUB_TOKEN=
COMMIT_HISTORY_GITLAB_TOKEN=
Where to look first: open the published config/commit-history.php and the package README's "Usage" section side by side. The README is the authoritative source for the exact class/method names (e.g. a CommitHistory entry point or a provider factory) — confirm the factory names before wiring anything to your app, because that surface is small and easy to adapt to.
First use case: render recent activity on an internal dashboard. A minimal controller:
use SpiriitLabs\CommitHistory\CommitHistory;
Route::get('/repo/{repo}/commits', function (CommitHistory $history, string $repo) {
return $history
->for($repo) // "owner/repo" or "group/project"
->from('github') // provider
->get(); // collection of commits
});
If your README's method names differ, the concept is identical: build a history from a repo identifier + provider, then iterate commits (SHA, message, author, date). Treat this first call as a smoke test that your token and repo identifier resolve correctly before you build anything on top of it.
Wrap it in a dedicated service. Don't call the library from controllers or Blade. A thin CommitRepository service gives you a single seam to mock in tests and to swap providers:
final class CommitRepository
{
public function __construct(private CommitHistory $history) {}
public function recent(string $repo, string $provider = 'github'): array
{
return $this->history->for($repo)->from($provider)->get()
->map(fn ($c) => [
'sha' => $c->sha ?? $c->hash,
'message' => $c->message,
'author' => $c->author,
'date' => $c->date,
])->all();
}
}
Persist to a table for anything user-facing. Hitting the GitHub/GitLab API per page view will burn rate limits fast. Sync commits into a commits table keyed by repo + sha and render from the database:
foreach ($commits as $commit) {
Commit::updateOrCreate(
['repository' => $repo, 'sha' => $commit->sha],
['message' => $commit->message, 'author' => $commit->author, 'committed_at' => $commit->date]
);
}
updateOrCreate on sha makes re-runs idempotent — important because you'll re-run the sync on a schedule.
Drive it from a scheduled command, not a request.
// app/Console/Commands/SyncCommits.php
foreach (config('repositories.tracked') as $repo) {
SyncRepositoryCommits::dispatch($repo);
}
// routes/console.php
Schedule::command('commits:sync')->hourly();
Dispatch a queued job per repo so one slow/failing repo doesn't block the others, and give the job a uniqueFor / WithoutOverlapping guard to prevent pile-ups if a previous run is still going.
Abstract the provider. Config-drive which provider a repo uses so the same service handles GitHub and GitLab:
// config/repositories.php
return [
'tracked' => [
'acme/api' => ['provider' => 'github'],
'acme/infra' => ['provider' => 'gitlab'],
],
];
Use it for release notes / changelog generation. Filter commits since a tag or date and pass them to a formatter or AI summarizer — this is where the package shines compared to hand-rolling HTTP calls.
Cache read paths. Wrap dashboard reads in Cache::remember("commits.{$repo}", now()->addMinutes(15), ...) rather than caching the API call itself; the DB is your cache of record.
X-RateLimit-Remaining) so you can debug 403s quickly.repo scope silently returns nothing for private repos (or 404s). Same for GitLab read_repository vs api scopes. If you get an empty collection with no exception, suspect scope first.owner/repo; GitLab is often group/subgroup/project and self-hosted GitLab needs a custom base URL in config. Don't assume one string works everywhere — normalize in your own service.Http::fake(), not the real API. The library ultimately makes HTTP calls, so Http::fake(['api.github.com/*' => Http::response($fixture)]) gives you deterministic tests without tokens. Build fixtures from one real response and reuse them.How can I help you explore Laravel packages today?