Git, CI/CD & GitOpsXI · RebasingAutosquash
Autosquash and fixup — folding review-fixup commits automatically
What you'll learn
- Use `git commit --fixup=<sha>` to mark a commit for autosquash onto a target
- Distinguish `git commit --fixup=<sha>` from `git commit --squash=<sha>`
- Use `git rebase -i --autosquash <upstream>` to reorder the todo list so fixup commits land on their targets
- Configure `rebase.autoSquash` to make autosquash the default for interactive rebases
- Describe the review-fixup cleanup workflow end-to-end
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
The autosquash workflow is the production discipline for keeping a
feature branch’s history clean while review is in progress. The
pattern is: an engineer opens a pull request, the reviewer asks
for changes, the engineer fixes them in a fresh commit marked with
git commit --fixup=<target-sha>, and just before merging the
engineer runs git rebase -i --autosquash <upstream>. The rebase
reads the markers, reorders the todo list automatically, and folds
each fixup commit into its target with the fixup verb. The
result is a branch whose history contains only the original commits,
not the review-fixup commits — without the engineer having to
manually edit the todo list.
The fixup marker
git commit --fixup=<sha> creates a commit whose subject line
begins with fixup! <subject-of-target>. The marker is the
mechanism by which autosquash identifies which target the fixup
belongs to:
# Target commit: 8a3f9d2 with message 'feat(iam): add role assumption policy'
git add terraform/modules/iam/main.tf
git commit --fixup=8a3f9d2
# [feature/iam-rotation 9f3c1d7] fixup! feat(iam): add role assumption policy
The new commit’s subject line is fixup! feat(iam): add role assumption policy — the literal prefix fixup! followed by the
target commit’s subject. Autosquash uses the fixup! prefix to
match the fixup commit to its target by message subject.
git commit --squash=<sha> is the same idea with the prefix
squash! instead of fixup!. The difference is the verb autosquash
will use: fixup for --fixup (message discarded) and squash
for --squash (message combined with the target’s message via the
editor).
# Same target, but the fixup commit's message will be preserved
git commit --squash=8a3f9d2
# [feature/iam-rotation a1b2c3d] squash! feat(iam): add role assumption policy
The autosquash flag
git rebase --interactive --autosquash <upstream> opens the
interactive rebase todo list, but with the fixup and squash
commits already moved into position directly after their targets.
Without --autosquash, the todo list preserves the chronological
order of the commits and the engineer must manually reorder the
fixup lines to land after their targets.
git rebase -i --autosquash main
# Opens editor with fixup commits already placed after their targets
The configured default for --autosquash is the rebase.autoSquash
config key:
git config --global rebase.autoSquash true
# Every interactive rebase now autosquashes by default
With rebase.autoSquash = true, the engineer does not need to
pass --autosquash on every rebase; the todo list is reordered
automatically. The flag remains available for one-off invocations.
The todo list before and after autosquash
Consider a feature branch with four commits, two of which are fixup commits against the first and third:
git log --oneline
# 7a1b2c3 fixup! feat(iam): add terraform module
# b2c3d4e feat(iam): add terraform module
# 9f3c1d7 fixup! feat(iam): add role assumption policy
# 8a3f9d2 feat(iam): add role assumption policy
# 6f4e5a6 chore: base commit
A plain interactive rebase opens this todo list (oldest first):
pick 8a3f9d2 feat(iam): add role assumption policy
pick 9f3c1d7 fixup! feat(iam): add role assumption policy
pick b2c3d4e feat(iam): add terraform module
pick 7a1b2c3 fixup! feat(iam): add terraform module
With --autosquash, the todo list is reordered so each fixup sits
directly after its target:
pick 8a3f9d2 feat(iam): add role assumption policy
fixup 9f3c1d7 fixup! feat(iam): add role assumption policy
pick b2c3d4e feat(iam): add terraform module
fixup 7a1b2c3 fixup! feat(iam): add terraform module
The engineer saves the file and exits. The rebase replays the four
commits: the first pick is replayed, the fixup line absorbs
the second commit’s diff into the first, the third pick is
replayed, the second fixup line absorbs the fourth commit’s
diff into the third. The result is two commits in the branch
history: the two feat(iam) commits, with the review-fixup diffs
absorbed into them and no fixup! lines preserved.
flowchart LR
subgraph BEFORE_AS["before autosquash"]
A["8a3f9d2 feat"] --> B["9f3c1d7 fixup! feat"]
B --> C["b2c3d4e feat"]
C --> D["7a1b2c3 fixup! feat"]
end
subgraph AFTER_AS["after git rebase -i --autosquash"]
A2["8a3f9d2 feat + absorbed fixup"] --> C2["b2c3d4e feat + absorbed fixup"]
end
The four-commit branch becomes a two-commit branch with the same total diff. The review-fixup commits are not in the final history.
The review-fixup workflow
The end-to-end workflow for cleaning up a feature branch that has been through code review:
# 1. Engineer addresses review feedback in a fresh commit
git add terraform/modules/iam/main.tf
git commit --fixup=8a3f9d2 # target = the original feat commit
# 2. Engineer pushes the branch and addresses more review feedback
git push origin feature/iam-rotation
# 3. Reviewer approves; engineer cleans up before merging
git fetch origin
git rebase -i --autosquash origin/main
# (editor opens with fixup commits in place; engineer saves and exits)
# 4. Engineer verifies the cleaned history
git log --oneline
# a1b2c3d feat(iam): add terraform module
# 8a3f9d2 feat(iam): add role assumption policy
# origin/main tip
The cleaned branch has only the original feat(iam) commits; the
review-fixup commits are folded in. The diff against origin/main
is unchanged: the total change is the same, but the history is
shorter and the reviewer feedback is not visible as separate
commits.
When to use fixup versus squash
The choice between --fixup and --squash is whether the
fixup commit’s message is informative:
--fixupdiscards the fixup commit’s message entirely. Use it when the fixup commit’s subject is a placeholder like “fix review feedback” or “correct typo” — a message that does not add to the target commit’s history.--squashopens an editor session to combine the fixup commit’s message with the target commit’s message. Use it when the fixup commit’s message contains useful information that should be preserved in the target’s history (e.g. “add error handling for the case where the role is not yet created”).
The autosquash rebase uses fixup for --fixup markers and
squash for --squash markers, so the verb choice is preserved
through the rebase.
Edge cases
Three cases where autosquash is not the right tool:
- Fixup commits against the same target. Two
--fixupcommits against the same target result in twofixuplines after the target; both are absorbed into the target in order. The behaviour is well-defined, but the engineer’s intent (“fold them in order, oldest first” or “fold them in order, newest first”) is not preserved by the markers alone. - Targets that get reordered. If the engineer reorders the target commits after creating the fixup commits, autosquash follows the original message-subject match, not the original SHA. A target commit that was renamed to “feat(iam): add role assumption policy” might match a fixup commit against a different target with the same subject.
- Fixup commits created against a rebase target. After a
rebase, the target commit’s OID changes. A fixup commit’s
--fixup=<old-sha>still references the old OID, but autosquash matches by message subject, not OID, so the fixup still lands on the right target. The engineer does not need to recreate fixup commits after a rebase.
Production discipline
- Configure
rebase.autoSquash = trueglobally. The flag is safe to default on; the engineer retains the option to disable it for one-off rebases that should not autosquash. - Use
--fixupfor noise; use--squashfor information. A review-fixup commit that says “fix typo” should not pollute the target commit’s message; a commit that adds error handling for a previously-unhandled case is worth a combined message. - Run autosquash before merge, not after. The operation is a
rebase, which rewrites OIDs. The branch should still be local
(or owned by a single engineer) when autosquash runs. Push the
cleaned branch with
--force-with-leaseif the branch was already pushed. - Verify with
git log --onelinebefore pushing. The cleaned history should contain only the original feature commits, in the right order, with the review-fixup commits absorbed.
Cross-course references
- CI/CD Pipeline Patterns - Part V (MergeQueues) describes merge queues that run autosquash on the merge queue’s temporary branch before the final merge into the trunk, so the trunk sees a clean history even if the contributor did not run autosquash themselves.
- GitOps with Argo CD - Part VI (MergeStrategies) maps the autosquash workflow onto the GitOps sync policy: a sync that prefers squash corresponds to autosquash having been run; a sync that preserves history corresponds to autosquash having been skipped.
- Terraform for Production Sysadmins - Part XI (PRWorkflows)
requires autosquash on Terraform plan branches: the final
history should show one commit per logical change, not one
commit per code-review iteration, so the
terraform planfor the merge commit corresponds to the merged result.
Quiz
Knowledge check · 4 questions
Q1. What does `git commit --fixup=<sha>` do, and how does autosquash use the resulting commit?
Q2. Autosquash matches fixup commits to their targets by the original target OID recorded in `git commit --fixup=<sha>`, so a rebase that changes the target's OID invalidates the fixup.
Q3. Name the Git config key that enables autosquash by default for every interactive rebase, and identify when in the review-fixup workflow the engineer should run `git rebase -i --autosquash`.
Q4. Walk through the review-fixup cleanup workflow for a feature branch that has been through two rounds of code review, identifying each command and the state of the branch at each step.
An engineer has a feature branch `feature/iam-rotation` with three original commits: `feat(iam): add role assumption policy`, `feat(iam): add terraform module`, `feat(iam): add output for assume-role`. The branch has been through two rounds of review. After round one, the engineer added `fix(iam): correct trust policy arn` as a `--fixup` against the first commit. After round two, the engineer added `fix(iam): add error handling for missing role` as a `--fixup` against the third commit. The engineer is about to merge.
Passing score: 75%. Answers are checked in this browser.