Fetch, pull and push: keeping in step with the team
The interactive lesson needs JavaScript. The text below is the complete lesson.
After this lesson you can
- explain why your origin/main does not move when a colleague pushes, until you run
git fetchunderstand - choose between
git fetchandgit pull, and between--ff-only,--no-rebaseand--rebasewhen you pull evaluate - publish commits with
git push, and resolve a rejected push by fetching, integrating and pushing again apply
Understand: Who knows what, and when?
You and Ada each have a clone of origin. Ada makes commit B and pushes. Main in origin moves to B. Your main and your origin/main still sit on A. Git sends you no notice. Nothing reaches your machine by itself.
git fetch asks origin what is new, downloads commit B and moves origin/main there. Your main does not move, and your files are unchanged. That is why fetch is safe at any time.
Then you must integrate. Here main is simply behind, so a merge can move main forward to B, a fast-forward. git pull does both steps in one command: first fetch, then integrate.
You make commit C. Only your main moves.
git push sends C and asks origin to move main. A normal push is accepted only as a fast-forward: the new commit must build on everything main in origin already has. C builds on B, so it works, and your origin/main follows.
But Ada has not fetched. She makes D on top of B and pushes. D does not build on C, so the push is not a fast-forward, and origin rejects it. Accepting it would throw C out of main. Nothing is broken.
The fix is always the same: fetch, integrate and push again. Ada runs git pull --no-rebase, which fetches C and makes the merge commit M. Now her push is a fast-forward. Forcing the push with --force would instead throw your C out of main.
Watch: Ada pushes, you are rejected, and you sort it out
Both you and Ada have a new commit that is not pushed. Ada is first. She pushes, and main in origin moves to her commit. Notice that your origin/main stays behind.
$ git push
Back in your repository. Ada has just pushed. What do you think git status says?
$ git status
We try to push anyway. Origin says no: rejected, fetch first. Origin has a commit you have never downloaded, and your push would throw it out of main. Nothing has changed. Read Git's hint.
$ git push
First we get the news. git fetch downloads Ada's commit and moves origin/main there. Your main stays put. Now the graph shows that the lines have split.
$ git fetch
Same question as before, new answer. Now Git knows about Ada's commit, and status says main and origin/main have diverged: one commit each.
$ git status
Now we integrate. git pull --no-rebase fetches and merges. The option says we want a merge commit. Without it, modern Git refuses to guess when the lines have split. Watch the new commit with two parents.
$ git pull --no-rebase
Now main contains both your commit and Ada's. The push is a fast-forward, and origin accepts it. Note that Ada's main does not move. It gets the commits only when she pulls.
$ git push
Tasks
- As Ada: publish her new commit to origin.
- Ask Git how main compares with its upstream.
- Try to publish your commit, and read what origin replies.
- Get what is new in origin, without changing your own main.
- Bring Ada's commit into your main.
- Publish main now that it contains Ada's work.
Practise: Transfer: this time Ada is rejected
This time you were first: your commit is already pushed, and Ada has an unpushed commit. Now Ada integrates with rebase instead of a merge commit. Git then replays her commit on top of yours, as a new copy, so main stays a straight line. That is safe because her commit exists only on her machine.
Tasks
- As Ada: get her commit into origin without losing yours, and without a merge commit.
- Back in your repository: update main so it equals main in origin. You have no commits of your own missing there.
Check yourself: Recall it
After git fetch, git status says main and origin/main have diverged, with 2 and 1 commits. Your two commits are also on the branch docs in origin, and Ada builds on them. How do you integrate?
git pull --ff-onlygit pull --rebasegit pull --no-rebase
What does git fetch change in your repository?
- Your main and the files in your working tree
- It downloads commits and moves remote-tracking branches such as origin/main
- Main inside origin
Your push is rejected as non-fast-forward. What do you do?
git push --force, because my version is the right one- Fetch, integrate the new commits and push again
git push --force-with-lease, the safe kind of force
git pull with no options stops with “Need to specify how to reconcile divergent branches”. Why?
- Origin cannot be reached
- You have uncommitted files
- Both sides have new commits, and Git will not choose between merge and rebase for you
Recall from module 2: you are on main and run git merge feature. When is the result a fast-forward?
- When there are no conflicts
- When main has no commits that feature lacks, so main can simply move forward
- When feature has only one commit
Common misconceptions
- “origin/main updates itself when someone pushes.” Git talks to origin only when you ask it to. Until then origin/main shows what you last saw.
- “fetch changes my files.” fetch downloads commits and moves remote-tracking branches. Your main and your working tree stay untouched.
- “You fix a rejected push with --force.” That removes your colleague's commits from the shared main. The right fix is to fetch, integrate and push again.
Read more
Fetch: update your notes, nothing else
git fetch downloads the objects you are missing and moves your remote-tracking branches. Each output line shows one ref moving from an old to a new commit:
$ git fetch
From /course/origin
28e7a75..ef25355 main -> origin/main
Your branches, the staging area and the working tree are untouched, so fetch is safe at any moment, even in the middle of other work. Useful variants:
git fetch --prunealso deletes remote-tracking branches whose branch has been deleted in the remote.git config --global fetch.prune truemakes that the default.git fetch --allfetches from every remote, which matters once you have bothoriginandupstream.
After fetching, look before you integrate: git status -sb prints ## main...origin/main [ahead 1, behind 1], git log --oneline main..origin/main lists what is new over there, and git log --oneline --graph --all shows where the lines split.
Pull: fetch plus one integration
git pull is git fetch followed by integrating the upstream into the current branch. There are three ways to integrate, and you should know which one you are asking for:
| Option | When your branch is only behind | When both sides have new commits |
|---|---|---|
git pull --ff-only |
fast-forward | refuses to integrate; your branch stays put, but the fetch has happened |
git pull --no-rebase |
fast-forward | merge commit with two parents |
git pull --rebase |
fast-forward | your commits the upstream lacks are replayed on top, with new IDs |
If both sides have moved and nothing is configured, modern Git stops and asks you to choose:
$ git pull
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint: git config pull.rebase false # merge
hint: git config pull.rebase true # rebase
hint: git config pull.ff only # fast-forward only
...
fatal: Need to specify how to reconcile divergent branches.
A good beginner default is git config --global pull.ff only. Then a plain pull only ever fast-forwards, and when the lines have split you decide each time with git pull --rebase or git pull --no-rebase; options on the command line override the configured default. With --ff-only the refusal looks like this:
$ git pull --ff-only
hint: Diverging branches can't be fast-forwarded, ...
fatal: Not possible to fast-forward, aborting.
Notice that the fetch half has already happened: origin/main is up to date even though your main did not move.
Merge or rebase when you pull? Rebase gives a straight line and is fine for commits that exist only on your machine. Never rebase commits others already have, because rebase makes new commits and leaves the old ones in their history. If in doubt, merge. Pull also refuses to start when your uncommitted edits would be overwritten, and with --rebase whenever tracked files have uncommitted changes at all. Commit or stash them first, or use git pull --rebase --autostash, which stashes and restores them for you.
Push: ask the remote to move a branch
git push sends your commits and asks the remote to move a branch. With the default setting push.default=simple, a plain push sends the current branch to its upstream branch of the same name. A normal push is accepted only as a fast-forward: the new commit must contain everything the branch had. There are two typical rejections:
! [rejected] main -> main (fetch first)
hint: Updates were rejected because the remote contains work that you do not
hint: have locally.
! [rejected] main -> main (non-fast-forward)
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart.
"fetch first" means the remote has a commit you have never downloaded, as in the demo. "non-fast-forward" means you have the commit locally, typically in origin/main, but have not integrated it. Both mean the push is not a fast-forward. The fix is the same: git fetch, inspect, integrate with merge or rebase, run your tests, git push. A rejection never damages anything.
Tags travel separately: git push origin v1.0 sends one tag, and git push --follow-tags sends annotated tags that point into the commits you push.
Do not force a rejected push
git push --force tells the remote to move the branch no matter what, and anything only that branch reached disappears from the shared history. A rejection on a shared branch means someone else's work is there: integrate it and push normally. Hosting services can forbid force pushes to protected branches, and on a server of your own receive.denyNonFastForwards does the same. When and how to force a rewritten branch of your own with --force-with-lease, and where it fails, is covered in the lesson “Rebase”.
Official documentation
- git fetch, git pull and git push, including the note about fast-forwards and
--force-with-lease - Pro Git: working with remotes
Per-Arne's Git Good!