Interactive Rebase: The Git Feature That Keeps Your History Clean

by Arif Ikhsanudin, Backend Developer

The Commit History Nobody Wants to Share

You've been working on a feature for three days. Your local branch has commits like this:

a3f9d24 add payment retry
b7c12e1 fix typo
f4d8a09 actually fix the thing
9e1b3c7 wip
c2a8f17 remove debug logging
1d5e3b9 add test
8f2a6c4 fix test
7b1d4e2 fix test again

This is perfectly normal development history. It's how code actually gets written. But sharing this as-is in a PR means your reviewers see eight commits when the logical work is probably two or three. The noise makes review harder and the history permanently noisier after merge.

Interactive rebase is the tool for turning the working history into the sharing history. You run it before opening the PR, never after merging to a shared branch.

How Interactive Rebase Works

# Rebase interactively from the point where your branch diverges from main
git rebase -i origin/main

# Or rebase the last N commits
git rebase -i HEAD~8

This opens your editor with a list of commits, oldest first, each preceded by an action keyword:

pick a3f9d24 add payment retry
pick b7c12e1 fix typo
pick f4d8a09 actually fix the thing
pick 9e1b3c7 wip
pick c2a8f17 remove debug logging
pick 1d5e3b9 add test
pick 8f2a6c4 fix test
pick 7b1d4e2 fix test again

# 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, meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# d, drop <commit> = remove commit

You edit this file to specify what to do with each commit, save, and Git executes the plan.

The Common Operations

Squash fixup commits into their parent:

Change the action from pick to squash (or s) for commits that should merge into the previous commit. Squash keeps the commit message (useful for merging two thoughtful messages). fixup (or f) merges the changes but discards the message:

pick a3f9d24 add payment retry
fixup b7c12e1 fix typo
fixup f4d8a09 actually fix the thing
pick 9e1b3c7 wip
fixup c2a8f17 remove debug logging
pick 1d5e3b9 add test
fixup 8f2a6c4 fix test
fixup 7b1d4e2 fix test again

After saving, Git executes three commits: the payment retry (with its fixups collapsed in), the WIP work (similarly collapsed), and the tests (with their fixups collapsed).

Reword a commit message:

reword a3f9d24 add payment retry

Git will pause at this commit and open your editor with the current message, letting you rewrite it.

Reorder commits:

Simply move lines up or down in the editor. Git replays commits in the order they appear. Be careful with reordering — commits that depend on each other must stay in order, or the replay will produce conflicts.

Drop a commit:

drop 9e1b3c7 wip

Removes the commit entirely. Its changes will not appear in subsequent commits. Use this for debugging commits, test code, or changes you've decided not to include.

Split a commit:

Change pick to edit. Git applies the commit but pauses for you to amend:

edit a3f9d24 add payment retry and fix unrelated bug

When Git pauses:

# Undo the commit but keep changes staged
git reset HEAD~1

# Selectively stage the first logical change
git add -p src/payment/
git commit -m "feat(payment): add retry with exponential backoff"

# Stage the second logical change
git add -p src/utils/
git commit -m "fix(utils): correct date formatting in log output"

# Continue the rebase
git rebase --continue

The Result After Cleanup

The eight-commit working history becomes:

c8d9a12 feat(payment): add retry with exponential backoff

Adds configurable retry logic to PaymentGatewayClient. Uses
exponential backoff starting at 1s, max 3 retries. Idempotency
key prevents duplicate charges on retry.

Refs: #1841

a1b2c34 test(payment): add retry behavior integration tests

Covers: successful retry after transient failure, max retry
exceeded, idempotency key prevents duplicate charge.

Two commits, both with clear messages, each independently understandable and revertable. This is what your PR reviewers see. This is what future developers see in git log.

When Not to Use Interactive Rebase

Interactive rebase rewrites history. The commits it produces have new SHAs. This is fine for local branches that haven't been pushed, or branches that only you work on.

Once a branch is pushed and others have based work on it, rebasing creates diverged histories. Force-pushing after a rebase on a shared branch is a coordination problem.

The rule: interactive rebase is for local cleanup before sharing. After the PR opens and reviewers have commented on specific commits, rebasing and force-pushing mid-review is disruptive (GitHub's "viewed" state for files resets on force push). Complete the rebase before requesting review.

The Workflow in Practice

# Finish feature work, run tests
# Then before opening PR:
git fetch origin
git rebase -i origin/main

# Clean up history: squash fixups, reword messages, drop debug commits
# Save and exit editor
# Resolve any conflicts during replay
# Push to remote (first push, or force-push on personal branch)
git push -u origin feature/payment-retry
# Open PR

This sequence makes the PR history a deliberate artifact rather than an accident of how the work happened. The best code reviewers will notice, and the codebase will be cleaner for it years later.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

The Difference Between a Mock, a Stub, and a Fake That Actually Matters

Mock, stub, and fake are used interchangeably in most codebases, but they describe different things with different tradeoffs. Knowing which to reach for — and when — determines whether your test doubles make tests clearer or more confusing.

Read more

How the JVM Manages Memory — Heap Regions, GC Algorithms, and What to Tune

JVM garbage collection is not magic — it follows predictable patterns that determine latency, throughput, and memory footprint. Understanding the model lets you tune effectively instead of guessing at flags.

Read more

The Most Dangerous Developer in a Company Is the One Nobody Can Replace

Every company has that one developer everyone depends on. At first it feels like a strength—until it quietly becomes your biggest risk.

Read more

The Real Cost of a Backend Hire in Stockholm in 2025 — And the Async Alternative

You budgeted SEK 65K a month for a backend engineer. The actual cost turned out to be closer to SEK 100K once you added everything the job listing didn't mention.

Read more