Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXI · RebasingFoundations

Rebase versus merge — when linear history is worth the rewrite

Advanced⏱ ~22 mingit

What you'll learn

  • Compare the topology produced by merge and rebase for the same diverged branches
  • Identify the audit trade-off between merge commits (visible branch event) and rebase (linear but rewritten OIDs)
  • Choose merge or rebase for a given scenario based on whether the branch is local or shared
  • Configure `pull.rebase` and `branch.<name>.rebase` to enforce a rebase-on-pull policy
  • Recognise when `git pull --rebase` is the correct default for a feature branch

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.

Rebase and merge are two operations that solve the same problem from different angles: how to integrate a diverged branch back into its upstream. Merge preserves the topology — the diverged branch remains a visible event, encoded as a merge commit with two parents. Rebase preserves linearity — the diverged branch’s commits are replayed onto the upstream tip so the result reads as a single straight line, at the cost of rewriting OIDs. The choice between them is not aesthetic; it is a trade-off between visible branch history and visible commit history, constrained by who else holds the OIDs that rebase would change.

What merge produces

Given a feature branch that has diverged from main, git merge creates a single commit on the branch that was checked out, with two parents: the previous tip of the checked-out branch, and the tip of the merged-in branch. The topology of the divergence is preserved as a node in the DAG.

git checkout main
git merge feature/iam-rotation
# Merge made by the 'recursive' strategy.
# 4d2c8e0 (main) Merge branch 'feature/iam-rotation'
flowchart LR
    subgraph MERGED["after git merge (no fast-forward)"]
        A["C (merge base)"] --> B["M1 (main)"]
        B --> M["M (merge commit)"]
        A --> F1["F1 (feature)"]
        F1 --> F2["F2"]
        F2 --> F3["F3 (feature tip)"]
        M --> F3
    end

The merge commit M has two parents: M1 (main’s tip before the merge) and F3 (feature’s tip). The first-parent walk from M follows M1 and skips F3; the second-parent edge encodes “feature came in here”. The branch event is preserved.

What rebase produces

The same diverged branches, after git rebase main on the feature branch and a subsequent fast-forward merge:

flowchart LR
    subgraph REBASED["after git rebase main + fast-forward merge"]
        A2["C (unreachable from branch view)"] --> M1b["M1 (unreachable)"]
        M1b --> M2["M2 (new main tip)"]
        M2 --> R1["R1 (was F1)"]
        R1 --> R2["R2 (was F2)"]
        R2 --> R3["R3 (was F3, also main tip)"]
    end

There is no merge commit. The feature’s three commits sit directly on top of main’s tip. git log --oneline main reads as a straight line: M2, R1, R2, R3. The branch event is not in the graph; it is only in the reflog and in the team’s pull-request records.

The trade-off, stated explicitly

PropertyMergeRebase
Topology of the branch eventpreserved (merge commit with two parents)erased (commits replayed, no merge node)
Linearity of trunk historymay be non-linearalways linear
OIDs of the integrated commitsunchangedrewritten
Audit trail of the integration eventvisible in git log --first-parentnot visible in trunk view; visible only in reflog and PR system
Force-push required afterwardsnoyes, if the branch was already pushed
Risk to downstream consumerslow (merge commit is new)high (replayed OIDs may invalidate pins, tags, attestations)

The trade-off is between two kinds of audit: the audit of integrations (which merge supports by recording the branch event) and the audit of commits (which rebase supports by giving every commit a single linear parent chain). A team whose audit posture is “show me every PR that landed in production last quarter” prefers merge commits; a team whose audit posture is “show me the exact sequence of commits that produced the artifact tagged v3.2.7” prefers rebase.

When to use which

The decision matrix that recurs across infrastructure teams:

  • Local feature branch, no remote yetrebase is safe and preferred. The branch has no downstream consumers; rewriting OIDs is contained.
  • Feature branch on a remote, no teammates have pulledrebase is safe with care. Confirm with the team; if no one else has fetched the branch, force-push with --force-with-lease is acceptable.
  • Feature branch on a remote, teammates have pulledmerge is preferred. Rebase + force-push would rewrite OIDs the teammates hold; merge preserves the topology without rewriting the feature’s commits.
  • Trunk branch (main, master, production)never rebase. A trunk branch has too many downstream consumers; the force-push required after rebase is not survivable.
  • Release branch being prepared from mainmerge with --no-ff if you want the release event visible; rebase if you want a linear history and accept the rewrite.

git pull --rebase and the rebase-on-pull policy

The most common rebase-in-production scenario is git pull. By default, git pull is git fetch followed by git merge: the remote’s new commits are merged into the local branch, creating a merge commit (or fast-forwarding if possible). For a feature branch where the local engineer wants their unpushed commits to sit on top of the remote’s new commits rather than be merged with them, the right default is git pull --rebase:

git pull --rebase
# Successfully rebased and updated refs/heads/feature/iam-rotation.

The same default can be configured per-branch or globally:

# Per-branch: feature/iam-rotation always pulls with rebase
git config branch.feature/iam-rotation.rebase true

# Global default: every branch pulls with rebase unless overridden
git config --global pull.rebase true

The branch.<name>.rebase setting applies to a specific branch and overrides the global setting; the global pull.rebase setting applies to every branch. Setting pull.rebase = true is the production-grade default for feature branches where the engineer wants linear history and is comfortable with the OID rewrite (the branch is local or owned by a single engineer).

The complementary setting for a trunk branch is pull.rebase = false (the default): when main pulls new commits, a merge is the right operation, because the trunk’s OIDs are shared with every downstream consumer.

The four-flag vocabulary

The pull operation has four flags that interact with rebase:

  • git pull — fetch + merge (default).
  • git pull --rebase — fetch + rebase.
  • git pull --ff-only — fetch, refuse anything but a fast-forward.
  • git pull --no-rebase — fetch + merge (explicit default).

For a feature branch where the local engineer wants their unpushed commits replayed onto the remote tip, git pull --rebase is the right verb. For a trunk branch where the engineer wants the remote’s new commits fast-forwarded onto the local trunk, git pull --ff-only is the right verb.

Comparison with a worked example

A team has two branches that diverged from main at commit C: main is now at M2, and feature/iam-rotation is at F3. The auditor wants to know what produced the production deploy tagged v3.2.7, which points at the merged result.

With merge: v3.2.7 points at merge commit M, whose parents are M2 and F3. The auditor runs git log --first-parent main and sees a single commit M with the message “Merge branch ‘feature/iam-rotation’”. The PR number is in the merge commit’s message; the feature’s individual commits are reachable via the second-parent edge. Audit answer: one PR, three commits, no rewrite.

With rebase: v3.2.7 points at the last replayed commit R3, whose parent is R2, whose parent is R1, whose parent is M2. The auditor runs git log --oneline main and sees a straight line from R3 back to M2. The PR number is not in the graph at all; it is in the PR system, attached to the merge event that happened out-of-band. Audit answer: linear, but the branch event is invisible without consulting the PR system.

Both are auditable. Merge’s audit trail is in the graph; rebase’s audit trail is in the PR system. The right choice depends on which system the team has invested in.

Production discipline

  1. Default to rebase for local feature branches; default to merge for shared branches. The local-vs-shared rule resolves most of the choice without deliberation.
  2. Configure branch.<name>.rebase = true per feature branch, not pull.rebase = true globally. The global setting affects trunks and is rarely what is wanted.
  3. Audit the operation that produced a release. A merge produces a merge commit in the graph; a rebase produces a linear history with no merge node. The auditor needs to know which one happened to follow the right trail.
  4. Never rebase a commit referenced by a signed tag. A signed tag commits to a specific OID; rewriting the OID invalidates the referent even though the signature itself remains cryptographically valid.

Cross-course references

  • GitOps with Argo CD - Part VI (MergeStrategies) maps Argo CD’s sync options onto this trade-off: the Merge option is a Git merge, the Replace option discards in-cluster changes, and there is no native rebase option because rebase would rewrite the cluster-side OIDs.
  • CI/CD Pipeline Patterns - Part V (MergeQueues) describes merge queues that rebase each pull request onto the trunk tip before building; the rebase is local to the merge queue’s temporary branch, so no force-push is required.
  • Terraform for Production Sysadmins - Part XI (PRWorkflows) recommends rebasing a Terraform plan branch before merging so the eventual merge into main is a fast-forward and the state file remains consistent.

Quiz

Knowledge check · 4 questions

  1. Q1. Which of the following is the deciding question when choosing between merge and rebase for integrating a diverged branch?

  2. Q2. Setting `pull.rebase = true` globally is not a safe default because it produces a linear history for every branch.

  3. Q3. Name the per-branch and global Git config keys that cause `git pull` to rebase instead of merge, and identify which one is the safer default for a production engineer who works on multiple branches.

  4. Q4. Choose between merge and rebase for two branches in the same repository and justify each choice using the local-vs-shared rule.

    An infrastructure engineer has two branches in flight. The first is `feature/iam-rotation`, a local branch that has never been pushed; it has fallen three commits behind `main`. The second is `release/2026-q3`, a shared branch that three teammates have already pulled; it has fallen two commits behind `main`. The engineer wants to bring both branches up to date with the latest `main` before continuing work.

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