Skip to content

GitLab → GitHub Contributions Sync

January 2026 → January 20263 min read

Code source

At work, all my code goes to the company’s GitLab. None of that work showed up on GitHub, even though that’s the profile people actually look at.

So I made a small script that fetches my commits from GitLab and copies them to a separate GitHub repo, each one on its own date. The first evening, more than two and a half years of commits showed up on my graph at once. It’s a Bun script written in TypeScript, and the code is on GitHub.

Nothing leaves the company

The code belongs to my employer, so nothing from the company should leave. The script doesn’t clone any repo. It queries the GitLab API with a read-only token, and only asks for the list of commits. For each of mine, it creates an empty commit on the GitHub repo, on the same date, always with the same message, “Work activity”. The code, the title, the hash and the project name stay inside the company.

That’s the point I pushed hardest on afterwards. The first version, made in one evening in January, put the project name and the original title in every commit, with the commit hash in a file next to it. There was no code in there, but a project name and a commit title already say a lot about what a company is working on. In July, I stripped all of that out and kept only the date.

How it works

I don’t have a list of projects to keep up to date. The script asks GitLab for every project I’m a member of, page by page, so a new repo gets picked up on its own at the next sync. In each project, it goes through the commits on every branch and keeps only the ones with my email address, so my colleagues’ commits don’t get copied. Projects are queried five at a time, to go faster without overloading the GitLab server.

Each GitHub commit keeps the date of the original one. Without that, everything would land on the day of the sync and the graph would mean nothing. The Git side goes through simple-git, which creates the commits from oldest to newest, then pushes everything in one go at the end. For GitHub to count them, the commits’ email address has to be verified on the account. The repo itself can stay private, as long as private contributions are shown on the profile.

The company’s GitLab isn’t on gitlab.com and has its own certificate. So the GitLab address is set in the config, and an option turns off certificate verification.

Only what’s new

The script remembers the commits it has already copied, by their hash, in a file that stays next to it and never goes to GitHub. Each run only handles the new ones, so I can rerun it as often as I like without creating duplicates. The list is also saved along the way, not just at the end. If the script gets cut off, it doesn’t start over from scratch.

Today

I don’t think about it anymore. CronLab, my task scheduler for Windows, runs it every evening and lets me know if it fails. And the code hasn’t changed since July.

Technologies used