Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
da5679d
Add empty Zig project
jstrieb Feb 12, 2026
6a2e061
Set up logging
jstrieb Feb 12, 2026
64503cc
Make an HTTP request
jstrieb Feb 12, 2026
6ee8c1b
Wrap HTTP client with nicer methods
jstrieb Feb 12, 2026
f893cb4
Arena allocate request bodies
jstrieb Feb 12, 2026
50bb92e
Simplify get and post args
jstrieb Feb 12, 2026
73344b9
Move HTTP client into separate file
jstrieb Feb 12, 2026
8623285
Pull GitHub contribution years from GraphQL API
jstrieb Feb 16, 2026
322b0bd
Pull initial repo stats
jstrieb Feb 16, 2026
b758e3a
Minor refactor
jstrieb Feb 16, 2026
5974224
Pull repo views
jstrieb Feb 18, 2026
a6192d5
Add basic logging
jstrieb Feb 18, 2026
2b9e70b
Get lines changed
jstrieb Feb 19, 2026
b6ef8ff
Deallocate correctly on errors in get_repos
jstrieb Feb 24, 2026
312e45d
Work around keep alive timeouts
jstrieb Feb 24, 2026
82c4407
Tweak API request delay values
jstrieb Feb 24, 2026
ed4cca8
Partial refactor to split up big function
jstrieb Feb 24, 2026
e07c288
Refactor statistics struct into separate file
jstrieb Feb 25, 2026
ce45bf2
Use probabilistic exponential backoff with jitter
jstrieb Feb 25, 2026
4c2a3a3
Fix keep alive failure retry logic once and for all
jstrieb Feb 25, 2026
adf437d
Refactor lines changed into separate function
jstrieb Feb 25, 2026
f5bbf6a
Get contribution lines early for faster runtime
jstrieb Feb 25, 2026
871a2f8
Get username only once
jstrieb Feb 27, 2026
729d421
Add very basic arg and env parsing
jstrieb Feb 27, 2026
1520a6c
Fix memory leaks
jstrieb Mar 2, 2026
1713e8d
Print usage
jstrieb Mar 2, 2026
68cd472
Parse different types of struct fields
jstrieb Mar 2, 2026
6aca7f1
Fix free_field bug, split long lines
jstrieb Mar 3, 2026
7b1d9d2
Print raw data to stdout or a file based on CLI
jstrieb Mar 3, 2026
0675cea
Refactor parts of arg parsing into helpers
jstrieb Mar 3, 2026
3a7f21e
Track different contribution types separately
jstrieb Mar 14, 2026
12191b2
Add verbose CLI flag
jstrieb Mar 14, 2026
08f3295
Don't forget to flush
jstrieb Mar 14, 2026
7b8c682
Pull user name in addition to username
jstrieb Mar 14, 2026
c963e02
Fix Chrome/Safari rendering bug (close #57, #71)
jstrieb Mar 19, 2026
44576f2
Add glob matching with tests
jstrieb Mar 20, 2026
8d45af0
Add glob matching with tests
jstrieb Mar 20, 2026
4e668f0
Merge branch 'zig' of github.com:jstrieb/github-stats into zig
jstrieb Mar 21, 2026
58b198b
Clean up and add some globbing tests
jstrieb Mar 21, 2026
38b2de5
Add --silent CLI arg
jstrieb Mar 21, 2026
6f895bb
Print CLI arg errors to stderror
jstrieb Mar 21, 2026
5a627a0
Add additional error checking to argparse
jstrieb Mar 21, 2026
44214fa
Optionally initialize stats from JSON file
jstrieb Mar 21, 2026
03a74b0
Fix minor bugs
jstrieb Mar 23, 2026
4017075
Add HTTP client retry limit
jstrieb Mar 24, 2026
804234d
Improve serialization of GraphQL variables structs
jstrieb Mar 24, 2026
ff92a40
Add pathologically slow test case
jstrieb Mar 24, 2026
854504e
Fix color memory leak
jstrieb Mar 24, 2026
0a737f5
Parse excluded repos and languages
jstrieb Mar 24, 2026
9fb4ffa
Calculate language totals with exclusions
jstrieb Mar 24, 2026
ffa8760
Aggregate statistics
jstrieb Mar 24, 2026
66a717d
Remove unused variable
jstrieb Mar 24, 2026
a4d51ed
Allow excluding private repos from stats
jstrieb Mar 24, 2026
510f668
Tweak README
jstrieb Mar 24, 2026
0f05ddf
Add logging messages
jstrieb Mar 25, 2026
0990a29
Move global allocators to arguments
jstrieb Mar 25, 2026
00061d2
Commit generated files on a separate branch
jstrieb Mar 25, 2026
51776b8
Output overview SVG
jstrieb Mar 25, 2026
ccd8ab4
Print large numbers with commas
jstrieb Mar 25, 2026
2576f12
Build languages SVG
jstrieb Mar 28, 2026
5a98fa7
Break templating into separate functions
jstrieb Mar 28, 2026
5e0c966
Clean up main function
jstrieb Mar 28, 2026
1c46654
Small fixes and tweaks
jstrieb Mar 28, 2026
f711476
Subdivide time interval when too many repos
jstrieb Mar 29, 2026
7922aca
Move many arguments to a context object
jstrieb Mar 29, 2026
85d53f1
Bump actions/checkout pinned version
jstrieb Mar 29, 2026
ca71ce6
Fix stripped debug logs in release builds
jstrieb Mar 29, 2026
471711c
Fix possible (unlikely) double counting of lines
jstrieb Mar 29, 2026
864885d
Support using custom runtime templates
jstrieb Mar 29, 2026
e53a857
Tweak timeout
jstrieb Mar 29, 2026
abde765
Cross-compile for many architectures
jstrieb Mar 29, 2026
c5314af
Add Actions workflow to build and upload releases
jstrieb Mar 29, 2026
a25c8f5
Tiny, non-functional tweak
jstrieb Mar 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 21 additions & 24 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,50 @@ name: Generate Stats Images

on:
push:
branches: [ master ]
branches:
- master
schedule:
- cron: "5 0 * * *"
workflow_dispatch:

permissions:
contents: write

defaults:
run:
shell: bash -euxo pipefail {0}

jobs:
build:
runs-on: ubuntu-latest

steps:
# Check out repository under $GITHUB_WORKSPACE, so the job can access it
- uses: actions/checkout@v3
- uses: actions/checkout@v6
- name: Checkout history branch
run: |
git config --global user.name "jstrieb/github-stats"
git config --global user.email "github-stats[bot]@jstrieb.github.io"
# Push generated files to the generated branch
git checkout generated || git checkout -b generated
git merge master

# Run using Python 3.8 for consistency and aiohttp
- name: Set up Python 3.8
uses: actions/setup-python@v4
- uses: mlugg/setup-zig@v2
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sidenote but I personally tend to pin hash versions for third party GH actions since tags can be repushed to in a compromise and actions tend to have a lot of access.

GitHub has very poor controls over action versioning.

with:
python-version: '3.8'
architecture: 'x64'
cache: 'pip'
version: 0.15.2

# Install dependencies with `pip`
- name: Install requirements
- name: Build
run: |
python3 -m pip install --upgrade pip setuptools wheel
python3 -m pip install -r requirements.txt
zig build --release

# Generate all statistics images
- name: Generate images
run: |
python3 --version
python3 generate_images.py
./zig-out/bin/github_stats
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EXCLUDED: ${{ secrets.EXCLUDED }}
API_KEY: ${{ secrets.GITHUB_TOKEN }}
EXCLUDED_REPOS: ${{ secrets.EXCLUDED_REPOS }}
EXCLUDED_LANGS: ${{ secrets.EXCLUDED_LANGS }}
EXCLUDE_FORKED_REPOS: true

# Commit all changed files to the repository
- name: Commit to the repo
run: |
git config --global user.name "jstrieb/github-stats"
git config --global user.email "github-stats[bot]@jstrieb.github.io"
git add .
# Force the build to succeed, even if no files were changed
git commit -m 'Update generated files' || true
Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Build Release Binaries

on:
push:
tags:
- '*'
workflow_dispatch:

defaults:
run:
shell: bash -euxo pipefail {0}

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- uses: mlugg/setup-zig@v2
with:
version: 0.15.2

- name: Build
run: |
zig build release

- name: Upload Release Artifacts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: |
(
cd zig-out/bin/
gh release create \
"${TAG}" \
--title "${TAG} Release" \
*
)
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,7 @@ dmypy.json

# PyCharm project files
.idea

# Zig files
.zig-cache
zig-out
165 changes: 61 additions & 104 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,153 +3,110 @@
<!--
https://github.community/t/support-theme-context-for-images-in-light-vs-dark-mode/147981/84
-->

<div align="center">
<a href="https://github.com/jstrieb/github-stats">
<img src="https://github.com/jstrieb/github-stats/blob/master/generated/overview.svg#gh-dark-mode-only" />
<img src="https://github.com/jstrieb/github-stats/blob/master/generated/languages.svg#gh-dark-mode-only" />
<img src="https://github.com/jstrieb/github-stats/blob/master/generated/overview.svg#gh-light-mode-only" />
<img src="https://github.com/jstrieb/github-stats/blob/master/generated/languages.svg#gh-light-mode-only" />
<img src="https://github.com/jstrieb/github-stats/blob/generated/overview.svg#gh-dark-mode-only" />
<img src="https://github.com/jstrieb/github-stats/blob/generated/languages.svg#gh-dark-mode-only" />
<img src="https://github.com/jstrieb/github-stats/blob/generated/overview.svg#gh-light-mode-only" />
<img src="https://github.com/jstrieb/github-stats/blob/generated/languages.svg#gh-light-mode-only" />
</a>
</div>

Generate visualizations of GitHub user and repository statistics with GitHub
Actions. Visualizations can include data for both private repositories, and for
Actions. Visualizations can include data from private repositories, and from
repositories you have contributed to, but do not own.

Generated images automatically switch between GitHub light theme and GitHub
dark theme.


## Background

When someone views a profile on GitHub, it is often because they are curious
about a user's open source projects and contributions. Unfortunately, that
user's stars, forks, and pinned repositories do not necessarily reflect the
contributions they make to private repositories. The data likewise does not
present a complete picture of the user's total contributions beyond the current
year.
When someone views a GitHub profile, it is often because they are curious about
the user's open source contributions. Unfortunately, that user's stars, forks,
and pinned repositories do not necessarily reflect the contributions they make
to private repositories. The data likewise does not present a complete picture
of the user's total contributions beyond the current year.

This project aims to collect a variety of profile and repository statistics
using the GitHub API. It then generates images that can be displayed in
repository READMEs, or in a user's [Profile
README](https://docs.github.com/en/github/setting-up-and-managing-your-github-profile/managing-your-profile-readme).
It also dumps all statistics to a JSON file that can be used for further data
analysis.

Since this project runs on GitHub Actions, no server is required to regularly
regenerate the images with updated statistics. Likewise, since the user runs the
analysis code themselves via GitHub Actions, they can use their GitHub access
token to collect statistics on private repositories that an external service
would be unable to access.

Since the project runs on GitHub Actions, no server is required to regularly
regenerate the images with updated statistics. Likewise, since the user runs
the analysis code themselves via GitHub Actions, they can use their GitHub
access token to collect statistics on private repositories that an external
service would be unable to access.

## Disclaimer

If the project is used with an access token that has sufficient permissions to
read private repositories, it may leak details about those repositories in
error messages. For example, the `aiohttp` library—used for asynchronous API
requests—may include the requested URL in exceptions, which can leak the name
of private repositories. If there is an exception caused by `aiohttp`, this
exception will be viewable in the Actions tab of the repository fork, and
anyone may be able to see the name of one or more private repositories.

Due to some issues with the GitHub statistics API, there are some situations
where it returns inaccurate results. Specifically, the repository view count
statistics and total lines of code modified are probably somewhat inaccurate.
Unexpectedly, these values will become more accurate over time as GitHub
caches statistics for your repositories. Additionally, repositories that were
last contributed to more than a year ago may not be included in the statistics
due to limitations in the results returned by the API.

For more information on inaccuracies, see issue
[#2](https://github.com/jstrieb/github-stats/issues/2),
[#3](https://github.com/jstrieb/github-stats/issues/3), and
[#13](https://github.com/jstrieb/github-stats/issues/13).
The GitHub statistics API returns inaccurate results in some situations:

- Repository view count statistics often seem too low, and many referring sites
are not captured
- If you lack permissions to access the view count for a repository, it will
be tallied as zero views – this is common for external repositories where
your only contribution is making a pull request
- Total lines of code modified may be inflated – it counts changes to files like
`package.json` that may impact the line count in surprising ways
- Only repositories with commit contributions are counted, so if you only open
an issue on a repo, it will not show up in the statistics
- Repos you created and own may not be counted if you never commit to them, or
if the committer email is not connected to your GitHub account


# Installation

<!-- TODO: Add details and screenshots -->

1. Create a personal access token (not the default GitHub Actions token) using
the instructions
[here](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token).
Personal access token must have permissions: `read:user` and `repo`. Copy
the access token when it is generated – if you lose it, you will have to
regenerate the token.
- Some users are reporting that it can take a few minutes for the personal
access token to work. For more, see
[#30](https://github.com/jstrieb/github-stats/issues/30).
2. Create a copy of this repository by clicking
[here](https://github.com/jstrieb/github-stats/generate). Note: this is
**not** the same as forking a copy because it copies everything fresh,
without the huge commit history.
3. Go to the "Secrets" page of your copy of the repository. If this is the
README of your copy, click [this link](../../settings/secrets/actions) to go
to the "Secrets" page. Otherwise, go to the "Settings" tab of the
newly-created repository and go to the "Secrets" page (bottom left).
4. Create a new secret with the name `ACCESS_TOKEN` and paste the copied
personal access token as the value.
5. It is possible to change the type of statistics reported by adding other
repository secrets.
- To ignore certain repos, add them (in owner/name format e.g.,
`jstrieb/github-stats`) separated by commas to a new secret—created as
before—called `EXCLUDED`.
- To ignore certain languages, add them (separated by commas) to a new
secret called `EXCLUDED_LANGS`. For example, to exclude HTML and TeX you
could set the value to `html,tex`.
- To show statistics only for "owned" repositories and not forks with
contributions, add an environment variable (under the `env` header in the
[main
workflow](https://github.com/jstrieb/github-stats/blob/master/.github/workflows/main.yml))
called `EXCLUDE_FORKED_REPOS` with a value of `true`.
- These other values are added as secrets by default to prevent leaking
information about private repositories. If you're not worried about that,
you can change the values directly [in the Actions workflow
itself](https://github.com/jstrieb/github-stats/blob/05de1314b870febd44d19ad2f55d5e59d83f5857/.github/workflows/main.yml#L48-L53).
6. Go to the [Actions
Page](../../actions?query=workflow%3A"Generate+Stats+Images") and press "Run
Workflow" on the right side of the screen to generate images for the first
time.
- The images will be automatically regenerated every 24 hours, but they can
be regenerated manually by running the workflow this way.
7. Take a look at the images that have been created in the
[`generated`](generated) folder.
8. To add your statistics to your GitHub Profile README, copy and paste the
following lines of code into your markdown content. Change the `username`
value to your GitHub username.
```md
![](https://raw.githubusercontent.com/username/github-stats/master/generated/overview.svg#gh-dark-mode-only)
![](https://raw.githubusercontent.com/username/github-stats/master/generated/overview.svg#gh-light-mode-only)
```
```md
![](https://raw.githubusercontent.com/username/github-stats/master/generated/languages.svg#gh-dark-mode-only)
![](https://raw.githubusercontent.com/username/github-stats/master/generated/languages.svg#gh-light-mode-only)
```
9. Link back to this repository so that others can generate their own
statistics images.
10. Star this repo if you like it!
TODO


# Support the Project

There are a few things you can do to support the project:
If this project is useful to you, please support it!

- Star the repository (and follow me on GitHub for more)
- Share and upvote on sites like Twitter, Reddit, and Hacker News
- Report any bugs, glitches, or errors that you find

These things motivate me to keep sharing what I build, and they provide
validation that my work is appreciated! They also help me improve the
project. Thanks in advance!
validation that my work is appreciated! They also help me improve the project.
Thanks in advance!

If you are insistent on spending money to show your support, I encourage you to
instead make a generous donation to one of the following organizations. By advocating
for Internet freedoms, organizations like these help me to feel comfortable
releasing work publicly on the Web.
instead make a generous donation to one of the following organizations. By
advocating for Internet freedoms, organizations like these help me to feel
comfortable releasing work publicly on the Web.

- [Electronic Frontier Foundation](https://supporters.eff.org/donate/)
- [Signal Foundation](https://signal.org/donate/)
- [Mozilla](https://donate.mozilla.org/en-US/)
- [The Internet Archive](https://archive.org/donate/index.php)


## Project Status

This project is actively maintained, but not actively developed. In other words,
I will fix bugs, but will rarely continue adding features (if at all). If there
are no recent commits, it means that everything has been running smoothly!

If you want to contribute to the project, please open an issue to discuss first.
Pull requests that are not discussed with me ahead of time may be ignored. It's
nothing personal, I'm just busy, and reviewing others' code is not my idea of
fun.

Even if something were to happen to me, and I could not continue to work on the
project, it will continue to work as long as the GitHub API endpoints it uses
remain active and unchanged.


# Related Projects

- Inspired by a desire to improve upon
[anuraghazra/github-readme-stats](https://github.com/anuraghazra/github-readme-stats)
- Makes use of [GitHub Octicons](https://primer.style/octicons/) to precisely
match the GitHub UI
- Uses [GitHub Octicons](https://primer.style/octicons/) to precisely match the
GitHub UI
Loading