Files
git-contribution-graph/README.md
T
haylan 5b07eae672 docs: update installation guide to use pre-built image
Replace the clone-and-build workflow with a docker-compose snippet
that pulls the image directly from the Gitea container registry.
Add semver tag reference and rename steps to match the new flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 12:39:33 +02:00

241 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# git-contribution-graph
A self-hosted Symfony service that merges contribution data from **GitHub**, **GitLab** and **Gitea** into a single GitHub-style heatmap SVG you can embed anywhere.
```
https://your-host/graph.svg?theme=dark
```
![Example graph](https://gitgraph.arthurerlich.de/graph.svg)
---
## Features
- **Three platforms** — GitHub, GitLab (cloud or self-hosted), Gitea (self-hosted)
- **Merged heatmap** — contributions from all sources are summed per day
- **GitHub colour palette** — exact dark/light theme tokens
- **Embeddable** — returns `image/svg+xml`, works in any `<img>` tag or Markdown
- **Cached** — responses cached for 1 hour, safe to embed in public READMEs
- **Graceful degradation** — if one platform fails, the others still render
---
## Deploy
### Requirements
- Docker + Docker Compose
### 1. Create a `docker-compose.yml`
Use the pre-built image — no need to clone the repo:
```yaml
services:
graph:
image: git.arthurerlich.de/haylan/git-contribution-graph:latest
ports:
- "8080:8080"
env_file:
- .env.local
volumes:
- cache:/var/www/html/var/cache
volumes:
cache:
```
The image is published to the Gitea container registry. Pull it manually with:
```bash
docker pull git.arthurerlich.de/haylan/git-contribution-graph:latest
```
Semver tags are published (`0`, `0.0`, `0.0.1`) alongside `latest` — see the [container registry](https://git.arthurerlich.de/haylan/-/packages/container/git-contribution-graph/latest) for all available tags.
### 2. Configure
Create a `.env.local` file next to your `docker-compose.yml`:
```dotenv
APP_SECRET=<generate with: openssl rand -hex 16>
# GitHub
GITHUB_USER=your-github-username
GITHUB_TOKEN=ghp_…
# GitLab (omit GITLAB_URL to use gitlab.com)
GITLAB_USER=your-gitlab-username
GITLAB_TOKEN=glpat-…
GITLAB_URL=
# Gitea
GITEA_USER=your-gitea-username
GITEA_TOKEN=…
GITEA_URL=https://git.example.com
# Optional: restrict to specific hostnames (comma-separated), leave empty to allow all
ALLOWED_HOSTS=
```
Only configure the platforms you use — unused ones are silently skipped.
### 3. Start
```bash
docker compose up -d
```
The service listens on **port 8080** by default. Put Traefik or nginx in front of it for HTTPS.
### 4. Verify
```bash
curl http://localhost:8080/health
# {"status":"ok"}
```
---
## API
### `GET /graph.svg`
| Parameter | Required | Description |
|---|---|---|
| `theme` | ✗ | `dark` (default) or `light` |
All credentials are configured via environment variables — see [Deploy](#deploy).
---
## Embedding in a README
```markdown
<!-- Dark theme (default) -->
![Contribution Graph](https://your-host/graph.svg)
<!-- Light theme -->
![Contribution Graph](https://your-host/graph.svg?theme=light)
```
---
## Token setup
### GitHub
**Fine-grained token (recommended):**
1. Go to **Settings → Developer settings → Personal access tokens → Fine-grained tokens**
2. Set **Resource owner** to your account
3. Under **Permissions → Account permissions**, set **Contribution activity** → Read-only
4. Generate and copy the token
**Classic token:** create a token with the `read:user` scope.
### GitLab
1. Go to **User Settings → Access Tokens**
2. Add a token with scopes: `read_user`, `read_api`
3. Generate and copy the token
### Gitea
1. Go to **Settings → Applications → Manage Access Tokens**
2. Add a token with permission: **user** → Read
3. Generate and copy the token
---
## Development
### Local (PHP)
```bash
# Install deps
composer install
# Run dev server
APP_ENV=dev php -S localhost:8080 -t public
# Test endpoint
curl "http://localhost:8080/graph.svg" -o graph.svg
```
### Docker (recommended)
`docker-compose.override.yml` is picked up automatically and targets the `dev` stage (Xdebug enabled, source mounted).
```bash
# Start dev container
docker compose up -d --build
# Shell into the container
docker compose exec graph sh
# Run tests inside the container
docker compose exec graph php bin/phpunit
# Disable Xdebug for faster test runs
XDEBUG_MODE=off docker compose up -d
```
Xdebug listens on port **9003**. On Linux, `host.docker.internal` is resolved via `host-gateway`.
To run with production config only (no override):
```bash
docker compose -f docker-compose.yml up -d --build
```
---
## Testing
```bash
# Run full suite
php bin/phpunit
# Human-readable output
php bin/phpunit --testdox
# Single file
php bin/phpunit tests/Unit/Service/SvgRendererTest.php
# Filter by name
php bin/phpunit --filter it_renders
```
---
## Architecture
```
GET /graph.svg?theme=dark|light
└─ GraphController
├─ host check (ALLOWED_HOSTS env, optional)
├─ cache lookup (filesystem, 1h TTL, key = "graph_{theme}")
│ └─ on miss:
│ ├─ GitHubProvider → GitHub GraphQL API (contributionCalendar)
│ ├─ GitLabProvider → GitLab REST API (/users/:id/events, paginated)
│ └─ GiteaProvider → Gitea REST API (/users/:user/heatmap)
│ each returns array<string, int> (Y-m-d => count)
│ failures are caught and logged; remaining providers still render
│ └─ merge by date (sum counts across providers)
│ └─ SvgRenderer::render()
└─ Response: image/svg+xml, Cache-Control: public max-age=3600
```
**Provider activation:** a provider only runs when its env vars are non-empty. GitHub and GitLab require `_USER` + `_TOKEN`; Gitea additionally requires `_URL`. GitLab resolves a numeric user ID from the username via a `/api/v4/users?username=` lookup before fetching events.
**SvgRenderer:** builds a 53-column × 7-row grid aligned so the last column always ends on the Saturday of the current week. Five intensity levels (0 → level 0, 13 → 1, 46 → 2, 79 → 3, 10+ → 4) mapped to GitHub's colour tokens. No external assets — the SVG is fully self-contained.
**Cache:** filesystem adapter (`var/cache/`), mounted as a Docker volume to survive container restarts. Theme is part of the cache key so dark and light are cached independently.
---
## License
MIT