Back to blog

Merge, rebase and squash

Three ways to combine branches. Each one rewrites history differently. Pick the wrong one and you'll spend an hour untangling commits.

March 26, 202612 min read
GitMergeRebaseSquashVersion Control
Merge, rebase and squash
Merge, rebase and squash - Three ways to combine branches
Merge, rebase and squash - Three ways to combine branches

Three ways to combine branches. Each one rewrites history differently.

Pick the wrong one and you'll spend an hour untangling commits. Pick the right one and your git log stays clean.


The setup

You have two branches: main and feature. They've diverged.

Main has moved forward. Feature has its own commits. Now you want to bring them together.

Three options.


Merge

The simplest. Git creates a new commit that has two parents.

git switch main
git merge feature

The merge commit ties both histories together. Nothing is rewritten. All original commits stay exactly where they are.

Pros:

  • Safe. No history rewriting.
  • Clear record of when branches merged.
  • Works even if others are working on the same branch.

Cons:

  • Creates extra merge commits.
  • History can look messy with lots of branches.

TIP

Use merge when working on shared branches or when you want to preserve the exact history of how work happened.


Rebase

Rebase takes your commits and replays them on top of another branch. Like you never branched in the first place.

git switch feature
git rebase main

Before:

After:

Commits 5 and 6 become 5' and 6'. New hashes, new parents, but same changes.

Pros:

  • Linear history. No merge commits.
  • Easier to read git log.
  • Cleaner for code review.

Cons:

  • Rewrites history. Original commits are gone.
  • Dangerous on shared branches.
  • Can create conflicts multiple times (once per commit).

WARNING

Never rebase commits that others have already pulled. You'll create duplicate commits and confuse everyone.

🔍 What "replaying" actually means

When you rebase, Git:

  1. Finds the common ancestor of both branches
  2. Saves all your commits as patches (diffs)
  3. Resets your branch to the target branch
  4. Applies each patch one by one

That's why conflicts can happen multiple times. Each patch is applied separately. And that's why the commits get new hashes, they have different parents now.


Squash

Squash isn't a separate command. It's an option during merge or rebase.

It takes multiple commits and combines them into one.

Squash merge

git switch main
git merge --squash feature
git commit -m "Add login feature"

All the changes from feature branch become a single commit on main. The individual commits (5, 6) disappear from main's history.

Interactive rebase with squash

git rebase -i HEAD~3

This opens your editor:

pick a1b2c3d Add login form
pick b2c3d4e Add validation
pick c3d4e5f Fix typo

Change to:

pick a1b2c3d Add login form
squash b2c3d4e Add validation
squash c3d4e5f Fix typo

The three commits become one. You'll be asked for a new commit message.

TIP

Use fixup instead of squash if you want to discard the commit messages entirely and just keep the first one.


When to use what

SituationUse
Merging a shared branch (develop → main)Merge
Updating feature branch with main changesRebase
Cleaning up messy commits before PRSquash (interactive rebase)
Merging a feature with many small commitsSquash merge
Working with others on the same branchMerge only
Solo work, clean history mattersRebase

The golden rule

"Don't rebase public history."

If commits exist only on your machine, rebase all you want. The moment you push and someone else might have pulled, stop. Use merge.

# Safe: rebase before first push
git rebase main

# Dangerous: rebase after push
git push
# ... time passes, others pull ...
git rebase main  # DON'T

Handling conflicts

All three methods can create conflicts. The resolution is the same:

  1. Git stops and tells you which files conflict
  2. Open the files, look for conflict markers:
<<<<<<< HEAD
your code
=======
their code
>>>>>>> feature
  1. Edit the file to keep what you want
  2. Mark as resolved:
# For merge
git add <file>
git commit

# For rebase
git add <file>
git rebase --continue

NOTE

During rebase, you might resolve the same conflict multiple times (once per commit being replayed). That's normal but annoying. Use git rerere to automate repeated resolutions.

🔍 Setting up rerere
git config --global rerere.enabled true

Git will remember how you resolved conflicts and automatically apply the same resolution next time.


Abort if things go wrong

Messed up? Bail out.

# Abort a merge in progress
git merge --abort

# Abort a rebase in progress
git rebase --abort

You'll be back to where you started.


Cheat sheet

CommandWhat it does
git merge featureMerge with merge commit
git merge --squash featureSquash all commits into one
git rebase mainReplay commits on top of main
git rebase -i HEAD~nInteractive rebase last n commits
git merge --abortCancel merge
git rebase --abortCancel rebase
git rebase --continueContinue after resolving conflict

The takeaway

  • Merge = safe, preserves history, creates merge commits
  • Rebase = clean history, rewrites commits, dangerous on shared branches
  • Squash = combine multiple commits into one

There's no universally correct answer. Teams pick conventions and stick with them. What matters is everyone agrees.

Next article: resolving conflicts in detail. What those markers mean, strategies for complex conflicts, and tools that make it easier.


This is part 3 of my "Git & GitHub in 10 articles" series.

Questions? Reach out on LinkedIn or check out my blog.