Git - Adding and Using Multiple Origins

First published — Oct 01, 2023
Last updated — Jan 10, 2024
#git

Git. Multiple repository origins. Projects mirrored at different hosting platforms.

Article Collection

This article is part of the following series:

1. Git

Table of Contents

Introduction

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 promotion and collaboration opportunities available there leave little incentive for users to organize project hosting themselves.

The Problem

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.

Software authors who give their own privacy and data to proprietary platforms also force the same to happen to users who visit their project repositories.

The Solution

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:

Account Registration

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.

Use the following table to decide which platforms you want to use.

Summary of required fields and specifics at different code hosting platforms as of Oct 2023:

Platform Email 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
NotABug Y N Y Optional Y https://notabug.org/USER_OR_ORG_NAME
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

Project Registration

In addition to creating accounts at code hosting providers of choice, you also need to think about creating code repositories afterwards.

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 populated with the same contents.

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.

Bitbucket

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

Codeberg

Gitea

GitHub

GitLab

NotABug

Savannah

SourceForge

SourceHut

Git Setup

Introduction

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 git@github.com:crystallabs/hugo-template-minima-crystallabs blog
cd blog

git remote -v
origin  git@github.com:crystallabs/hugo-template-minima-crystallabs (fetch)  # Read
origin  git@github.com:crystallabs/hugo-template-minima-crystallabs (push)   # Write

git remote get-url origin
git@github.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 by just directly modifying the file.

Adding Multiple Origins

Multiple origins can be configured in two basic ways:

  1. Multiple URLs can be added under the existing origin entry

  2. 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 .git/config.

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 origin-PROVIDER. 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:

ACCOUNT=crystallabs
REPOSITORY=hugo-template-minima-crystallabs

git remote add origin-github git@github.com:$ACCOUNT/$REPOSITORY.git
git remote add origin-bitbucket git@bitbucket.org:$ACCOUNT/$REPOSITORY.git
git remote add origin-codeberg git@codeberg.org:$ACCOUNT/$REPOSITORY.git
git remote add origin-sourcehut git@git.sr.ht:~$ACCOUNT/$REPOSITORY.git
git remote add origin-gitlab git@gitlab.com:$ACCOUNT/$REPOSITORY.git
git remote add origin-gitea git@gitea.com:$ACCOUNT/$REPOSITORY.git

git remote -v
origin                  git@github.com:crystallabs/hugo-template-minima-crystallabs (fetch)
origin                  git@github.com:crystallabs/hugo-template-minima-crystallabs (push)
origin-bitbucket        git@bitbucket.org:crystallabs/hugo-template-minima-crystallabs.git (fetch)
origin-bitbucket        git@bitbucket.org:crystallabs/hugo-template-minima-crystallabs.git (push)
origin-codeberg         git@codeberg.org:crystallabs/hugo-template-minima-crystallabs.git (fetch)
origin-codeberg         git@codeberg.org:crystallabs/hugo-template-minima-crystallabs.git (push)
origin-github           git@github.com:crystallabs/hugo-template-minima-crystallabs (fetch)
origin-github           git@github.com:crystallabs/hugo-template-minima-crystallabs (push)
origin-sourcehut        git@git.sr.ht:~crystallabs/hugo-template-minima-crystallabs (fetch)
origin-sourcehut        git@git.sr.ht:~crystallabs/hugo-template-minima-crystallabs (push)
origin-gitea            git@gitea.com:crystallabs/hugo-template-minima-crystallabs.git (fetch)
origin-gitea            git@gitea.com:crystallabs/hugo-template-minima-crystallabs.git (push)
origin-gitlab           git@gitlab.com:crystallabs/hugo-template-minima-crystallabs.git (fetch)
origin-gitlab           git@gitlab.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 = git@github.com: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 = git@github.com:crystallabs/hugo-template-minima-crystallabs
  fetch = +refs/heads/*:refs/remotes/origin-github/*
[remote "origin-bitbucket"]
  url = git@bitbucket.org:crystallabs/hugo-template-minima-crystallabs.git
  fetch = +refs/heads/*:refs/remotes/origin-bitbucket/*
[remote "origin-codeberg"]
  url = git@codeberg.org:crystallabs/hugo-template-minima-crystallabs.git
  fetch = +refs/heads/*:refs/remotes/origin-codeberg/*
[remote "origin-sourcehut"]
  url = git@git.sr.ht:~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

Command 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 that pushing to multiple origins could be scripted.

for p in `git remote | grep origin`; do echo -n "$p "; git push $p; done

origin Everything up-to-date
origin-github Everything up-to-date
origin-bitbucket Everything up-to-date
origin-codeberg Everything up-to-date
origin-sourcehut Everything up-to-date

The workflow assumes that a successful push will always be done to all origins. That is recommended and what users generally expect, although it is not critical because nothing bad will happen if some origins are temporarily behind.

Git Pull, Git Fetch

Internally, git pull does a git fetch and then updates the current tree.

Git does not support pulling from multiple origins, but that is expected. If your repositories at different providers are all in sync, it does not matter which one you pull from – you can just pull from the default origin:

git pull

But what if you or other team members have pushed changes to an incomplete list of remote origins, or to just the default origin? What you can do in that case is fetch updates from all origins, and then pull from the specific, newest one:

git fetch --all

Fetching origin
Fetching origin-github
Fetching origin-bitbucket
Fetching origin-codeberg
Fetching origin-sourcehut

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 and versions they contain – as long as the code did not diverge, a git push to all origins will bring them to the same state.

Branches

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. 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

Article Collection

This article is part of the following series:

1. Git

Automatic Links

The following links appear in the article:

1. https://bitbucket.org/
2. https://bitbucket.org/account/settings/ssh-keys/
3. https://bitbucket.org/account/signup
4. https://codeberg.org/
5. https://codeberg.org/repo/create
6. https://codeberg.org/user/login
7. https://codeberg.org/user/settings/keys
8. Gitea - https://gitea.com/
9. https://gitea.com/repo/create
10. https://gitea.com/user/login
11. https://gitea.com/user/settings/keys
12. https://gitea.com/user/sign_up
13. GitHub - https://github.com/
14. https://github.com/login
15. https://github.com/new
16. https://github.com/settings/keys
17. https://github.com/signup
18. GitLab - https://gitlab.com/
19. https://gitlab.com/-/profile/keys
20. https://gitlab.com/projects/new
21. https://gitlab.com/users/sign_in
22. https://gitlab.com/users/sign_up
23. https://id.atlassian.com/login
24. https://meta.sr.ht/keys
25. https://meta.sr.ht/login
26. https://meta.sr.ht/register
27. NotABug - https://notabug.org/
28. https://notabug.org/repo/create
29. https://notabug.org/user/login
30. https://notabug.org/user/settings/ssh
31. https://notabug.org/user/sign_up
32. Savannah - https://savannah.nongnu.org/
33. https://savannah.nongnu.org/account/login.php
34. https://savannah.nongnu.org/account/register.php
35. https://savannah.nongnu.org/my/admin/editsshkeys.php
36. https://savannah.nongnu.org/register/
37. SourceForge - https://sf.net/
38. https://sourceforge.net/auth/
39. https://sourceforge.net/auth/shell_services
40. https://sourceforge.net/p/add_project
41. https://sourceforge.net/user/registration
42. SourceHut - https://sr.ht/
43. https://sr.ht/projects/create