Remotes and Tracking Branches
A remote is a named URL — almost always origin — that points at another copy of the repository, typically the one hosted on a server everyone shares. You do not pull from a remote by its URL each time; you give the URL a short name once, and Git remembers it.
The piece that trips people up is the remote-tracking branch. Names like origin/main live in your local repository, but they are read-only snapshots of where the remote's branches stood the last time you talked to it. Understanding that origin/main is a local cache, not a live window onto the server, is what makes "ahead by 2, behind by 3" mean something.
Remotes and Refspecs
You register a remote with git remote add origin <url>, and from then on commands accept origin in place of the URL. Each remote carries a fetch refspec, which is the rule that says how the remote's branches map into your local namespace. The default looks like +refs/heads/*:refs/remotes/origin/*: take every branch under refs/heads/ on the remote and store it under refs/remotes/origin/ here.
That mapping is why the remote's main shows up locally as origin/main rather than overwriting your own main. The leading + allows non-fast-forward updates to those tracking refs, since they are just a mirror and should always follow the remote even when it was rewritten.
Remote-Tracking Branches
origin/main is an ordinary local pointer that Git moves only when you fetch or push. Nothing on the server reaches in to update it; between fetches it sits frozen at whatever it last saw, which is exactly why it can be out of date. You cannot check it out and commit on top of it — it is not a working branch, and attempting to do so lands you in detached HEAD.
List them with git branch -r, or see local and remote-tracking branches together with git branch -a. These refs are the baseline every "ahead/behind" comparison is measured against.
Upstream (Tracking) Relationship
An upstream link records which remote-tracking branch a local branch follows. You set it with git push -u origin <branch> on the first push, or after the fact with git branch --set-upstream-to=origin/main. Once the link exists, bare git pull and git push know where to go, and git status can print "Your branch is ahead of 'origin/main' by 2 commits."
Without an upstream, those bare commands fail with "no upstream branch," and git status stays silent about divergence because it has nothing to compare against.
Multiple Remotes
A repository can have several remotes at once. The standard fork workflow keeps your fork as origin (where you push) and the canonical project as upstream (where you fetch others' changes from). git remote -v lists every remote with its fetch and push URLs shown separately, so you can confirm you are pushing to your fork and not the project you do not control.
Each remote has its own set of tracking refs — origin/main and upstream/main coexist — letting you compare your fork against the source of truth at any time.
- Expecting
origin/mainto update on its own and trusting a stale "behind by N" — it only moves ongit fetchorgit pull, so the count is as old as your last fetch. - Trying to check out and commit onto a remote-tracking ref like
origin/main— you land in detached HEAD because it is not a local branch. - Forgetting
-uon the first push, then having baregit pushandgit pullfail with "no upstream branch." - Confusing your local
mainwithorigin/mainin a comparison and reading the divergence backwards, then pushing or resetting the wrong way. - Adding the canonical project as
originin a fork workflow and accidentally pushing to a repo you do not own instead of your fork.
- Set the upstream on your first push with
git push -u origin <branch>so later bare commands just work. - Run
git fetchbefore judging ahead/behind, since the counts are only as current as your last fetch. - Inspect the full picture with
git remote show originto see URLs, tracked branches, and push behavior. - Use
git branch -vvto see each local branch's upstream and its ahead/behind counts at a glance. - In fork workflows, name the canonical repo
upstreamand your forkoriginso push always lands in the right place.
Knowledge Check
Why can origin/main be out of date, and what updates it?
- It is a local snapshot that moves only on
git fetchorgit pull, so it lags until you sync - It updates live whenever the server changes, so any staleness means a network fault on your end
- It refreshes every time you run any Git command, including
git logandgit status - It only updates when you run
git commiton the matching local branch
What happens if you check out origin/main and commit?
- You land in detached HEAD, because a remote-tracking ref is not a local branch you can advance
- The commit is pushed straight to the server without an explicit
git push, since checking out a remote-tracking ref opens a live write channel back to origin - Git rewrites the remote branch to match your new commit immediately
- It works exactly like committing on a named branch such as
main, advancing the tracking ref forward and keeping HEAD attached the whole time
What does the upstream link enable?
- Bare
git pull/git pushknow which remote branch to use, andgit statuscan report ahead/behind - It mirrors every local commit up to the server in real time as you work, syncing each new object to the remote branch the moment you record it
- It prevents anyone else from pushing to that branch while you hold the link
- It compresses the branch into a packfile on the remote, bundling its commits into one optimized archive each time you record the upstream
What does the fetch refspec +refs/heads/*:refs/remotes/origin/* do?
- Maps every branch on the remote into your
refs/remotes/origin/namespace as tracking refs - Pushes your local branches up to the remote under
refs/heads/, uploading every commit you have made so the server's branches match yours - Deletes tracking refs for remote branches that no longer exist
- Renames your local
mainbranch toorigin/mainon fetch, moving it out ofrefs/heads/and into the remote-tracking namespace
In a fork workflow, which remote should be the canonical project?
upstream, with your own fork asoriginwhere you pushorigin, with your own fork registered asupstreaminstead- Both
originandupstreamshould point at the canonical project - It does not matter; either remote name works the same way
You got correct