Coding tips

git fetch vs git pull (vs git pull --rebase): Differences, Divergent Branches and When to Use Each (Tested Examples)

git fetch downloads new commits without touching your branch; git pull downloads them and then merges or rebases into your branch. Here is what each one really changes, why pull stops on divergent branches, and when to use --rebase, --ff-only and --autostash, with output from Git 2.47.3.

By 12 min read 7 views
Cover image for git fetch vs git pull (vs git pull --rebase): Differences, Divergent Branches and When to Use Each (Tested Examples)

git fetch downloads new commits, branches and tags from a remote and updates your remote-tracking branches (like origin/main), but it never touches your local branch or your working files. git pull runs a fetch and then integrates the result into your current branch, by fast-forward, merge or rebase. Use git fetch when you want to look before you change anything, and git pull (ideally --ff-only or --rebase) when you are ready to update.

That one-line difference explains most "why did Git do that?" moments. The git pull documentation describes pull as a fetch followed by a merge or rebase, so every surprise from pull really comes from the second half.

To test all of this properly, I built a small setup on my machine: a bare repository acting as the remote, and clones called alice, bob and carol taking turns to push. Everything ran on Git 2.47.3 with an empty global config, so no personal settings changed the results. I fixed the commit dates, which keeps the hashes below identical every time the script runs. The remote path in the output is /tmp/gitdemo-nov08/origin.

In this guide:

  • What git fetch actually changes
  • Integrating after a fetch
  • What git pull does in one step
  • "fatal: Need to specify how to reconcile divergent branches"
  • git pull --ff-only: refuse anything but a fast-forward
  • git pull --rebase vs git pull --no-rebase
  • When pull says your local changes would be overwritten
  • git fetch --prune: clean up deleted remote branches
  • Previewing with --dry-run and FETCH_HEAD
  • git fetch vs git pull comparison table
  • Set sensible defaults once
  • Which should you use?
  • Frequently asked questions

What git fetch actually changes

Alice pushes a commit. Bob runs git fetch. Here is everything Bob sees:

$ git log --oneline -1 main
8c404c0 first commit

$ git fetch origin
From /tmp/gitdemo-nov08/origin
   8c404c0..cc18d13  main       -> origin/main

$ git status
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

nothing to commit, working tree clean

$ git log --oneline main..origin/main
cc18d13 alice: add v2

$ git diff --stat main origin/main
 app.txt | 1 +
 1 file changed, 1 insertion(+)

$ cat app.txt
v1

Read it top to bottom. The fetch line 8c404c0..cc18d13 main -> origin/main says only origin/main moved. Bob's main still points at 8c404c0, and app.txt still holds just v1. git status now knows Bob is one commit behind, because it compares main with the freshly updated origin/main.

This is why fetch is always safe. It cannot cause conflicts and it cannot lose work. The git fetch docs describe it as downloading objects and refs from another repository, and that is all it does. One practical tip: git status only reports "behind" accurately after a fetch. Without one, it compares against stale data.

Integrating after a fetch

Once Bob has looked at git log main..origin/main, he can integrate. The safest choice is a fast-forward-only merge, which refuses to create a merge commit:

$ git merge --ff-only origin/main
Updating 8c404c0..cc18d13
Fast-forward
 app.txt | 1 +
 1 file changed, 1 insertion(+)

$ cat app.txt
v1
v2 from alice

Fetch plus git merge --ff-only origin/main is exactly what git pull --ff-only does in one step. Splitting it in two just gives you a moment to review. You can also run git rebase origin/main instead of the merge if you have local commits, which leads to the same result as git pull --rebase. The git merge docs explain fast-forward in detail.

What git pull does in one step

Alice pushes again. This time Bob just runs git pull:

$ git pull
From /tmp/gitdemo-nov08/origin
   cc18d13..2d2db15  main       -> origin/main
Updating cc18d13..2d2db15
Fast-forward
 app.txt | 1 +
 1 file changed, 1 insertion(+)

$ git log --oneline -3
2d2db15 alice: add v3
cc18d13 alice: add v2
8c404c0 first commit

The first two lines are the fetch. The rest is the merge, and because Bob had no commits of his own, Git simply moved main forward. This is the happy path, and it is why many people never think about the difference. Trouble starts when both sides have new commits.

If git pull ever stops with fatal: refusing to merge unrelated histories, that is a different problem: the two branches share no common commit at all. I covered it in fatal: refusing to merge unrelated histories.

"fatal: Need to specify how to reconcile divergent branches"

Now Alice and Bob both commit. Alice pushes first, then Bob pulls with no pull.rebase or pull.ff setting:

$ git pull
From /tmp/gitdemo-nov08/origin
   2d2db15..3711840  main       -> origin/main
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
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.

$ git status -sb
## main...origin/main [ahead 1, behind 1]

The fetch succeeded (note the 2d2db15..3711840 line), so origin/main is up to date. Only the integration step stopped, and nothing in Bob's branch or files changed. [ahead 1, behind 1] means each side has one commit the other lacks.

Current Git versions refuse to guess here, and the hint lists all three choices. You can pick one per command with --rebase, --no-rebase or --ff-only, or set a default with git config. The next three sections show each choice on real diverged branches.

git pull --ff-only: refuse anything but a fast-forward

With --ff-only, Git only updates your branch if it can do so without a merge or rebase. On diverged branches it stops:

$ git pull --ff-only
hint: Diverging branches can't be fast-forwarded, you need to either:
hint:
hint: 	git merge --no-ff
hint:
hint: or:
hint:
hint: 	git rebase
hint:
hint: Disable this message with "git config advice.diverging false"
fatal: Not possible to fast-forward, aborting.

That refusal is the point. On a shared main branch you usually never commit directly, so a fast-forward should always work. If it does not, something unexpected happened, and you want to stop and look rather than create a surprise merge commit.

git pull --rebase vs git pull --no-rebase

Bob decides to rebase. His commit gets replayed on top of Alice's:

$ git pull --rebase
Rebasing (1/1)
Successfully rebased and updated refs/heads/main.

$ git log --oneline --graph -4
* 17c931b bob: add bob.txt
* 3711840 alice: add alice.txt
* 2d2db15 alice: add v3
* cc18d13 alice: add v2

History stays in a straight line. Note that Bob's commit now has a new hash (17c931b), because rebase creates new commits. That is fine for work you have not pushed yet, and risky for commits other people already have.

For comparison, I set up a second clone, bob2, with the same kind of divergence and ran --no-rebase:

$ git pull --no-rebase
Merge made by the 'ort' strategy.
 alice.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 alice.txt

$ git log --oneline --graph -4
*   8f1d343 Merge branch 'main' of /tmp/gitdemo-nov08/origin
|\  
| * 3711840 alice: add alice.txt
* | 4973363 bob2: add bob2.txt
|/  
* 2d2db15 alice: add v3

Now there is a merge commit with two parents, and the graph shows the fork. Neither approach is wrong. Rebase gives a tidy log, merge keeps an exact record. I compared them side by side, plus squash, in git merge vs rebase vs squash.

When pull says your local changes would be overwritten

Uncommitted edits can block a pull too. Bob changed line 1 of config.txt without committing, while Alice pushed a change to line 10 of the same file:

$ git status --short
 M config.txt

$ git pull --ff-only
From /tmp/gitdemo-nov08/origin
   74986d9..2ebd87b  main       -> origin/main
error: Your local changes to the following files would be overwritten by merge:
	config.txt
Please commit your changes or stash them before you merge.
Aborting
Updating 74986d9..2ebd87b

$ git pull --ff-only --autostash
Updating 74986d9..2ebd87b
Created autostash: ee764a3
Fast-forward
 config.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Applied autostash.

$ git status --short
 M config.txt

$ head -1 config.txt
setting 1 = bob (uncommitted)

$ tail -1 config.txt
setting 10 = alice

$ git stash list

The first pull fetched, then refused to touch the file. (The Updating line lands after the error in my log only because output went to a file, where Git buffers normal output but not errors. In a terminal it comes first.) Adding --autostash stashed Bob's edit, fast-forwarded, and put the edit back. Both changes are now in the file and the stash list is empty.

If both sides had edited the same lines, the autostash would have needed manual conflict resolution. You can do the same thing by hand with git stash, pull, then git stash pop. The difference between pop and apply matters there, and I explained it in git stash pop vs apply.

git fetch --prune: clean up deleted remote branches

Fetch adds new remote branches, but by default it never removes ones that were deleted on the server. Here a feature-x branch appears, gets deleted on the remote, and lingers locally until a prune:

$ git fetch

$ git branch -r
  origin/HEAD -> origin/main
  origin/main

$ git fetch --prune
From /tmp/gitdemo-nov08/origin
 - [deleted]         (none)     -> origin/feature-x

After the prune, git branch -r showed only origin/HEAD and origin/main. Stale remote-tracking branches are harmless but confusing, especially in tab completion. To remove the branch on the remote and locally in one go, see how to delete a Git branch locally and remotely.

Previewing with --dry-run and FETCH_HEAD

Two lesser-known fetch details are handy when you are nervous about a remote:

$ git fetch --dry-run
From /tmp/gitdemo-nov08/origin
   2ebd87b..d886bbb  main       -> origin/main

$ git fetch
From /tmp/gitdemo-nov08/origin
   2ebd87b..d886bbb  main       -> origin/main

$ cat .git/FETCH_HEAD
d886bbb1f19422983234d444b5e71eae083912ab		branch 'main' of /tmp/gitdemo-nov08/origin

$ git status -sb
## main...origin/main [ahead 1, behind 1]

--dry-run printed what would change without updating anything, and the real fetch then printed the same line. FETCH_HEAD records the last fetched tip, so git log FETCH_HEAD or git diff HEAD FETCH_HEAD works even when you fetched a URL with no remote-tracking branch. The Pro Git chapter on remote branches explains how these refs fit together.

git fetch vs git pull comparison table

Here is the whole comparison in one place, based on the runs above:

CommandDownloads new commits?Changes your branch and files?Diverged branchesBest for
git fetchYesNo. Only origin/* refs moveNothing happensChecking what changed before you integrate
git pull (no config)YesYes, if it can fast-forwardStops with "Need to specify how to reconcile divergent branches"Quick updates on a branch you never commit to
git pull --ff-onlyYesOnly by fast-forwardStops with "Not possible to fast-forward"Safe default for shared main branches
git pull --rebaseYesYes, replays your commits on topLinear history, rewrites your local commitsFeature work you have not pushed yet
git pull --no-rebaseYesYes, creates a merge commitAdds a "Merge branch..." commitTeams that want every integration recorded

Set sensible defaults once

You do not have to type flags every time. Here I configured a fresh clone, carol, with diverged history, then ran a plain git pull and git fetch:

$ git config pull.rebase true

$ git config fetch.prune true

$ git status -sb
## main...origin/main [ahead 1, behind 1]

$ git pull
Rebasing (1/1)
Successfully rebased and updated refs/heads/main.

$ git log --oneline --graph -3
* 9e8d54f carol: add carol.txt
* d886bbb alice: add v6
* 2ebd87b alice: change setting 10

$ git fetch
From /tmp/gitdemo-nov08/origin
 - [deleted]         (none)     -> origin/temp-branch

$ git branch -r
  origin/HEAD -> origin/main
  origin/main

With pull.rebase true, the plain pull rebased instead of stopping. With fetch.prune true, a normal fetch removed the deleted temp-branch by itself. Add --global to apply either setting to every repository. If you would rather never rewrite anything automatically, use git config --global pull.ff only instead, and decide case by case.

Which should you use?

  • Starting your day or before a review: git fetch, then git log main..origin/main to see what is new.
  • Updating a shared main branch you never commit to: git pull --ff-only.
  • Updating a feature branch with unpushed commits: git pull --rebase.
  • Your team wants merges recorded: git pull --no-rebase.
  • You have uncommitted edits: commit them, or add --autostash.
  • Remote branches look out of date: git fetch --prune, or set fetch.prune true.

When a pull goes wrong, nothing is lost: git reflog shows where your branch was a moment ago. If you also need to undo a commit afterwards, see how to undo the last commit and keep your changes.

Frequently asked questions

What is the main difference between git fetch and git pull?

git fetch only downloads new commits and updates remote-tracking branches such as origin/main. git pull does that fetch and then merges or rebases the changes into your current branch and working files.

Is git fetch safe to run at any time?

Yes. Fetch never changes your local branches, your staged changes or your working files, so it cannot cause conflicts. In my tests the working file was unchanged after every fetch.

Why does git pull say "Need to specify how to reconcile divergent branches"?

Your branch and the remote branch both have new commits, and you have not told Git whether to merge, rebase or only fast-forward. Pass --rebase, --no-rebase or --ff-only, or set pull.rebase or pull.ff in git config.

Is git pull --rebase better than git pull?

It keeps history linear and avoids extra merge commits, which most people prefer for unpushed feature work. Avoid it when your local commits have already been pushed and shared, because rebase gives them new hashes.

Does git fetch update my local branch?

No. It updates origin/main, not main. Run git merge --ff-only origin/main or git rebase origin/main afterwards, or use git pull to do both steps at once.

How do I see what git pull will change before running it?

Run git fetch first, then git log main..origin/main for the new commits and git diff main origin/main for the changes. git fetch --dry-run shows which refs would update without changing anything.

Does git pull fetch all branches?

A plain git pull fetches from the remote configured for your current branch and normally updates all its remote-tracking branches, but it only integrates into the branch you have checked out. Use git fetch --all to fetch from every remote.

How do I undo a git pull?

Use git reflog to find where your branch pointed before the pull, then git reset --hard to that entry if you have no uncommitted work to keep. A pull with --ff-only never creates a merge, so there is less to undo.

Last checked: 8 November 2026. Figures, settings and guidance can change, so confirm anything important with the sources linked above.

Share X LinkedIn WhatsApp Facebook
Was this helpful?

0%