Skip to main content

Command Palette

Search for a command to run...

Git Rebase vs Merge: Stop Making History Ugly

Learn: Git Rebase vs Merge: Stop Making History Ugly

Updated
7 min readView as Markdown
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

Git Rebase vs Merge: Stop Making History Ugly

The Hook: Why Your Git History Looks Like a Toddler's Spaghetti Dinner

You open your project's git log and see this:

*   Merge branch 'feature/auth' into develop
|\
| * Fix typo in login form
| * Add password validation
| * Merge branch 'develop' into feature/auth
| |\
| | * Update dependencies
| | * Fix database connection
| * | Refactor auth service
|/ /
* | Add user model
|/
*   Merge branch 'feature/payments' into develop

Your team lead asks, "What actually happened here?" You stare blankly. Nobody knows. This is the merge commit nightmare—a tangled web of parallel universes where the actual story of your code is buried under layers of "Merge branch X into Y" commits.

There's a better way. And it's called rebase.


The Story: Two Philosophies, One Goal

The Merge Philosophy: "Preserve Everything"

When you merge, Git creates a merge commit—a special commit with two parents. It says: "Here's what was on main, here's what was on my branch, and here's how I combined them."

Pros:

  • Complete historical record
  • Shows exactly when branches converged
  • Safe for shared branches
  • Easier to understand "what happened when"

Cons:

  • Cluttered history
  • Hard to follow the actual development story
  • Merge commits add noise
  • Difficult to bisect (finding which commit broke things)

The Rebase Philosophy: "Rewrite History for Clarity"

When you rebase, Git replays your commits on top of the target branch. It's like saying: "Pretend my work happened after all the recent changes on main."

Pros:

  • Linear, clean history
  • Easy to follow the story
  • Simpler to bisect
  • Beautiful git log
  • Easier to understand what changed and why

Cons:

  • Rewrites history (scary if you don't understand it)
  • Can't use on shared branches (breaks other people's work)
  • Requires discipline and understanding
  • Merge conflicts can be tedious to resolve

The Code: Let's See It In Action

Scenario: You're working on a feature branch

# Your branch history
feature/dark-mode
├── Add dark mode toggle
├── Style dark theme colors
└── Fix contrast issues

# Meanwhile, main got updates
main
├── Update dependencies
├── Fix critical bug
└── Add new API endpoint

Option 1: The Merge Approach

git checkout main
git pull origin main
git checkout feature/dark-mode
git merge main

Result:

*   Merge branch 'main' into feature/dark-mode
|\
| * Add new API endpoint
| * Fix critical bug
| * Update dependencies
* | Fix contrast issues
* | Style dark theme colors
* | Add dark mode toggle
|/
* Previous commit

The history is messy. You have to trace through the merge commit to understand what happened.

Option 2: The Rebase Approach

git checkout feature/dark-mode
git rebase main

Result:

* Fix contrast issues
* Style dark theme colors
* Add dark mode toggle
* Add new API endpoint
* Fix critical bug
* Update dependencies
* Previous commit

Clean. Linear. You can read it like a book: "First we updated dependencies, fixed a bug, added an endpoint, then implemented dark mode."

Handling Conflicts During Rebase

# Start the rebase
git rebase main

# Git stops if there are conflicts
# CONFLICT (content): Merge conflict in src/theme.css

# Fix the conflicts in your editor
# Then continue
git add .
git rebase --continue

# If you mess up, abort and start over
git rebase --abort

Interactive Rebase: The Power Move

Want to clean up your commits before rebasing?

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

This opens an editor:

pick a1b2c3d Add dark mode toggle
pick d4e5f6g Style dark theme colors
pick h7i8j9k Fix contrast issues

# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the message
# s, squash = use commit, but meld into previous
# f, fixup = like squash, but discard log message

Squash those fixup commits:

pick a1b2c3d Add dark mode toggle
squash d4e5f6g Style dark theme colors
squash h7i8j9k Fix contrast issues

Result: One beautiful commit with all your dark mode work.


The Tips: Git Rebase Mastery

1. The Golden Rule: Never Rebase Public Branches

# ❌ DON'T DO THIS
git checkout main
git rebase develop  # DANGER! Others are using main

# ✅ DO THIS
git checkout feature/my-work
git rebase main  # Rebase YOUR branch onto main

Why? Rebase rewrites history. If others have pulled main, they'll have conflicting histories. Chaos ensues.

2. Rebase Before Pushing

# Make commits on your feature branch
git commit -m "Add feature"
git commit -m "Fix typo"

# Before pushing, clean it up
git rebase -i origin/main

# Now push
git push origin feature/my-work

3. Use --force-with-lease When Pushing After Rebase

# After rebasing, you need to force push
# But use --force-with-lease to be safe
git push origin feature/my-work --force-with-lease

# NOT this (dangerous)
git push origin feature/my-work --force

--force-with-lease checks that nobody else pushed to your branch. --force just obliterates everything.

4. Combine Rebase and Merge for Best Results

# On your feature branch
git rebase main          # Get latest changes, keep history clean

# Back on main
git checkout main
git pull origin main
git merge --ff-only feature/my-work  # Fast-forward merge (no merge commit)
git push origin main

This gives you:

  • Clean history (rebase)
  • No unnecessary merge commits (fast-forward)
  • Linear progression

5. Understand git reflog for Disaster Recovery

Rebased something and realized you messed up?

# See your recent actions
git reflog

# Output:
# a1b2c3d HEAD@{0}: rebase: Fix contrast issues
# d4e5f6g HEAD@{1}: rebase: Style dark theme colors
# h7i8j9k HEAD@{2}: checkout: moving to feature/dark-mode

# Go back to before the rebase
git reset --hard HEAD@{2}

6. Configure Git for Rebase by Default

# Make pull use rebase instead of merge
git config --global pull.rebase true

# Make rebase interactive by default
git config --global rebase.autoStash true

7. The Workflow: Feature Branch Best Practices

# 1. Create feature branch from main
git checkout main
git pull origin main
git checkout -b feature/awesome-thing

# 2. Make commits (messy is fine)
git commit -m "WIP: add feature"
git commit -m "Fix bug"
git commit -m "Oops, typo"

# 3. Before pushing, clean up
git rebase -i origin/main

# 4. Push to remote
git push origin feature/awesome-thing

# 5. Create pull request
# (GitHub/GitLab/Bitbucket handles this)

# 6. After PR approval, merge to main
git checkout main
git pull origin main
git merge --ff-only feature/awesome-thing
git push origin main

# 7. Delete feature branch
git branch -d feature/awesome-thing
git push origin --delete feature/awesome-thing

8. When to Use Merge (Yes, Sometimes!)

# Merging a long-lived branch (develop → main)
git checkout main
git merge develop

# Merging a release branch
git checkout main
git merge release/v1.2.0

# Merging hotfixes
git checkout main
git merge hotfix/critical-bug

These are integration points—you want to see that a branch was merged. The merge commit documents the event.


The Payoff: Your Git Log Will Thank You

Before (Merge Hell):

*   Merge pull request #247 from team/feature-x
|\
| *   Merge branch 'main' into feature-x
| |\
| | * Update dependencies
| * | Fix typo
| * | Add feature
* | | Merge pull request #246 from team/feature-y
|\ \ \
| | |/
| |/|
| * | Add another feature

After (Rebase Heaven):

* Add feature X
* Add feature Y
* Fix typo
* Update dependencies
* Previous stable version

You can actually read your history. Future you (and your team) will be grateful.


Final Thoughts

Use rebase for:

  • Feature branches before merging
  • Keeping your local history clean
  • Interactive commits (squashing, rewording)
  • Linear, readable project history

Use merge for:

  • Integrating long-lived branches
  • Shared/public branches
  • When you want to preserve the exact history
  • Release and hotfix branches

The real secret? Understand both, use them intentionally, and your git log will be a beautiful story instead of a crime scene.

Now go forth and stop making history ugly. 🚀