🧩 The problem
The title may seem provocative, but think about it for a moment: lots of open source projects host their Git repositories exclusively on GitHub. What happens when the website is down or experiences other issues? Here are just a few examples:
- GitHub suffers outage
- Major GitHub outage affects pull requests and other services
- GitHub suffers major outage, "OH MY GOD" | Cybernews
- Standing up for developers: youtube-dl is back
You can find older news articles such as the famous 2018 DDoS attack.
In this post I show you how to use multiple Git remotes to increase "Git hosting resilience".
The way you set up mirrors can change between services (web UI), while using remotes is platform-agnostic (Git CLI). For this reason we'll avoid Git mirrors in this discussion.
📓 Software and services
Notes:
- The order of items in the tables is alphabetical
- The choices I list here are just a tiny subset of the ones available
🌐 Services
Service name | Is open source | Notes |
---|---|---|
bitbucket.org | No | - |
codeberg.org | Yes | powered by Forgejo |
framagit.org | Yes | powered by Gitlab |
gitlab.com | No (only the community edition is fully libre) | powered by Gitlab, probably the most direct competitor of GitHub |
sr.ht | Yes | powered by Sourcehut |
🖥️ Software
These are all open source and self-hostable.
Software name | Notes |
---|---|
Forgejo | Fork of Gitea, maintained by Codeberg e.V. and contributors. Powers Codeberg |
Gitea | Fork of Gogs |
GitLab | Use the community edition |
Gogs | - |
Sourcehut | Powers sr.ht |
🤔 So, how do you do it
Given the distributed nature of Git you can keep GitHub as main and then add one or more of these services as non-default remotes.
First of all check the existing remotes:
git remote -v
By today's standards it's expected not to use HTTP basic auth with Git anymore, but something like SSH, possibly with unique encrypted keys for each service. For example, in my local md-toc project checkout I have these remotes:
codeberg [email protected]:frnmst/md-toc.git (fetch)
codeberg [email protected]:frnmst/md-toc.git (push)
framagit [email protected]:frnmst/md-toc.git (fetch)
framagit [email protected]:frnmst/md-toc.git (push)
origin ssh://[email protected]/frnmst/md-toc.git (fetch)
origin ssh://[email protected]/frnmst/md-toc.git (push)
forgejo ssh://[email protected]/frnmst/md-toc.git (fetch)
forgejo ssh://[email protected]/frnmst/md-toc.git (push)
To add a new remote such as Codeberg, what I did at the time was simply:
git remote add codeberg [email protected]:frnmst/md-toc.git
This method, however, has the disadvantage that you need to push changes one remote at a time. Alternatively, you can create a new all
label and combine multiple push remotes into one. This way there is no need to manually git push
each remote one by one. You can simply git push all
.
Here's how you do it:
git remote add all ssh://[email protected]/frnmst/md-toc.git
git remote set-url --add --push all ssh://[email protected]/frnmst/md-toc.git
git remote set-url --add --push all ssh://[email protected]/frnmst/md-toc.git
git remote set-url --add --push all [email protected]:frnmst/md-toc.git
git remote set-url --add --push all [email protected]:frnmst/md-toc.git
So we now have a new virtual all
remote we can push to but a single pull remote from GitHub. Having multiple Git pull remotes could cause conflicts if all these don't have the same history. If there is a change in a single remote you can pull the changes directly from it and merge when necessary.
You can check the result of the previous commands like this:
git remote -v | grep all
all ssh://[email protected]/frnmst/md-toc.git (fetch)
all ssh://[email protected]/frnmst/md-toc.git (push)
all ssh://[email protected]/frnmst/md-toc.git (push)
all [email protected]:frnmst/md-toc.git (push)
all [email protected]:frnmst/md-toc.git (push)
Let's test the setup. In my case I didn't have any changes so I got the same message 4 times:
git push all dev --verbose
Pushing to ssh://github.com/frnmst/md-toc.git
To ssh://github.com/frnmst/md-toc.git
= [up to date] dev -> dev
updating local tracking ref 'refs/remotes/all/dev'
Everything up-to-date
Pushing to ssh://software.franco.net.eu.org/frnmst/md-toc.git
To ssh://software.franco.net.eu.org/frnmst/md-toc.git
= [up to date] dev -> dev
updating local tracking ref 'refs/remotes/all/dev'
Everything up-to-date
Pushing to codeberg.org:frnmst/md-toc.git
To codeberg.org:frnmst/md-toc.git
= [up to date] dev -> dev
updating local tracking ref 'refs/remotes/all/dev'
Everything up-to-date
Pushing to framagit.org:frnmst/md-toc.git
To framagit.org:frnmst/md-toc.git
= [up to date] dev -> dev
updating local tracking ref 'refs/remotes/all/dev'
Everything up-to-date
🎉 Conclusion
Git has tons of options and there's an ecosystem of web services and libre software you can choose from to host your repository. Don't limit yourself to exclusively host on GitHub. The real challenge is how to sync stuff like issues, pull requests, projects, actions, etc... between all these platforms. Alternative systems like Fossil could help solve this.
Do you already use multiple remotes? If not, which ones do you intend to try? Are there easier ways to manage this use case (I'm not a Git expert)? Let me know in the comments.
Top comments (0)