Skip to content

The Art of Git Rebase: A Cleaner History for a Cleaner Mind

How I stopped fearing rebase and learned to love a linear commit history.


Why I Switched From Merge to Rebase

I used to merge everything. Every feature branch ended with a merge commit that said nothing useful — just "Merge branch 'feature-x' into main." My git log looked like a plate of spaghetti. One day, I had to track down a regression across 200 commits. The merge bubbles made git bisect nearly useless. That was the day I committed to rebase.

Rebase rewrites your branch's commits on top of the target branch. Instead of creating a merge commit, it replays your changes as if you started from the latest state. The result is a straight line of commits, each telling a clear story.

The Basic Rebase Workflow

My daily workflow is simple. I work on a feature branch, and before pushing or creating a pull request, I rebase onto the latest main:

# Fetch latest changes
git fetch origin

# Rebase my branch onto origin/main
git rebase origin/main

# If conflicts arise, resolve them file by file
git add <resolved-file>
git rebase --continue

# Force push (because history was rewritten)
git push --force-with-lease

The key insight: --force-with-lease is safer than --force. It refuses to push if someone else pushed to your branch since your last fetch. This has saved me from overwriting a colleague's work more than once.

Interactive Rebase: The Power Tool

Interactive rebase (git rebase -i) is where the real magic happens. Before pushing, I clean up my commit history:

# Rebase the last 5 commits interactively
git rebase -i HEAD~5

This opens an editor with a list of commits and actions:

pick a1b2c3d Add user validation
squash e4f5g6h Fix typo in validation
pick h7i8j9k Add user endpoint
fixup l0m1n2o Remove debug logging
reword p3q4r5s Add integration tests

The actions I use most: - squash: combine with previous commit, merge commit messages - fixup: combine with previous commit, discard this message - reword: change the commit message - drop: remove the commit entirely

I squash "fix typo" and "remove debug" commits into their parent. The result is a clean series of commits where each one represents a complete, logical unit of work.

Handling Rebase Conflicts Like a Professional

Conflicts during rebase used to terrify me. Now I treat them as opportunities to understand the codebase better. Here's my approach:

  1. Don't panic. git rebase --abort always gets you back to safety.
  2. Resolve one commit at a time. Unlike merge conflicts where everything hits at once, rebase shows conflicts commit by commit.
  3. Use the context. Since you're replaying commits in order, you know exactly what each commit was trying to do.
# When a conflict occurs during rebase:
# 1. Check which files conflict
git status

# 2. Open each conflicted file, resolve it
# 3. Stage the resolution
git add .

# 4. Continue the rebase
git rebase --continue

When the same file conflicts on multiple commits, I sometimes use git rerere (reuse recorded resolution). It remembers how I resolved a conflict and automatically applies the same resolution next time:

# Enable rerere globally
git config --global rerere.enabled true

The Golden Rule: Never Rebase Public History

There is one rule I never break: never rebase commits that others have based work on. If a branch is shared and someone else has commits on top of yours, rebasing rewrites the commits they depend on. Their branches will diverge, and chaos ensues.

My rule of thumb: - My feature branch, not yet reviewed → rebase freely - My feature branch, under review → rebase only if I coordinate with reviewers - Shared branch or main → never rebase, merge only

Rebase vs. Merge: When I Use Each

After years of practice, here's my decision matrix:

  • Rebase: updating my feature branch with latest main, cleaning up local commits before push, making history linear for easier review.
  • Merge: integrating a completed feature into main (via pull request), preserving the exact history of a long-lived release branch, when the team agrees on merge-based workflow.

The combination works beautifully: rebase to keep my branch current and clean, then merge (usually via PR) to record the integration point.

Key Takeaway

Rebase is not about rewriting history for vanity. It's about communication. A clean, linear history tells future developers — including future you — exactly what happened and why. Every commit becomes a self-contained unit that can be understood, reverted, or cherry-picked independently. That's not just clean — it's professional.


Tags: git, rebase, workflow, version-control, collaboration

Reference