Interactive rebase is how you rewrite Git history to make it cleaner, more logical, and easier for others to understand.

Let’s see it in action. Imagine you have a branch with a few commits that you want to clean up before merging.

# Make sure you're on the branch you want to rebase
git checkout my-feature-branch

# Start an interactive rebase from the commit you want to start changing
# For example, to rebase the last 3 commits:
git rebase -i HEAD~3

This will open your default text editor with a list of commits. Each line represents a commit, and the command at the beginning (pick) tells Git what to do with it.

pick abc1234 First commit
pick def5678 Second commit
pick ghi9012 Third commit

# Rebase abc1234..ghi9012 onto abc1234 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#

The most common operations you’ll perform here are pick, reword, squash, and fixup.

  • pick: This is the default. It means "use this commit as is."
  • reword: Use this if you want to change the commit message of a specific commit. Git will stop at that commit and let you edit its message.
  • squash: This command merges a commit into the previous commit. Git will stop and prompt you to combine the commit messages of both commits into a new, single message.
  • fixup: Similar to squash, but it discards the commit message of the commit being "fixed up." The commit’s changes are applied, but its message is thrown away, and only the previous commit’s message is used. This is great for small, typo-fixing commits.

Let’s say you want to:

  1. Keep the first commit as is.
  2. Edit the message of the second commit.
  3. Combine the third commit into the second, keeping the second’s message.

You would change your git rebase -i HEAD~3 file to look like this:

pick abc1234 First commit
reword def5678 Second commit
fixup ghi9012 Third commit

Save and close the editor. Git will then execute these commands. For the reword command, it will stop and open another editor for you to change the commit message of def5678. After you save that, it will continue. For the fixup command, it will simply apply the changes from ghi9012 to the def5678 commit and discard ghi9012’s message.

You can also reorder lines to change the order of your commits, or drop lines entirely to remove commits.

When you’re done, your history will be rewritten. Remember that rewriting history on a branch that has already been pushed to a shared remote is generally a bad idea, as it can cause problems for collaborators. If you must do it, you’ll need to force-push (git push --force-with-lease).

The one thing most people don’t realize is that squash and fixup operate on the commit immediately preceding them in the rebase instruction list. So, if you have pick A, squash B, squash C, commit B will be squashed into A, and then commit C will be squashed into the newly combined A+B commit. The order is critical.

After a successful interactive rebase, you’ll have a cleaner, more linear history, ready for a merge or a pull request.

Want structured learning?

Take the full Git course →