Git workflow comparison
git pull vs git fetch
Both commands download data from a remote repository — but git fetch stops there, leaving your working tree untouched, while git pull immediately integrates the changes into your current branch. Knowing when to choose each is one of the habits that separates cautious, professional Git users from those who occasionally produce unwanted merge commits.
TL;DR
- git fetch downloads new commits, branches, and tags from the remote and updates your remote-tracking references (e.g.
origin/main). Your local branch and working directory are not changed. - git pull is
git fetch+git merge(orgit rebasewhen configured). It downloads and immediately integrates the remote changes into your current branch. - Safer pattern for shared branches:
git fetch, inspect withgit logorgit diff, then decide how to integrate. - git pull --rebase is a popular middle ground: it fetches and integrates whilst avoiding an unwanted merge commit.
Side-by-side comparison
| Aspect | git fetch | git pull |
|---|---|---|
| What it does | Downloads remote commits; updates origin/* refs | Downloads + merges (or rebases) into current branch |
| Affects working directory | No | Yes — may create a merge commit or cause conflicts |
| Auto-merge | No | Yes (default: merge; configurable: rebase) |
| Safety | Always safe; non-destructive | Can cause conflicts or unwanted merge commits |
| Remote-tracking update | Yes — origin/main updated | Yes — same fetch step happens first |
| Rebase option | N/A (fetch is always fetch-only) | git pull --rebase for linear history |
| Handles diverged history | Leaves decision to you | Merges automatically; may create merge commit |
| Typical use case | Inspect before integrating; shared branches | Quick sync on your own private branch |
What is git fetch?
git fetch contacts the remote (typically origin) and downloads any commits, branches, or tags that your local repository does not yet have. Crucially, it only updates your remote-tracking branches — local references like origin/main that mirror the remote's state. Your actual local branch (main) and your working directory are completely untouched.
This makes git fetch a read-only, risk-free operation. You can run it at any time, even with uncommitted changes, without any risk of conflicts or overwritten work. After fetching you can inspect what is new before deciding how to integrate it:
# Download all remote changes safely
git fetch origin
# See commits on origin/main that you don't have locally
git log HEAD..origin/main --oneline
# Compare your current branch with the remote
git diff HEAD origin/main
# Check whether your history has diverged
git status
# → "Your branch is behind 'origin/main' by 3 commits"
# → "Your branch and 'origin/main' have diverged" What is git pull?
git pull is a convenience command that combines two steps: git fetch followed by git merge. It downloads the remote changes and immediately integrates them into your current branch. This is efficient when you know you want the latest changes and are confident the integration will be clean — for instance, on a private feature branch with no local commits yet.
The default merge behaviour can produce a merge commit whenever your local and remote histories have diverged. Many teams consider this noise in the history. The --rebase flag solves this by replaying your local commits on top of the fetched commits instead of merging them:
# Simple pull (fetch + merge — may create a merge commit)
git pull
# Pull with rebase (fetch + rebase — linear history, no merge commit)
git pull --rebase
# Set rebase as the global default
git config --global pull.rebase true
# Pull a specific remote branch into your current branch
git pull origin feature/payments FETCH_HEAD
After every fetch, Git writes a file called FETCH_HEAD recording the tip of the branch just fetched. You can reference it directly: git diff FETCH_HEAD shows what changed, and git merge FETCH_HEAD integrates those changes. Most developers prefer named refs like origin/main because they are more explicit, but FETCH_HEAD is useful in scripts and CI pipelines.
Fast-forward vs diverged history
When you pull or merge, Git decides between two outcomes based on the state of your histories:
Fast-forward: If your local branch has no commits that the remote does not have — i.e. your branch is simply behind — Git can move the branch pointer forward without creating a merge commit. This is the cleanest outcome.
Diverged history: If both your local branch and the remote have commits the other side lacks, the histories have diverged. A plain git merge will create a merge commit with two parents. Running git pull --rebase instead replays your local commits on top of the remote commits to restore a linear history.
# Fast-forward scenario (local is only behind)
# origin/main: A → B → C
# local main: A → B
# After: git pull → fast-forwards to C (no merge commit)
# Diverged scenario (both sides have new commits)
# origin/main: A → B → C
# local main: A → B → D (your commit)
# git pull → creates merge commit M(C, D)
# git pull --rebase → replays D on top of C → linear A→B→C→D' The safer workflow: fetch → inspect → integrate
On shared branches such as main or develop, the fetch-inspect-merge pattern gives you full visibility before altering your branch. This is the workflow recommended in most professional Git guidelines:
# Step 1: fetch safely — working directory unchanged
git fetch origin
# Step 2: inspect what is incoming
git log HEAD..origin/main --oneline --graph
# Step 3: see the actual diff
git diff HEAD origin/main
# Step 4: integrate consciously — merge or rebase
git merge origin/main
# — or for a linear history —
git rebase origin/main Compare this with the git merge vs rebase guide to decide which integration strategy suits your team's branching model.
How engineers talk about git pull and git fetch
In standups and Slack
- "Pull the latest before you start — there were several merges overnight."
- "I'll do a fetch first to see what's landed on main before I rebase my branch."
- "Can you sync your branch with main? You are about 15 commits behind."
- "I'm on an old commit — let me fetch and fast-forward."
In code reviews and documentation
- "This branch has diverged from main — please rebase before we merge."
- "We set
pull.rebase trueon all developer machines to keep history linear." - "Run
git fetch --pruneto clean up stale remote-tracking refs." - "Our CI always does
git fetch origin mainto detect conflicts early without touching the working tree."
When to use git fetch
- Before a code review or deployment. Fetch to see whether the remote has diverged; inspect the incoming changes; then integrate deliberately.
- On shared branches (main, develop). Fetching gives you a window to review before your local branch is affected.
- When you have uncommitted work in progress. A fetch is always safe; a pull might conflict with your unstaged changes.
- Refreshing your view of all remotes. Run
git fetch --all --pruneto update every remote-tracking branch and remove stale references for deleted branches.
When to use git pull
- Your own private feature branch. If you are the only developer on the branch and you want the latest remote commits,
git pullis the quickest path. - Clean working directory with no divergence. When you have no local commits beyond the remote, the pull will fast-forward cleanly — no merge commit, no surprises.
- Automated scripts and CI pipelines. CI typically runs a pull or shallow checkout; the merge behaviour is expected and controlled within the pipeline.
- Team has configured
pull.rebase true. When your organisation has standardised on rebase pulls,git pullis safe and keeps history linear.
Quick decision guide
- Want to see what changed on the remote without touching your branch → git fetch
- Private branch, clean working directory, no local commits → git pull is fine
- Want a linear history with no merge commits → git pull --rebase
- On a shared branch (main, develop) → git fetch, then review, then merge or rebase
- Have uncommitted work in progress → git fetch (stash first if you need to pull)
- Need to update all remote-tracking refs at once → git fetch --all --prune
- Unsure whether your history has diverged → git fetch then
git status
Key vocabulary
- Remote-tracking branch — a local read-only reference that mirrors the state of a branch on the remote (e.g.
origin/main). Updated by fetch. - Fast-forward merge — a merge that simply moves the branch pointer forward because there is no divergence; no merge commit is created.
- Diverged history — two branches that have each moved forward independently from a common ancestor, each having commits the other lacks.
- Merge commit — a commit with two parents, created when two diverged branches are merged. Visible as a "diamond" in the history graph.
- FETCH_HEAD — a file Git writes after every fetch, pointing at the tip of the branch just fetched; usable as a ref in subsequent commands.
- Rebase — replaying commits from one branch on top of another to produce a linear history without merge commits.
- Prune — removing stale remote-tracking references for branches that have been deleted on the remote (
git fetch --prune). - Stash — temporarily shelving uncommitted changes so you can fetch or pull without conflicts (
git stash/git stash pop).
Frequently asked questions
Is git pull dangerous?
git pull can create unexpected merge commits or trigger conflicts that interrupt your work at an inconvenient moment. The risk is highest on shared branches (main, develop) or when you have uncommitted local changes. On a private feature branch with a clean working directory it is perfectly safe. The recommended habit for shared branches is: git fetch, inspect the diff, then decide whether to merge or rebase.
What is a remote-tracking branch?
A remote-tracking branch is a local, read-only reference that records the last known state of a branch on the remote. For example, origin/main reflects where main was on the remote at the time of your last fetch or pull. It is updated every time you run git fetch or git pull, but it is never directly edited by you — only by Git when it contacts the remote.
What is git pull --rebase and when should I use it?
git pull --rebase performs a fetch followed by a rebase instead of a fetch + merge. Your local commits are replayed on top of the latest remote commits, avoiding a merge commit and keeping history linear. Many teams set this as the default with git config --global pull.rebase true. Use it when you want to keep a clean, linear history. Avoid it on commits that are already shared with others, because rebasing rewrites commit SHAs.
What is FETCH_HEAD?
FETCH_HEAD is a special file Git writes after every fetch. It records the tip of the remote branch that was just fetched. You can use it immediately after a fetch: git diff FETCH_HEAD shows what changed on the remote compared to your current state. git merge FETCH_HEAD integrates those changes. Most developers prefer named refs like origin/main over FETCH_HEAD because they are more explicit.
What does "fast-forward" mean in Git?
A fast-forward occurs when the branch you are merging into has no new commits since it diverged — it simply needs its pointer moved forward to the newer commit. No merge commit is created. For example, if your local main is directly behind origin/main (no local commits), git merge origin/main fast-forwards your pointer to match the remote. If both sides have new commits, Git cannot fast-forward and must create a real merge commit instead.
What does "diverged history" mean?
Branches are said to have diverged when both the local branch and the remote branch have commits that the other side lacks — i.e. they have moved forward independently from a common ancestor. Git will warn you: "Your branch and 'origin/main' have diverged, and have N and M different commits respectively." In this state a plain merge produces a merge commit; a rebase replays your commits on top of the remote ones to restore a linear history.
How do I update all remote-tracking branches at once?
Run git fetch --all to contact every configured remote and update all remote-tracking branches. Add --prune (git fetch --all --prune) to simultaneously remove stale references for branches that have been deleted on the remote. This is a safe, read-only operation — your local branches are untouched.