I maintain gitstats, a Python CLI that turns a git repo into a static HTML report — commit activity, author stats, code ownership, a year-by-year history of the project. It's a modernized fork of a tool that's been around since 2007.
For a while there's been one thing bugging me. The reports are nice, but they live behind a link nobody clicks. Meanwhile, the top of every README has a row of badges — CI status, PyPI version, download counts — and none of them say anything about the repository itself. How active is this project? When was the last commit? How many people have touched it?
That data is sitting right there in the report. So in v2.6.0, the report now ships it as a badge.
The badge
Every report gets a badge.svg written next to index.html:
[](https://you.github.io/your-repo/)
It shows the live commit count, styled like a shields.io badge, and clicking it lands on the full report. Because it's regenerated with the report, the number is never stale.
Here's the part I want to talk about, because it shaped the whole design: there is no badge server.
Static hosting can't do query params
My first instinct was the shields.io model — one endpoint, customization via URL: badge.svg?metric=last-commit&color=green. That requires a server rendering SVGs per request. gitstats produces static files that people host on GitHub Pages, GitLab Pages, or some nginx box inside a company network. A static file server ignores everything after the ?.
So customization had to work with files and config instead of query strings. It ended up in three layers:
1. One file per metric. The report includes a badges/ directory with every metric pre-rendered: commits.svg, last-commit.svg (shows e.g. Aug 2026), authors.svg, files.svg, lines.svg. Changing what the badge says is just changing the URL. Pre-rendering five small SVGs costs nothing; a query parameter was never actually necessary.
2. Config keys for appearance. Label text, color (shields color names, hex, or any SVG color), and style (flat or flat-square) come from gitstats' existing config system:
gitstats -c badge_metric=last-commit -c badge_color=green -c badge_style=flat-square . report
3. A bridge to shields.io for everything else. Each metric is also exported as badges/<metric>.json in the shields endpoint schema. If someone wants style=for-the-badge or a logo or whatever shields supports, they point img.shields.io/endpoint?url=... at the JSON and get all of it — while the numbers still come from their own report. I didn't have to rebuild shields' customization surface, and I didn't have to run their infrastructure either.
The SVG itself borrows shields' rendering tricks: text drawn at 10x scale then scaled down for crisp edges, and textLength to pin the layout so a 7-digit commit count doesn't overflow the badge. Width is estimated with a rough per-character table; textLength forgives the estimate being a little off.
For GitHub repos it's three lines
The gitstats-action generates the report and deploys it to Pages:
- uses: shenxianpeng/gitstats-action@v1
with:
deploy-to-pages: true
After the run, the job summary contains the badge markdown for your repo, ready to paste. Not on GitHub? Host the report directory anywhere static — the badge is just another file in it. There's a GitLab CI example in the README.
Also in v2.6.0
gitstats --serve . generates the report and serves it locally in one step, printing the URL. Small thing, but it removes the "now go find index.html" step, and it's how I preview changes while working on the tool.
Dogfooding
The gitstats README now carries its own badge, served from the demo report — which currently reads 532 commits across 19 years, since the history goes back to the original 2007 project. There's also a gallery of reports for large open-source projects, regenerated weekly.
Repo: https://github.com/shenxianpeng/gitstats
If you try it, I'd genuinely like to hear what other metrics would be worth a badge. Test coverage came up already; I'm not sure a git-history tool should be the one reporting that. Opinions welcome.
Top comments (0)