Git, CI/CD & GitOpsXI · RebasingFoundations
Rebase versus merge — when linear history is worth the rewrite
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
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
| Property | Merge | Rebase |
|---|---|---|
| Topology of the branch event | preserved (merge commit with two parents) | erased (commits replayed, no merge node) |
| Linearity of trunk history | may be non-linear | always linear |
| OIDs of the integrated commits | unchanged | rewritten |
| Audit trail of the integration event | visible in git log --first-parent | not visible in trunk view; visible only in reflog and PR system |
| Force-push required afterwards | no | yes, if the branch was already pushed |
| Risk to downstream consumers | low (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 yet — rebase is safe and preferred. The branch has no downstream consumers; rewriting OIDs is contained.
- Feature branch on a remote, no teammates have pulled —
rebase is safe with care. Confirm with the team; if no one
else has fetched the branch, force-push with
--force-with-leaseis acceptable. - Feature branch on a remote, teammates have pulled — merge 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
main— merge with--no-ffif 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
- 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.
- Configure
branch.<name>.rebase = trueper feature branch, notpull.rebase = trueglobally. The global setting affects trunks and is rarely what is wanted. - 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.
- 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
Mergeoption is a Git merge, theReplaceoption 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
mainis a fast-forward and the state file remains consistent.
Quiz
Knowledge check · 4 questions
Q1. Which of the following is the deciding question when choosing between merge and rebase for integrating a diverged branch?
Q2. Setting `pull.rebase = true` globally is not a safe default because it produces a linear history for every branch.
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.
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.