Remotes: push, fetch and pull without surprises
How your repository stays in sync with GitHub: remotes and remote-tracking branches, pushing a new branch, why a push gets rejected, fetching to look before you merge, and pulling with rebase.
By the end of this lesson you'll be able to
0 of 6 completeThree copies of every branch
A remote is another copy of the repository, usually on GitHub, with a short name. The one you
cloned from is called origin. Once you have a remote, each branch exists in three places, and
knowing which one you're looking at removes most of the confusion around push and pull:
| Name | Lives | Updated by |
|---|---|---|
main on GitHub | the server | anyone's git push |
origin/main | your .git, read-only | git fetch, git pull, git push |
main | your .git, where you commit | you |
origin/main is a snapshot from the last time you talked to GitHub. It doesn't update on its own, so
always fetch before deciding you're up to date.
Managing remotes
$ git remote -v # names and URLs origin https://github.com/rajeev/shop.git (fetch) origin https://github.com/rajeev/shop.git (push) $ git remote add upstream https://github.com/owner/shop.git # a second remote, e.g. for a fork $ git remote set-url origin git@github.com:rajeev/shop.git # switch from HTTPS to SSH $ git remote rename upstream source $ git remote remove source $ git remote show origin # branches it has, and what tracks what
git remote -vgitversion control
By convention origin is where you push (your repository or your fork) and upstream is the original
project you forked from.
Push and pull
Pushing a new branch
$ git switch -c feature/x $ git push -u origin feature/x * [new branch] feature/x -> feature/x branch 'feature/x' set up to track 'origin/feature/x'.
git switch -c feature/xgitversion control
git push -u origin feature/xpushupload commits
-u sets the branch's upstream. From then on, plain git push and git pull know where to go,
and git status can compare:
$ git status -sb ## main...origin/main [ahead 1] $ git branch -vv * main 99fec5d [origin/main: ahead 1, behind 1] Add Ravi's file
git status -sbgitversion controlstatuswhat changed
| Status | Means | Do |
|---|---|---|
ahead 2 | you have 2 commits GitHub doesn't | git push |
behind 3 | GitHub has 3 commits you don't | git pull |
ahead 1, behind 1 | both moved: the branches have diverged | pull (with rebase), then push |
When a push is rejected
Asha and Ravi both cloned the repository. Asha pushed first. Now Ravi's push fails:
! [rejected] main -> main (fetch first) error: failed to push some refs to 'github.com:rajeev/shop.git' hint: Updates were rejected because the remote contains work that you do not hint: have locally.
Git refuses because accepting Ravi's push would throw away Asha's commit. The fix is to bring her work in first, then push:
$ git pull --rebase # fetch, then replay my commits on top of theirs Successfully rebased and updated refs/heads/main. $ git log --oneline -2 e0c625c Add Ravi's file ff897a0 Add Asha's note $ git push ff897a0..e0c625c main -> main
git pull --rebasegitversion controlpulldownload and merge
git log --oneline -2loghistory
git pushpushupload commits
How should pull combine work?
When both sides have new commits, git pull must merge or rebase, and recent Git asks you to choose
instead of guessing:
fatal: Need to specify how to reconcile divergent branches.
Set your default once:
$ git config --global pull.rebase true # replay your local commits on top: straight history
git config --global pull.rebase truegitversion controlconfigread or set settings--globalfor your user, in every repo
| Setting | Diverged pull does | History |
|---|---|---|
pull.rebase true | replays your commits after theirs | a straight line |
pull.rebase false | makes a merge commit | a "Merge branch 'main' of github…" bubble every time |
pull.ff only | refuses unless it's a simple fast-forward | you decide each time |
Rebasing on pull is what most teams want for their own unpushed commits. The Merge strategies lesson explains rebase properly.
Fetch: look before you merge
git fetch downloads everything new and updates origin/*, and changes nothing else: not your
branches, not your files. That makes it the safe way to see what your team did:
$ git fetch 9c53c90..ff897a0 main -> origin/main $ git log --oneline main..origin/main # commits on GitHub that I don't have ff897a0 Add Asha's note $ git log --oneline origin/main..main # my commits GitHub doesn't have 99fec5d Add Ravi's file $ git diff main origin/main --stat # what their commits change $ git merge origin/main # or rebase, when you're ready
git fetchgitversion control
git log --oneline main..origin/mainloghistory
A..B means "commits in B that aren't in A". Read it as from A to B.
$ git fetch --prune # also forget branches deleted on GitHub $ git config --global fetch.prune true # always do that
git fetch --prunegitversion control
git config --global fetch.prune trueconfigread or set settings--globalfor your user, in every repo
Two developers, one branch
Simulate two teammates on your own machine: clone one repository into two folders, make a commit in each, push from the first, and get the second one's push accepted without a force push and without losing either commit.
Both commits on the shared main, in a straight lineReveal one safe solution
$ cd ~ && git init --bare team.git # plays the role of GitHub $ git clone team.git asha && git clone team.git ravi # "cloned an empty repository" is expected $ cd asha && echo "# Team" > README.md && git add . && git commit -m "Add README" && git push -u origin main $ cd ../ravi && git pull # Ravi gets the README $ cd ../asha && echo "asha" >> README.md && git commit -am "Add Asha's note" && git push $ cd ../ravi && echo "ravi" > ravi.txt && git add . && git commit -m "Add Ravi's file" $ git push # rejected: fetch first $ git fetch && git log --oneline main..origin/main $ git pull --rebase && git push $ git log --oneline
cd ~ && git init --bare team.gitcdchange directory~your home directory&&run the next command only if this one workedgitversion controlinitcreate a repository here
git clone team.git asha && git clone team.git raviclonecopy a repository
cd asha && echo "# Team" > README.md && git add . && git commit -m "Add README" && git push -u origin mainechoprint text>write output to a file, replacing itaddstage changes.this foldercommitsave staged changespushupload commits
cd ../ravi && git pullpulldownload and merge
cd ../asha && echo "asha" >> README.md && git commit -am "Add Asha's note" && git push>>append output to a file
git fetch && git log --oneline main..origin/mainloghistory
The last log shows Ravi's commit on top of Asha's, then the README commit, with no merge commit. Both
folders end with the same history after git pull in asha.
Try it yourself
0 of 6 steps doneRun each task in your own account or terminal, then tick it off.
Quick check
What is origin/main?
Official reading for this lesson
Or get every quiz answer right and it completes itself.