Skip to content

← Writing

engineering

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:

  1. git fetch upstream downloads upstream’s new commits into upstream/main without touching your working files — a look-before-you-leap.
  2. git rebase upstream/main replays any commits your main has on top of upstream’s, producing a linear history with no merge bubble. (If your fork’s main is untouched — the common case — this is just a fast-forward.)
  3. git push origin main pushes the synced main back 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 main and 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 main pristine. Don’t commit directly to it; let it mirror upstream. Do all work on feature branches. A clean main makes 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.