git fetch vs pull comes down to one question: do you want to see what changed
before it touches your branch, or do you want Git to merge it in immediately? git
fetch downloads new commits, branches, and tags from a remote and updates your
remote-tracking branches — origin/main, origin/develop — without
touching your working tree or your local main at all. git pull does
that same fetch, then immediately merges (or rebases) the result into whatever branch you have
checked out. Confusing the two is how "surprise merge commit" and "why did my file just change"
tickets get filed. This guide covers what each command actually touches, the
FETCH_HEAD file almost nobody explains well, the pull.ff=only default
that prevents most of the damage, and the exact review workflow — fetch, log, diff, then decide
— for reading a change before you merge it. If you haven't cloned the repo yet, start with
cloning a remote Git repository first;
everything below assumes an origin already exists.
Quick Answer: git fetch vs pull
git fetch downloads; git pull downloads and merges. That's the whole
difference between git fetch and git pull in one sentence — everything else in this guide is
detail. Run git fetch when you want to look before you leap: it updates your
remote-tracking branches and writes FETCH_HEAD, but your checked-out branch and
working tree are untouched. Run git pull when you're confident the incoming commits
should be merged into your current branch right now, with no review step in between.
# See what changed, without touching your branch
git fetch origin
# Download AND merge into your current branch, immediately
git pull origin main
Which of the two is the right call for a given moment depends less on the command and more on
whether your team has already agreed on a merge strategy — check which branch you're on and
what it tracks before running either one blind. The sections below work through every real
shape of the question: what each command touches mechanically, the FETCH_HEAD
model nobody draws out, the concrete review workflow, and the config settings —
pull.rebase, pull.ff — that turn git pull from a gamble
into a predictable operation.
One clarification before going further: a GitHub pull request has nothing to do
with git pull. It's a review workflow GitHub layered on top of Git, and it doesn't
run either command on your machine — searches for github pull vs fetch almost
always mean the two Git commands, not the PR feature. Phrasing aside, the underlying question is
always the same one. Whether you searched for the git difference between fetch and
pull, are comparing git fetch vs git pull for a team convention, or
just want the git fetch meaning spelled out because you've only ever run
pull, the answer is that single distinction: does the merge happen automatically,
or do you get to look first?
What Does git fetch Do?
In plain English, the git fetch meaning is "go and get whatever is new on the remote, but don't
change anything I'm working on." What does git fetch do, mechanically? It contacts the remote
named in the command, downloads any
commits, branches, and tags you don't have yet, and updates your remote-tracking branches —
origin/main, origin/feature-x — to match what's currently on the
remote. Nothing about your working tree, your index, or your currently checked-out branch
changes. If you're sitting on main and a teammate pushed three commits to
origin/main, after git fetch your local main is still
exactly where it was; only origin/main moved. The full flag set and exact behavior
is documented in Git's official git fetch reference.
What does git fetch origin do specifically? git fetch origin fetches every branch
and tag tracked from the remote named origin — the default remote name Git assigns
when you clone. Naming a branch narrows that scope: git fetch origin main updates
only origin/main, skipping every other remote-tracking branch, which is noticeably
faster on a large repo with dozens of branches. Narrowing it permanently instead of per-call is
a clone-time decision: cloning a single
branch writes a scoped fetch refspec into .git/config, so every later fetch
stays narrow without you naming the branch each time.
# Fetch everything from origin — all branches, all tags
git fetch origin
# Fetch just main
git fetch origin main
# Fetch from every configured remote at once
git fetch --all origin/main is a remote-tracking branch, not a branch you commit to directly — it's
Git's local bookmark for where main sat on origin the last time you
fetched. You can check it out (Git detaches HEAD when you do) or diff against it,
but there's no such thing as pushing to it; you push to main on origin,
and your next fetch moves the bookmark to match. The mechanics of remote-tracking branches are
covered in more depth in the Git Branching chapter on remote branches, and it's worth
confirming locally too — checking out a remote
branch only works once at least one fetch or clone has created the bookmark you're checking
out.
What Does git pull Do?
git pull is fetch plus one more step: internally it runs the equivalent of
git fetch, then git merge FETCH_HEAD — or, depending on configuration,
rebasing onto the fetched commits — into your current branch. That second step is the entire difference between
git fetch and git pull; pull never skips it. Run git pull origin main while checked
out on a feature branch and Git fetches origin's main, then tries to
merge origin/main into your feature branch — not into main itself,
which is a common source of confusion for anyone who assumed pull always updates the branch
named in the command.
git pull origin main
# is roughly equivalent to running these two commands back to back:
git fetch origin main
git merge FETCH_HEAD
Run git pull with no arguments and Git resolves the remote and branch from your
current branch's tracking configuration — set automatically by git clone and by
git push -u — rather than reading them off the command line. The complete option
set, including the merge and rebase variants, is in Git's official git pull reference. When the two branches
haven't diverged, that merge step fast-forwards silently and nothing about it feels risky; the
divergent case, covered a few sections down, is where git pull's convenience starts
costing something — a surprise merge commit that's already been pushed comes back out only by
undoing the merge commit with revert -m 1.
Key Differences Between git fetch and git pull
Laid out side by side, git fetch vs pull is really a question of how many steps run and how much of that is automatic versus something you control.
| Aspect | git fetch | git pull |
|---|---|---|
| What it downloads | New commits, branches, tags from the remote | Same download — via an internal git fetch |
| Updates working tree? | No | Yes — merges or rebases into the checked-out branch |
| Updates current branch? | No, only remote-tracking branches move | Yes, the checked-out branch moves |
| Can create a merge commit | No | Yes, when history diverged and pull.rebase isn't true |
| Can rewrite local commits | No | Yes, when pull.rebase=true |
| Risk of surprise conflicts | None — nothing merges | Real — conflicts can appear mid-command |
| Safe to run repeatedly | Always | Only if you're fine with an auto-merge or rebase each time |
| Undo if you don't like the result | Nothing to undo — no local state changed | git merge --abort, or a reset back to ORIG_HEAD |
That last row is the one worth internalizing: a plain git fetch never leaves
anything to undo, because it never touches your branch. A git pull that goes wrong
might need aborting a merge that's already in progress
or, if you already committed the result, resetting your
branch back to where it was before the pull ran. Both are real, safe fixes — but they're
fixes you only need after pull, never after a bare fetch.
FETCH_HEAD: The Piece Nobody Explains
Every git fetch writes a file: .git/FETCH_HEAD. It's a plain-text
record of every ref the fetch just downloaded, each line pairing a commit SHA with where it came
from. git pull's merge step reads this file directly — a bare git pull
runs git merge FETCH_HEAD, merging whichever fetched ref matches your current
branch's configured upstream. Most days you never look at FETCH_HEAD on purpose,
but the moment git pull does something you didn't expect, it's the first place with
the actual answer of what got fetched and from where.
$ git fetch origin
$ cat .git/FETCH_HEAD
a1b2c3d4e5f6... branch 'main' of https://github.com/you/repo
f6e5d4c3b2a1... not-for-merge branch 'develop' of https://github.com/you/repo
Refs that were fetched but don't match your branch's merge configuration get marked
not-for-merge in that file, exactly like develop in the example above
— git pull downloads them for reference but ignores them when deciding what to
merge. The distinction between FETCH_HEAD and a remote-tracking branch like
origin/main is the part most explanations skip: origin/main is a
durable pointer that persists between fetches and always reflects the last time
main was fetched. FETCH_HEAD is transient — overwritten by every
single git fetch, regardless of branch, recording only what the most recent fetch
pulled down. That makes it the tool for fetching something you don't want a permanent
remote-tracking branch for — a pull request ref, for instance: git fetch origin
pull/123/head followed by git merge FETCH_HEAD merges that ref without ever
creating a local branch for it.
The Review Workflow: Fetch, Log, Diff, Then Decide
How do I review changes before merging? Skip git pull entirely and run three
commands in sequence instead of one — that's the whole trick, and it's the workflow this guide
is really arguing for.
# 1. Download without touching anything
git fetch origin
# 2. See which commits are new, oldest first
git log --oneline main..origin/main
# 3. See the actual content changes
git diff main origin/main
# 4. Now decide, deliberately
git merge origin/main
# or
git rebase origin/main
The double-dot range in step 2, main..origin/main, means "commits reachable from
origin/main but not from main" — exactly the set that would land on
your branch if you merged right now. git diff main origin/main in step 3 shows the
actual content behind that commit list: every line added, changed, or removed, computed once
between the two tips rather than commit by commit.
Raw git diff output is readable for a three-line change and a wall of text for a
rewritten module. Pasting it into a dedicated side-by-side diff viewer turns the
same information into color-coded, word-level highlighted changes instead of a page of plus and
minus signs. That's the actual use case for a tool like Diff Checker here: it doesn't run git
commands or talk to your remote — it just makes the output of git diff main
origin/main, or the fetched version of a single file pasted next to your local copy,
something you can actually read before you commit to a merge or a rebase.
If you have uncommitted local edits, git fetch is still safe on its own — it never
touches the working tree — but the merge or rebase step after it isn't. Stashing those changes first keeps the review honest instead of
mixing your own in-progress edits into the diff you're trying to read; if those edits were
throwaway anyway, discarding local changes
outright clears the way just as well.
git pull --ff-only: The Modern Safe Default
Why use git fetch instead of git pull? Because a bare git pull merges or rebases
without asking first, and if your local branch and origin/main have diverged, that
merge can land a commit you never actually reviewed. --ff-only closes that gap
without giving up the convenience of pull entirely.
# Pull, but refuse unless it's a clean fast-forward
git pull --ff-only
# Make that the permanent default for this repo
git config pull.ff only
# Or globally, for every repo on this machine
git config --global pull.ff only
With pull.ff=only set, git pull succeeds silently exactly when your
branch hasn't diverged — no local commits sitting ahead of the last fetch — and fails loudly the
moment it has, instead of picking a reconciliation strategy for you. "Fails loudly" means an
actual error and a non-zero exit code, not a merge commit you discover three days later scrolling
through git log. That failure is the point: it hands you the same signal
git fetch always gives, forcing the review-then-decide workflow from the section
above instead of letting you skip straight to a merge you didn't mean to make.
Treat pull.ff=only the way you'd treat a seatbelt, not a restriction — it doesn't
stop you from merging or rebasing, it stops Git from choosing for you on the one day you weren't
paying attention. That's the single highest-leverage config change most of the confusion behind
git fetch or git pull questions is actually trying to solve.
Configuring git pull: pull.rebase and pull.ff
How do I configure git pull behavior? Three settings cover every case —
pull.rebase, pull.ff, and the interaction between them — and one of
them is the reason git pull started printing a warning in the first place.
Since Git 2.27, released June 2020, running git pull on a branch that has diverged
from its upstream — both sides have new commits since the last common point — prints: "Pulling
without specifying how to reconcile divergent branches is discouraged." Git still defaults to a
merge commit in that case, exactly as it always did; the warning exists because that silent
default was the actual complaint. Teams that wanted a rebase were getting merge commits without
ever deciding to, and teams that wanted a merge had no record of having consciously chosen it.
The fix isn't a new default behavior — it's making the existing choice explicit before Git acts
on it.
| Setting | How to Set It | What git pull Does | Best For |
|---|---|---|---|
| Merge (classic default) | git config pull.rebase false | Creates a merge commit on divergence; fast-forwards otherwise | Shared branches, preserving exact history |
| Rebase | git config pull.rebase true | Replays local commits on top of the fetched upstream instead of merging | Personal feature branches, before they're pushed |
| Fast-forward only | git config pull.ff only | Refuses to pull at all unless a fast-forward is possible | Anyone who wants an explicit stop instead of an automatic decision |
| Not set, Git before 2.27 | (no config) | Silently merges on divergence, no warning printed | Legacy behavior, not recommended today |
| Not set, Git 2.27 or newer | (no config) | Warns about divergence on every affected pull, still merges | Transitional — pick one of the three rows above instead |
Set any of these per-repo with a plain git config, or add --global to
apply the default across every repo on the machine. There's no wrong answer among the first
three rows — the only genuinely bad state is the unset default, which is the one worth
eliminating on any machine you use regularly.
Fetching Into a Local Branch: git fetch origin main:main
git fetch accepts a refspec — source:destination — that lets you fetch
directly into a local branch ref without checking it out first. git fetch origin
main:main updates your local refs/heads/main to match
origin/main's tip, bypassing the usual remote-tracking indirection and the merge
step pull would otherwise run.
# Works from a completely different branch
$ git status
On branch feature/login
# Update local main from origin, without ever checking main out
$ git fetch origin main:main
$ git branch -vv
main a1b2c3d [origin/main] ...
* feature/login 9f8e7d6 [origin/feature/login] ...
Git refuses that refspec form if the target branch is the one currently checked out — updating the ref under your own feet mid-edit would leave your
working tree out of sync with what the branch now points to. This refusal applies regardless of whether it would be a fast-forward. Stay on a different branch and the
same refspec updates main safely in the background, which is the actual use case:
keeping a local main current for comparison, or for branching off a fresh checkout
later, without ever switching away from the branch you're actually working on.
Rebase or Merge: Choosing a pull Strategy
Merge (pull.rebase=false) preserves the true chronological history, including
concurrent work from other people — git log --graph shows the actual branching and
rejoining, and reverting an entire merge is a single command if the change turns out to be
wrong. The cost is a merge commit every time your branch and its upstream diverged, which adds
up to a noisier log on a busy repo.
Rebase (pull.rebase=true) produces a linear history that reads cleanly with
git log --oneline and bisects easily, at the cost of rewriting your local commits'
SHAs every time it runs. Anything already pushed and then rebased needs a force push to update
the remote, and a branch other people have already built work on top of must never be rebased
out from under them — that's the one hard rule this strategy comes with.
In practice: personal feature branches nobody else has branched from are safe with
pull.rebase=true, kept tidy right up until you push the branch to share it. Shared branches — main,
release branches, anything protected — are safer with pull.rebase=false or
pull.ff=only, never silently rewritten underneath the team. If a long-lived feature
branch needs to catch up with main without rebasing its own history, merging master into the branch is the
conventional way to do that periodically, and a pile of rough commits accumulated along the way
can still be squashed into one right before it's reviewed —
so choosing merge over rebase for safety doesn't have to mean living with messy history forever.
GitHub Pull vs Fetch: Desktop, the Web UI, and Pull Requests
Hosting a repository on GitHub changes nothing about what these commands do — Git is Git, and
origin is just an HTTPS or SSH URL. What changes is the wrapper you click instead of
typing. That's why github pull vs fetch is its own question: plenty of people
meet the two operations as buttons long before they meet them as commands, and a button hides
which one it's about to run.
GitHub Desktop puts both behind a single toolbar button. It reads Fetch origin
and runs a plain git fetch; the moment that fetch turns up commits your branch
doesn't have, the same button relabels itself Pull origin with a count beside
it, and clicking it now runs git pull. Desktop also fetches periodically on its own,
which is why the label sometimes changes while you're doing something else. Framed that way, the
Git difference between fetch and pull is easier to see than it is on the command line: one label
downloads, the other downloads and then rewrites the files in front of you. It's also why so many
people use git pull and fetch interchangeably for years — the button never made them choose.
On github.com itself there is no fetch and no pull, because both are things your machine does to its own clone. The web UI only ever shows you the state of the remote. Sync fork on a fork, and Update branch on an open pull request, do their merging on GitHub's servers, inside the remote copy — your local clone learns about the result on your next fetch and not a second sooner. Editors split the same way: VS Code exposes Git: Fetch and Git: Pull as separate commands, and its Sync button is a pull followed by a push.
As for the feature itself, a pull request borrows the word from an older workflow — asking a
maintainer to pull from your repository — but nothing about opening or reviewing one touches your
working tree. Getting a PR's commits onto your machine to read them is a fetch, not a pull:
git fetch origin pull/123/head followed by a look at FETCH_HEAD, the
same trick from the section above, and no local branch created for something you only wanted to
read.
git fetch vs git pull: When to Use Which
- Starting your day, want to see what's new first —
git fetch, thengit log --oneline main..origin/mainandgit diff main origin/main. - Automated build or CI script —
git fetch, notpull, unless the merge strategy is pinned exactly and reviewed like any other script. - Confident, no local changes, branch tracks a clean remote —
git pull --ff-only, or the equivalent config setting. - Personal branch catching up with main, no merge commit wanted —
git pull --rebase, orgit fetchfollowed bygit rebase origin/main. - Reviewing a teammate's push before touching your own work — always
fetch, review withdiff, decide after.
git fetch or git pull isn't really a choice between two competing commands —
fetch is strictly safer, pull is strictly more convenient, and
pull only ever calls fetch internally to begin with. Default to
fetch when you're not sure what changed; default to a pinned pull
strategy — --ff-only on shared branches, --rebase on your own — once
you are.
Frequently Asked Questions
What does git fetch do?
git fetch contacts a remote, downloads any commits, branches, and tags you
don't already have, and updates your remote-tracking branches — origin/main,
origin/develop — to match. It never touches your working tree, your index, or
whichever branch you currently have checked out; only the origin/* bookmarks
move. That's what makes it safe to run at any point, including with uncommitted local
changes sitting in your working tree.
What does git fetch origin do?
git fetch origin contacts the remote named origin — the default
name Git assigns when you clone — and downloads every branch and tag that remote tracks,
updating all of your origin/* remote-tracking branches in one go. Adding a
branch name after it narrows the scope to that single ref, which is noticeably faster on a
repo with dozens of branches. Either form leaves your checked-out branch and working tree
completely alone.
Is git pull the same as git fetch?
No. Every git pull runs a git fetch as its first step, but not the
other way around. After the download, pull keeps going and merges or rebases the fetched
commits into whatever branch you have checked out. People often say git pull and fetch as
though the two were interchangeable; the half of pull that isn't
fetch is exactly the half that can change your files and create commits.
What is the difference between git fetch and git pull?
git fetch only downloads and updates remote-tracking branches. git
pull runs that same fetch internally, then immediately merges — or rebases, if
configured — the result into your current branch. The download step is identical; the
difference is entirely in what happens after: fetch stops, pull keeps going and changes your
checked-out branch.
Should I use git fetch or git pull?
Use git fetch whenever you don't already know what's waiting on the remote. It
gives you a chance to look before anything changes: run git fetch, then
git log --oneline main..origin/main and git diff main origin/main
to see exactly what's incoming, and only then run git merge or git
rebase deliberately. Reach for git pull when you already know what's
coming and want it applied now — and pin it with --ff-only or
--rebase so Git can't quietly pick a reconciliation strategy for you.
What is the difference between fetch and pull in GitHub?
The commands behave identically regardless of where the repository is hosted — GitHub changes nothing about Git itself. What differs is the wrapper around them. GitHub Desktop's toolbar button reads Fetch origin, then switches to Pull origin once a fetch finds commits you don't have, keeping the two steps visible and separate. On github.com there is no fetch or pull at all: buttons like Sync fork and Update branch merge on GitHub's servers, and you still need a local fetch or pull afterwards. A GitHub pull request is neither command.
What are remote-tracking branches?
Remote-tracking branches — origin/main, origin/feature-x — are
local bookmarks for where a branch sat on the remote the last time you fetched or cloned.
You can check one out or diff against it, but you can't commit directly to it; the only
thing that moves origin/main is your next git fetch, or the fetch
git pull runs internally. They're separate from FETCH_HEAD, which
is overwritten by every fetch instead of persisting per branch.
What is FETCH_HEAD?
FETCH_HEAD is a file — .git/FETCH_HEAD — that git
fetch writes on every run, listing each ref it downloaded along with its commit SHA
and source. git pull's merge step reads it: a plain git pull runs
git merge FETCH_HEAD, or rebases against it, using whichever fetched ref matches
your branch's configured upstream. Unlike a remote-tracking branch, FETCH_HEAD
is transient — the next fetch overwrites it completely.