A Symfony bundle that fetches commit history from GitLab or GitHub repositories and displays them in a beautiful vertical timeline UI — similar to the Symfony releases page.
- Multi-Provider Support: Fetch commits from GitLab or GitHub repositories
- REST API Integration: Uses official APIs with pagination to fetch all commits
- Self-Hosted Support: Works with GitLab self-hosted instances and GitHub Enterprise
- Year Filtering: Filter commits by year with a dropdown, fetched via API with per-year caching
- Dependency Tracking: Automatically detect and display dependency changes (composer.json, package.json, etc.)
- Vertical Timeline UI: Beautiful, responsive timeline inspired by Symfony releases
- Caching: 1-hour cache by default to reduce API calls (cached per year)
- Private Repository Support: Supports authentication tokens for private repositories
- Standalone Page: Ready-to-use page with embedded CSS
- Embeddable Fragment: Include the timeline in your own layouts
- PHP 8.2 or higher
- Symfony 6.4+ or 7.0+
Install the bundle using Composer:
composer require spiriitlabs/commit-history-bundleIf you're using Symfony Flex, the bundle will be automatically registered. Otherwise, add it to your config/bundles.php:
<?php
return [
// ...
Spiriit\Bundle\CommitHistoryBundle\SpiriitCommitHistoryBundle::class => ['all' => true],
];Create a configuration file config/packages/spiriit_commit_history.yaml:
spiriit_commit_history:
provider: gitlab
feed_name: "My Project"
cache_ttl: 3600
available_years_count: 6
gitlab:
project_id: "123" # or "group%2Fproject" for path
base_url: "https://gitlab.com" # or your self-hosted GitLab URL
token: "%env(GITLAB_TOKEN)%" # optional, only for private repos
ref: "main" # optional, defaults to default branchspiriit_commit_history:
provider: github
feed_name: "My Project"
cache_ttl: 3600
available_years_count: 6
github:
owner: "myorg"
repo: "myproject"
base_url: "https://api.github.com" # or your GitHub Enterprise URL
token: "%env(GITHUB_TOKEN)%" # optional, only for private repos
ref: "main" # optional, defaults to default branch| Option | Type | Default | Description |
|---|---|---|---|
provider |
string | required | Provider type: gitlab or github |
feed_name |
string | Commits |
Display name for the timeline |
cache_ttl |
integer | 3600 |
Cache duration in seconds |
available_years_count |
integer | 6 |
Number of years to show in the year filter dropdown |
track_dependency_changes |
boolean | true |
Enable dependency change detection |
dependency_files |
array | [composer.json, composer.lock, package.json, package-lock.json] |
Files to track for dependency changes |
| Option | Type | Default | Description |
|---|---|---|---|
project_id |
string | required | Numeric ID or URL-encoded path |
base_url |
string | https://gitlab.com |
GitLab instance URL |
token |
string | null |
Personal Access Token (for private repos) |
ref |
string | null |
Branch or tag name |
| Option | Type | Default | Description |
|---|---|---|---|
owner |
string | required | Repository owner |
repo |
string | required | Repository name |
base_url |
string | https://api.github.com |
GitHub API URL |
token |
string | null |
Personal Access Token (for private repos) |
ref |
string | null |
Branch or tag name |
Add the routes to your config/routes.yaml:
spiriit_commit_history:
resource: '@SpiriitCommitHistoryBundle/Resources/config/routes.php'
type: php
prefix: /commits # Customize the URL path hereVisit /commits (or your configured prefix) to see the timeline. The page displays commits for the current year by default, with a dropdown to select other years.
You can also directly access a specific year by adding the year query parameter:
/commits?year=2024
You can embed the timeline in your own templates by injecting the FeedFetcherInterface service:
<?php
namespace App\Controller;
use Spiriit\Bundle\CommitHistoryBundle\Service\FeedFetcherInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class MyController extends AbstractController
{
public function changelog(Request $request, FeedFetcherInterface $feedFetcher): Response
{
// Get year from query parameter, defaults to current year
$year = $request->query->getInt('year') ?: (int) date('Y');
$commits = $feedFetcher->fetch($year);
$availableYears = $feedFetcher->getAvailableYears();
return $this->render('changelog.html.twig', [
'commits' => $commits,
'available_years' => $availableYears,
'selected_year' => $year,
]);
}
}Then in your template:
{# templates/changelog.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<h1>Changelog</h1>
{{ include('@SpiriitCommitHistory/_commits.html.twig', {
commits: commits
}) }}
{% endblock %}Refresh the commit cache and re-detect dependency badges:
# Refresh current year (default)
php bin/console spiriit:commit-history:refresh
# Refresh a specific year
php bin/console spiriit:commit-history:refresh 2024
# Refresh all available years
php bin/console spiriit:commit-history:refresh --all
php bin/console spiriit:commit-history:refresh -aThis command clears the commits list cache and dependency detection cache, then fetches fresh data from the provider. Each year is cached separately.
Clear all caches (commits list, dependency detection, and dependency changes):
# Clear all caches for all years
php bin/console spiriit:commit-history:clear
# Clear caches for a specific year only
php bin/console spiriit:commit-history:clear 2024This command clears all cached data without re-fetching. Useful if you want to force a complete refresh on the next page load.
The bundle automatically detects commits that modify dependency files (composer.json, package.json, etc.) and displays a "DEPENDENCIES" badge on these commits. Clicking the badge reveals the list of added, updated, or removed dependencies.
-
Badge Detection: When commits are loaded, the bundle checks if any dependency files were modified. This check is cached per-commit (forever, since commit content is immutable).
-
Lazy Loading: The dependency details are only fetched when a user clicks the badge, reducing initial page load time.
-
Per-Commit Caching: Both badge detection and dependency details are cached per-commit. Since commit IDs are immutable, this cache never needs to expire.
spiriit_commit_history:
# Enable or disable dependency tracking entirely (default: true)
track_dependency_changes: true
# Files to track for dependency changes
dependency_files:
- composer.json
- composer.lock
- package.json
- package-lock.jsonThe bundle uses a tagged service pattern for diff parsers. You can add support for additional dependency file formats by creating your own parser.
- Create a parser class implementing
DiffParserInterface:
<?php
namespace App\Service\DiffParser;
use Spiriit\Bundle\CommitHistoryBundle\DTO\DependencyChange;
use Spiriit\Bundle\CommitHistoryBundle\Service\DiffParser\DiffParserInterface;
class GemfileDiffParser implements DiffParserInterface
{
public function supports(string $filename): bool
{
return basename($filename) === 'Gemfile.lock';
}
/**
* @return DependencyChange[]
*/
public function parse(string $diff, string $filename): array
{
// Parse the diff and return DependencyChange objects
$changes = [];
// ... your parsing logic
return $changes;
}
}- Tag your service:
services:
App\Service\DiffParser\GemfileDiffParser:
tags: ['spiriit_commit_history.diff_parser']- Add the file to the tracked files:
spiriit_commit_history:
dependency_files:
- composer.json
- composer.lock
- package.json
- package-lock.json
- Gemfile.lock # Your new fileFor private repositories, create a Personal Access Token:
- Go to GitLab → User Settings → Access Tokens
- Create a token with
read_apiscope - Add to your
.envfile:
GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxxFor private repositories, create a Personal Access Token:
- Go to GitHub → Settings → Developer settings → Personal access tokens
- Create a token (classic or fine-grained) with
reposcope - Add to your
.envfile:
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxOverride the default templates by creating files in your project:
templates/bundles/SpiriitCommitHistoryBundle/timeline.html.twig
templates/bundles/SpiriitCommitHistoryBundle/_commits.html.twigThe templates use BEM naming convention:
| Class | Description |
|---|---|
.timeline |
Main timeline container |
.timeline--empty |
Empty state modifier |
.timeline__filter |
Year filter form container |
.timeline__filter-select |
Year dropdown select |
.timeline__item |
Individual commit item |
.timeline__title |
Commit title |
.timeline__meta |
Metadata container |
.timeline__hash |
Commit hash |
.timeline__date |
Commit date |
.timeline__author |
Author name |
.timeline__badge |
Dependency badge (DEPENDENCIES label) |
.timeline__badge--loading |
Loading state for badge |
.timeline__dependencies |
Dependencies detail container |
.timeline__dependencies-list |
List of dependency changes |
.timeline__dependencies-item |
Individual dependency change |
.timeline__dependencies-name |
Package name |
.timeline__dependencies-version |
Version information |
.timeline__dependencies-type |
Change type indicator dot |
.timeline__dependencies-type--added |
Added dependency (green) |
.timeline__dependencies-type--updated |
Updated dependency (orange) |
.timeline__dependencies-type--removed |
Removed dependency (red) |
Run the test suite:
vendor/bin/phpunitContributions are welcome! Please feel free to submit a Pull Request.
This bundle is released under the MIT License. See the LICENSE file for details.
For questions and support, please open an issue on GitHub or contact dev@spiriit.com.