
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:
- Finds the common ancestor of both branches
- Saves all your commits as patches (diffs)
- Resets your branch to the target branch
- 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
| Situation | Use |
|---|---|
| Merging a shared branch (develop → main) | Merge |
| Updating feature branch with main changes | Rebase |
| Cleaning up messy commits before PR | Squash (interactive rebase) |
| Merging a feature with many small commits | Squash merge |
| Working with others on the same branch | Merge only |
| Solo work, clean history matters | Rebase |
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:
- Git stops and tells you which files conflict
- Open the files, look for conflict markers:
<<<<<<< HEAD
your code
=======
their code
>>>>>>> feature
- Edit the file to keep what you want
- 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
| Command | What it does |
|---|---|
git merge feature | Merge with merge commit |
git merge --squash feature | Squash all commits into one |
git rebase main | Replay commits on top of main |
git rebase -i HEAD~n | Interactive rebase last n commits |
git merge --abort | Cancel merge |
git rebase --abort | Cancel rebase |
git rebase --continue | Continue 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.