Git sync fork with upstream: three safe methods
fetch upstream, merge, push origin — the whole sync, plus the rebase policy, the GitHub button's limits, and the four reasons a fork won't sync.
■ space · prose lane
git fetch upstream && git merge upstream/main && git push origin main — that is the whole sync.
Prefer linear history? Swap merge for git rebase upstream/main + push --force-with-lease.
Fork "won't sync"? Missing upstream remote is cause #1 — wire it once, below.
THE PIPELINE
A sync always travels one direction — upstream → your local main → origin. Nothing on the fork flows back upstream; that is what pull requests are for.
origin = your fork · upstream = the project you forked from — a common mistake is pointing both remotes at your fork; then a "sync" quietly does nothing, because you fetched from a copy just as stale as your own
STEP 1 — WIRE THE REMOTE (ONCE PER CLONE)
# inside your local clone of the fork
git remote add upstream https://github.com/ORIGINAL_OWNER/REPO.git
git remote -v # confirm: origin -> your fork, upstream -> the original
Find the correct URL on the original repo's page, under the green Code button. This wiring is permanent — everything below is what you type next time.
STEP 2 — FETCH, MERGE, PUSH
git checkout main
git fetch upstream
git merge upstream/main
git push origin main
verify: git rev-list --count main..upstream/main → 12 before the merge → 0 after the merge
A fast-forward is the normal outcome — your main had nothing new, so it simply slides forward to upstream/main and no merge commit is created. To see how far behind you are before merging: git rev-list --count main..upstream/main after the fetch. That's the whole routine; step 3 is remembering it exists: git fetch upstream && git merge upstream/main && git push origin main.
MERGE OR REBASE?
Both land the same code in your fork; they differ in the history they leave behind. Pick one policy per repo and stick to it:
| Method | Command | History result | Best for |
|---|---|---|---|
| Merge | git merge upstream/main | Extra merge commit on diverged branches | Feature branches with open PRs — never rewrites anything |
| Rebase | git rebase upstream/main | Your commits replayed on top, linear history | Keeping your fork's main clean; diverged forks you want to reset |
| GitHub UI | Sync fork button / PR merge | Same as merge | Quick catch-up with no clone open |
# the rebase variant, linear history:
git fetch upstream
git rebase upstream/main
git push --force-with-lease origin main
The force push is required because rebasing rewrites commit IDs — your fork's remote branch no longer descends from your local one. Always prefer --force-with-lease over --force: it refuses to overwrite the remote if someone (or another machine of yours) pushed in the meantime, which makes the dangerous command safe by default. One rule worth tattooing on your wrist: never rebase a branch that has an open pull request unless you know what you are doing — rebasing changes commit IDs, which can detach an open PR from its commits. Sync main with merge, keep PR branches out of it.
"MY FORK WON'T SYNC" — THE FOUR USUAL SUSPECTS
- No
upstreamremote —git remote -vshows onlyorigin. Symptom:git fetch upstreamfails with'upstream' does not appear to be a git repository. Fix: step 1. - Fetched but never merged — fetching updates
upstream/mainlocally but touches no working branch. Symptom:git loglooks old after a successful fetch. Fix:git merge upstream/main. - Diverged history — you committed to your fork's
mainand upstream moved too. Fix, if you want upstream to win:git reset --hard upstream/main— it throws away your local main-only commits, so back them up with a branch first; if a bad reset already happened,git reflogstill knows the old tip. - Push rejected as non-fast-forward after a rebase — you rebased but pushed normally. Fix:
git push --force-with-lease origin main.
A fifth, rare case: the upstream repo was renamed or deleted, so even step 1's URL 404s. GitHub redirects renamed repos, so a hard failure usually means deleted or made private — nothing to sync to.
THE BUTTON
Yes, the website can do it: on your fork's page, the branch dropdown shows a Sync fork button whenever your branch is behind; one click pulls upstream in. Below the fold, the same thing works as a pull request from upstream/main into your fork's main. The button's limits explain when to fall back to the CLI: it only does a fast-forward or merge — it will not rebase, and it refuses outright when the branches have diverged, telling you to discard commits or use the command line. It also syncs only the default branch. For everything past a simple catch-up, the three commands are the tool.
HOW OFTEN?
Before every new piece of work is the honest answer: branch off a fresh main, and no PR you open starts with "this is based on a version from three weeks ago". For forks you actively contribute to, a daily or per-session sync of main costs seconds. For a fork you only read or deploy, sync when upstream ships something you want — subscribe to the original repo's releases feed and sync on release. Syncing is cheap precisely because it is routine; a fork six months behind often needs surgery instead of a merge, which is how "sync my fork" turns into an afternoon.
08THE CHEAT SHEET
# one-time setup
git remote add upstream https://github.com/ORIGINAL_OWNER/REPO.git
# routine sync (merge policy)
git fetch upstream && git merge upstream/main && git push origin main
# routine sync (rebase policy, linear history)
git fetch upstream && git rebase upstream/main && git push --force-with-lease origin main
# how far behind am I?
git fetch upstream && git rev-list --count main..upstream/main
# diverged beyond repair — make main identical to upstream (destructive)
git fetch upstream && git reset --hard upstream/main && git push --force-with-lease origin main
Keep the routine two-liner in your muscle memory and the diverged fork stays a curiosity you read about, not a problem you fix.
end of the sync page · nothing fetched · everything local