Rewriting History with Interactive Rebase

Real work is messy: "WIP" commits, typo fixes, a forgotten file. Before sharing a branch, you can clean it up with git rebase -i — combine commits, reword messages, reorder them, or drop ones you don't want. The result is a history that's easy to review and understand.

The messy branch

$
git log --oneline
Output
0d5547f (HEAD -> main) Temporary debugging
92f1601 Show basket on home page
c134279 fix total, oops
2773a93 WIP
dbe9cea Add basket total function
271266e Create home page

Sam wants to: fold "WIP" and "fix total, oops" into the commit that added the function, drop the debugging commit, and reword the basket commit to be more precise.

Starting an interactive rebase

Tell Git how far back to go — here the last five commits, HEAD~5. Git opens your editor with a todo list, one line per commit, oldest first:

$
git rebase -i HEAD~5
TXT
pick dbe9cea Add basket total function
pick 2773a93 WIP
pick c134279 fix total, oops
pick 92f1601 Show basket on home page
pick 0d5547f Temporary debugging

Below the list, the file also contains comment lines (starting with #) summarising the available commands.

Each line starts with a command, followed by the commit's hash and message. Changing the commands and the order of the lines tells Git what to do:

CommandEffect
pick (p)Keep the commit as it is
reword (r)Keep the changes, but edit the message
edit (e)Stop at this commit so you can change it (amend, split…)
squash (s)Combine with the previous commit and edit the combined message
fixup (f)Combine with the previous commit, discarding this commit's message
drop (d), or delete the lineRemove the commit entirely
Move a lineReorder commits

Editing the plan

Sam changes the todo list to this, then saves and closes the editor:

TXT
pick dbe9cea Add basket total function
fixup 2773a93 WIP
fixup c134279 fix total, oops
reword 92f1601 Show basket on home page
drop 0d5547f Temporary debugging

Git then works through the list. For the reword line it opens the editor again with the old message; Sam changes it to "Show basket summary on home page":

$
git rebase -i HEAD~5
Output
[detached HEAD 7f0c0d3] Show basket summary on home page
 Date: Mon Mar 2 09:49:00 2026 +0000
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/main.
$
git log --oneline
ls
Output
7f0c0d3 (HEAD -> main) Show basket summary on home page
e9128a5 Add basket total function
271266e Create home page
basket.js  index.html

Three commits instead of six, each one complete and clearly described, and the debugging file is gone. The final code in basket.js is exactly what it was before — only the history changed.

Fixup commits and autosquash

When you notice a mistake in an earlier commit while you're still working, you can mark the fix for later. git commit --fixup=<commit> creates a commit whose message starts with fixup! followed by the target's message:

$
echo "function total(items) { return items.reduce((sum, i) => sum + i.price * i.qty, 0); }" > basket.js
git commit -a --fixup=HEAD~1
git log --oneline
Output
[main 9be0547] fixup! Add basket total function
 1 file changed, 1 insertion(+), 1 deletion(-)
9be0547 (HEAD -> main) fixup! Add basket total function
7f0c0d3 Show basket summary on home page
e9128a5 Add basket total function
271266e Create home page

Later, --autosquash moves each fixup commit next to its target in the todo list and marks it fixup automatically — you just save the list:

$
git rebase -i --autosquash HEAD~3
git log --oneline
Output
Successfully rebased and updated refs/heads/main.
a31396c (HEAD -> main) Show basket summary on home page
1c79c12 Add basket total function
271266e Create home page

Set git config --global rebase.autoSquash true to make this the default for every interactive rebase.

Splitting a commit

Mark a commit with edit. When the rebase stops there, undo the commit but keep its changes, then commit them in pieces:

$
git reset HEAD~1           # changes are now unstaged edits
git add basket.js
git commit -m "Add basket total function"
git add index.html
git commit -m "Show basket summary on home page"
git rebase --continue

Rules and safety

  • Same rule as any rebase: only rewrite commits that you haven't shared, or that are on your own feature branch.
  • If something goes wrong mid-way, git rebase --abort puts everything back.
  • If you notice a mistake afterwards, the reflog (lesson 20) still has the old commits: git reset --hard ORIG_HEAD right after the rebase, or find the entry with git reflog.
  • To edit the very first commit, use git rebase -i --root.

Try it yourself

Make five quick commits with sloppy messages. Use one interactive rebase to squash two of them, reword one, drop one and swap the order of the remaining two. Check the result with git log --oneline and git show.