Git Remotes Revisited

git
github
gitlab
Author

Neil Shephard

Published

February 17, 2024

I’ve written before about Git Remotes but in my on-going effort to improve my understanding of the tools I use daily I’ve discovered that it is possible to have a single remote push to two different URLs so am revisiting the topic and perhaps writing a little more clearly on it.

Remotes are where other copies of your repository exist and typically where collaboration occurs (i.e. issue tracking, merge requests, bug reports etc.).

The main remote that a repository is configured to use by default is called origin but it is possible to have multiple remotes tracked by your local copy.

Listing Remotes

List remotes with git remote [-v] the -v flag will show the URLs that are stored for the short-cut.

 git remote -v
forgejo forgejo@forgejo.hopto.org:nshephard/mvdate.git (fetch)
forgejo forgejo@forgejo.hopto.org:nshephard/mvdate.git (push)
origin git@gitlab.com:nshephard/mvdate.git (fetch)
origin git@gitlab.com:nshephard/mvdate.git (push)

You can get more information about a remote using git remote show origin

 git remote show origin
 * remote origin
   Fetch URL: git@gitlab.com:nshephard/mvdate.git
   Push  URL: git@gitlab.com:nshephard/mvdate.git
   HEAD branch: main
   Remote branches:
     main                                                tracked
     refs/merge-requests/18/head                         new (next fetch will store in remotes/origin
     refs/pullreqs/15                                    stale (use 'git remote prune' to remove)
     refs/remotes/origin/nshephard/update-pre-commit     stale (use 'git remote prune' to remove)
     refs/remotes/origin/nshephard/update-readme         stale (use 'git remote prune' to remove)
   Local branches configured for 'git pull':
     main                            merges with remote main
     nshephard/fix-mtime             merges with remote nshephard/fix-mtime
     nshephard/update-pre-commit     merges with remote nshephard/update-pre-commit
   Local ref configured for 'git push':
     main pushes to main (local out of date)

This can be useful to show you what you need to tidy up if there are lots of stale branches around. In this example I can run git remote prune origin as advised to remove these.

Default Remote

The default remote to push to can be set with the following command, it will likely already be set to origin so this would not change anything.

git config --local remote.pushDefault origin

This adds the following to your .git/config if it wasn’t already there.

[remote]
    pushDefault = origin

Adding Remotes

It is straight forward to add a remote with git remote add <shortcut> <URL> where the URL is either the https or the git URL.

 git remote add forgejo forgejo@forgejo.hopto.org:nshephard/mvdate.git

This adds details to your .git/config so that it has the following

[remote "origin"]
    url = git@gitlab.com:nshephard/mvdate.git
    fetch = +refs/heads/*:refs/remotes/origin/*
    fetch = +refs/merge-requests/*/head:refs/pullreqs/*
[remote "forgejo"]
    url = ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git
    fetch = +refs/heads/*:refs/remotes/forgejo/*
[remote]
    pushDefault = origin
[branch "main"]
    remote = origin
    merge = refs/heads/main

Two remotes are defined, origin and forgejo, the default to push to is set to origin and the main branch is setup to track the remote origin.

Pushing to specific remote

With two remotes setup you can choose, at the branch level, where to push your changes by specifying the remote you wish to use. If you wanted to push a newly created branch, change-just-for-forgejo, to the newly added forgejo remote you would configure it with.

 git switch -c change-just-for-forgejo
 git commit --allow-empty -m "Test push just to forgejo"
 git push --set-upstream forgejo change-just-for-forgejo
Enumerating objects: 2, done.
Counting objects: 100% (2/2), done.
Writing objects: 100% (2/2), 376 bytes | 376.00 KiB/s, done.
Total 2 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a new pull request for 'change-just-for-forgejo':
remote:   <https://forgejo.hopto.org/nshephard/mvdate/compare/main...change-just-for-forgejo>
remote:
remote: . Processing 1 references
remote: Processed 1 references in total
To ssh://forgejo.hopto.org:1234/nshephard/mvdate.git

-   [new branch]      change-just-for-forgejo -> change-just-for-forgejo

branch 'change-just-for-forgejo' set up to track 'forgejo/change-just-for-forgejo'.

Git reports that the local change-just-for-forgejo has been setup to track forgejo/change-just-for-forgejo and the following entry has been added to .git/config

[branch "change-just-for-forgejo"]
    remote "forgejo"
    merge = refs/heads/change-just-for-forgejo

A Note on SSH Ports

If a remote is using a non-standard port for SSH connections (i.e. anything other than 22) then you have to use a different format for specifying the remote URL. Instead of forgejo@forgejo.hopto.org:nshephard/mvdate.git you must explicitly state the protocol (ssh://) and include the port so that it reads ssh://forgejo@forgejo.hopto.org:1234 and so to add it you would be added with the following

 git remote add forgejo ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git
 git remote -v
forgejo ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git (fetch)
forgejo ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git (push)
origin  git@gitlab.com:nshephard/mvdate.git (fetch)
origin  git@gitlab.com:nshephard/mvdate.git (push)

I use a non-standard port and so use that convention for the remainder of this article. If you do not use a non-standard port you can either change the port (1234) to the default (22) or use the conventional syntax for referring to the remote.

Mirroring Remotes

The really neat thing is that it is possible to have a local repository track multiple remotes, which means when you push your changes it will go to both. You could configure an alias to push to both of the remotes we currently have defined, but there is an excellent post on StackOverflow that shows how to do this with Git itself because each remote can have multiple pushurls.

As we have added a second remote to our configuration our .git/config for mvdate our configuration currently looks like this.

[remote "origin"]
    url = git@gitlab.com:nshephard/mvdate.git
    fetch = +refs/heads/*:refs/remotes/origin/*
    fetch = +refs/merge-requests/*/head:refs/pullreqs/*
[remote "forgejo"]
    url = ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git
    fetch = +refs/heads/*:refs/remotes/forgejo/*
[remote]
    pushDefault = origin
[branch "main"]
    remote = origin
    merge = refs/heads/main
[branch "change-just-for-forgejo"]
    remote = forgejo
    merge = refs/heads/change-just-for-forgejo

As above, there are two remotes are defined, origin and forgejo, the default to push to is set to origin and the main branch is setup to track origin whilst the change-just-for-forgejo branch is setup to track forgejo.

 git remote -v
forgejo ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git (fetch)
forgejo ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git (push)
origin  git@gitlab.com:nshephard/mvdate.git (fetch)
origin  git@gitlab.com:nshephard/mvdate.git (push)

How do we get the origin remote setup and configured to push to both gitlab and forgejo? This can be done using the set-url --add --push options to git remote, below we add forgejo as a push target to origin.

NB Note I use a non-standard SSH port in the following, see above note.

 git remote set-url --add --push origin ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git
 git remote -v
forgejo ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git (fetch)
forgejo ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git (push)
origin  git@gitlab.com:nshephard/mvdate.git (fetch)
origin  ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git (push)

But this has removed the original push target under origin which pointed to gitlab so we need to add that back in.

 git remote set-url --add --push origin git@gitlab.com:nshephard/mvdate.git
 git remote -v
forgejo ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git (fetch)
forgejo ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git (push)
origin  git@gitlab.com:nshephard/mvdate.git (fetch)
origin  ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git (push)
origin  git@gitlab.com:nshephard/mvdate.git (push)

We now have two push targets on origin, one pointing to gitlab.com (using the default port 22) and one pointing to forgejo.hopto.org (on port 1234) and as the default target is origin when we git push it will send the changes to both. We still have the forgejo remote defined and it only tracks the forgejo URL.

We can test this with an empty commit on a new branch, test-both, which we first create.

 git switch -c test-both
 git commit --allow-empty -m "Testing pushing to GitLab and Forgejo"
[test-both c07caf6] Testing pushing to GitLab and Forgejo
 git push
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 210 bytes | 210.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a new pull request for 'test-both':
remote:   <https://forgejo.hopto.org/nshephard/mvdate/compare/main...test-both>
remote:
remote: . Processing 1 references
remote: Processed 1 references in total
To ssh://forgejo.hopto.org:1234/nshephard/mvdate.git

-   [new branch]      test-both -> test-both

branch 'test-both' set up to track 'origin/test-both'.
Enumerating objects: 26, done.
Counting objects: 100% (26/26), done.
Writing objects: 100% (26/26), 16.75 KiB | 8.37 MiB/s, done.
Total 26 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: To create a merge request for test-both, visit:
remote:   <https://gitlab.com/nshephard/mvdate/-/merge_requests/new?merge_request%5Bsource_branch%5D=test-both>
remote:
To gitlab.com:nshephard/mvdate.git

-   [new branch]      test-both -> test-both

branch 'test-both' set up to track 'origin/test-both'.

The output above shows that the branch test-both was pushed to both the URLs we have configured as push targets to origin and if you visit the repositories you will find the branches now exist there.

Deleting Remotes

In my use case I simply want to push both remotes so that they mirror each other so I can delete the forgejo remote. This will leave the push URL for that remote under the configuration for origin and allows us to set any branch to use the origin as a the remote and any changes will be pushed to both.

There may be instances where you want to leave the additional remote in place if you wanted to push some changes just to that remote so its not essential that you remove it, but if you want to you can delete reference to a remote from your local configuration.

 git remote remove forgejo
 git remote -v
origin  git@gitlab.com:nshephard/mvdate.git (fetch)
origin  ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git (push)
origin  git@gitlab.com:nshephard/mvdate.git (push)

Because the change-just-for-forgejo was setup to track the forejo remote we would need to change that target, we can do so with the following

 git switch change-just-for-forgejo
 git branch --set-upstream-to=origin/change-just-for-forgejo change-just-for-forgejo

This changes the remote target for the branches definition and our configuration now looks like the following.

[remote "origin"]
    url = git@gitlab.com:nshephard/mvdate.git
    fetch = +refs/heads/*:refs/remotes/origin/*
    fetch = +refs/merge-requests/*/head:refs/pullreqs/*
    pushurl = ssh://forgejo@forgejo.hopto.org:1234/nshephard/mvdate.git
    pushurl = git@gitlab.com:nshephard/mvdate.git
[remote]
    pushDefault = origin
[branch "main"]
    remote = origin
    merge = refs/heads/main
[branch "change-just-for-forgejo"]
    remote = origin
    merge = refs/heads/change-just-for-forgejo

Conclusion

Having a local repository push to two remotes is a simple way of a mirroring. Whether you have a use case for it depends on what you are doing. I could easily imagine this could get very complicated if changes were pushed by others to each remote, but I wouldn’t be surprised if Git is regularly used in this way by others.

Of course if you want to push a branch that you wish to keep private to one remote only then you would have to be very careful in how you use this setup. The original StackOverflow solution inspired that this post suggests creating an independent remote (e.g. all) so that you can push changes to origin or the second remote (in this example forgejo) and use all only when you wish to push changes to both.

Its been good for me to return to a topic I’ve delved into in the past, this second time round I feel I’ve got a slightly better grasp of what I’m doing and have a neater solution to achieve what is required.

No matching items

Reuse

Citation

BibTeX citation:
@online{shephard2024,
  author = {Shephard, Neil},
  title = {Git {Remotes} {Revisited}},
  date = {2024-02-17},
  url = {https://blog.nshephard.dev/posts/git-remotes-revisited/},
  langid = {en}
}
For attribution, please cite this work as:
Shephard, Neil. 2024. “Git Remotes Revisited.” February 17, 2024. https://blog.nshephard.dev/posts/git-remotes-revisited/.