Git, CI/CD & GitOpsXI · RebasingInteractive Rebase
Interactive rebase — the editor interface and the six verbs
What you'll learn
- Open an interactive rebase session with `git rebase -i <upstream>`
- Identify the six verbs (pick, reword, edit, squash, fixup, drop) and the operation each performs
- Reorder commits by reordering the pick lines in the todo list
- Squash multiple commits into one and explain how the combined commit message is composed
- Use `edit` to pause the rebase for amendment and `reword` to change a message without pausing
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
Interactive rebase is the operation that turns rebase from “move my
commits onto a new base” into “let me rewrite the commits themselves
before they leave my machine”. git rebase -i <upstream> opens an
editor with a todo list: one line per commit that will be replayed,
prefixed by a verb that tells Git what to do with that commit. The
engineer edits the verbs, saves the file, and Git performs the
replay according to the new instructions. The result is a rewritten
history with new OIDs, exactly as in non-interactive rebase, but
with the engineer in control of which commits survive, in what
order, and with what messages.
Opening an interactive rebase
git checkout feature/iam-rotation
git rebase -i main
# Opens $EDITOR with a todo list of commits
The -i (or --interactive) flag opens the editor before any
replay begins. The editor shows the commits that would be replayed,
oldest first, with the default verb pick on every line. The
default editor is $EDITOR, falling back to $VISUAL, then to
vi. Setting git config core.editor overrides the default for a
repository.
A typical todo list looks like this (commit messages abbreviated):
pick 8a3f9d2 feat(iam): add role assumption policy
pick 9f3c1d7 fix(iam): correct trust policy arn
pick a1b2c3d feat(iam): add terraform module
pick b2c3d4e chore: bump provider versions
# Rebase 6f4e5a6..b2c3d4e onto 6f4e5a6 (4 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
The four commits above the comment block are the commits that will be replayed. The engineer edits the verbs, saves, and exits. Git reads the todo list and performs each operation in order.
The six verbs
Every line in the todo list is one commit and one verb. The verbs, in order of frequency:
pick(p) — keep the commit as-is. The default verb. Apickline causes Git to cherry-pick the commit onto the current HEAD.reword(r) — keep the commit but edit its message. Git opens the editor again with the current message; the engineer rewrites it and the commit is replayed with the new message. No working-tree pause.edit(e) — keep the commit but pause the rebase so the engineer can amend it. Git replays the commit, then stops at that commit with the working tree in the commit’s state. The engineer can change files,git add, andgit commit --amend, thengit rebase --continue. The commit’s OID changes.squash(s) — meld the commit into the previous commit and combine the messages. Git opens the editor with both messages; the engineer writes a combined message and the result is one commit with the combined diff. The previous commit’s OID changes (because its tree changes), and the squashed commit is eliminated.fixup(f) — likesquash, but discard this commit’s message. The previous commit absorbs the diff; the combined message is the previous commit’s message unchanged. Useful for folding review-fixup commits without polluting the message.drop(d) — remove the commit entirely. Its diff is lost (unless it has been merged elsewhere). The line is omitted from the new history.
A seventh verb, exec (x), runs a shell command at that
point in the replay without affecting the commits. exec is the
mechanism behind git rebase --exec <cmd> (XI-04).
pick 8a3f9d2 feat(iam): add role assumption policy
squash 9f3c1d7 fix(iam): correct trust policy arn
fixup a1b2c3d feat(iam): add terraform module
reword b2c3d4e chore: bump provider versions
The todo list above, applied to the original four-commit branch, produces: one commit (the squash result) containing the diffs of the first two original commits, one commit (the fixup result) containing the diffs of the third original commit absorbed into the
flowchart LR
subgraph BEFORE_V["before rebase (4 commits)"]
A1["8a3f9d2 feat: policy"] --> B1["9f3c1d7 fix: trust arn"]
B1 --> C1["a1b2c3d feat: module"]
C1 --> D1["b2c3d4e chore: bump"]
end
subgraph AFTER_V["after git rebase -i (2 commits)"]
A2["pick+absorbed: feat: policy + fix: trust arn"]
B2["pick+absorbed: feat: module + chore: bump"]
A2 --> B2
end
squash result, and one commit (the reword of the fourth original) with a new message. The original three commits are gone from the branch view; their OIDs are no longer reachable.
Reordering by reordering lines
The todo list is executed top-to-bottom. Moving a pick line up
or down moves the corresponding commit to that position in the
new history. This is how an engineer reorders a feature branch
without rewriting individual commits:
pick b2c3d4e chore: bump provider versions
pick 8a3f9d2 feat(iam): add role assumption policy
pick 9f3c1d7 fix(iam): correct trust policy arn
pick a1b2c3d feat(iam): add terraform module
The chore commit is now first in the new history, before the
feat and fix commits. This kind of reorder is useful when the
chore is logically independent of the feature and should land
first in the eventual merge.
Squashing and the combined message
A squash line produces an editor session in which the engineer
writes the combined message. The default combined message is:
# This is a combination of 2 commits.
# This is the first commit message:
feat(iam): add role assumption policy
# This is the commit message #2:
fix(iam): correct trust policy arn
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
The engineer edits the file, removes the comments, and writes the combined message. After save and exit, the two original commits are replaced by one commit with the combined diff and the new message. The new commit’s OID is unrelated to either original OID.
fixup skips the editor session entirely: the previous commit’s
message is preserved unchanged, and the fixup commit’s diff is
absorbed into the previous commit without comment.
edit versus reword
Both edit and reword change a single commit; the difference
is whether the rebase pauses.
rewordopens the editor with the current message and continues the replay as soon as the engineer saves and exits.editreplays the commit, then stops with the working tree in the commit’s state. The engineer cangit addfurther changes andgit commit --amend, thengit rebase --continue.
edit is the verb for “I need to change the content of this
commit, not just its message”. reword is the verb for “the
content is right; only the message needs work”.
# Inside an edit pause, the engineer might run:
git add terraform/modules/iam/main.tf
git commit --amend
git rebase --continue
Why the todo list is a text file
The todo list is a text file rather than an interactive prompt
because text files are scriptable: the engineer can write the todo
list programmatically (with sed, with a heredoc, with a script),
which is the foundation of tools like git commit --fixup and
git rebase --autosquash (XI-05). The format is stable across Git
versions; a saved todo list can be replayed verbatim against the
same upstream to reproduce the same rewrite.
# Save the todo list, edit, replay:
git rebase -i main
# (editor opens; engineer edits; saves; exits)
# Non-interactive equivalent: pipe a todo list into `git rebase -i`
# via the --interactive-todo flag (advanced; rarely needed).
Production discipline
- Interactive rebase on local branches only. The risk concentration in interactive rebase compounds the shared-history risk of ordinary rebase; the safety rule is the same but the consequence of a violation is larger.
- Review the todo list carefully before saving. A single
dropon the wrong line removes a commit from history. A single misplacedsquashcombines the wrong commits. - Verify with
git log --onelinebefore pushing. The rebased history should match the engineer’s intent; if it does not, the reflog retains the pre-rebase tip for rollback. - Push with
--force-with-lease, never--force. The lease refuses the push if the remote’s tip has moved since the last fetch, catching the “I am about to clobber a teammate’s commit” case. - Document the verb conventions in CONTRIBUTING. Teams that
use
fixupfor review-fixup commits andsquashfor combined features need the convention documented so reviewers and contributors agree on what the final history looks like.
Cross-course references
- GitOps with Argo CD - Part VI (MergeStrategies) maps onto
the
pick/squashverbs: a sync strategy that prefers linear history corresponds to squashing commits at sync time; a sync strategy that preserves history corresponds to keeping all picks. - CI/CD Pipeline Patterns - Part V (MergeQueues) describes merge queues that perform a final interactive rebase to squash review-fixup commits before merging, so the trunk sees a clean linear history.
- Terraform for Production Sysadmins - Part XI (PRWorkflows)
recommends
fixupfor review-fixup commits on Terraform plan branches, so the final history shows one commit per logical change rather than one commit per code-review iteration.
Quiz
Knowledge check · 4 questions
Q1. In an interactive rebase todo list, what is the difference between `squash` and `fixup`?
Q2. An interactive rebase pauses at an `edit` line and continues immediately at a `reword` line without pausing.
Q3. List the six verbs available in an interactive rebase todo list and state in one phrase what each verb does to the corresponding commit.
Q4. Write the interactive rebase todo list that produces a single commit containing the diffs of three original commits, with a chosen combined message.
An engineer has a feature branch with three commits: `feat(iam): add role assumption policy`, `fix(iam): correct trust policy arn`, and `chore: bump provider versions`. The engineer wants to combine the first two into one commit titled `feat(iam): add role assumption policy` (the first message), absorb the third commit's diff into the same combined commit (the chore is a tooling concern, not worth a separate commit), and produce a final branch with exactly one commit.
Passing score: 75%. Answers are checked in this browser.