Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXI · Fetch vs PullFetchVsPull

git pull --rebase versus git pull --merge — linear history versus merge commits

Intermediate⏱ ~18 mingit

What you'll learn

  • Decompose git pull --rebase into git fetch followed by git rebase
  • Decompose git pull --no-rebase (the default) into git fetch followed by git merge
  • Predict the resulting local history in each mode when the local branch has diverged from the remote
  • Identify the trade-off between a clean linear history (rebase) and an explicit merge node (merge)
  • Choose between the two modes for local feature branches, shared branches, and CI clones

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

git pull has exactly two modes: it either merges the fetched upstream into the local branch (the default) or rebases the local branch onto the fetched upstream. The choice between the two is one of the most consequential defaults a team can set, because it determines the shape of every local branch’s history. A team that pulls with rebase sees linear history; a team that pulls with merge sees merge nodes at every integration point. Both are valid; neither is universally correct.

Pull —rebase: linear history

When git pull --rebase runs, it performs git fetch and then git rebase FETCH_HEAD (or the upstream of the current branch). The local commits are replayed on top of the fetched upstream, one by one, producing a linear history where the local branch tip is the last replayed commit and the fetched upstream is its sole parent.

flowchart LR
    subgraph Before["Before pull --rebase"]
        A1["origin/main"] --> B1["upstream commit A"]
        A1 --> C1["upstream commit B"]
        D1["local main"] --> E1["local commit 1"]
        D1 --> F1["local commit 2"]
        G1["common ancestor"] --> A1
        G1 --> D1
    end
    subgraph After["After pull --rebase"]
        A2["origin/main"] --> B2["upstream A"]
        A2 --> C2["upstream B"]
        D2["local main (rewritten)"] --> E2["local 1 (new SHA)"]
        D2 --> F2["local 2 (new SHA)"]
        E2 --> C2
    end

The replayed commits get new SHAs because their parents changed; the commit objects’ content (author, message, diff) is preserved, but their position in the DAG is different. If the upstream contains changes that conflict with a local commit, the rebase pauses at that commit and lets the engineer resolve the conflict before continuing.

# Rebase-mode pull
git pull --rebase origin main
# From github.com:acme/infra
#    4d2c8e0..9e1f2a3  main           -> origin/main
# Rebasing (2/2)
# Successfully rebased and updated refs/heads/main.

# The result: linear history
git log --oneline --graph main
# * 9f1c2a3 local commit 2 (rebased)
# * 7d8e9f0 local commit 1 (rebased)
# * 9e1f2a3 upstream commit B
# * 4d2c8e0 upstream commit A

The local commits now sit on top of the upstream commits with no merge commit in between. git log --graph shows a straight line, which is what most readers of the history find easier to follow than a tree with merge nodes.

Pull —merge (the default): merge commits

When git pull runs without --rebase, it performs git fetch and then git merge FETCH_HEAD (or the upstream). The local commits stay where they are; the fetched upstream is integrated via a three-way merge that produces a merge commit with both the previous local tip and the fetched upstream tip as parents.

# Merge-mode pull (the default)
git pull origin main
# From github.com:acme/infra
#    4d2c8e0..9e1f2a3  main           -> origin/main
# Merge made by the 'ort' strategy.
#  infra/networking.tf | 4 ++++
#  1 file changed, 4 insertions(+)

# The result: non-linear history with a merge node
git log --oneline --graph main
# *   8a3f9d2 Merge branch 'origin/main' into main
# |\
# | * 9e1f2a3 upstream commit B
# | * 4d2c8e0 upstream commit A
# * | 7d8e9f0 local commit 2
# * | 3c4d5e6 local commit 1

The merge commit (8a3f9d2) records that two lines of development were integrated at this point. Engineers reading the history later can see exactly which commits came from upstream and which were local, and they can git revert -m 1 the merge commit to undo the integration if needed. The cost is the merge node itself, which adds noise to git log --graph output.

When to use which mode

The trade-off is not abstract; it has concrete operational consequences:

  • Use --rebase on local branches that have not been pushed. The local commits get rewritten, but no one else has the old SHAs, so no one notices. The benefit is a clean linear history that is easy to read and easy to fast-forward when the branch is eventually merged.
  • Use --rebase on CI clones. CI clones never push (typically), so the SHA rewrite is harmless and the linear history makes log parsing easier.
  • Use --merge (the default) on shared branches. When multiple engineers pull from the same branch and each has local commits ahead of the upstream, a merge-mode pull preserves everyone’s SHAs and produces a merge commit that documents the integration. Rebasing in this case would rewrite SHAs that other engineers’ local clones might have, producing confusing “your branch and origin/main have diverged” messages.
  • Use --merge when the local commit has been pushed. A rebase of a pushed commit changes its SHA; the next push becomes a force-push. On a shared branch, a force-push is a coordination event that should never happen by accident.
# Equivalent sequences, side by side
git pull --rebase origin main
#   == git fetch origin
#   == git rebase origin/main

git pull origin main   # default
#   == git fetch origin
#   == git merge origin/main

# Override the global pull.rebase for one pull only
git pull --no-rebase origin main

Pull —rebase and the cost of a clean history

Teams that value a strictly linear history (no merge commits) often configure pull.rebase = true globally so that every pull on every branch defaults to rebase mode. The benefit is that the shared branch history is always linear; the cost is that every engineer must remember not to pull with rebase on shared branches they have already pushed to. For an infrastructure team with a single main branch and many short-lived feature branches, rebase-mode pulls are appropriate. For a team with long-lived shared branches where multiple engineers commit concurrently, merge-mode pulls are safer.

Production discipline

  1. Pull with rebase on local branches; pull with merge on shared branches. This is the simplest rule and the one that most teams converge on.
  2. Never rebase commits that have been pushed and pulled by someone else. A rebase rewrites SHAs; rewriting SHAs that other engineers depend on is a coordination event.
  3. Configure the default at the repo level, not the global level. pull.rebase = true at the global level means every pull on every repository behaves the same way; a repo-level setting lets shared branches opt out without requiring every engineer to remember --no-rebase.
  4. Treat merge-mode pull as the safer default. A merge commit can always be reverted with -m 1; a rebase can only be reverted by identifying the original SHAs in the reflog. When in doubt, merge.

Cross-course references

  • Git, CI/CD & GitOps - Part XXII (RebaseDeeper) covers rebase in detail; the pull —rebase command is a thin wrapper over the same machinery.
  • GitOps with Argo CD - Part IV (SyncPolicies) describes how an Argo CD repo-server prefers merge-mode reconciliation so the GitOps history is auditable per sync wave; rebase-mode reconciliation would obscure which commits were applied when.
  • Ansible for Production Sysadmins - Part XL (BranchPolicy) recommends rebase-mode pulls on local feature branches but merge-mode pulls on the team’s shared integration branch.

Quiz

Knowledge check · 4 questions

  1. Q1. What does `git pull --rebase origin main` do that `git pull origin main` (the default) does not?

  2. Q2. Running `git pull --rebase` on a branch whose commits have already been pushed to a shared remote is not always safe because rebase is a local operation.

  3. Q3. Explain the trade-off between rebase-mode and merge-mode pulls, and identify one scenario where each is the right choice.

  4. Q4. A team has configured `pull.rebase = true` globally. An engineer on a shared release branch runs `git pull` and accidentally force-pushes rebased commits. Diagnose and recommend a fix.

    A team uses `release/v2.0` as a long-lived shared branch for stabilisation work before a release. An engineer working on the branch has pulled (with rebase, because of the global config) several times and pushed their rebased commits. Another engineer pulled the same branch and has based work on the old SHAs. The first engineer's latest push is a force-push; the second engineer's `git status` now says 'your branch and origin/release-v2.0 have diverged' even though they have not committed anything new.

Passing score: 75%. Answers are checked in this browser.