Medium DevOps

Git Interactive Rebase: Clean Up Your Commit History Before a PR

Learn how to use git rebase -i to squash, reorder, and edit commits so your pull requests tell a clean, reviewable story.

23 Jul, 2026 4 min 12 Views 7 Code blocks
Diagram
graph TD A[Start: git rebase -i HEAD~5] --> B[Git opens commit list in editor] B --> C{You edit each line} C -->|pick| D[Commit kept as-is] C -->|reword| E[Edit message only] C -->|squash/fixup| F[Merge into previous commit] C -->|drop| G[Commit removed] D --> H[Git replays commits onto new base] E --> H F --> H G --> H H --> I[New commit history with new SHAs] I --> J[Force-push to update remote branch]

Every developer who's shipped a "WIP", "fix typo", "actually fix it", "oops" string of commits knows the pain of opening a pull request and seeing a commit history that tells no story at all. Interactive rebase is the tool that turns that mess into a clean, linear narrative your reviewers can actually follow.

Why Clean History Matters

A pull request's commit history isn't just a log — it's documentation. Future developers (including you, in six months) will use git blame and git log to understand why a change was made. A history full of noise makes that archaeology painful.

A clean commit history is a gift to your future self and your reviewers.

The Basic Command

git rebase -i HEAD~5

This opens your last 5 commits in an editor, each prefixed with pick. From here you can reorder lines, or swap pick for one of several rebase commands.

The Rebase Commands

Command Shorthand Effect
pick p Keep the commit as-is
reword r Keep changes, edit the commit message
edit e Pause here to amend the commit
squash s Merge into the previous commit, combine messages
fixup f Merge into the previous commit, discard this message
drop d Remove the commit entirely

How It Works Under the Hood

Rebase doesn't move commits — it replays them. Git walks your branch's commits one at a time, applying each as a patch on top of the new base, and builds brand-new commit objects (with new SHAs) along the way.

graph TD A[Start: git rebase -i HEAD~5] --> B[Git opens commit list in editor] B --> C{You edit each line} C -->|pick| D[Commit kept as-is] C -->|reword| E[Edit message only] C -->|squash/fixup| F[Merge into previous commit] C -->|drop| G[Commit removed] D --> H[Git replays commits onto new base] E --> H F --> H G --> H H --> I[New commit history with new SHAs] I --> J[Force-push to update remote branch]

A Real Example

Say your branch has this history before opening a PR:

a1b2c3d WIP
e4f5g6h fix typo
h7i8j9k actually add the feature
k0l1m2n oops forgot a file

Run git rebase -i HEAD~4, then change the plan to:

pick h7i8j9k actually add the feature
fixup k0l1m2n oops forgot a file
fixup a1b2c3d WIP
fixup e4f5g6h fix typo

Save and exit — Git squashes all four into a single, well-described commit.

Editing a Commit Message Mid-Rebase

Mark a commit reword, and after saving the plan, Git will stop and open your editor just for that commit's message:

git rebase -i HEAD~3
# change 'pick' to 'reword' on the line you want to rename
# Git opens your editor — update the message, save, close
# rebase continues automatically

Splitting or Amending a Commit

Mark a commit edit to pause the rebase right after it's applied:

git rebase -i HEAD~3
# mark the target commit as 'edit'

# rebase pauses here — make your changes
git add <file>
git commit --amend

# then continue
git rebase --continue

Handling Conflicts

Since rebase replays commits one by one, conflicts can surface at any step:

# fix the conflicting files
git add <resolved-files>
git rebase --continue

# or bail out entirely and go back to where you started
git rebase --abort

Pushing Rebased History

Because rebase rewrites commit SHAs, a normal git push will be rejected. You need a force push — but use the safer variant:

git push --force-with-lease

--force-with-lease refuses to overwrite the remote branch if someone else has pushed to it since your last fetch, protecting teammates from losing work — unlike a plain --force.

When to Use Which

Use squash when a commit's changes belong with the previous one but the message itself still adds context worth keeping. Use fixup for pure "oops" commits where the message adds nothing. Use reword when the code is fine but the message is misleading or too terse. Use drop for commits that turned out to be dead ends, like an experiment you abandoned.

Common Pitfalls

Never rebase commits that other people have already pulled and built on top of — you'll rewrite history out from under them and create painful merge conflicts on their end. Rebase is for local or PR-branch cleanup before merge, not for branches already shared and built upon. Also double-check your rebase plan before saving; a misplaced drop silently deletes a commit's changes, though git reflog can usually recover it if you catch the mistake quickly.

Interactive rebase rewrites history — that's exactly why it's powerful, and exactly why it should stay confined to branches only you (or your PR) are working on.

More from DevOps