skip to content

How do you bring your fork's main branch up to date with the upstream repository in Git?

level: middleimportance: must knowfreq 66%

answer

  1. Two remotes, one direction of truth
  2. Fetch first, integrate second
  3. The integration should never create a commit
  4. --ff-only turns drift into a loud error
  5. fetch upstream, merge --ff-only, push origin

basics

~10 s

Fetch the upstream remote, switch to your local main, fast-forward it onto upstream/main, then push that to your fork. Keeping main free of your own commits is what makes the fast-forward always succeed.

solid answer

~40 s

The routine is four commands: `git fetch upstream`, `git switch main`, `git merge --ff-only upstream/main`, then `git push origin main`. Fetching updates `refs/remotes/upstream/`; the fast-forward-only merge moves your local `main` to exactly upstream's commit, and the push makes your fork's copy match. I use `--ff-only` deliberately: if it fails, that is a signal that my `main` has commits upstream does not have — usually a mistake, because in a fork workflow `main` should be a pure mirror and all real work should live on topic branches. Fixing that is a decision I want to make consciously rather than have Git silently create a merge commit. A single `git pull --ff-only upstream main` collapses the fetch and merge if you prefer one line.

code

bash · 4 lines
bash
git fetch upstream --prune
git switch main
git merge --ff-only upstream/main
git push origin main

go deeper

for a junior

Learn the four-command sequence by heart: fetch upstream, switch main, merge --ff-only upstream/main, push origin main. Know that a fork never updates itself.

for a middle

Explain why the integration step is a fast-forward and not a real merge, and what --ff-only protects against. Be able to read what a fast-forward failure implies about your branch's history.

for a senior

Demonstrate the discipline behind it — main as a pure mirror, all work on topic branches, and a diagnosis path when the mirror has drifted. Mention branching straight from upstream/main as the cheaper alternative.

for a principal

Talk about it as contributor ergonomics: how much sync ritual you impose on outside contributors, whether stale forks cause review churn, and when documenting one sync recipe beats letting everyone invent their own.

## The problem A fork is a snapshot. The hosting service copies the repository once, and from that moment your fork's `main` is frozen while the original project keeps moving. Nothing updates it automatically. Before you start a new topic branch — and before you ask anyone to review one — you want your base to be the project's current `main`, not a month-old copy of it. ## The canonical routine ``` git fetch upstream git switch main git merge --ff-only upstream/main git push origin main ``` Step by step: 1. **`git fetch upstream`** contacts the original repository and updates `refs/remotes/upstream/*`. No local branch and no working-tree file changes. Add `--prune` if you also want tracking refs for deleted upstream branches cleaned up. 2. **`git switch main`** puts you on the branch you are about to move. (`git checkout main` does the same; `switch` is the modern branch-only spelling.) 3. **`git merge --ff-only upstream/main`** advances `main` to upstream's commit. Because your `main` should have no commits of its own, upstream's tip is a descendant of yours and the merge is a pure pointer move — no merge commit, no new SHAs, no conflicts possible. 4. **`git push origin main`** publishes that pointer move to your fork so the server-side copy matches too. This push is also a fast-forward, so it needs no force. `git pull --ff-only upstream main` combines steps 1 and 3 in one command, at the cost of not leaving `upstream/main` updated for other branches in some configurations — fetching the whole remote first is the habit worth keeping. ## Why `--ff-only` and not a bare merge A bare `git merge upstream/main` succeeds either way: if your `main` has drifted, it silently creates a merge commit that exists only in your fork. That commit then rides along in every topic branch you cut afterwards and shows up as noise in every proposed change. `--ff-only` turns that silent divergence into a loud failure: ``` fatal: Not possible to fast-forward, aborting. ``` That message means one thing: your `main` contains commits `upstream/main` does not. Either you committed directly on `main` by mistake, or a previous sync created a merge commit. Both are worth fixing explicitly. You can inspect the damage with `git log --oneline upstream/main..main`, move any real work onto a topic branch, and then reset `main` back onto `upstream/main`. ## Keep `main` a mirror The whole routine stays trivial only if you never commit on your fork's `main`. Treat it as a read-only mirror of upstream and cut every change as a topic branch: ``` git fetch upstream git switch -c feature/thing upstream/main ``` That form creates the branch directly at upstream's tip and skips the sync of `main` entirely — useful when you only need a fresh base and do not care whether your fork's `main` is current. ## Do you even need to push `main`? Often not. Locally you can always base work on `upstream/main`, so a stale `main` in your fork is cosmetic. Two reasons to push it anyway: your fork's default branch is what other people see and what the hosting service compares proposed changes against, and a wildly stale fork makes it hard for *you* to reason about what your fork contains. It is cheap, so most contributors keep it in step. ## Tags and other branches `git fetch upstream` brings tags reachable from the fetched refs by default. If the project releases from long-lived branches you also care about, fetch and mirror those the same way, or simply reference `upstream/<branch>` directly instead of maintaining local copies. ## What a strong answer sounds like Name the four commands in order, say why the merge is a fast-forward rather than a real merge, and state the discipline that makes it so — never commit on `main` in a fork. Then add the diagnostic: if `--ff-only` refuses, your mirror has drifted, and you should find out why rather than paper over it with a merge commit.

  • `git merge --ff-only upstream/main` fails with "Not possible to fast-forward". What does that tell you?
    That your local `main` has at least one commit `upstream/main` does not contain, so the histories have diverged. Inspect it with `git log --oneline upstream/main..main`. Usually it is work accidentally committed on `main`, or a merge commit from an earlier sloppy sync. Move any real commits to a topic branch, then put `main` back on `upstream/main`.
  • Do you have to sync `main` at all before starting a new topic branch?
    No. `git fetch upstream` followed by `git switch -c feature/x upstream/main` bases the branch on upstream's current tip directly, regardless of how stale your `main` is. Syncing `main` is mostly about keeping your fork's default branch honest for anyone looking at it, and keeping your own mental model accurate.
  • Why is `git push origin main` after the sync never a force-push?
    Because the sync only fast-forwarded `main` — no commit was rewritten, and your fork's `main` is an ancestor of the new tip. The push is itself a fast-forward, which the server accepts by default. If that push ever demands a force, it means your fork's `main` contains commits your local one does not.

saying these in an interview costs you the question

  • Using a bare merge and creating fork-only merge commits
  • Expecting the fork to update itself on the server
  • Committing work directly on the fork's main branch
  • Thinking git fetch alone moves the local main branch
  • Force-pushing main to fix routine drift

context