Skip to main content

git fetch vs git pull: Remote Tracking and Rebase Workflows

·5 mins
Table of Contents
git fetch is always safe. git pull is fetch plus merge (or rebase), and that second step is where things go wrong. Understanding the difference changes how you collaborate on shared branches.

Most developers learn git pull first and use it reflexively. It works fine in isolation, but on a team with an active shared branch it quietly adds merge commits to your history, can fail mid-operation if your working tree is dirty, and obscures what actually arrived from the remote. Knowing when to use fetch instead gives you back control.

What Remote Tracking Branches Actually Are
#

When you clone a repository, Git creates two kinds of branch pointers:

  • main – your local branch, pointing to whatever commit you are working on.
  • origin/main – a remote tracking branch, pointing to the last known state of main on the origin remote.

origin/main is a read-only snapshot. Git updates it whenever you run git fetch, but you never commit to it directly. It is how Git answers the question “what does the remote look like as of my last sync?”

# See all remote tracking branches
git branch -r

# Compare your local main to the remote tracking ref
git log main..origin/main --oneline

git fetch: Download, Don’t Integrate
#

git fetch contacts the remote, downloads any new commits and refs, and updates your remote tracking branches. It does not touch your working tree or your local branches.

# Fetch all remotes
git fetch

# Fetch a specific remote
git fetch origin

# Fetch a specific branch
git fetch origin main

After a fetch you can inspect what arrived before deciding what to do with it:

# What came in?
git log origin/main --oneline -10

# What would merging look like?
git diff main origin/main

This is why git fetch is always safe. It cannot break your working tree, cannot cause a merge conflict, and cannot interrupt work in progress.

git pull: The Shorthand and Its Tradeoffs
#

git pull is shorthand for git fetch followed by git merge FETCH_HEAD. Running it on a branch that has diverged from the remote produces a merge commit:

*   a3b2c1d Merge branch 'main' of github.com:org/repo
|\
| * 9f8e7d6 Teammate's commit
* | 4c5d6e7 Your local commit
|/
* 1a2b3c4 Common ancestor

On a long-lived shared branch like main, these merge commits accumulate quickly and make git log hard to read. They also make bisecting more complicated.

git pull –rebase: Keeping a Linear History
#

git pull --rebase

This is equivalent to git fetch followed by git rebase origin/main. Instead of creating a merge commit, it replays your local commits on top of the newly fetched remote commits:

* 4c5d6e7 Your local commit (rebased)
* 9f8e7d6 Teammate's commit
* 1a2b3c4 Common ancestor

The history is linear. Your commit still contains exactly the changes you authored, but it now sits cleanly on top of everyone else’s work.

You can make this the default behavior for all pulls:

git config --global pull.rebase true
Note

git pull --rebase will still fail if your working tree has uncommitted changes that conflict with incoming commits. Stash or commit your work before pulling.

git fetch –prune: Bonus Cleanup
#

git fetch --prune

This combines a standard fetch with automatic removal of remote tracking refs that no longer exist on the remote. If a teammate deleted origin/feature-x on GitHub, --prune removes the stale origin/feature-x ref from your local repo. It is a good habit to use this instead of plain git fetch.

Sequence Diagram: fetch vs pull –rebase
#

sequenceDiagram
    participant L as Local (main)
    participant T as origin/main (tracking)
    participant R as Remote (main)

    Note over L,R: Teammate pushes a commit to remote

    L->>R: git fetch
    R-->>T: Updates origin/main with new commit
    Note over L: Local main is unchanged. You inspect origin/main.

    L->>L: git rebase origin/main
    Note over L: Your commits replayed on top of new commits. Linear history.

    alt Using git pull --rebase instead
        L->>R: git pull --rebase
        R-->>T: Fetch: updates origin/main
        T-->>L: Rebase: replays local commits on top
        Note over L: Same result, one command.
    end

Team Workflow Recommendation
#

The pattern that scales best on shared branches:

# 1. Fetch first to see what's there
git fetch --prune

# 2. Inspect what arrived
git log main..origin/main --oneline

# 3. Decide: rebase for linear history, merge if you need a merge commit
git rebase origin/main
# or
git merge origin/main

Fetching first and deciding second keeps you in control. You never get surprised by a mid-operation conflict when your working tree is dirty.

**Pulling on a dirty working tree.** If you have uncommitted changes and run `git pull`, Git will either refuse (if the pull would overwrite your files) or produce a confusing state. Always commit or stash before pulling. **Not knowing which remote and branch are configured for the current branch.** Run `git branch -vv` to see the upstream tracking configuration for every local branch. If there is no upstream set, `git pull` will not know where to pull from. **Never fetching before reviewing a teammate's PR.** If you review a PR using only the web UI, you are reviewing stale data if the branch was force-pushed recently. Fetch the branch locally and run `git log` and `git diff` before approving. **Using `git pull` on `main` in a rebase-based workflow.** If your team uses rebase, configure `pull.rebase = true` globally so that plain `git pull` never accidentally creates merge commits.

If you want to go deeper on any of this, I offer 1:1 coaching sessions for engineers working on AI integration, cloud architecture, and platform engineering. Book a session (50 EUR / 60 min) or reach out at manuel.fedele+website@gmail.com.