Skip to content

Mirroring Your Code to GitHub and GitLab Simultaneously

Context

In a professional environment, relying on a single Git hosting provider introduces availability, policy, and vendor-lock risks. In my workflows, I adopted a multi-platform Git strategy to ensure redundancy, portability, and CI/CD continuity across providers.

This document captures a real-world implementation, including edge cases and failure modes encountered during setup.

The Objective

To configure a single local Git repository to push to both GitHub and GitLab using one command (git push origin main), ensuring redundancy and synchronized CI/CD across platforms. Moving from a single-cloud setup to a "Multi-Cloud Git" setup is a rite of passage for many developers. This tutorial shows how to do that, including avoiding tricky roadblocks.


The Setup

Step 1: Establish your "Origin"

First, ensure your primary remote (usually GitHub) is set up.

git remote add origin git@github.com:username/repo.git

Example:

zettelkasten on  main
 git remote add origin git@github.com:hemroda/zettelkasten.git
error: remote origin already exists.

Step 2: The "Stacking" Technique

To push to two places, you don't add a new remote name; you add a push URL to the existing one.

git remote set-url --add --push origin git@gitlab.com:username/repo.git

In our example:

zettelkasten on  main
 git remote set-url --add --push origin git@gitlab.com:hemroda/zettelkasten.git

The "First" Gotcha: When you add a push URL for the first time, Git stops using the default "fetch" URL for pushing. Indeed if you check the remote URLs, you'll see only the push URL for GitLab:

zettelkasten on  main
 git remote -v
origin  git@github.com:hemroda/zettelkasten.git (fetch)
origin  git@gitlab.com:hemroda/zettelkasten.git (push)

You must explicitly re-add your GitHub URL to the push list so both are present:

git remote set-url --add --push origin git@github.com:hemroda/zettelkasten.git

Now, if you check the remote URLs again, you'll see both GitHub and GitLab URLs:

zettelkasten on  main
 git remote -v
origin  git@github.com:hemroda/zettelkasten.git (fetch)
origin  git@gitlab.com:hemroda/zettelkasten.git (push)
origin  git@github.com:hemroda/zettelkasten.git (push)

Step 3: Configure Authentication (The SSH Shortcut)

Using SSH for both is the smoothest experience. To add your key to GitLab quickly on macOS:

  1. Copy your key: pbcopy < ~/.ssh/id_ed25519.pub
  2. Paste it into GitLab Settings > SSH Keys.
  3. Test it: ssh -T git@gitlab.com

Step 4: Resolving the "Diverged History" Conflict

When you try to push to GitLab, you might encounter a "Diverged History" conflict.

zettelkasten on  main
 git push
To gitlab.com:hemroda/zettelkasten.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'gitlab.com:hemroda/zettelkasten.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Everything up-to-date

If you created your GitLab repo with a README or .gitignore, or other files, it has a history your local machine doesn't know about. A standard push will be rejected.

The Fix:

  1. Fetch the GitLab-specific data:
git fetch git@gitlab.com:hemroda/zettelkasten.git main
  1. Merge it into your local branch:
git merge FETCH_HEAD --allow-unrelated-histories

(The --allow-unrelated-histories flag is the secret sauce here; it forces Git to combine two projects that didn't start from the same commit.)


Summary of "Gotchas"

Gotcha Why it happens The Fix
The "Replacement" Bug Adding one push URL often hides the default one. Always verify with git remote -v and re-add the primary URL.
Protocol Mismatch Using HTTPS for one and SSH for the other causes inconsistent login prompts. Stick to SSH for both for a "silent" push experience.
Push Rejected GitLab often has an initial commit (README) that your local Git doesn't have. Use fetch followed by merge --allow-unrelated-histories.

The Final Result

Now, your workflow is simply:

git add .
git commit -m "Feature complete"
git push origin main

The terminal will show two separate progress bars—one for GitHub and one for GitLab.