DEV Community

Cover image for I Built a Tool to Track My Open Source Contributions
MABD
MABD

Posted on

I Built a Tool to Track My Open Source Contributions

Github contributions graph is great at showing activity, but it does not answer the question: what open source projects I have contributed to.

I wanted to display open source projects I have contributed to on my personal website. I can do this manually. However, this can get annoying and add extra thing to remember.
I want to show projects I contributed to, how many PR’s merged, lines of code contributed. Github does not surface this easily, so i built a tool to do so.

The Problem

If you contribute to external repositories (projects you don’t own) Github buries this info. You can get it manually by searching your PR’s, but their is no API endpoint that says “give me all this user’s contributions to external repositories”

I wanted:

  • List of external projects contributed to
  • Number of merged PR’s per project
  • Commit count and number of lines added/removed (per project)
  • JSON output I can feed to my website

So I created gh-oss-stats

The Approach

The core insight is to use Github’s search query

author:USERNAME type:pr is:merged -user:USERNAME

This find all pull requets:

  • Authored by you (author:USERNAME)
  • That are PR’s not issues (type:pr)
  • That are merged (is:merged)
  • For repos you don’t own (-user:USERNAME) That's your OSS contribution history in one query.

Request looks like this:

https://api.github.com/search/issues?q=author:mabd-dev+type:pr+is:merged+-user:mabd-dev
Enter fullscreen mode Exit fullscreen mode

output looks like this:

{
  "total_count": 20,
  "incomplete_results": false,
  "items": [
      {
          {
      "url": "https://api.github.com/repos/qamarelsafadi/JetpackComposeTracker/issues/9",
      "repository_url": "https://api.github.com/repos/qamarelsafadi/JetpackComposeTracker",
      "labels_url": "https://api.github.com/repos/qamarelsafadi/JetpackComposeTracker/issues/9/labels{/name}",
      "comments_url": "https://api.github.com/repos/qamarelsafadi/JetpackComposeTracker/issues/9/comments",
      "events_url": "https://api.github.com/repos/qamarelsafadi/JetpackComposeTracker/issues/9/events",
      "html_url": "https://github.com/qamarelsafadi/JetpackComposeTracker/pull/9",
      "id": 3204496021,
      "node_id": "PR_kwDONQBujs6diLmP",
      "number": 9,
      "title": "🔧 Refactor: Add Global Theme Support for UI Customization",
      "user": {...},
      "labels": [...],
      "state": "closed",
      },
      ...
  ]
}
Enter fullscreen mode Exit fullscreen mode

From there, it's a matter of:

  1. Fetching PR details (commits, additions, deletions)
  2. Enriching with repo metadata (stars, description)
  3. Aggregating into useful statistics

Architecture Decision: Library First

I built this as a Go library with a CLI wrapper, not just a CLI tool. The core logic lives in an importable package:

import "github.com/gh-oss-tools/gh-oss-stats/pkg/ossstats"

client := ossstats.New(
    ossstats.WithToken(os.Getenv("GITHUB_TOKEN")),
    ossstats.WithLOC(true), LOC: lines of code
)

stats, err := client.GetContributions(ctx, "mabd-dev")
Enter fullscreen mode Exit fullscreen mode

This means I can use the same code in:

  • The CLI tool (for local use)
  • GitHub Actions (automated updates)
  • A future badge service (SVG generation)
  • Anywhere else I need this data The CLI is just a thin wrapper that parses flags and calls the library.

Handling GitHub's Rate Limits

GitHub's API has limits: 5,000 requests/hour for authenticated users, but only 60 requests/hour for the Search API. For someone with many contributions, you can burn through this quickly.

The tool implements:

  • Exponential backoff on rate limit errors
  • 2-second delays between search API calls
  • Controlled concurrency (5 parallel requests for PR details)
  • Partial results if rate limited mid-fetch

The Output

Running the tool produces JSON like this:

{
  "username": "mabd-dev",
  "generatedAt": "2025-12-21T06:46:57.823990311Z",
  "summary": {
    "totalProjects": 7,
    "totalPRsMerged": 17,
    "totalCommits": 58,
    "totalAdditions": 1270,
    "totalDeletions": 594
  },
  "contributions": [
    {
      "repo": "qamarelsafadi/JetpackComposeTracker",
      "owner": "qamarelsafadi",
      "repoName": "JetpackComposeTracker",
      "description": "This is a tool to track you recomposition state in real-time !",
      "repoURL": "https://github.com/qamarelsafadi/JetpackComposeTracker",
      "stars": 94,
      "prsMerged": 2,
      "commits": 14,
      "additions": 181,
      "deletions": 78,
      "firstContribution": "2025-06-14T20:55:24Z",
      "lastContribution": "2025-07-21T21:39:53Z"
    },
    ...
  ]
}
Enter fullscreen mode Exit fullscreen mode

This feeds directly into my website's contributions section.


Using It

Installation

go install github.com/gh-oss-tools/gh-oss-stats/cmd/gh-oss-stats@latest
Enter fullscreen mode Exit fullscreen mode

Basic Usage

# Set your GitHub token
export GITHUB_TOKEN=ghp_xxxxxxxxxxxx

# Run it
gh-oss-stats --user YOUR_USERNAME

# Save to file
gh-oss-stats --user YOUR_USERNAME -o contributions.json
Enter fullscreen mode Exit fullscreen mode

Automating with GitHub Actions

I run this weekly via GitHub Actions to keep my website updated automatically:

name: Update OSS Contributions

on:
  schedule:
    - cron: '0 0 * * 0'   # Weekly on Sunday
  workflow_dispatch:      # Manual trigger

permissions:
  contents: write

jobs:
  update-stats:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: '1.25'

      - name: Install gh-oss-stats
        run: go install github.com/gh-oss-tools/gh-oss-stats/cmd/gh-oss-stats@latest

      - name: Fetch contributions
        env:
          GITHUB_TOKEN: ${{ secrets.GH_OSS_TOKEN }}
        run: |
          gh-oss-stats \
            --user YOUR_USERNAME \
            --exclude-orgs="your-org" \
            -o data/contributions.json

      - name: Commit changes
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add data/contributions.json
          if ! git diff --staged --quiet; then
            git commit -m "Update OSS contributions"
            git push
          fi
Enter fullscreen mode Exit fullscreen mode

Now my website always has fresh data without any manual work.

Displaying on My Website

On mabd.dev, I read the JSON file and render it. The exact implementation depends on your stack, but the data structure makes it straightforward:

  • Loop through contributions array
  • Display repo name, stars, PR count
  • Show totals from summary
  • Link to the actual repos

The JSON is the contract — however you want to display it is up to you.


What I Learned

GitHub's Search API is powerful but quirky. The -user: exclusion syntax does not exclude repos you own on your organization. I had to do custom logic to detect that.

Library-first design pays off. Building the core as an importable package meant the CLI came together in under an hour. It also means future tools (like a badge service) can reuse 100% of the logic.


What's Next

I'm planning to build a companion service gh-oss-badge that generates SVG badges you can embed in your GitHub profile README:

markdown

![OSS Stats](https://oss-badge.example.com/mabd-dev.svg)
Enter fullscreen mode Exit fullscreen mode

Same data, different presentation. The library-first architecture means this service will just import gh-oss-stats/pkg/ossstats and add an HTTP layer on top.


If you want to track your own OSS contributions, give gh-oss-stats a try. It's open source (naturally), and contributions are welcome

Resources

Top comments (0)