Rewrite Git commit history cleanly with interactive rebase

A clean Git history makes code review faster, bisecting bugs easier, and changelogs more meaningful. When you work on a feature branch you naturally accumulate messy commits — “WIP”, “fix typo”, “oops” — that have no value after the feature is complete. Interactive rebase (git rebase -i) lets you rewrite those commits before merging: squash multiple commits into one, edit a commit message, reorder commits, or split a large commit into focused atomic changes. This is one of the most powerful — and most feared — Git features, but once you understand the mechanics it becomes a routine part of every pull request workflow. The golden rule is to never rebase commits that have already been pushed to a shared branch, because rebasing rewrites history and will force everyone else to reconcile diverging timelines. On your own feature branch, however, interactive rebase is safe and should be used freely before opening a PR. This article covers the most common use cases: squashing WIP commits, fixing the most recent commit message without a full rebase, and combining a set of commits while writing a single comprehensive commit message. The examples use a simple WordPress plugin development scenario to keep the context familiar. Pair this knowledge with the Gitflow branching guide and the git stash guide for a complete feature branch workflow.

Problem: You have accumulated several messy WIP commits on a feature branch and need to squash them into one clean, descriptive commit before opening a pull request.

Solution: Run git rebase -i to open the interactive editor, mark commits as squash or fixup, and force-push the cleaned branch with --force-with-lease:

# Situation: you are on feature/my-plugin-settings with 5 messy commits
git log --oneline -5
# abc1234 oops fix nonce check
# def5678 WIP: settings page works
# ghi9012 Add settings page scaffold
# jkl3456 Add plugin boilerplate
# mno7890 Initial commit (main)

# Start interactive rebase for the last 4 commits (stop before Initial commit)
git rebase -i HEAD~4

# The editor opens with a pick list — change picks:
# pick  jkl3456  Add plugin boilerplate
# squash ghi9012 Add settings page scaffold
# squash def5678 WIP: settings page works
# squash abc1234 oops fix nonce check
# Save and close → Git opens another editor to combine commit messages

# Write a single clean message:
# Add plugin settings page with nonce-verified form

# --- common interactive rebase commands ---
# pick   p  — use commit as-is
# reword r  — use commit but edit the message
# squash s  — meld into previous commit (combine messages)
# fixup  f  — meld into previous commit (discard this message)
# drop   d  — remove the commit entirely

# Edit only the last commit message (no rebase needed)
git commit --amend -m "Add plugin settings page with nonce-verified form"

# After a successful squash, force-push your feature branch
# (safe because no one else is on this branch)
git push origin feature/my-plugin-settings --force-with-lease

NOTE: Always use --force-with-lease instead of --force when pushing a rebased branch. --force-with-lease refuses to push if the remote branch has been updated since you last fetched, protecting you from accidentally overwriting a teammate’s push. If the rebase goes wrong at any point you can abort it with git rebase --abort, which restores the branch to its state before the rebase started. Review the Git hooks guide to automate quality checks that run before a rebase commit is accepted.