Devpuff
Pricing
Log InStart Free
  1. Home
  2. Blog
  3. git pull vs fetch: the difference, shown
Tools

git pull vs fetch: the difference, shown

Git pull vs fetch: fetch updates origin/main and leaves your files alone, pull does that and then merges. Here are the refs before and after.

By Max Arthur
Co-Founder & Content Marketer·September 20, 2026·8 Min read
White text reading 'Git pull vs fetch' on a dark purple dotted background
git pull = git fetch + git merge

git fetch downloads new commits and updates your remote-tracking branches. It does not touch your branch or your files.

git pull does that, then immediately merges the result into your current branch, which does touch your files.

Here is the difference measured on a real clone with git 2.50.0.

Fetch, measured

A clone that is one commit behind. Before fetching:

local main:  02cdc5f first
origin/main: 02cdc5f first
a.txt:       one

After git fetch:

local main:  02cdc5f first          <- unchanged
origin/main: 4b1e3b0 second from A  <- moved
status: ## main...origin/main [behind 1]
a.txt:       one                     <- unchanged

origin/main moved forward. Your branch did not, and neither did your file. Git now knows about the new commit and has done nothing with it.

The status line is the payoff: [behind 1] tells you exactly where you stand, and you could not know that without fetching first.

Then the second half, which is what pull would have done for you:

git merge origin/main
local main:  4b1e3b0 second from A
a.txt:       one two

Now the branch moved and the file changed.

What origin/main actually is

The thing that makes this click: origin/main is a local branch. It lives in your repository, not on the server.

It is git's record of where main was on the remote the last time you talked to it. It is not live, it does not update on its own, and it can be out of date by days if you have not fetched.

That is why git status can say "up to date" when your teammate pushed an hour ago. It is comparing you against your local copy of origin/main, which is stale. git fetch is what refreshes that copy, and it is why the habit of fetching before you judge anything is worth building.

You can inspect it like any branch:

git log origin/main
git diff main origin/main

That second command answers "what am I about to pull", which is the question fetch exists to let you ask.

Why fetch first

Fetching is not just cautious, it is more informative:

git fetch
git log --oneline main..origin/main    # what is coming in
git diff main origin/main              # what will change
git merge origin/main                  # do it

Four commands instead of one, and you know what happened before it happens. The middle two are the ones people miss, and they are the reason experienced developers fetch by default.

git fetch cannot break anything. It only writes to remote-tracking branches, which are git's bookkeeping and not your work. There is no situation where fetching costs you something.

When pull is fine

Most of the time. If you are on a branch nobody else touches, have no local commits, and just want the latest, git pull is one command and does the right thing.

Pull becomes awkward in two situations:

You have local commits. Now the merge is a real merge, and you get a merge commit joining your work to theirs. Repeated over a busy branch, this produces a history full of "Merge branch 'main' of ..." commits that record nothing except that you pulled.

You have uncommitted changes. Git refuses:

error: Your local changes to the following files would be overwritten by merge

That is git protecting you. Commit or stash first.

pull --rebase

The fix for the merge-commit noise:

git pull --rebase

Instead of merging their work into yours, this replays your commits on top of theirs, producing a straight line with no merge commit.

The trade-off is the one that applies to all rebasing: your commits get new hashes. That is fine for local commits nobody has seen, and a problem for commits you have already pushed. Merge versus rebase covers what actually happens to the history in each case.

To make it the default:

git config --global pull.rebase true

Many teams do this. It is a reasonable default precisely because the commits being rebased are almost always your own unpushed work.

The pull that asks you to choose

On a recent git, pulling with divergent branches and no configured strategy gives this:

hint: You have divergent branches and need to specify how to reconcile them.

Git stopped guessing, because merging and rebasing produce genuinely different history and it will not pick for you. Answer it once, globally:

git config --global pull.rebase true    # rebase
git config --global pull.ff only        # refuse anything but a fast-forward

ff only is the most conservative: the pull succeeds when you have no local commits and fails otherwise, leaving you to decide deliberately. It is a good setting for people who want to be told before history is reshaped.

Tracking, and where the default comes from

git pull with no arguments works because your branch has an upstream: a remote branch it is paired with. That pairing is what makes pull, push and the ahead/behind counts work without you naming anything.

See it for one branch:

git rev-parse --abbrev-ref --symbolic-full-name @{u}

Or for all of them at once, which is more useful:

git branch -vv
* main   4b1e3b0 [origin/main] second from A
  topic  a1b2c3d [origin/topic: ahead 2] some work

The bracketed part is the upstream and the current relationship. A branch with no bracket has no upstream, which is why git pull on it answers "There is no tracking information for the current branch".

Set one with -u on the first push:

git push -u origin my-branch

Or after the fact:

git branch -u origin/my-branch

This is the same configuration that silently stays wrong after a rename, which is covered in renaming a branch.

Reading ahead and behind

After a fetch, git status summarises the relationship in one line:

Status says Meaning
up to date You and the remote agree, as of your last fetch
behind 3 Three commits to bring in
ahead 2 Two commits to push
ahead 2, behind 3 Both. The branches have diverged

The last row is the one that needs a decision, because bringing those together means either a merge commit or a rebase. That is where the "divergent branches" prompt below comes from.

The counts are only as fresh as your last fetch, which is worth repeating because it is the single most common misreading of git status. Fetch, then read.

A daily routine that avoids surprises

git fetch --prune                    # refresh, clean up dead branches
git status                           # where do I stand
git log --oneline main..origin/main  # what is coming in
git merge origin/main                # or: git rebase origin/main

Four commands, and each answers a question before acting. The value is not caution for its own sake: it is that you know whether you are about to fast-forward, create a merge commit, or start a conflict, before any of those happen.

Once that sequence is familiar, git pull is a fine shortcut for the case where you already know the answer is "fast-forward". The reason to learn the long version first is that it is the only way to build the mental model that makes the short version safe.

Fetching everything, and pruning

git fetch --all       # every remote, not just origin
git fetch --prune     # drop refs for branches deleted on the remote

--prune matters more than it looks. Without it, remote-tracking branches for deleted branches stay in your repository indefinitely, which is covered in deleting a branch. Turn it on permanently:

git config --global fetch.prune true

More than one remote

origin is a name, not a keyword. A repository can have several remotes, which is the normal setup when you have forked something:

git remote -v
origin    git@github.com:you/project.git (fetch)
upstream  git@github.com:original/project.git (fetch)

origin is your fork, upstream is the project you forked from. Keeping current means fetching from the one you do not own:

git fetch upstream
git merge upstream/main

The remote-tracking branches are namespaced per remote, so origin/main and upstream/main are separate refs that can point at different commits. Reading git branch -r with two remotes configured makes the whole remote-tracking idea concrete in a way one remote never does.

Three common mistakes

Trusting git status without fetching. It compares you against your local copy of origin/main, which is only as fresh as your last fetch. "Up to date" can be hours stale, and this is the single most common misreading in git.

Pulling with uncommitted changes and then forcing past the refusal. The refusal is protecting files it would overwrite. Commit or stash instead; there is no version of overriding it that ends well.

Treating git pull as read-only. It merges, which means it can create a merge commit, start a conflict, or move your branch somewhere you did not intend. git fetch is the read-only one, and the distinction matters most on a branch where you have local work.

Quick reference

git fetch                     # update remote-tracking branches only
git fetch --prune             # and clean up deleted ones
git status                    # ahead/behind, after a fetch
git log main..origin/main     # what is incoming
git diff main origin/main     # what will change
git merge origin/main         # apply it
git pull                      # fetch + merge in one step
git pull --rebase             # fetch + rebase instead

Want to see refs move as you run these? Work through Git basics in a real repository in the browser.

Keep reading

More from the blog

White text reading 'Objects are not valid as a React child' on a dark purple dotted background
October 7, 2026·7 min readTools

Objects are not valid as a React child: what to render

Objects are not valid as a React child, and React tells you which one by listing its keys. In React 19 the promise version throws a different error.

Read more
White text reading 'How to fix npm install errors' on a dark purple dotted background
October 6, 2026·8 min readTools

npm install errors: read the code, not the stack

Every npm ERR code names one cause. ERESOLVE, E404, EJSONPARSE and the rest, with what each one means, measured against real installs.

Read more
{ }
✦

Ready to write some code?

Put this into practice - start your first free lesson. No setup, no credit card.

Start learning free
Devpuff

Learn to code by doing. One tiny, playful lesson at a time.

Learn
ProgramsCoursesPricing
Company
AboutBlogResourcesAffiliates
Support
Help CenterContactStatus
Programs
Frontend DevelopmentFull-Stack DevelopmentReact DeveloperPython Developer
Courses
Advanced ReactAsync JavaScriptAsync PythonCSS BasicsCSS LayoutDSA Basics
Learn to Code
Learn JavaScriptLearn PythonLearn SQLLearn HTMLLearn CSSLearn ReactBrowse All Topics
Platform Comparisons
Devpuff vs CodecademyDevpuff vs Mimo
© 2026 Devpuff. All rights reserved.Privacy PolicyTerms and ConditionsCookies PolicyRefund Policy
Devpuff vs Sololearn
Devpuff vs freeCodeCamp
Read All Comparisons