Tidy up commit history with interactive rebase: reword to change messages, squash and fixup to combine commits, drop to delete, plus the Golden Rule of Rebase that forbids rewriting shared public branches.

In the previous episode 15 we tidied the working directory: storing unfinished work with git stash and cleaning untracked files with git clean. In episode 16 we go up one level and tidy something far more valuable: the commit history itself, through Interactive Rebase.
Why does this matter? Commit history is documentation read by the entire team. A messy history — fix typo, wip, test again, asdf — makes code review, debugging, and regression tracking painful. Interactive rebase gives you full control to reshape history before code goes to the remote, exactly like editing a draft before it is published.
We touched basic rebase in episode 7 when comparing merge vs rebase. In essence: rebase reapplies commits on top of a new base so history becomes linear. Because commits are reapplied, every commit produces a new SHA — a rebased history is never identical to the original. Interactive mode just adds one feature: you can intervene in the middle of the process. Make sure you are using a recent Git with git --version (stable version 2.4x in 2026).
git rebase -i HEAD~3The command above opens an editor containing the last three commits, ordered from the oldest at the top line to the newest at the bottom:
pick a1b2c3d feat: tambah model User
pick e4f5g6h fix: perbaiki validasi email
pick i7j8k9l chore: rapikan dokumentasiThis is called the todo list. You are free to rearrange the lines, then save — Git executes the commands one by one from top to bottom.
| Command | Abbreviation | Function |
|---|---|---|
pick | p | Use the commit as is |
reword | r | Use the commit and change its message |
squash | s | Combine the commit with the one above it, messages merged |
fixup | f | Combine the commit with the one above it, message discarded |
drop | d | Delete the commit from history |
edit | e | Stop at that commit to change its contents |
Change pick to reword (or r) on the line of the commit whose message you want to change. After the todo is saved, a new editor opens to type the final message. Result: the commit content stays, the SHA and message change.
Three small commits that should have been one can be unified:
pick a1b2c3d feat: tambah model User
squash e4f5g6h fix: perbaiki validasi email
squash i7j8k9l chore: rapikan dokumentasiAfter the todo is saved, Git opens a second editor to compose the combined message. Save, and the three commits become one:
git log --onelineThe order in the todo list matters a lot: squash always attaches to the commit on the line above it (the older one). So arrange the lines in chronological order, not a random order.
fixup is identical to squash, but the message of the fixup commit is discarded without showing the message-combining editor:
pick a1b2c3d feat: tambah model User
fixup e4f5g6h typo kecilThe end result is a single clean commit feat: tambah model User — ideal for small corrections that do not deserve their own commit.
Change to drop (or d) to delete a commit from history. This is a cleaner option than leaving a wrong commit on a shared branch.
If you realize something is wrong mid-session, or you simply want to back out:
git rebase --abortThe session is cancelled and history returns to exactly what it was before the rebase began.
There is one non-negotiable rule:
Never rebase a branch that has been published and is shared with other people.
Rebase rewrites commit SHAs. If main on the remote is used by many people, rewriting its history makes everyone's repository out of sync — like changing a house address already printed on thousands of business cards.
Warning
Rebase may only touch local or private history: your own feature branch or commits that were never pushed. To fix commits that have already been pushed, use git revert from episode 14 — it creates a new reversal commit and keeps the audit trail intact.
A feature branch that was pushed and then rebased will be rejected by the remote because the history differs. The solution is a safe force push:
git push --force-with-lease origin nama-branch--force-with-lease checks the remote state before overwriting: if someone else has added commits to that branch, the push is rejected. Far safer than a bare git push --force.
Caution
Never use git push --force without --force-with-lease on a shared branch. Overwriting remote history can destroy your teammates' commits and is extremely hard to recover. This discipline is part of the Golden Rule of Rebase.
The points to take with you:
git rebase -i HEAD~N opens a todo list to tidy up the last N commits.reword changes a message, squash combines commits along with their messages, fixup combines without messages, and drop deletes a commit.squash and fixup attach to the commit on the line above.--force-with-lease, and only for your own history.Now that you can reshape history, you need tools to navigate it intelligently. In the next episode 17 we cover Advanced History Navigation: taking a specific commit from another branch with git cherry-pick, recovering deleted commits through git reflog, and finding the commit that caused a bug with git bisect. See you in episode 17!