Your Repository Now Lives in Two Places
A remote is a nickname for a URL, origin is a convention rather than a keyword, and the ahead/behind counts you trust are computed from a snapshot that may be hours old. Fetch, pull, and push one branch and watch each side move.
Worth reading first: A Branch Is a Sticky Note on One Commit
A remote is a nickname for a URL, origin is a convention rather than a keyword, and the ahead/behind counts you trust are computed from a snapshot that may be hours old. Fetch, pull, and push one branch and watch each side move.
A remote is a nickname for a URL
Everything so far worked with no network and no account. A remote is how a repository on your machine learns about a repository somewhere else.
$ git remote -v# origin [email protected]:you/project.git (fetch)# origin [email protected]:you/project.git (push) $ git remote add origin [email protected]:you/project.git$ git remote rename origin upstream$ git remote remove old-server$ git remote set-url origin [email protected]:you/renamed.gitThat is the whole concept. A remote stores a name and a URL so you can type origin instead of the address every time. A repository can have as many as you like — which is exactly how open-source contribution works, with one remote for your fork and another for the original project.
Cloning sets one up for you automatically:
$ git clone [email protected]:you/project.git# creates ./project, downloads the full history,# adds a remote called origin, and checks out the default branchorigin is a convention, not a keyword
origin is not special to Git. It is the name git clone happens to use, and you can rename it to anything without breaking a thing. Knowing this is what makes the multi-remote setups later on stop looking like magic.
There is a second, more important thing that is also just a name: origin/main. It looks like it refers to the server. It does not — it is a local ref, on your disk, recording what main looked like on the server the last time you asked.
$ cat .git/refs/remotes/origin/main# 9f3c1a2e8b4d7f0192c5e6a8b3d4f5c6e7a8b9c0 <- just another file on your machine $ git branch -a# main# * fix/login# remotes/origin/main <- a remote-tracking branch: a cache, not a live view# remotes/origin/fix/loginmain
Your branch, on your machine.
3 commits
origin/main
Your machine's cached snapshot of the server. Only git fetch moves this.
3 commits
the server
What is genuinely on GitHub right now. You cannot see this without asking.
3 commits
git status: ahead 0, behind 0
actually behind: 0 (matches)
Everything is in sync. Try letting a teammate push, then look at what your counters say.
fetch and pull are not the same operation
git fetch downloads new commits and updates your remote-tracking branches. It changes nothing about your branch or your files. It is completely safe and cannot cause a conflict.
git pull is a fetch, followed immediately by a merge into your current branch. It moves your branch and can conflict.
$ git fetch # update origin/*, touch nothing else$ git log --oneline main..origin/main # what is on the server that I do not have$ git diff main origin/main # what would change if I merged it $ git pull # fetch + merge, in one stepThe three-command sequence is a good habit on any branch that matters. Fetch, look at what arrived, then decide. Pulling blind into a branch with local work is how people find themselves in an unexpected conflicted merge in the middle of something else.
pull (merge)
The default. Creates a merge commit if both sides moved. Honest, and it litters the history with "Merge branch main of…" commits.
pull --rebase
Replays your local commits on top of what arrived. No merge commit. Safe for unpushed work, which is the usual case when pulling.
pull --ff-only
Only if your branch has not diverged. Otherwise it refuses and makes you choose deliberately. The safest default.
# Pick one, once, and stop thinking about it$ git config --global pull.rebase true # always rebase on pull$ git config --global pull.ff only # refuse anything that is not a fast-forwardpush needs an upstream the first time
Pushing a brand-new branch fails, because Git does not know which remote branch it corresponds to. The error tells you the exact command to fix it.
fatal: The current branch fix/login has no upstream branch.To push the current branch and set the remote as upstream, use git push --set-upstream origin fix/login$ git push -u origin fix/login # -u is short for --set-upstream$ git push # every push after thatSetting the upstream links your local branch to the remote one permanently. From then on, git push and git pull with no arguments know where to go, and git status can tell you how far ahead or behind you are.
# Make -u the default and never think about it again$ git config --global push.autoSetupRemote trueThe other push failure is the important one:
! [rejected] main -> main (fetch first)error: failed to push some refs to 'github.com:you/project.git'hint: Updates were rejected because the remote contains work that you dohint: not have locally. This is usually caused by another repository pushinghint: to the same ref.This is Git protecting somebody else's commits. The fix is to integrate their work and push again — git pull then git push. The fix is not --force, which deletes their commits, and which is the first suggestion you will find if you paste that error into a search engine.
Ahead and behind are counted from a stale copy
git statusreports things like "Your branch is ahead of origin/main by 2 commits". Read that literally, because it is literally true and not what most people take it to mean.
Commits you have that origin/main — your cached copy — does not. This one is reliable, because it is about your own commits.
Commits origin/main has that you do not. Only as fresh as your last fetch, which may have been on Tuesday.
Unknowable without asking the server. git fetch is how you ask.
# The real answer, always$ git fetch && git status # Every branch, with its upstream and how far off it is$ git branch -vv # Just the numbers, scriptably$ git rev-list --left-right --count main...origin/main# 2 3 <- 2 ahead, 3 behindOne last piece of housekeeping. When a branch is merged and deleted on GitHub, your machine keeps its remote-tracking ref forever, so git branch -a slowly fills with branches that no longer exist.
$ git fetch --prune # delete refs for branches gone from the server$ git config --global fetch.prune true # do it automatically, every fetchKey takeaways
- A remote is a name for a URL. A repository can have several, which is how forks work.
- origin is just the name git clone uses. Nothing about it is special to Git.
- origin/main is a file on your machine recording what the server looked like when you last fetched.
- git fetch updates that cache and changes nothing else. It is always safe.
- git pull is fetch plus merge, moves your branch, and can conflict.
- Configure pull.rebase or pull.ff to stop Git asking you to choose every time.
- The first push of a branch needs -u to link it to a remote branch; push.autoSetupRemote makes that automatic.
- A rejected push means the remote has commits you do not. Pull, then push. Never --force.
- "Behind by N" is measured against your last fetch, so it can be confidently wrong. Fetch first.
- fetch.prune true clears out remote-tracking branches for branches deleted on the server.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.