Multiple Git Remotes

A remote is a named pointer to another copy of a repository — nothing more. origin is only a convention: the name git clone gives the remote it cloned from. A repo can have any number of remotes with any names, and that’s how one local repo pushes to both a self-hosted GitLab and GitHub.

your working copy
      ├── origin  → ssh://git@gitlab.example.com/team/project.git   (primary)
      └── github  → https://github.com/you/project.git              (mirror)

The commands

git remote -v                          # list remotes and their URLs
git remote add github <url>            # add a second remote named "github"
git push github master                 # push a branch to a specific remote
git remote rename origin gitlab        # names are yours to choose
git remote remove github               # detach (never touches the remote repo)

Fetch and push URLs can even differ per remote (git remote set-url --push), though that’s rarely needed.

Upstream tracking — where a bare git push goes

Every local branch can have one upstream (tracking branch): the remote/branch pair that plain git push, git pull, and git status’s “ahead/behind” compare against. With multiple remotes this is the detail that bites:

git branch -vv            # shows each branch's upstream, e.g. [origin/master]
git status -sb            # first line: ## master...origin/master
git branch -u origin/master   # set (or fix) the upstream explicitly

A branch tracks exactly one upstream — pushing to the other remote is always an explicit git push <remote> <branch>.

Creating the second remote with a CLI (gh, glab)

The hosting CLIs can create the server-side repo and wire the remote in one step. GitHub’s:

gh repo create <name> --private --source . --remote github --push
  • --source . — use the current repo instead of creating a fresh one
  • --remote github — add the new repo as a remote with this name
  • --push — push the current branch immediately

The trap: --push also re-points the current branch’s upstream to the new remote. From then on, a bare git push goes to GitHub, not to the remote you think of as primary — silently. If the original remote is the primary, restore it right after:

git branch -u origin/master

GitLab’s glab repo create behaves analogously for self-hosted GitLab.

Visibility: private vs public vs internal

Repo visibility is a property of the server-side repo, set at creation (--private / --public; GitLab adds --internal — visible to anyone logged into that GitLab instance, which on a company or self-hosted server is not the same as private). Check, don’t assume:

gh repo view <owner>/<name> --json visibility

A safe habit: default to private and open up deliberately — you can flip private → public in one setting, but nothing un-publishes a repo that was cloned while public.

Keeping the mirror current

Two remotes never sync themselves; every update is an explicit push:

git push origin master && git push github master

For a repo where the mirror must never lag, that pair can live in a small script or a git alias. Accept that the mirror lags otherwise, and treat the primary remote as the source of truth.


[!note] In your repos (internal — remove this section before sharing) House convention (global CLAUDE.md, established 2026-07-23 with nat-demo): the self-hosted GitLab is always origin and keeps upstream tracking; GitHub (unixtool1192) is a second remote named github, and repos pushed there are private by default — this work is distributed only by Tony, for TD SYNNEX. The --push upstream-stealing trap in this note was hit live during the nat-demo push and fixed with git branch -u origin/master.