Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXI · RebasingFoundations

What rebase does — replaying commits on a new base

Advanced⏱ ~20 mingit

What you'll learn

  • Explain what `git rebase <upstream>` does to the branch DAG in terms of replaying commits
  • Identify why each replayed commit receives a new OID even when its diff is unchanged
  • Distinguish a replay (cherry-pick loop) from a merge (three-way combine)
  • Predict the topology before and after a rebase from the upstream and branch tips alone
  • Recognise the local-vs-shared boundary that determines when rebase is safe

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 rebase is the operation that turns a diverged branch into a linear extension of its upstream. It does not combine two tips the way a merge does; it replays each commit from the current branch on top of a new base, one commit at a time, as if each commit had been authored against the new tip from the start. The cost is that every replayed commit gets a new OID. The benefit is a history that reads as a straight line. Understanding what rebase does — and what it does not do — is the prerequisite for everything else in this part.

What rebase does in one sentence

git rebase <upstream> finds the merge base of the current branch and <upstream>, then replays each commit from the current branch that is not on <upstream> onto the tip of <upstream>, in order, one at a time. After the rebase, the current branch points at the last replayed commit, which has <upstream>’s tip as its parent.

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

The verb is replay, not combine. A merge takes two tips and produces a single merge commit whose tree is a three-way combination. A rebase takes one branch and rewrites its commits so that they sit on a different parent. The merge creates a node; the rebase moves commits.

Topology before and after

The two diagrams below show the same repository before and after git checkout feature/iam-rotation && git rebase main. The commits on feature/iam-rotation (F1, F2, F3) move from sitting on top of C (the old base) to sitting on top of M2 (the new base).

flowchart LR
    subgraph BEFORE["before rebase"]
        C["C (merge base)"] --> M1["M1 (main)"]
        C --> F1["F1 (feature)"]
        F1 --> F2["F2 (feature)"]
        F2 --> F3["F3 (feature tip)"]
    end
    subgraph AFTER["after git rebase main"]
        C2["C (gone from branch view)"] --> M1b["M1"]
        M1b --> M2["M2 (new main tip)"]
        M2 --> R1["R1 (was F1)"]
        R1 --> R2["R2 (was F2)"]
        R2 --> R3["R3 (was F3, new tip)"]
    end

Two things changed:

  1. The parent chain of the feature commits now ends at M2 instead of C. The first parent of R1 is M2, not C.
  2. The OIDs of R1, R2, R3 are different from F1, F2, F3 even though their diffs are identical, because a commit’s OID is a hash of (tree, parents, author, committer, message).

The original commits F1, F2, F3 still exist in the object store — nothing in a rebase deletes objects — but they are no longer reachable from the branch. The reflog and git reflog feature/iam-rotation retain a record of the previous tip until the reflog entries expire (default 90 days).

Replay is a cherry-pick loop

The mechanism by which rebase moves a commit is a sequence of git cherry-pick operations. For each commit on the original branch that is not on the upstream, Git runs the equivalent of:

git cherry-pick $ORIGINAL_COMMIT_OID

A cherry-pick is itself a three-way merge: given the cherry-picked commit, its parent, and HEAD, Git computes the merge base (the cherry-picked commit’s parent), takes the diff from base to cherry-picked commit, and applies that diff to HEAD. The resulting commit has HEAD as its parent and the cherry-picked diff as its tree. That is why a replayed commit’s parent changes — the merge base of the cherry-pick is still the original parent, but the new commit’s parent is HEAD at replay time.

# Equivalent to what rebase does internally, one commit at a time:
git checkout main
git cherry-pick F1    # creates R1 with parent = M2
git cherry-pick F2    # creates R2 with parent = R1
git cherry-pick F3    # creates R3 with parent = R2
git branch -f feature/iam-rotation

A conflict during a rebase is a conflict during the corresponding cherry-pick: the diff from base to original-commit cannot be applied cleanly to the new base, so the working tree is left with conflict markers and the rebase pauses. The conflict resolution path is the same as for a cherry-pick: edit, git add, then git rebase --continue.

The local-versus-shared boundary

Rebase is safe when the commits being rewritten are not yet visible to anyone else. A feature branch that exists only in the local repository can be rebased freely; the reflog retains the previous tips until the entries expire, and the rewritten OIDs replace the old OIDs from the perspective of that one local ref.

Rebase is dangerous when the commits being rewritten are already on a remote, or have been pulled by a teammate, or are referenced by a signed tag, or are pinned by an external system. Once another repository holds the old OIDs, replacing them with new OIDs in the upstream repository breaks every reference that points at the old OIDs. The mechanism that pushes the new OIDs is git push --force (or, safely, --force-with-lease), covered in XI-06.

The rule that recurs throughout this part:

Never rebase commits that have been pushed to a shared branch and might have downstream consumers.

The corollary:

Rebase freely while a feature branch is local. Push the rebased branch once, with --force-with-lease, only after confirming no downstream consumer holds the old OIDs.

When rebase is the right verb

The cases where rebase is the canonical operation:

  • A feature branch has fallen behind main and the engineer wants the feature’s commits to sit on top of the current main tip rather than an old one, so the eventual merge into main is a fast-forward.
  • An engineer wants to clean up local commits (squash, fixup, reword, drop) before opening a pull request.
  • A pull request has had review feedback and the engineer wants to fold review-fixup commits into the original commits before merging (autosquash workflow, XI-05).
  • git pull --rebase is configured so a local branch’s pending commits are replayed on top of the fetched remote tip instead of being merged.

The cases where rebase is the wrong verb:

  • The branch has been pushed and is used by teammates. Rebase + force-push rewrites OIDs the teammates already hold.
  • The branch is main or another trunk branch. Rebasing a trunk rewrites every commit reachable from it.
  • The repository has signed tags pointing at commits that would be rewritten. Signed tags commit to a specific OID; rewriting the OID invalidates the signature’s referent.

Production discipline

  1. Rebase only local, unpushed commits. The first rule of rebasing. The reflog is your safety net for local rebases; it is not a safety net once commits are pushed.
  2. Confirm with --force-with-lease, not --force, when pushing a rebased branch. --force-with-lease refuses the push if the remote’s branch tip has moved since your last fetch, which is the guard rail that catches “I am about to clobber a teammate’s commit”. --force does not have that guard.
  3. Treat the replayed branch as a new branch for downstream consumers. After git rebase main + git push --force-with-lease, any tag, comment, or external pin that referenced the old OIDs is stale and must be updated.
  4. Never rebase commits referenced by signed tags. A signed tag’s signature commits to a specific OID; rewriting the OID invalidates the referent. The signature remains cryptographically valid against the new OID, but the meaning of the tag — “this object” — is lost.

Cross-course references

  • GitOps with Argo CD - Part VI (MergeStrategies) maps the rebase-versus-merge trade-off onto GitOps sync policies: a sync policy that prefers rebase produces a linear cluster history, while a sync policy that prefers merge preserves the topology of in-cluster changes.
  • CI/CD Pipeline Patterns - Part V (MergeQueues) describes merge queues that rebase each pull request onto the current trunk tip before building, so the build runs against the same parent the eventual merge will use.
  • Terraform for Production Sysadmins - Part XI (PRWorkflows) uses rebase as the standard operation before merging a Terraform plan branch, so the merge into main is a fast-forward and the state file remains consistent with the new tip.

Quiz

Knowledge check · 4 questions

  1. Q1. What is the operation that `git rebase <upstream>` performs on each commit of the current branch?

  2. Q2. A rebase is not safe to run on commits that have already been pushed to a shared branch, as long as the engineer remembers to run `git push --force` afterwards.

  3. Q3. Why does each commit replayed by `git rebase` receive a new OID, even when its tree and message are unchanged?

  4. Q4. Decide whether rebase is the right operation, and if so, identify the exact commands to run in order.

    An engineer has a local feature branch `feature/iam-rotation` that is three commits ahead of `main`. The branch has never been pushed. Meanwhile, two new commits have landed on `main` since the branch was cut. The engineer wants to merge the feature into `main` and have the merge be a fast-forward so the audit log shows the feature's commits as a clean linear extension of `main`.

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