# Git Pull vs Fetch: What Each Command Really Does

> Originally published on [DevToolHub](https://devtoolhub.com/git-pull-vs-fetch/).

![Flow from remote to origin/main to the local branch, with fetch and pull arrows](https://devtoolhub.com/wp-content/uploads/2026/11/2026-11-05-git-pull-vs-fetch-diagram.webp)

`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](https://git-scm.com/docs/git-fetch) describe the first half. The [git pull docs](https://git-scm.com/docs/git-pull) 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:

```bash
git status -sb
```

```text
## main...origin/main
```

Then I fetched:

```bash
git fetch
```

```text
From /tmp/gpf/remote
   144625a..22c2c9f  main       -> origin/main
```

Now Git knows the remote moved ahead:

```text
## main...origin/main [behind 1]
```

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

```bash
git log --oneline main..origin/main
git diff main origin/main --stat
```

```text
22c2c9f teammate edits app
 app.txt | 1 +
 1 file changed, 1 insertion(+)
```

`.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:

```text
Updating 144625a..22c2c9f
Fast-forward
 app.txt | 1 +
 1 file changed, 1 insertion(+)
```

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`:

```text
branch.main.remote origin
branch.main.merge refs/heads/main
```

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:

```text
## main...origin/main [ahead 1, behind 1]
```

```text
* 8e9826c me adds mine.txt
| * 0ed7d96 teammate adds line 2
|/
* 22c2c9f teammate edits app
```

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

```text
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.
```

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](https://devtoolhub.com/git-branching-merging-strategies/) covers when each fits, and the [rebase, stash and cherry-pick guide](https://devtoolhub.com/advanced-git-rebase-stash-cherry-pick/) 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.

```bash
git reset --hard ORIG_HEAD
```

My branch returned to `8e9826c`, the commit from before the rebase. The [reset, revert and restore guide](https://devtoolhub.com/git-reset-vs-revert-vs-restore/) 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:

```text
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
```

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:

```text
  origin/HEAD -> origin/main
  origin/feature/login
  origin/main
```

With `--prune`, the stale ref went away:

```bash
git fetch --prune
```

```text
 - [deleted]         (none)     -> origin/feature/login
```

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](https://git-scm.com/docs/BreakingChanges) 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](https://devtoolhub.com/git-fundamentals/).

## 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.
