Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXI · RebasingAutosquash

Autosquash and fixup — folding review-fixup commits automatically

Advanced⏱ ~20 mingiteditor

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

Not yet marked complete on this device.

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:

  • --fixup discards 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.
  • --squash opens 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:

  1. Fixup commits against the same target. Two --fixup commits against the same target result in two fixup lines 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.
  2. 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.
  3. 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

  1. Configure rebase.autoSquash = true globally. The flag is safe to default on; the engineer retains the option to disable it for one-off rebases that should not autosquash.
  2. Use --fixup for noise; use --squash for 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.
  3. 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-lease if the branch was already pushed.
  4. Verify with git log --oneline before 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 plan for the merge commit corresponds to the merged result.

Quiz

Knowledge check · 4 questions

  1. Q1. What does `git commit --fixup=<sha>` do, and how does autosquash use the resulting commit?

  2. 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.

  3. 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`.

  4. 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.