Articles/Git/Git: Tracking a Remote Branch for Changes

Git: Tracking a Remote Branch for Changes

When you fork a project, you need a way to pull in changes from the original repository, usually called upstream. Here is how to wire up an upstream remote, actually sync your fork, and set up branch tracking so plain git pull and git push just work.

November 4, 2018·4 min read

Sometimes you need to track a remote branch for changes, typically the original project you forked from, conventionally named upstream. Say you are on a team where you forked a project and have your own copy. Before you open a pull request, you want to pull in any changes from the originating repository so your work sits on top of the latest code. Here is how to set that up and keep it in sync.

Two different things called "upstream"

The word upstream gets used for two related but distinct things, and mixing them up is where people get confused.

  1. The upstream remote. A named remote (conventionally upstream) pointing at the original repository you forked, so you can fetch the project's new commits. Your own fork is usually the origin remote.
  2. A branch's upstream tracking ref. The link between one local branch and the specific remote branch it pulls from and pushes to. Your local main "tracks" origin/main. This relationship is what powers a bare git pull and git push, and what produces the Your branch is up to date with 'origin/main' line in git status.

This post covers both. First the fork scenario, then branch tracking at the end.

Add the upstream remote

Start by checking what remotes you already have. Right after forking and cloning, you will usually see only origin, which is your fork.

BASH
git remote -v

Add the original repository as a second remote named upstream.

BASH
git remote add upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPO.git

Swap ORIGINAL_OWNER and ORIGINAL_REPO for the username and repository name from the original project's URL, the one you forked.

Confirm it is configured. You should now see both origin (your fork) and upstream (the original), each with a fetch and a push entry.

BASH
git remote -v

Actually sync: fetch, merge, push

Adding the remote is only half the job. The whole point is to pull the upstream project's new commits into your fork. That is three steps: fetch, merge, push.

BASH
git fetch upstream          # download upstream's commits and refs (merges nothing yet)
git checkout main           # be on your local default branch
git merge upstream/main     # bring upstream's main into your local main
git push                    # update YOUR fork (origin) on GitHub

A couple of things worth calling out. git fetch upstream downloads everything but changes none of your branches; it just makes upstream/main available locally. The final git push matters: syncing only updates your local clone, so without it your fork on GitHub is still behind.

Merge or rebase?

I used git merge above, which is the safe default. The alternative is to rebase your branch on top of upstream instead.

BASH
git rebase upstream/main

Merging is non-destructive, but it adds a merge commit each time you sync. Rebasing replays your commits on top of upstream for a cleaner, linear history, at the cost of rewriting commit hashes. The Golden Rule of Rebasing applies: never rebase a branch that other people have already pulled. For keeping a personal fork's main in step with upstream, either works. If you keep your main as a clean mirror of upstream and do all your real work on feature branches, these syncs fast-forward and the whole question goes away.

The quick way: gh CLI and the Sync fork button

If your fork lives on GitHub, you often do not need the manual commands at all. The GitHub CLI can sync the fork for you.

BASH
gh repo sync                      # sync your local clone from its parent
gh repo sync OWNER/YOUR_FORK      # sync the fork on GitHub from upstream

GitHub's web UI has the same thing: on your fork's page, the "Sync fork" dropdown shows how far behind you are and updates your branch with one click. These are convenient, but knowing the fetch and merge underneath is what saves you when a sync hits a conflict.

Bonus: tracking a remote branch for real

The fork case is one kind of "tracking." The other is setting a branch's upstream tracking ref, so plain git pull and git push know where to go without you naming a remote and branch every time.

When you first push a new branch, -u (short for --set-upstream) creates the branch on the remote and wires up tracking in one go.

BASH
git push -u origin my-feature

For a branch that already exists, you can set or change its upstream after the fact.

BASH
git branch --set-upstream-to=origin/main

To see what each branch is tracking, ask for the verbose listing. It shows the tracked branch and how far ahead or behind you are.

BASH
git branch -vv

That tracking link is exactly what git status reads when it tells you Your branch is up to date with 'origin/main', and it is what lets a bare git pull know which remote branch to fetch and merge.

Keep your main clean

One habit makes all of this painless: never commit directly to the branch you sync. If your local main only ever moves by syncing from upstream, every sync fast-forwards cleanly. The moment you commit your own work onto main, easy fast-forwards turn into merge conflicts. Do your work on feature branches off a clean main, and keep main as a mirror of the source of truth.

That is the whole loop: add the upstream remote once, then fetch and merge (or let gh do it) whenever you need the latest, and push to update your fork. When you are coordinating this across a team, it pairs nicely with a shared branching strategy, which I cover in Git Flow vs GitHub Flow.