Build the mental model
Interactive rebase, started with git rebase -i, lets you rewrite a range of recent commits before anyone else sees them. It opens a small script listing every commit in the range, one per line, and lets you choose what happens to each: pick keeps it unchanged, reword edits its message, squash merges it into the commit above with a combined message, fixup does the same but discards the small commit's message, and reordering lines changes replay order.
The most common real-world use is cleaning up work-in-progress commits before opening a pull request. Nobody wants fix, fix again, and typo as three separate lines in a PR; reviewers want one commit with a clear message, like Add checkout validation. Squashing turns an honest but messy debugging record into a single presentable unit that's easy to review, revert as a whole, and understand later in git log or git blame.
Same shared-history caution applies
Interactive rebase rewrites commits exactly like a normal rebase, creating new hashes for everything it touches. Only squash or reword commits that exist solely on your own local, unpushed branch — the moment a commit is pushed and someone might have built on it, rewriting it becomes a coordination problem.
INTERACTIVE REBASE: SQUASHING MESSY COMMITS
-------------------------------------------
BEFORE (3 messy commits on top of the initial commit)
init---fix---fix again---typo
AFTER (git rebase -i HEAD~3, squash all three)
init---Add checkout validation
Three separate hashes become one new hash; the messy
intermediate commits no longer exist in history.Connect it to a real scenario
This example creates three deliberately messy commits on top of an initial one, fix, fix again, and typo, each adjusting the same checkout.js file. git log --oneline before cleanup shows all four commits — the honest but noisy record of how the work actually happened.
Scriptable squash
git reset --soft HEAD~3 followed by one git commit -m is the fully scriptable equivalent of an interactive squash, producing a deterministic result.
The everyday way
Normally you'd run git rebase -i HEAD~3, mark the first commit pick and the other two squash in the editor, then write the combined message — same end result, different interface.
Result
git log --oneline afterward shows just two commits: the original initial commit and one new commit, Add checkout validation.
Try the working example
export GIT_AUTHOR_NAME="Thuta Learner"
export GIT_AUTHOR_EMAIL="learner@example.com"
export GIT_COMMITTER_NAME="Thuta Learner"
export GIT_COMMITTER_EMAIL="learner@example.com"
git init
export GIT_AUTHOR_DATE="2026-01-01T09:00:00"
export GIT_COMMITTER_DATE="2026-01-01T09:00:00"
echo "function checkout() {}" > checkout.js
git add checkout.js
git commit -m "Initial commit"
export GIT_AUTHOR_DATE="2026-01-01T10:00:00"
export GIT_COMMITTER_DATE="2026-01-01T10:00:00"
echo "function checkout(cart) { return cart; }" > checkout.js
git add checkout.js
git commit -m "fix"
export GIT_AUTHOR_DATE="2026-01-01T10:05:00"
export GIT_COMMITTER_DATE="2026-01-01T10:05:00"
echo "function checkout(cart) { if (!cart) return null; return cart; }" > checkout.js
git add checkout.js
git commit -m "fix again"
export GIT_AUTHOR_DATE="2026-01-01T10:10:00"
export GIT_COMMITTER_DATE="2026-01-01T10:10:00"
echo "function checkout(cart) { if (!cart) return null; return cart.total; }" > checkout.js
git add checkout.js
git commit -m "typo"
git log --oneline
# Scriptable equivalent of: git rebase -i HEAD~3 (pick, squash, squash)
export GIT_AUTHOR_DATE="2026-01-01T10:15:00"
export GIT_COMMITTER_DATE="2026-01-01T10:15:00"
git reset --soft HEAD~3
git commit -m "Add checkout validation"
git log --onelineBefore the squash, git log --oneline shows:
e2042af typo
7fe5cc1 fix again
1cc7705 fix
53f2738 Initial commit
After git reset --soft HEAD~3 and one git commit -m "Add checkout validation", the same command shows:
dd1d8cc Add checkout validation
53f2738 Initial commit
The three messy commits are gone entirely, replaced by one new commit with its own hash.5-minute try-it
Make four or five small commits with intentionally messy messages on a test repo, then squash the last three into one using git reset --soft HEAD~3 followed by a single commit. Then try the real interactive version: git rebase -i HEAD~3, mark commits squash in the editor, and compare the result.
One important caution
Squashing commits that have already been pushed and pulled by teammates rewrites shared history and forces everyone else to reconcile diverging branches.
git reset --soft HEAD~N silently drops any commits beyond N if you miscount — always check git log first to confirm exactly which commits you're about to squash.
Git - git-rebase Documentation (interactive mode) — Git & GitHub