Handcrafting Codes← Index

Engineering

Working with multiple remotes

A fresh clone has one remote called `origin`, but you have more...

Jul 02, 2026 / 4 min read / By the author

COVER IMAGE — 16:9

A fresh clone has one remote called origin, and most people go years without ever needing a second. Then you fork a repo, or you mirror to a backup host, or you split a project across two servers, and suddenly git pull does something you didn't expect. The fix isn't complicated. It's just that the mental model of "the remote" has to grow up into "which remote."

A remote is nothing but a named URL plus a namespace for the branches it carries. origin isn't special. It's a default name git picks at clone time, and you can have as many remotes as you want, each tracking its own copy of the branches under its own prefix.

The setup that makes this matter: forks

The most common multi-remote situation is the fork workflow. You don't have write access to the canonical repo, so you fork it, clone your fork, and now you need a line back to the original to stay current. By convention:

  • origin is your fork, the one you push to.
  • upstream is the canonical repo, the one you pull from but never push to.
        upstream (canonical repo)
              │  fetch / pull
              ▼
   ┌────────────────────┐
   │   your local repo   │
   └────────────────────┘
              │  push
              ▼
        origin (your fork)

This is a triangle, not a line. Changes flow down from upstream into your machine, and your work flows out to origin. The two remotes do different jobs, and once you internalize that, the commands stop surprising you.

Add the second remote:

git remote add upstream https://github.com/canonical/project.git
git remote -v   # list every remote and its URL

Each remote has its own branch namespace

This is the part that clears up most of the confusion. After fetching from both, your local repo holds two independent sets of remote-tracking branches:

git fetch upstream
git fetch origin

git branch -r
#   origin/main
#   origin/my-feature
#   upstream/main
#   upstream/release-2.0

origin/main and upstream/main are two different pointers to two different copies of "main," and they drift apart the moment upstream gets a commit your fork doesn't have. Neither is "the" main. They're snapshots of where each remote's main was the last time you fetched it. Seeing them as separate is the whole game.

This is what divergence looks like after upstream moves ahead while you've been building on your feature branch:

gitGraph
   commit id: "base"
   commit id: "shared"
   branch my-feature
   checkout my-feature
   commit id: "mine-1"
   commit id: "mine-2"
   checkout main
   commit id: "upstream-A"
   commit id: "upstream-B"

Here main (tracking upstream/main) has two commits you don't have, and my-feature has two commits upstream has never seen. Bringing them together is a deliberate act, which is the next part.

Pull and push, but say which remote

With one remote, git pull and git push guess correctly because there's only one option. With two, be explicit, at least until your branches have tracking set up.

Update your local main from the canonical repo, rebasing your work on top so history stays linear:

git pull --rebase upstream main

Push your feature branch to your own fork, not upstream:

git push origin my-feature

Mixing these up is the classic fork-workflow mistake: trying to push to upstream (you don't have access, it fails) or pulling from origin when the real changes are landing on upstream (you stay out of date and wonder why). Name the remote and the branch, every time, and the failure mode disappears.

Tracking, so you can stop typing the remote

Once a local branch knows which remote branch it follows, bare git pull and git push work again because the ambiguity is resolved. Set it when you push the first time:

git push -u origin my-feature

The -u (short for --set-upstream) ties my-feature to origin/my-feature. After that, git push from that branch knows where to go. You can check what's tracking what:

git branch -vv

The output marks each local branch with its upstream, so you can see at a glance which branch follows origin and which follows upstream. When a pull or push does something unexpected, this is the first thing to check, because nine times out of ten a branch is tracking a remote you didn't mean it to.

Keeping remote URLs straight

You can repoint a remote without removing it, which is handy when a repo moves hosts or you switch from HTTPS to SSH:

git remote set-url origin git@github.com:you/project.git
git remote rename upstream canonical
git remote remove canonical

The model to hold onto is small. A remote is a named URL with its own branch namespace. origin is just the one you got for free. The moment you add a second, stop saying "the remote" and start naming it, and multi-remote work turns out to be ordinary git with the labels filled in.