Table of Contents
Code hosting platforms have become de-facto a standard today. If you have a software project and want to share it with the world, there’s a good chance you will be hosting it on a platform like GitHub , GitLab , or Codeberg .
The convenience as well as project collaboration and promotion opportunities available there leave little incentive for users to organize project hosting themselves.
But some of the code hosting platforms are proprietary, and their popularity is an example of trading freedoms and power for short-term convenience.
Proprietary code hosting platforms collect, process, and use (primarily at scale, but also individually in some cases), all data and metadata that is extracted from code, projects, project authors, project contributors, and end users.
In addition to software authors giving their own freedoms and data to proprietary platforms, which they do with consent, they also force the same to happen to users who visit their project repositories, without consent.
It is not possible to stop these undesirable side-effects within the existing, established culture. Lacking choice or care, developers and users have accepted that companies process and exploit their data and metadata, and have also proven time and time again that convenient solutions will proliferate, regardless of negative consequences.
But software authors can immediately improve the situation by uploading repositories to multiple platforms.
Users are then able to share their data and metadata with the platform of their choosing. Also, it gives platforms an equal opportunity to protect or abuse user data, rather than reserving the privilege for one dominant platform, further reinforcing its position.
And doing so up-front, when there is no immediate need to migrate a project to another code hosting platform, will make the whole process much easier in the future. Eventual migrations from one primary platform will be almost effortless.
This article explains how to set up Git code repositories on multiple code hosting platforms with minimal impact on the standard, single-platform workflow.
Available Code Hosting Platforms
It seems that the most popular code hosting platforms today, listed in alphabetical order, are:
Your own, local Git server
All mentioned services have a web-based account registration procedure. The procedure only has to be done once, of course.
This section documents the procedure for creating user accounts on all of the mentioned platforms, although the steps are very similar at any one provider.
It is probably most convenient to pick a single, unique username and register it on all platforms. That’s gonna ensure that the username is free and gets registered across multiple platforms for brand recognition.
Most platforms support users and organizations. Organizations are optional and are created by users, but user and organization names often cannot have the same name because they are accessed using the same public URL.
Required fields and specifics of different platforms as of Oct 2023:
|Platform||Account Verification||Username||Organization Name||Password||Full / Real Name||OpenID Connect Federation||Public URL||Notes|
|Bitbucket||Y||Y (email 6-digit PIN code)||Y||Y (called “Workspace Name”)||Y||Y||Apple, Google, Microsoft, Slack||https://bitbucket.org/ORG_NAME|
|Codeberg||Y||Y (email link + password)||Y||Optional||Y||GitHub, GitLab||https://codeberg.org/USER_OR_ORG_NAME|
|Gitea||Y||Y (email link + password)||Y||Optional||Y||GitHub, GitLab, Google||https://gitea.com/USER_OR_ORG_NAME|
|GitHub||Y||Y (email 8-digit PIN code)||Y||Optional||Y||(Passkey)||https://github.com/USER_OR_ORG_NAME||Optional personalization form|
|GitLab||Y||Y (SMS 7-digit PIN code or credit card, and email 6-digit code)||Y||Y (called “Group”)||Y||Bitbucket, GitHub, Google, Salesforce||https://gitlab.com/ORG_NAME||Required personalization form, must immediately create or import project|
|Savannah||Y||Y (email link, username, and password)||Y||Y||Optional||https://savannah.nongnu.org/u/USER_NAME||Required GNU GPL-compatible license; stricter project requirements; manual project approval|
|SourceForge||Y||Y (email link, username, and password)||Y||N/A||Y||Y||https://sf.net/u/USER_NAME|
|SourceHut||Y||Y (email link)||Y||N/A||Y||https://sr.ht/~USER_NAME||Expected credit card and $2/month or more; provides sr.ht platform code as open source|
Create an account at https://bitbucket.org/account/signup
Log in at https://id.atlassian.com/login
Set up an SSH key for access at https://bitbucket.org/account/settings/ssh-keys/
Create an account by going to https://codeberg.org/ and clicking “Register” on the top-right
Log in at https://codeberg.org/user/login
Set up an SSH key for access at https://codeberg.org/user/settings/keys
Create an account at https://gitea.com/user/sign_up
Log in at https://gitea.com/user/login
Set up an SSH key for access at https://gitea.com/user/settings/keys
Create an account at https://github.com/signup
Log in at https://github.com/login
Set up an SSH key for access at https://github.com/settings/keys
Create an account at https://gitlab.com/users/sign_up
Log in at https://gitlab.com/users/sign_in
Set up an SSH key for access at https://gitlab.com/-/profile/keys
Create an account at https://notabug.org/user/sign_up
Log in at https://notabug.org/user/login
Set up an SSH key for access at https://notabug.org/user/settings/ssh
Create an account at https://savannah.nongnu.org/account/register.php
Set up an SSH key for access at https://savannah.nongnu.org/my/admin/editsshkeys.php
Create an account at https://sourceforge.net/user/registration
Log in at https://sourceforge.net/auth/
Set up an SSH key for access at https://sourceforge.net/auth/shell_services
Create an account at https://meta.sr.ht/register
Log in at https://meta.sr.ht/login
Set up an SSH key for access at https://meta.sr.ht/keys
After you have created accounts at code hosting providers of choice, you can move on to creating repositories.
Most platforms support importing existing repositories. You can import, but there is no major difference compared to creating empty repositories – as soon as you run
git push for the first time, all repositories will be synced.
All platforms except Savannah have simple requirements – you can create project repositories immediately, without any particular restrictions.
Projects at Savannah need to pass the following checklist since they will be manually reviewed and approved by admins:
Project software runs primarily on a completely free OS
Project license is compatible with GNU GPLv3 and later or GFDLv1.3 and later
Project dependencies are compatible with project package license
All project files include valid copyright notices
All project files include a license notice
Origin and license of media files is specified
Project tar file includes a copy of the license
Making projects compatible with this checklist is a good idea even if they are hosted on other platforms.
There is a concept of “projects” and “repositories”. Each repository must belong to a project. Project and repository name can be identical, but project names have a length limit.
- Create a new repository by going to https://bitbucket.org/ and clicking “Repositories” or “Create -> Repository” in the menu at the top
Create a new repository at https://codeberg.org/repo/create
If you want to perform an import of existing repository, the option is found at the top-right, titled “+”, under which there is “New Migration”. Migration from GitHub requires a GitHub access token, or it will fail due to API limit
- Create a new repository at https://gitea.com/repo/create
- Create a new repository at https://github.com/new
- Create a new repository at https://gitlab.com/projects/new
- Create a new repository at https://savannah.nongnu.org/register/
- Create a new repository at https://sourceforge.net/p/add_project
- Create a new repository at https://sr.ht/projects/create
In Git, remote endpoints are called “remotes”. Each remote has a name, a URL, and a read-write flag.
By default, the default upstream remote is called an “origin”. That is the place from which you have cloned the repository. We will consider that one the primary origin.
One can list all remotes on a project by running
git remote -v. For example:
git clone firstname.lastname@example.org:crystallabs/hugo-template-minima-crystallabs blog cd blog git remote -v origin email@example.com:crystallabs/hugo-template-minima-crystallabs (fetch) # Read origin firstname.lastname@example.org:crystallabs/hugo-template-minima-crystallabs (push) # Write git remote get-url origin email@example.com:crystallabs/hugo-template-minima-crystallabs
Note that all Git-related settings are stored in the file
.git/config in the project’s root directory. The settings can be changed by running various
git commands or just directly modifying the file.
Adding Multiple Origins
Multiple origins can be configured in two basic ways:
Multiple URLs can be added under the existing
Separate, differently named origin entries can be created, each with a single URL
Our procedure for adding multiple origins will be the second one – adding separate, provider-specific origins to the project configuration in
The primary origin will thus appear in the list twice – the first time because it was cloned from and is already configured as
origin, and the second time because it will be added as
That’s done simply for consistency and it does not affect behavior.
For a project that was cloned from GitHub and is about to be configured for multiple providers, the procedure might look like this:
git remote add origin-github firstname.lastname@example.org:crystallabs/hugo-template-minima-crystallabs.git git remote add origin-bitbucket email@example.com:crystallabs2/hugo-template-minima-crystallabs.git git remote add origin-codeberg firstname.lastname@example.org:crystallabs/hugo-template-minima-crystallabs.git git remote add origin-sourcehut email@example.com:~crystallabs/hugo-template-minima-crystallabs.git git remote add origin-gitlab firstname.lastname@example.org:crystallabs/hugo-template-minima-crystallabs.git git remote add origin-gitea email@example.com:crystallabs/hugo-template-minima-crystallabs.git git remote -v origin firstname.lastname@example.org:crystallabs/hugo-template-minima-crystallabs (fetch) origin email@example.com:crystallabs/hugo-template-minima-crystallabs (push) origin-bitbucket firstname.lastname@example.org:crystallabs2/hugo-template-minima-crystallabs.git (fetch) origin-bitbucket email@example.com:crystallabs2/hugo-template-minima-crystallabs.git (push) origin-codeberg firstname.lastname@example.org:crystallabs/hugo-template-minima-crystallabs.git (fetch) origin-codeberg email@example.com:crystallabs/hugo-template-minima-crystallabs.git (push) origin-github firstname.lastname@example.org:crystallabs/hugo-template-minima-crystallabs (fetch) origin-github email@example.com:crystallabs/hugo-template-minima-crystallabs (push) origin-sourcehut firstname.lastname@example.org:~crystallabs/hugo-template-minima-crystallabs (fetch) origin-sourcehut email@example.com:~crystallabs/hugo-template-minima-crystallabs (push) origin-gitea firstname.lastname@example.org:crystallabs/hugo-template-minima-crystallabs.git (fetch) origin-gitea email@example.com:crystallabs/hugo-template-minima-crystallabs.git (push) origin-gitlab firstname.lastname@example.org:crystallabs/hugo-template-minima-crystallabs.git (fetch) origin-gitlab email@example.com:crystallabs/hugo-template-minima-crystallabs.git (push)
When we take a look at what was configured in
.git/config, we see:
[core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true [remote "origin"] url = firstname.lastname@example.org:crystallabs/hugo-template-minima-crystallabs fetch = +refs/heads/*:refs/remotes/origin-github/* [branch "master"] remote = origin-github merge = refs/heads/master [remote "origin-github"] url = email@example.com:crystallabs/hugo-template-minima-crystallabs fetch = +refs/heads/*:refs/remotes/origin-github/* [remote "origin-bitbucket"] url = firstname.lastname@example.org:crystallabs2/hugo-template-minima-crystallabs.git fetch = +refs/heads/*:refs/remotes/origin-bitbucket/* [remote "origin-codeberg"] url = email@example.com:crystallabs/hugo-template-minima-crystallabs.git fetch = +refs/heads/*:refs/remotes/origin-codeberg/* [remote "origin-sourcehut"] url = firstname.lastname@example.org:~crystallabs/hugo-template-minima-crystallabs fetch = +refs/heads/*:refs/remotes/origin-sourcehut/*
Working with Multiple Origins
Many commands default to
origin being the default value. As long as you do not specify anything manually,
origin will be used. That is the standard workflow, unaffected by our addition of origins.
git push does not support an option like
--all-remotes to push to all remotes at once. That even makes sense because not all remotes configured on a project are its origins.
You can push to specific origins with e.g.:
git push origin-codeberg
But our naming was purposely
origin-* so that origins could be easily recognized among the remotes, and it is easy to run a push loop manually.
for p in `git remote | grep origin`; do git push $p; done Everything up-to-date Everything up-to-date Everything up-to-date Everything up-to-date Everything up-to-date
The workflow assumes that a successful push will always be done to all origins. That is recommended for clarity and standardized workflow, although it is not critical because nothing bad will happen if some origins are behind.
Git Pull, Git Fetch
Git does not support pulling from multiple origins, but a fetch can be done:
git fetch --all Fetching origin Fetching origin-github Fetching origin-bitbucket Fetching origin-codeberg Fetching origin-sourcehut
If a push has not been done to all origins and a particular branch is newer on one origin, you can manually pull from it, e.g.:
git pull # Pulls from default origin git pull origin-codeberg master # Pulls the master branch from `origin-codeberg`
There’s no need to worry about remote origins – as long as the code did not diverge, a
git push to all origins will bring them to the same state.
Each branch can track a single remote branch. This is visible in
.git/config under a particular branch, e.g.:
[branch "master"] remote = origin-github merge = refs/heads/master
Branch in the example is called
master, although sometimes copy-paste instructions create a branch called
main instead. Adjust as necessary in your specific case.
The remote used by a branch can be tuned either in the file, or with
git checkout BRANCH; git branch -u REMOTE/BRANCH. For example:
git checkout master git branch -u origin-codeberg/master cat .git/config | grep -A3 master [branch "master"] remote = origin-codeberg merge = refs/heads/master
This article is part of the following series:
Automatic LinksThe following links appear in the article:
8. Gitea - https://gitea.com/
13. GitHub - https://github.com/
18. GitLab - https://gitlab.com/
27. NotABug - https://notabug.org/
31. Savannah - https://savannah.nongnu.org/
36. SourceForge - https://sf.net/
41. SourceHut - https://sr.ht/