Compare Git fetch and pull before integration

Comparison Last tested 27 October 2026

Git fetch updates remote-tracking information without integrating it into the current branch. Git pull fetches and then integrates the selected branch; the integration can be fast-forward only, a merge or a rebase, depending on the chosen option and configuration.

Compare fetch with pull in Git

Fetch updates remote-tracking references without moving the checked-out branch1; pull fetches and then integrates a selected branch. Integration can use a fast-forward, merge or rebase according to the chosen option and configuration. The pull manual describes explicit fast-forward, merge and rebase options as well as configuration that affects the choice2.

This difference makes fetch useful when a reader wants to inspect incoming commits before changing a branch. It is not a promise that the remote remains unchanged after the inspection. Another update can arrive between a fetch and a later integration; the safe comparison is against the references actually fetched, at the time they were fetched.

Know the state before fetching

Start by identifying the current branch, its intended upstream and any staged or unstaged work. A pull that would overwrite local changes can stop; a conflict can require a decision. Saving work and reading status before integration is more useful than assuming that a short command is harmless. Fetch itself still updates remote-tracking references and the fetched object database, so it is not a read-only operation on repository metadata.

In the example, a publisher and readers are separate directories, all connected to one local bare remote. The publisher first sends a new commit. The reader fetches it and verifies that its own current commit remains the same. Counting the commits between the current branch and the updated remote-tracking branch identifies the incoming work in this controlled case. It does not review the contents or prove the incoming change is suitable.

Read the three integration outcomes

The fast-forward case has no local divergence. After fetch, the reader pulls with the explicit fast-forward-only option and checks that the branch reaches the fetched reference. A fast-forward moves the branch to a descendant; it does not need a merge commit. If the histories have diverged, the same option refuses to create one. Inspecting the relationship first prevents that refusal from being mistaken for a missing fetch.

Two further readers begin at the same state, each with a local commit while the publisher adds another remote commit. The merge reader uses an explicit no-rebase pull and the example checks for two parents. Its resulting merge commit connects both lines of history. The rebase reader explicitly requests rebase; the example checks that its local subject appears after the new upstream subject. These are two different histories produced from comparable starting work.

Neither result is automatically the right choice for a team. Rebasing can replace commit identities, while merging can add a merge commit. Policies for branches other people already use belong to the team and its repository settings. The test does not simulate collaborators, protected branches or an approval process. It demonstrates local Git behavior with a bare remote that has no hosting policy.

Choose the comparison you actually need

An incoming commit count answers how many commits are reachable from the fetched reference and not the current branch in this fixture. It is not a list of every changed line, and it says nothing about a commit’s quality. Read the relevant commit descriptions and content before deciding to integrate. A branch can be ahead and behind at once, and the practical decision may depend on work that is not represented by a single count.

A pull always runs its own fetch, even after a separate fetch. That is useful when it intentionally updates the remote-tracking reference again, but it can mean the integrated tip is newer than the one previously inspected. For a careful workflow, check the actual reference and integration result instead of assuming the first preview remains current. Network failure, changed permissions or new remote history can also stop the operation; this example has no network connection.

When to stop

If the branch is not the intended destination, do not integrate merely because updates are visible. If there are valuable edits in the working tree, preserve them and understand whether the chosen operation can proceed without loss. If the histories diverged, decide explicitly whether a fast-forward-only refusal, merge or rebase matches the agreed workflow. The example uses explicit flags so that a reader can see the choice rather than rely on an unseen personal setting.

Fetching is a separate step, not a guarantee that a later pull will be conflict-free. Likewise a successful fast-forward says nothing about tests or application behavior. The example asserts commit relationships and file history, not a quality or deployment outcome. For a stopped operation, first identify whether Git is merging or rebasing; use the relevant operation’s instructions rather than treating the two states as interchangeable.

Run the complete local example

Before running: This example creates only fictional repositories. Pull changes the current branch; review local state before integrating in a real repository. Run this complete MIT-licensed example with Git 2.56.0 in an empty directory. It creates fetch-example/, including its own local bare remote. It performs no network access and refuses to reuse an existing example directory.

Download the complete example. The printed file is the one that ran.

#!/bin/sh
# SPDX-License-Identifier: MIT
set -eu
export TZ=UTC LC_ALL=C
export GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null
export GIT_AUTHOR_NAME='Example Author' GIT_AUTHOR_EMAIL='author@example.invalid'
export GIT_COMMITTER_NAME='Example Author' GIT_COMMITTER_EMAIL='author@example.invalid'
export GIT_AUTHOR_DATE='2026-10-27T12:00:00+00:00'
export GIT_COMMITTER_DATE='2026-10-27T12:00:00+00:00'
test "$(git version)" = 'git version 2.56.0'

mkdir fetch-example
cd fetch-example
git init --template= --bare -q --initial-branch=main origin.git
git init --template= -q --initial-branch=main publisher
cd publisher
printf 'base\n' > note.txt
git add note.txt
git commit -qm 'Base'
git remote add origin ../origin.git
git push -q -u origin main >/dev/null
cd ..
git clone --template= -q origin.git reader
cd publisher
printf 'upstream\n' >> note.txt
git commit -qam 'Upstream change'
git push -q origin main
cd ../reader
before=$(git rev-parse HEAD)
git fetch -q origin
test "$(git rev-parse HEAD)" = "$before"
test "$(git rev-list --count HEAD..origin/main)" = 1
printf 'Fetch kept HEAD; incoming commits: '
git rev-list --count HEAD..origin/main
git pull --ff-only -q origin main >/dev/null
test "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)"
printf 'Fast-forward pull integrated the fetched commit\n'
cd ..
git clone --template= -q origin.git merge-client
git clone --template= -q origin.git rebase-client
for client in merge-client rebase-client; do
  (cd "$client" && printf 'local\n' > local.txt && git add local.txt && git commit -qm 'Local change')
done
cd publisher
printf 'remote\n' > remote.txt
git add remote.txt
git commit -qm 'Another upstream change'
git push -q origin main
cd ../merge-client
git pull -q --no-rebase --no-edit origin main >/dev/null
test "$(git rev-list --parents -n 1 HEAD | wc -w | tr -d ' ')" = 3
printf 'Merge pull created a commit with two parents\n'
cd ../rebase-client
git pull -q --rebase origin main >/dev/null 2>&1
test "$(git log -1 --format=%s)" = 'Local change'
test "$(git log -2 --format=%s | tail -1)" = 'Another upstream change'
printf 'Rebase pull replayed the local commit after upstream\n'

Both runs produced:

Fetch kept HEAD; incoming commits: 1
Fast-forward pull integrated the fetched commit
Merge pull created a commit with two parents
Rebase pull replayed the local commit after upstream

The script leaves its example directory and its repositories in place. Remove that newly created directory when it is no longer needed; inspect the name first so cleanup does not remove another project. The assertions stop the run if the demonstrated state differs. No command in the example connects to an external remote.

Not covered on this page

Sources

  1. Git 2.56.0 fetch reference. Git v2.56.0; checked 27 October 2026 UTC. ↩

  2. Git 2.56.0 pull reference. Git v2.56.0; checked 27 October 2026 UTC. ↩

How we test: methodology. Found a mistake? Corrections.