Git interactive rebase: clean up your branch before merging
← Back
April 4, 2026Git7 min read

Git interactive rebase: clean up your branch before merging

Published April 4, 20267 min read

A branch with forty commits like "WIP", "fix typo", "fix fix", "actually fixed", and "ok now it works" is technically correct code shipped in an unreadable way. Interactive rebase turns that history into a clean narrative that your reviewers will thank you for. Here is the complete workflow — squashing, reordering, splitting, and editing messages.

The interactive rebase command

bash
# Interactively rebase the last N commits
git rebase -i HEAD~8

# Or rebase everything since branching from main
git rebase -i main

# Or specify a specific commit as the base
git rebase -i abc1234^

This opens your editor with a list of commits and commands:

text
pick a1b2c3 feat: add user profile endpoint
pick d4e5f6 WIP: still working on validation
pick 789abc fix validation bug
pick def012 fix typo in error message
pick 345678 add tests
pick 9abcde refactor: extract validation helper
pick 012345 WIP
pick 678901 finally done

# 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, meld into previous commit
# f, fixup = like squash, but discard this commit's log message
# d, drop = remove commit

Squashing WIP commits

The most common operation: squash all the "WIP" and fix commits into their parent feature commit:

text
pick a1b2c3 feat: add user profile endpoint
f d4e5f6 WIP: still working on validation
f 789abc fix validation bug
f def012 fix typo in error message
pick 345678 test: add profile endpoint tests
f 9abcde refactor: extract validation helper
f 012345 WIP
f 678901 finally done

Using f (fixup) instead of s (squash) discards the WIP commit messages automatically. Save and close — git produces two clean commits: the feature commit and the test commit.

Reordering commits

You committed the tests before the feature (reverse order). Just move the lines:

text
pick a1b2c3 feat: add user profile endpoint
pick 345678 test: add profile endpoint tests
# (tests were originally earlier — just moved the lines)

Rewriting a commit message

text
r a1b2c3 feat: add user profile endpoint

Change pick to r (reword). Git pauses at that commit and opens the editor for just the message. Edit, save, and git continues.

Splitting a commit that did too much

A commit that touches unrelated files should be split. Use e (edit):

text
e a1b2c3 feat: add profile endpoint and fix unrelated login bug

Git pauses at this commit. The files are staged. Un-stage everything and re-commit in two parts:

bash
# Unstage everything
git reset HEAD^

# Stage and commit just the profile endpoint changes
git add src/api/profile.ts src/api/profile.test.ts
git commit -m "feat: add user profile endpoint"

# Stage and commit the login fix separately
git add src/auth/login.ts
git commit -m "fix: correct login redirect URL"

# Continue the rebase
git rebase --continue

Dropping commits you do not want

text
pick a1b2c3 feat: add user profile endpoint
d def012 debug: temporarily log request body  ← drop this
pick 345678 test: add profile endpoint tests

The --autosquash workflow

If you commit with --fixup or --squash flags during development, --autosquash sorts them automatically:

bash
# While working, mark commits as fixups for a specific earlier commit
git commit --fixup=a1b2c3   # "fixup! feat: add user profile endpoint"

# When ready to clean up, autosquash handles the ordering automatically
git rebase -i --autosquash main

Git automatically marks the fixup commits with f and places them after their target. You just review and save.

Recovering from a botched rebase

bash
# If something goes wrong during rebase, abort it
git rebase --abort

# If you finished but it looks wrong, use reflog to get back
git reflog
git reset --hard HEAD@{N}  # N = index of state before rebase started

Before pushing a cleaned-up branch

After interactive rebase, the branch history has changed. You will need to force-push:

bash
# Force push with lease — fails if someone else pushed to the branch
git push --force-with-lease origin feature/my-branch

Always use --force-with-lease instead of --force. The lease checks that the remote is in the state you expect before overwriting it, preventing you from overwriting someone else's work on a shared branch.

A clean branch history is not aesthetic — it makes code review faster, makes git blame useful, and makes git bisect work properly. It is worth the five minutes before every PR.

Share this
← All Posts7 min read