TechEarl

git pull vs git fetch: What's the Difference?

git fetch downloads new commits from the remote without touching your working files. git pull does the same fetch and then merges them straight into your branch.

Ishan Karunaratne⏱️ 7 min readUpdated
Share thisCopied
git pull vs git fetch compared, showing how pull is fetch plus merge

git fetch downloads new commits from the remote and updates your remote-tracking branches, but it leaves your working files and your current branch exactly as they are. git pull does that same fetch and then immediately merges the new commits into the branch you have checked out. So a pull is really two commands in one: fetch, then merge. The practical difference is that fetch lets you look before you leap, while pull changes your working branch right away.

The one-line answer

If you only remember one thing: git fetch is read-only against your work, git pull modifies it.

bash
# Download remote changes, touch nothing in your working tree
git fetch origin

# Download remote changes AND merge them into the current branch
git pull origin main

After a git fetch, your files are unchanged. The new commits sit in a remote-tracking branch named origin/main (or whatever the remote and branch are called), and you can inspect them before deciding what to do. After a git pull, those commits are already in your branch and your files may have changed.

What git fetch actually does

A fetch contacts the remote (over whatever connection you configured, which is its own decision between the difference between SSH and HTTPS remotes), downloads any commits, branches, and tags you do not have yet, and stores them under refs/remotes/. Your local branch pointer does not move. Your working directory is untouched: fetch only writes to the remote-tracking refs, not to your index or working tree, which is worth understanding alongside how the remote-tracking index and staging area work.

bash
git fetch origin

The useful mental model: after a fetch, your local main and the remote's version of main are two separate pointers. The remote's version is now visible to you as origin/main. You can compare them:

bash
# See what commits the remote has that you don't
git log main..origin/main --oneline

# See the actual file changes coming in
git diff main origin/main

Nothing in that workflow can clobber your uncommitted work, because fetch never merges. This is exactly why I reach for fetch when I am not sure what landed upstream. If you want to understand how origin/main relates to your local main, the Git branching basics walk through branch pointers in detail.

What git pull actually does

git pull is a convenience wrapper. By default it runs a fetch and then a merge:

bash
git pull origin main
# is roughly equivalent to:
git fetch origin
git merge origin/main

If the remote only added commits on top of where you were (a fast-forward), the merge is silent and your branch pointer just moves forward. If your local branch and the remote branch have both moved on independently, the pull creates a merge commit, or it stops with a conflict you have to resolve. When that happens, the merge conflict resolution guide covers the markers and how to finish the merge.

You can also tell pull to rebase instead of merge, which replays your local commits on top of the fetched ones rather than tying them together with a merge commit:

bash
git pull --rebase origin main

That is the same fetch, followed by git rebase origin/main instead of git merge. Which one you want is its own decision; I compare them in git merge vs git rebase.

pull = fetch + merge (or rebase)

Here is the whole relationship in one table.

Stepgit fetchgit pull (default)git pull --rebase
Downloads remote commitsYesYesYes
Updates origin/mainYesYesYes
Moves your local branchNoYesYes
Changes working filesNoYesYes
Can create a merge commitNoYesNo (replays instead)
Can stop on a conflictNoYesYes
Safe with uncommitted workYesRiskyRisky

The bottom two rows are the reason this distinction matters in day-to-day work.

Why fetch then review is safer than a blind pull

A blind git pull is fine when you are alone on a branch and your working tree is clean. It gets risky when either of those is not true.

If you have uncommitted changes and the incoming commits touch the same files, the merge step of a pull can refuse to run, leaving you staring at error: Your local changes would be overwritten by merge. I cover that exact case and the ways out of it in the "local changes would be overwritten" fix. The same divergence bites on the push side too, where it shows up as git pull failing with a non-fast-forward push error. Fetching first sidesteps the surprise entirely: you fetch, you see what is coming, and you decide whether to stash, commit, or wait. And if a merge or rebase ever moves your branch somewhere you did not expect, you can recover them with git reflog, which logs every position the branch tip has occupied.

The fetch-first habit looks like this:

bash
# 1. Pull down the remote state without touching anything
git fetch origin

# 2. See what changed and where you stand
git status
git log --oneline main..origin/main

# 3. Now merge (or rebase) on your own terms
git merge origin/main

If step 2 shows incoming changes you are not ready for, you can stash your work first, then merge, then pop the stash. If you decide you want none of your local edits anyway, you can discard local changes before merging. The point is that fetch buys you a decision point that pull skips.

Divergent branches and the reconcile prompt

Git 2.27 (mid-2020) started warning when you ran git pull on a diverged branch without telling it how to reconcile, but it still completed the pull with the default merge:

text
warning: Pulling without specifying how to reconcile divergent branches is
discouraged.

Git 2.34 (late 2021) turned that into a hard error: now git pull aborts and refuses to guess if your branch and the remote have diverged and you have not set a reconcile strategy:

text
fatal: Need to specify how to reconcile divergent branches.

This is Git asking whether you want a merge or a rebase. You set the behaviour once and the message goes away:

bash
# Default to merge on pull
git config --global pull.rebase false

# Or default to rebase on pull
git config --global pull.rebase true

# Or only fast-forward, and error out if a real merge would be needed
git config --global pull.ff only

If you hit that message, the divergent branches fix walks through choosing the right setting for your workflow.

When to use which

A quick rule of thumb I follow:

  • Use git fetch when you want to know what is on the remote without committing to integrating it yet: before starting work in the morning, before a tricky merge, or when reviewing what a teammate pushed.
  • Use git pull when you are on a clean branch, you already know roughly what is upstream, and you just want it integrated now. On a personal feature branch with nothing staged, a pull is perfectly fine and saves keystrokes.
  • Use git pull --rebase on a shared branch where you want to keep history linear and avoid a clutter of merge commits.

If you are brand new to all of this, start with the Git for beginners hub, which sequences these commands into a learnable order rather than dropping them on you all at once. Working on a project someone else set up is covered in starting on an existing Git project, and if you do not yet have a remote at all, here is how to set up a project's remote in the first place.

Updating the remote-tracking refs without merging anything

One more practical note: git fetch is also how you prune branches that were deleted on the remote, so your origin/* list stays honest.

bash
# Fetch and drop remote-tracking branches that no longer exist upstream
git fetch --prune origin

That keeps git branch -r from listing branches that were merged and deleted weeks ago. A plain git pull does not prune by default, which is another small reason the explicit fetch is worth keeping in your toolbox.

Sources

Authoritative references this article was fact-checked against.

Tagsgit pull vs fetchGitVersion Controlgit fetchgit pullremote tracking

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts