A self-hosted, single-binary web service that clones a public git repository and renders its commit history as a timeline-style graph — inspired by the layouts GitKraken and SourceTree use, but stripped down to one screen and one container.
Designed to be deployed as a subdomain (
gitgraph.example.com) behind your usual reverse proxy. Everything ships off the container root; the SPA is embedded in the Go binary.
- Single
docker compose upfrom a fresh clone — backend, frontend and the bare git clone all live in one image. - Timeline layout: the day a commit was authored maps to a vertical slot, with the slot's height proportional to commits-in-day.
- All branches from
originare drawn as colored lanes, with vertical branch-name labels on each active HEAD lane. - Merge commits drawn with cubic Bézier curves; shallow boundaries shown as dashed stubs.
- Hover any commit → its lane stays bright while every other lane and commit dims out.
- Infinite scroll: an
IntersectionObserver200 px above the bottom callsPOST /api/expand, which runsgit fetch --shallow-since=…and reloads the graph. After three consecutive expansions return zero new commits the button is replaced with "End of history". - Background
git fetch --all --force --pruneon a configurable interval.
cp .env.example .env
# Edit REPO_URL in .env if you want a different repository.
docker compose up --buildOpen http://localhost:8080. While the initial clone is in flight
/healthz returns 503 and the UI shows a "Cloning…" placeholder.
All settings come from .env. See .env.example for the canonical defaults.
| Variable | Default | Meaning |
|---|---|---|
REPO_URL |
https://github.com/Mindburn-Labs/helm-ai-kernel |
Public repository to clone. |
INITIAL_DEPTH_DAYS |
10 |
Shallow window on first start. |
EXPAND_DEPTH_DAYS |
14 |
How many extra days each "Load more" / expand adds. |
FETCH_INTERVAL_SECONDS |
300 |
Background git fetch --force --prune cadence. |
PORT |
8080 |
HTTP listen port inside the container. |
DATA_DIR |
/data |
Where the bare clone and state.json live. |
LOG_LEVEL |
info |
debug, info, warn, or error. |
GITHUB_TOKEN |
empty | Reserved. Private repositories are not supported yet. |
HOST_PORT |
8080 |
Host-side port for the published container. |
Changing REPO_URL and restarting wipes /data/repo and re-clones from
scratch.
| Method | Path | Description |
|---|---|---|
| GET | /healthz |
200 once the initial clone has finished, 503 until then. |
| GET | /api/status |
repo_url, current_since, last_fetch, ready, branches, commits count. |
| GET | /api/commits |
Graph payload — commits + lanes + edges. Filters: since, until (RFC 3339). |
| POST | /api/expand |
Extend shallow history by EXPAND_DEPTH_DAYS. Returns the new since. |
Full JSON contract: see SPEC1.md section 6.
┌────────────────────────────────────────────────┐
│ Docker container (single Go binary) │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ HTTP server (:8080) │ │
│ │ - GET / embedded Svelte SPA│ │
│ │ - GET /api/commits graph JSON │ │
│ │ - POST /api/expand shallow expansion │ │
│ │ - GET /api/status │ │
│ │ - GET /healthz │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ Background fetcher (goroutine) │ │
│ │ every FETCH_INTERVAL_SECONDS: │ │
│ │ git fetch --all --force --prune │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ Graph cache │ │
│ │ 2-pass lane / edge builder, in-memory. │ │
│ │ Invalidated on fetch + expand. │ │
│ └──────────────────────────────────────────┘ │
└─────────────────┬──────────────────────────────┘
│
▼
/data (volume)
├── repo/ bare git clone
└── state.json repo_url, current_since, last_fetch
git-graph/
├── cmd/server/ entrypoint (lifecycle, signals, embed wiring)
├── embed.go //go:embed all:web/dist → DistFS
├── internal/
│ ├── api/ chi router + REST + SPA fallback
│ ├── config/ env parsing
│ ├── git/ git CLI wrapper, log parser, refs reader
│ ├── graph/ lane / edge builder, palette, in-memory cache
│ ├── state/ state.json read / write
│ └── worker/ background fetcher
├── web/ Svelte 5 + Vite + TS SPA
│ ├── src/lib/api.ts typed client
│ ├── src/lib/store.svelte.ts $state runes
│ └── src/lib/graph/ Timeline, Graph, Lane, BranchHead, …
├── Dockerfile three-stage build
├── docker-compose.yml
└── SPEC1.md design document
Two-process workflow: run the Go backend as usual, then run Vite's dev
server which proxies /api/* and /healthz back to it.
# terminal 1: backend with hot rebuild via your tool of choice (or just rerun)
REPO_URL=https://github.com/Mindburn-Labs/helm-ai-kernel \
DATA_DIR=$(mktemp -d) \
go run ./cmd/server
# terminal 2: frontend with HMR
cd web && npm install && npm run dev
# → http://localhost:5173Production-style build (single binary serving the embedded SPA):
cd web && npm ci && npm run build
cd .. && go build -o git-graph ./cmd/server
DATA_DIR=$(mktemp -d) ./git-graphgo test ./...The graph builder has unit coverage for the five synthetic cases from
SPEC1.md section 7 (linear history, simple merge, octopus, long-lived
branch with back-merges, shallow boundary) plus slice and head-branch
attachment.
MIT — see LICENSE.