Multiple Remotes & Upstream Tracking
Manage multiple remotes and upstream tracking in Git. Hands-on guide for developers learning step by step — practical exercises, troubleshooting, and next steps.
Focus: manage multiple remotes and upstream tracking
You cloned a repo, pushed a few branches, and life was good. Then you forked a project on GitHub, added the original as a second remote, and suddenly git push fails with fatal: The current branch has no upstream branch. Or you’re juggling an internal mirror and a public GitHub repo, and you push to the wrong one by accident. Managing multiple remotes and upstream tracking is the difference between a Git setup that feels like a superpower and one that sends you scrambling for git log to figure out where your commits actually went. This lesson gives you a precise mental model and hands-on commands to tame multiple remotes, so you can push, pull, and collaborate without fear.
The problem this lesson solves
You’ve probably felt this pain: you’re working on a feature branch, you run git push, and Git refuses, telling you the branch has no upstream. Or you type git pull and changes come from a remote you didn’t expect. Worse, you add a second remote for a fork or a mirror, and now you mix up which remote is which — pushing work to the wrong place or pulling stale code. These aren’t just annoyances; they cause wasted time, broken builds, and embarrassing force-pushes. The root cause is that Git’s remotes are just labels for URLs, and upstream tracking is the invisible link that tells Git which remote branch your local branch should talk to by default. Once you understand both, you can control exactly what happens on every push, pull, and fetch.
Core concept / mental model
Think of your Git repo as a workshop. Each remote is a door to a different warehouse — one door labeled origin might lead to your team’s shared server, another labeled upstream leads to the original open-source project, and a third labeled backup leads to a private mirror. Each door has an address (the remote URL), and each local branch has a preferred door — that’s upstream tracking. When you say git push without arguments, Git uses that preferred door. If you haven’t set it, Git has no idea which door you mean, so it fails. In Git terms, a remote is a named reference to a URL, and an upstream is the default remote-tracking branch associated with a local branch. Remote-tracking branches look like origin/main and live in your repo as read-only snapshots of what’s on the remote. Upstream tracking tells Git: "This local main should follow origin/main by default." It’s a one-to-one mapping, but you can change it anytime with git branch --set-upstream-to.
How it works step by step
-
List existing remotes — Use
git remote -vto see every remote name and URL. This is your starting map. -
Add a new remote — Use
git remote add <name> <url>to create a new door. The name is local; it can be anything (e.g.,upstream,backup,fork). The URL is typically HTTPS or SSH. -
Fetch from any remote — Use
git fetch <remote>to update your remote-tracking branches (e.g.,upstream/main) without merging anything. -
Set upstream tracking — When creating a branch, use
git push -u <remote> <branch>to set upstream on the first push. For an existing branch, usegit branch --set-upstream-to=<remote>/<branch> <branch>. -
Push and pull with confidence — With upstream set,
git pushandgit pulluse that remote/ref by default. To target a different remote, be explicit:git push <remote> <branch>. -
Change or remove remotes — Use
git remote rename,git remote set-url, andgit remote removeto keep your door list tidy.
Read more in the hands-on section below.
Hands-on walkthrough
Scenario: Collaborating with a fork and an upstream
You forked a repository on GitHub, cloned your fork, and want to sync with the original. Here’s how you set up and manage two remotes.
# Start in a fresh clone of your fork
$ git clone https://github.com/yourname/project.git
$ cd project
# Add the original repository as 'upstream'
$ git remote add upstream https://github.com/original/project.git
# Verify both remotes
$ git remote -v
origin https://github.com/yourname/project.git (fetch)
origin https://github.com/yourname/project.git (push)
upstream https://github.com/original/project.git (fetch)
upstream https://github.com/original/project.git (push)
# Fetch upstream's branches
$ git fetch upstream
# Create a feature branch based on upstream's latest
$ git checkout -b feature/my-change upstream/main
# Push it to your fork and set upstream tracking
$ git push -u origin feature/my-change
What just happened? git push -u origin feature/my-change created origin/feature/my-change on your fork and set your local branch’s upstream to that. Now git push and git pull on this branch work without arguments.
Updating a fork from upstream
# Get the latest from the original repo
$ git fetch upstream
# Switch to your main branch (assuming it tracks origin/main)
$ git checkout main
# Merge upstream/main into your main
$ git merge upstream/main
# Push the updates to your fork
$ git push
If you prefer rebase over merge, use git rebase upstream/main instead. Both keep your fork current.
Setting upstream for an existing branch
Maybe you created a local branch, pushed it, but never set upstream. Fix it after the fact:
$ git branch --set-upstream-to=origin/feature/x feature/x
Branch 'feature/x' set up to track remote branch 'feature/x' from 'origin'.
Now git push and git pull work. If you want to change which remote a branch tracks, point to a different remote-tracking branch.
Inspecting upstream config
# Show the upstream for the current branch
$ git rev-parse --abbrev-ref --symbolic-full-name @{upstream}
origin/feature/x
# Show all branches with tracking info
$ git branch -vv
main abc1234 [origin/main] commit message
* feature/x def5678 [origin/feature/x] commit message
Pro tip:
git remote show origingives a verbose report of all upstream relationships and which local branches are ahead/behind. Run it when you’re confused about your tracking setup.
Compare options / when to choose what
| Approach | What it sets | Best for | Drawback |
|---|---|---|---|
git push -u on first push |
Sets upstream immediately | New branches | Easy to forget if you push later |
git branch --set-upstream-to |
Sets upstream for existing branch | Branches already pushed | Requires you to remember the remote branch name |
git config branch.<branch>.remote/.merge |
Manual config | Automation / scripting | Fiddly, error-prone |
For most workflows, git push -u is the cleanest. Use --set-upstream-to as a repair tool. If you need precise control (e.g., a branch tracking a different remote), edit the config directly, but know what you’re doing.
Variations: remote names, URL rewriting, and hubs
- Multiple remotes for mirrors — You might add
internalandpublicremotes to push the same code to two places. Usegit remote addfor each. - Rename remotes —
git remote rename origin githubkeeps youroriginname free for another purpose. - Fetch from all remotes —
git remote updatefetches from every remote at once.
Troubleshooting & edge cases
- "fatal: The current branch ... has no upstream branch" — You didn’t set tracking, or you’re on a new branch. Fix with
git push -u origin <branch>orgit branch --set-upstream-to=origin/<branch>. - "error: remote origin already exists" — You tried to add a remote that already exists. Check with
git remote -v; if the URL is wrong, usegit remote set-url origin <new-url>. - Upstream branch deleted on remote —
git branch -vvshows[origin/deleted]. You can still track, butgit pullwill try to merge from a non-existent ref. Solution: fetch with--prune(git fetch origin --prune) and reset the upstream withgit branch --set-upstream-to=origin/main main(or a valid branch). - Multiple remotes with the same branch name —
git pullpulls from the configured upstream only. If you meant to pull from another remote, be explicit:git pull upstream main. Never rely on guesswork. - Accidentally pushed to the wrong remote — If you pushed to
upstreaminstead oforigin, you can’t easily undo on the remote. Prevention: always set upstream, and double-checkgit remote -vbefore pushing to a shared repo. Usegit push --force-with-leaseonly if you understand the consequences.
Pro tip: Set a default push behavior with
git config push.default upstream— thengit pushwill only ever push to the upstream branch, never to a random remote.
What you learned & what's next
You now know how to list, add, and remove remotes, set upstream tracking with push -u or --set-upstream-to, and switch between remotes with explicit arguments. You understand the connection between local branches, remote-tracking branches, and upstream configuration. You’ve seen how to sync a fork with an upstream and how to handle common upstream errors. In the next lesson, "Manage multiple remotes and upstream tracking" expands into advanced workflows like multi-remote fetch strategies and remote-tracking branch cleanup. Practice setting up a fork with two remotes and syncing it daily — that muscle memory will save you hours in real open-source work.
Quick recap
- A remote is a named URL; upstream is your branch's default remote ref.
git remote -vreveals your setup;git add,git rename,git removemanage doors.git push -usets upstream;git branch --set-upstream-tofixes it later.- Explicitly target remotes (
git push upstream main) when you need to go off the default path. - Common errors like "no upstream branch" have fast, safe fixes.
Practice recap
Set up a practice repo with two remotes: create a bare repo locally for origin, add a second bare repo as backup, push a branch with -u, then change its upstream to the second remote and verify git push works. Experiment with git remote rename and git remote set-url to solidify your understanding.
Common mistakes
- Adding a remote with a name that already exists — Git throws an error. Always check
git remote -vfirst. - Forgetting
-uon the first push, leaving the branch with no upstream — fix withgit push -u origin <branch>. - Setting upstream to a remote-tracking branch that is stale or deleted — run
git fetch --pruneand reset the upstream. - Pushing to the wrong remote because you didn't verify the URL — use
git remote -vand explicitgit push <remote> <branch>when uncertain.
Variations
- Instead of
git push -u, you can configurepush.default simpleand set upstream manually withgit branch --set-upstream-to. - For multi-remote workflows, use
git remote updateto fetch from all remotes at once, orgit fetch --all. - Use
git remote renameto give remotes semantic names (e.g.,upstreamvsvendor) for clarity.
Real-world use cases
- Contributing to open source: fork a repo on GitHub, add the original as
upstream, and sync daily withfetch upstream && merge upstream/main. - Maintaining an internal mirror and a public release repo — push to both by running
git push internal main && git push public mainwith separate remotes. - Working on a monorepo with separate staging and production remotes — set each branch's upstream to the appropriate remote to deploy with a simple
git push.
Key takeaways
- A remote is just a named URL; upstream tracking is the default link between your local branch and a remote-tracking branch.
- List remotes with
git remote -vand add them withgit remote add <name> <url>to manage multiple doors. - Set upstream on first push with
git push -u, or repair withgit branch --set-upstream-to=<remote>/<branch>. - When you need a different remote, always specify it explicitly:
git pull upstream mainorgit push backup feature. - Use
git branch -vvto see all upstream relationships; troubleshoot withgit remote show origin. - Prevent cross-remote accidents with
git config push.default upstreamand frequentgit fetch --prune.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.