DEV Community

Amaresh Pelleti
Amaresh Pelleti

Posted on Edited on Originally published at devtoolhub.com

Git Pull vs Fetch: What Each Command Really Does

Originally published on DevToolHub.

Flow from remote to origin/main to the local branch, with fetch and pull arrows

git fetch downloads new commits and leaves your branch and your files alone. git pull runs a fetch and then merges or rebases the result into your current branch. The git pull vs fetch choice comes down to one question: do you want to look at incoming changes first, or take them right now?

I ran every command below on Git 2.56.0. A bare repository played the remote, and a second clone played a teammate who keeps pushing to main.

What Is the Difference Between Git Pull vs Fetch?

git fetch updates your remote-tracking branches, such as origin/main, and records what it got in .git/FETCH_HEAD. It never touches your local branches or your working tree. git pull runs git fetch first, then integrates the fetched branch into your current branch. So fetch is safe to run at any time, and pull can change your files.

git fetch git pull
Updates origin/main Yes Yes
Moves your local branch No Yes, by merge or rebase
Changes files in your working tree No Yes
Can stop on a conflict No Yes
Safe with uncommitted changes Yes Only if they don't overlap

The git fetch docs describe the first half. The git pull docs describe the second.

What Does Git Fetch Change in Your Repository?

Fetch changes your remote-tracking refs and nothing else. In my test the teammate pushed one commit. Before the fetch, git status saw nothing:

git status -sb
Enter fullscreen mode Exit fullscreen mode
## main...origin/main
Enter fullscreen mode Exit fullscreen mode

Then I fetched:

git fetch
Enter fullscreen mode Exit fullscreen mode
From /tmp/gpf/remote
   144625a..22c2c9f  main       -> origin/main
Enter fullscreen mode Exit fullscreen mode

Now Git knows the remote moved ahead:

## main...origin/main [behind 1]
Enter fullscreen mode Exit fullscreen mode

My app.txt still said v1. The new commit sat on origin/main, where I could read it first:

git log --oneline main..origin/main
git diff main origin/main --stat
Enter fullscreen mode Exit fullscreen mode
22c2c9f teammate edits app
 app.txt | 1 +
 1 file changed, 1 insertion(+)
Enter fullscreen mode Exit fullscreen mode

.git/FETCH_HEAD held one line, the commit hash plus branch 'main' of /tmp/gpf/remote. Scripts use that file, and you rarely need to open it.

What Does Git Pull Do Beyond a Fetch?

Pull does the fetch, then moves your branch. From the same starting point, git pull printed this:

Updating 144625a..22c2c9f
Fast-forward
 app.txt | 1 +
 1 file changed, 1 insertion(+)
Enter fullscreen mode Exit fullscreen mode

That is the same result as running git fetch and then git merge origin/main. I checked: the two-step version printed the identical Fast-forward output. Pull knows which branch to merge because of the upstream settings Git stored when I first pushed with -u:

branch.main.remote origin
branch.main.merge refs/heads/main
Enter fullscreen mode Exit fullscreen mode

A fast-forward is the easy case. The trouble starts when both sides have new commits.

Git Pull vs Fetch When Your Branches Have Diverged

When your branch and origin/main both have commits the other lacks, a bare git pull stops with an error until you say how to combine them. I added mine.txt locally while the teammate pushed another commit. After a fetch, status showed the split:

## main...origin/main [ahead 1, behind 1]
Enter fullscreen mode Exit fullscreen mode
* 8e9826c me adds mine.txt
| * 0ed7d96 teammate adds line 2
|/
* 22c2c9f teammate edits app
Enter fullscreen mode Exit fullscreen mode

With no pull.* config set, git pull refused:

hint: You have divergent branches and need to specify how to reconcile them.
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.
Enter fullscreen mode Exit fullscreen mode

The command exited with code 128 and changed nothing. You have three ways forward, and each leaves different history:

Command What it does History after
git pull --ff-only Refuses unless a fast-forward is possible Unchanged. It failed with Not possible to fast-forward, aborting.
git pull --rebase Replays your commits on top of origin/main Linear. My commit became 1b5ae05
git pull --no-rebase Merges origin/main into your branch A merge commit, Merge branch 'main' of ...

Rebase gave me a straight line of four commits. The merge version added a fifth commit with two parents. Neither is wrong. Rebase keeps git log readable, and merge never rewrites a commit you already made. If you want the longer comparison, the branching and merging strategies guide covers when each fits, and the rebase, stash and cherry-pick guide covers the rebase details.

Did the pull go the wrong way? Git records where your branch was before an integration in ORIG_HEAD. After my --rebase pull, I ran this to go back:

⚠️ Note: git reset --hard throws away every uncommitted change to tracked files in the whole repository. Commit or stash first.

git reset --hard ORIG_HEAD
Enter fullscreen mode Exit fullscreen mode

My branch returned to 8e9826c, the commit from before the rebase. The reset, revert and restore guide explains the three reset modes.

Why Does Git Pull Fail With Uncommitted Changes?

Pull fails when your uncommitted edits touch a file the incoming commits also change. Git aborts before it changes anything. I edited app.txt without committing while the teammate pushed a new line to the same file:

error: Your local changes to the following files would be overwritten by merge:
    app.txt
Please commit your changes or stash them before you merge.
Aborting
Enter fullscreen mode Exit fullscreen mode

A fetch in the same state worked without complaint. It printed 0ed7d96..b67314f main -> origin/main, and git status still showed my M app.txt edit untouched. That is the practical reason to fetch first: you see the incoming change, then decide whether to commit, stash, or wait.

How Do You Remove Stale Remote Branches With Fetch?

A plain git fetch adds new branches but never deletes old remote-tracking refs. Run git fetch --prune to remove the ones whose branch is gone from the remote. The teammate pushed feature/login, then deleted it from the remote. My next plain fetch printed nothing, and git branch -r still listed it:

  origin/HEAD -> origin/main
  origin/feature/login
  origin/main
Enter fullscreen mode Exit fullscreen mode

With --prune, the stale ref went away:

git fetch --prune
Enter fullscreen mode Exit fullscreen mode
 - [deleted]         (none)     -> origin/feature/login
Enter fullscreen mode Exit fullscreen mode

This only removes remote-tracking refs, never your local branches. Per the docs, tags are left alone unless you add --prune-tags. To make it the default for every fetch and pull, run git config --global fetch.prune true.

Git Pull vs Fetch: Which Should You Use?

Use git fetch when you want to see what changed before you integrate it, and git pull when your branch is simply behind and you trust the incoming commits. If you pull on a shared, busy branch, expect the divergence prompt sooner or later.

  • Fetch first when you have uncommitted work, when the branch is shared and busy, or when a script only needs to know whether you're behind.
  • Pull when you are a plain fast-forward behind. My first pull above was exactly that.
  • Use a two-step habit when you want control: git fetch, then git status -sb, then git merge --ff-only origin/main or git rebase origin/main.

Settle the divergent-branch prompt once instead of per command. For linear history, set git config --global pull.rebase true. To refuse anything except a fast-forward, set git config --global pull.ff only. Those two keys come from Git's own hint text above. I tested the matching command-line flags, not the config keys themselves.

Nothing in the Git 3.0 breaking changes list touches pull or fetch, so nothing planned for Git 3.0 changes how these two commands behave. For the bigger picture of how these commands fit together, start with the Git fundamentals guide.

Frequently Asked Questions

Q: Is git pull the same as git fetch plus git merge?
A: Yes, by default. git pull runs git fetch, then merges the upstream branch into your current branch. In my test, git fetch followed by git merge origin/main printed the same Fast-forward output as a single pull. With --rebase it rebases instead of merging.

Q: Does git fetch change my working directory?
A: No. It updates remote-tracking branches like origin/main and .git/FETCH_HEAD only. My edited app.txt was untouched after a fetch, and git status still showed it as modified.

Q: Why does git pull say "Need to specify how to reconcile divergent branches"?
A: Your branch and the remote both have new commits, and no pull.rebase or pull.ff setting tells Git what to do. Pass --rebase, --no-rebase or --ff-only, or set pull.rebase or pull.ff once.

Q: Should I use git fetch or git pull?
A: Fetch when you want to inspect incoming commits first or have uncommitted work. Pull when you're just behind and want to catch up. Many people fetch, check git status -sb, then merge or rebase by hand.

Q: How do I delete remote-tracking branches that no longer exist?
A: Run git fetch --prune. It removes stale origin/* refs and leaves local branches alone. Set git config --global fetch.prune true to do it on every fetch.

Quick Summary:

  • git fetch updates origin/main and .git/FETCH_HEAD, and never touches your branch or files
  • git pull is a fetch plus a merge or rebase, so it can change files and stop on conflicts
  • A bare pull on diverged branches fails with exit 128 until you pass --rebase, --no-rebase or --ff-only
  • Pull aborts if uncommitted edits overlap incoming changes, while fetch works fine
  • Plain fetch keeps stale origin/* refs, and --prune removes them
  • git reset --hard ORIG_HEAD brought my branch back to its pre-rebase commit

Next time the git pull vs fetch question comes up, run git fetch and git status -sb first. If the status says behind, pull is a safe fast-forward. If it says ahead and behind, pick a strategy before you pull.

Top comments (0)