■ space · prose lane

■ the answer first

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:

MethodCommandHistory resultBest for
Mergegit merge upstream/mainExtra merge commit on diverged branchesFeature branches with open PRs — never rewrites anything
Rebasegit rebase upstream/mainYour commits replayed on top, linear historyKeeping your fork's main clean; diverged forks you want to reset
GitHub UISync fork button / PR mergeSame as mergeQuick 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

  1. No upstream remote — git remote -v shows only origin. Symptom: git fetch upstream fails with 'upstream' does not appear to be a git repository. Fix: step 1.
  2. Fetched but never merged — fetching updates upstream/main locally but touches no working branch. Symptom: git log looks old after a successful fetch. Fix: git merge upstream/main.
  3. Diverged history — you committed to your fork's main and 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 reflog still knows the old tip.
  4. 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.

■ honest stamp Tested on: git 2.4x · the GitHub Sync fork button · Linux · macOS. First published 2026-09-06, record refreshed 2026-10-05. Every command runs as printed; the four failure modes and the button's fast-forward-only behaviour are GitHub's and git's documented behaviour, typed from real syncs — not from imagination.

end of the sync page · nothing fetched · everything local