In open source, most work happens through a fork. The typical setup:
github.com/original-project/awesome ← the original, you do not write here
github.com/me/awesome ← your fork, you write here
Locally it is convenient to configure two remotes:
# After cloning your fork
git clone git@github.com:me/awesome.git
cd awesome
# Add the original as upstream
git remote add upstream git@github.com:original-project/awesome.git
git fetch upstream
Now:
originis your fork. You push your branches here.upstreamis the original. You pull others' updates from here.
Why you need both
Without upstream you get updates only through the GitHub UI: "Sync
fork" in the browser. That works, but the cycle is slow and does not
fit scripts or CI.
With upstream, "sync with the original" takes two commands:
git fetch upstream
git switch main
git rebase upstream/main # or git merge upstream/main
git push origin main # update main in your fork
No UI required. Repeatable from the terminal.
Workflow for a new PR
# 1. Fetch the original
git fetch upstream
# 2. Create the feature branch from fresh upstream/main, not from origin/main
git switch -c feat/my-fix upstream/main
# 3. ... commits ...
# 4. Push to your fork (origin), not to upstream
git push -u origin feat/my-fix
# 5. On GitHub open a PR: from me:feat/my-fix → original-project:main
The key point in step 2: branch from upstream/main, not from
origin/main. Otherwise your fork may be a day behind, and a bunch
of stale commits will end up in the PR.
These are just names
Git knows nothing about "original" and "fork." This is a convention,
nothing more. You can call them mine and theirs and everything
works the same:
git remote rename origin mine
git remote rename upstream theirs
But in a team it is easier to use common names so that instructions like "git fetch upstream && git rebase upstream/main" work the same for everyone.
When you do not need upstream
If you have no fork and push directly to the main repository (a team
member, an owner), upstream is not needed. One origin, standard
flow.
Pitfalls
- Accidental push to upstream. If you have write access to upstream
(for example, you are a maintainer of the original and your fork is
for experiments), you can accidentally
git push upstream mainand update the original's main. Branch protection rules prevent that. - origin/main and upstream/main diverging. If someone else pushed
to your fork separately (another contributor, a copy), main in the
fork and main in upstream will drift apart. Fix: sync with
git push origin upstream/main:main(or recreate the fork). - Forgetting to switch upstream after a forge migration (GitHub to
GitLab). If the original moved, update with
git remote set-url upstream <new>.