Keeping a Fork in Sync
· Jerwin Arnado ·
The moment you fork a repo to contribute, a clock starts: the original keeps moving, your fork doesn’t, and within days your branch is built on a stale base. Submit a PR from there and a maintainer sees conflicts before they see your work. Keeping a fork synced is the unglamorous habit behind every clean open-source contribution — and once it’s a routine, it takes ten seconds. I run it constantly across my own open-source repos and the ones I send patches to.
The one-time setup: an upstream remote
When you clone your fork, origin points at your copy. Git has no idea the original
(“upstream”) exists — you have to introduce them. Check what you’ve got:
git remote -v
origin [email protected]:you/the-project.git (fetch)
origin [email protected]:you/the-project.git (push)
Add the original repo as a second remote named upstream:
git remote add upstream [email protected]:original-author/the-project.git
git remote -v
origin [email protected]:you/the-project.git (your fork — you push here)
upstream [email protected]:original-author/the-project.git (the original — read-only)
Now you have two remotes: origin (yours, you push to it) and upstream (theirs, you
only pull from it). This setup is per-clone, done once.
The sync loop: fetch and rebase
To bring your main up to date with the original:
git checkout main
git fetch upstream # download upstream's latest, don't merge yet
git rebase upstream/main # replay your main on top of theirs
git push origin main # update your fork on GitHub
What each step does:
git fetch upstreamdownloads upstream’s new commits intoupstream/mainwithout touching your working files — a look-before-you-leap.git rebase upstream/mainreplays any commits yourmainhas on top of upstream’s, producing a linear history with no merge bubble. (If your fork’smainis untouched — the common case — this is just a fast-forward.)git push origin mainpushes the syncedmainback to your fork on GitHub.
Prefer rebase over merge here to avoid littering your fork with “Merge upstream/main”
commits — same linear-history reasoning as
everywhere else.
Rebasing your feature branch onto fresh upstream
Syncing main is half the job. Your actual work lives on a feature branch that also
needs to sit on top of the latest upstream, or the PR conflicts:
git checkout feat/my-contribution
git fetch upstream
git rebase upstream/main
This replays your feature commits onto upstream’s current tip. You’ll likely hit
conflicts here — that’s expected and fine; resolve them, git add, git rebase --continue
(conflict resolution is its own post). Because rebasing rewrites your branch’s commits,
the push back to your fork needs a force — but a safe one:
git push --force-with-lease origin feat/my-contribution
--force-with-lease refuses to overwrite the remote if someone else has pushed to your
branch in the meantime — the safe alternative to a blind --force. On your own fork’s
feature branch this is exactly right; it updates the open PR with your rebased commits.
The shortcut: GitHub’s “Sync fork”
For just catching main up — no local feature work involved — GitHub’s web UI has a
Sync fork button on your fork’s page, and the gh CLI does it headless:
gh repo sync you/the-project --branch main
Handy for keeping main current, but it only does fast-forward-style syncs of a branch.
The moment you need to rebase a feature branch onto fresh upstream, you’re back to the
fetch + rebase loop above.
Caveats and best practices
- Sync before you start, not after you’re done. Branch your feature off a freshly
synced
mainand you front-load the conflict resolution instead of discovering a week’s worth at PR time. --force-with-lease, never bare--force. The lease check is what stops you from silently clobbering work. Make it the only force-push you ever type.- Force-push only your own fork’s feature branches. Never force-push a branch others
build on, and never anything on
upstream— the one rebase rule still governs. - Keep your fork’s
mainpristine. Don’t commit directly to it; let it mirror upstream. Do all work on feature branches. A cleanmainmakes every future sync trivial.
Conclusion
git remote add upstream <original-repo-url> # once
git fetch upstream && git rebase upstream/main && git push origin main # sync main
git rebase upstream/main && git push --force-with-lease # rebase your feature branch
Two remotes, a fetch-and-rebase reflex, and a safe force-push. Run it as a habit and your fork never drifts far enough to make a maintainer’s life hard — which is most of what makes your contributions easy to accept.