<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Parental leave projects]]></title><description><![CDATA[Parental leave projects]]></description><link>https://turbolego.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ac8c47c7f56cb3086475ffd/eb2cd7a9-dd76-4551-9c83-4b2a6260c8b7.png</url><title>Parental leave projects</title><link>https://turbolego.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 13 Oct 2026 15:18:43 GMT</lastBuildDate><atom:link href="https://turbolego.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[GitHub tells me how much I contributed. I wanted to know where.]]></title><description><![CDATA[GitHub tells me how much I contributed. I wanted to know where.

This is the badge. Click it to open the interactive 3D globe. The animated GIF rotates on its own so it works as a static profile badge]]></description><link>https://turbolego.hashnode.dev/github-tells-me-how-much-i-contributed-i-wanted-to-know-where</link><guid isPermaLink="true">https://turbolego.hashnode.dev/github-tells-me-how-much-i-contributed-i-wanted-to-know-where</guid><category><![CDATA[GitHub]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[WebGL]]></category><dc:creator><![CDATA[Turbolego]]></dc:creator><pubDate>Fri, 09 Oct 2026 10:46:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ac8c47c7f56cb3086475ffd/27e159ba-7f12-486b-875e-43418d0ab1cb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>GitHub tells me how much I contributed. I wanted to know where.</p>
<p><a href="https://turbolego.github.io/github-contrib-globe-badge/"><img src="https://raw.githubusercontent.com/turbolego/github-contrib-globe-badge/main/badge.gif" alt="My contributions badge" style="display:block;margin:0 auto" /></a></p>
<p>This is the badge. Click it to open the interactive 3D globe. The animated GIF rotates on its own so it works as a static profile badge too.</p>
<p>I built a GitHub profile badge that traces contributions to public repositories back to their owners and maps them onto an interactive globe.</p>
<h2>Try it yourself</h2>
<p>Want your own globe? It takes about two minutes.</p>
<ol>
<li><p>Fork the repository: <a href="https://github.com/turbolego/github-contrib-globe-badge">https://github.com/turbolego/github-contrib-globe-badge</a></p>
</li>
<li><p>Fork the repository, then the workflow will automatically use your GitHub username to generate the badge. No changes to the workflow are needed.</p>
</li>
<li><p>Enable GitHub Pages → Source: Deploy from branch → <code>main</code> / <code>(root)</code></p>
</li>
<li><p>Add this to your profile README:</p>
</li>
</ol>
<pre><code class="language-markdown">[![My contributions badge](https://raw.githubusercontent.com/turbolego/github-contrib-globe-badge/main/badge.gif)](https://turbolego.github.io/github-contrib-globe-badge/)
</code></pre>
<p>Replace <code>turbolego</code> with your username. GitHub Actions regenerates the badge daily.</p>
<p>Repo: <a href="https://github.com/turbolego/github-contrib-globe-badge">https://github.com/turbolego/github-contrib-globe-badge</a></p>
<p>Interactive demo: <a href="https://turbolego.github.io/github-contrib-globe-badge/">https://turbolego.github.io/github-contrib-globe-badge/</a></p>
<hr />
<h2>How does it work?</h2>
<pre><code class="language-plaintext">GitHub commits authored by me
         │
         ├── Co-authored-by trailers
         │
         ▼
Resolve true repository origin
         │
         ├── GitHub fork source → follow to ultimate origin
         │
         └── Commits-search fallback → find oldest repo sharing the SHA
         │
         ▼
Repository owner (not the fork owner)
         │
         ▼
GitHub profile location (free-text string)
         │
         ▼
Nominatim geocoding
         │
         ├── Country polygon lookup (primary)
         │
         └── Reverse geocode fallback (coastal / edge cases)
         │
         ▼
Country code + flag
         │
         ▼
┌─────────────────────┬─────────────────────┐
│ badge.gif           │ data.json           │
│ (animated GIF)      │ (Cobe globe data)   │
└─────────┬───────────┴─────────┬───────────┘
          ▼                     ▼
    GitHub README          GitHub Pages
    (static badge)         (interactive Cobe globe)
</code></pre>
<p>The entire thing runs on GitHub. No server, no database, no backend API that has to stay running. A daily GitHub Action collects contributions, resolves repository origins, geocodes owners, generates the GIF and JSON, and GitHub Pages serves the interactive globe from the committed <code>data.json</code>.</p>
<hr />
<h2>The surprisingly hard part: where did the contribution actually happen?</h2>
<p>The visual is fun, but the engineering is the interesting part.</p>
<p>A contribution might exist in a fork, but you don't necessarily want to attribute it to the person who owns the fork.</p>
<pre><code class="language-plaintext">Your fork → someone else's repository → original repository → repository owner → owner's location → country on the globe
</code></pre>
<p>The project has to resolve the <strong>true origin repository</strong> for each commit:</p>
<ol>
<li><p><strong>Follow GitHub's fork</strong> <code>source</code> <strong>field</strong> — if the repo is a registered GitHub fork, walk the fork chain to the ultimate origin.</p>
</li>
<li><p><strong>Fall back to commits-search origin detection</strong> — when a repository duplicates commit history without being a registered GitHub fork (common when someone seeds a new repo from a full clone of another repo's history), search all public repositories that contain the commit SHA and pick the oldest by creation date.</p>
</li>
<li><p><strong>Cache origins per repository</strong> — this runs at most once per distinct repository, not once per commit. The cache is persisted across Action runs with a 30-day TTL (a repo that was private can later become public and turn out to be the true older origin).</p>
</li>
</ol>
<pre><code class="language-js">// Simplified: the core origin resolution logic
const candidates = new Set([fullName]);
// 1. Search all repos containing this commit SHA
const data = await searchCommits(`sha:${sha} is:public`);
for (const item of data.items) candidates.add(item.repository.full_name);
// 2. Expand fork networks
for (const name of candidates) {
  const info = await getRepositoryInfo(name);
  if (info?.fork &amp;&amp; info.source?.full_name) candidates.add(info.source.full_name);
}
// 3. Pick oldest by creation date
let oldest = null;
for (const name of candidates) {
  const info = await getRepositoryInfo(name);
  if (info?.created_at &amp;&amp; (!oldest || info.created_at &lt; oldestDate)) {
    oldest = name;
  }
}
return oldest || fullName;
</code></pre>
<hr />
<h2>What counts as a contribution?</h2>
<p>A simple GitHub commit search isn't quite enough. Collaborative commits can contain <code>Co-authored-by:</code> trailers, so I also look for those.</p>
<p>The generator searches:</p>
<ul>
<li><p><strong>Authored commits</strong>: <code>author:me is:public -user:me</code></p>
</li>
<li><p><strong>Co-authored commits</strong>: full-text search for <code>"Co-authored-by: me"</code> then verifies each hit against the actual trailers in the commit message (matching by GitHub noreply address, public profile email, or login name)</p>
</li>
</ul>
<p>This catches contributions to repositories where you weren't the primary author but were credited in the commit message.</p>
<hr />
<h2>Turning a GitHub profile location into a country</h2>
<p>GitHub's profile location is free-form user-entered text. That's not a structured address.</p>
<pre><code class="language-plaintext">Oslo, Norway
Berlin
California
Internet
Earth
Remote
</code></pre>
<p>The pipeline handles this:</p>
<ol>
<li><p><strong>Nominatim forward geocode</strong> — convert the string to lat/lon</p>
</li>
<li><p><strong>Country polygon lookup</strong> — test the point against Natural Earth country polygons (the <code>geo-countries</code> dataset)</p>
</li>
<li><p><strong>Reverse geocode fallback</strong> — if the point falls outside any polygon (coastal locations, small islands), call Nominatim reverse geocode with <code>zoom=3</code> to get the country code from the address details</p>
</li>
<li><p><strong>Flags</strong> — from the MIT-licensed <code>flag-icons</code> library for the animated GIF</p>
</li>
</ol>
<pre><code class="language-js">// Country lookup with fallback
marker.countryCode = countryCodeAt(marker.location, geojson);
if (!marker.countryCode) {
  // wait for Nominatim rate limit
  await sleep(1100);
  marker.countryCode = await reverseCountryCode(marker.location);
}
</code></pre>
<hr />
<h2>Why GitHub Actions?</h2>
<table>
<thead>
<tr>
<th>What you'd normally need</th>
<th>This project</th>
</tr>
</thead>
<tbody><tr>
<td>Database for caching</td>
<td><code>commit-cache.json</code> committed to repo</td>
</tr>
<tr>
<td>Server for geocoding</td>
<td>Calls Nominatim directly from the Action</td>
</tr>
<tr>
<td>Backend API</td>
<td>GitHub Actions does all computation</td>
</tr>
<tr>
<td>Cron job</td>
<td>GitHub Actions scheduled workflow</td>
</tr>
<tr>
<td>Hosting for globe</td>
<td>GitHub Pages serves <code>index.html</code> + <code>data.json</code></td>
</tr>
<tr>
<td>SSL, domain, DNS</td>
<td>Free on <code>username.github.io</code></td>
</tr>
</tbody></table>
<p>The Action runs daily at midnight UTC, commits <code>badge.gif</code> + <code>data.json</code> + <code>commit-cache.json</code> back to <code>main</code>, and Pages picks up the new data automatically. Zero infrastructure to maintain.</p>
<hr />
<h2>What my globe looks like</h2>
<table>
<thead>
<tr>
<th>Country</th>
<th>Commits</th>
<th>PRs</th>
<th>Tracked %</th>
<th>Repositories</th>
</tr>
</thead>
<tbody><tr>
<td>🇳🇴 Norway</td>
<td>119</td>
<td>4</td>
<td>63.7%</td>
<td>7</td>
</tr>
<tr>
<td>🇧🇷 Brazil</td>
<td>9</td>
<td>9</td>
<td>9.3%</td>
<td>1</td>
</tr>
<tr>
<td>🇺🇸 USA</td>
<td>3</td>
<td>3</td>
<td>3.1%</td>
<td>4</td>
</tr>
<tr>
<td>🇵🇹 Portugal</td>
<td>2</td>
<td>7</td>
<td>4.7%</td>
<td>1</td>
</tr>
<tr>
<td>🇯🇴 Jordan</td>
<td>1</td>
<td>1</td>
<td>1.0%</td>
<td>1</td>
</tr>
<tr>
<td>🇲🇪 Montenegro</td>
<td>1</td>
<td>0</td>
<td>0.5%</td>
<td>1</td>
</tr>
<tr>
<td>🇮🇳 India</td>
<td>1</td>
<td>1</td>
<td>1.0%</td>
<td>1</td>
</tr>
<tr>
<td>🇨🇳 China</td>
<td>1</td>
<td>0</td>
<td>0.5%</td>
<td>1</td>
</tr>
</tbody></table>
<p><strong>Total tracked: 137 commits, 25 merged PRs across 8 countries.</strong></p>
<hr />
<h2>Limitations</h2>
<table>
<thead>
<tr>
<th>Limitation</th>
<th>What it means</th>
</tr>
</thead>
<tbody><tr>
<td><strong>GitHub locations aren't authoritative</strong></td>
<td>Self-entered free text. "Internet", "Earth", "Remote" don't geocode.</td>
</tr>
<tr>
<td><strong>Not every owner has a location</strong></td>
<td>Org accounts, bots, and users without a location set are invisible.</td>
</tr>
<tr>
<td><strong>Repository owner ≠ all contributors</strong></td>
<td>You're visualizing repository-owner geography, not the geography of every person who touched the code.</td>
</tr>
<tr>
<td><strong>Country attribution is approximate</strong></td>
<td>Especially when profile locations are vague ("California" → US, but which state?)</td>
</tr>
<tr>
<td><strong>History is bounded</strong></td>
<td>Currently queries from account creation date (fallback <code>2023-01-01</code>). Very old contributions may be missed if the account is older than the search API can reliably return.</td>
</tr>
<tr>
<td><strong>Percentages are "of tracked contributions"</strong></td>
<td>If a country shows 42%, that's 42% of the <em>resolvable</em> contribution set — not 42% of all your GitHub activity.</td>
</tr>
</tbody></table>
<hr />
<h2>Why I built this</h2>
<p>GitHub's contribution graph tells me <em>how much</em> I've contributed.</p>
<p>I started wondering about a different question:</p>
<p><strong>Who are all these contributions actually reaching?</strong></p>
<p>A contribution to an open-source project isn't just an entry on my GitHub graph. It's a connection to another developer, organization, or community somewhere else in the world.</p>
<p>I wanted to visualize that network — not as an abstract chart, but as a literal map. Where does my open-source work actually end up?</p>
<hr />
<h2>What's next?</h2>
<p>Things I'd like to explore (no promises):</p>
<ul>
<li><p>Historical animation — scrub through time to watch the globe grow</p>
</li>
<li><p>Repository-level markers — not just country aggregates</p>
</li>
<li><p>Organization support — attribute contributions to orgs, not just owners</p>
</li>
<li><p>Better ambiguous-location handling — disambiguate "Springfield" etc.</p>
</li>
<li><p>City-level view — zoom in on dense regions</p>
</li>
<li><p>Contribution types beyond commits/PRs — issues, reviews, discussions</p>
</li>
<li><p>Interactive repository filtering — click a country, see its repos</p>
</li>
<li><p>Time-range selection — "last year", "all time", custom range</p>
</li>
<li><p>Caching/API optimization — smarter search query batching</p>
</li>
</ul>
<hr />
<h2>What does your globe look like?</h2>
<p>Fork the project, add the badge to your profile, and see what your globe looks like.</p>
<p>I'm especially curious to see whether anyone gets a more geographically distributed contribution map than mine.</p>
<p>If you find a weird location that the geocoder gets wrong, open an issue or let me know in the comments.</p>
<p>Where does your open-source work take you?</p>
<hr />
<p><em>GitHub repository:</em> <a href="https://github.com/turbolego/github-contrib-globe-badgeInteractive"><em>https://github.com/turbolego/github-contrib-globe-badge</em></a></p>
<p><em>Interactive demo:</em> <a href="https://turbolego.github.io/github-contrib-globe-badge/"><em>https://turbolego.github.io/github-contrib-globe-badge/</em></a></p>
]]></content:encoded></item></channel></rss>