Merging & Rebasing
Two strategies for combining branches - merge preserves history as-is, rebase rewrites it for a linear timeline.
Search across all documentation pages
Two strategies for combining branches - merge preserves history as-is, rebase rewrites it for a linear timeline.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Quick-reference recipe card - copy-paste ready.
# Merge a feature branch into main
git checkout main
git merge feature/auth-page
# Rebase a feature branch onto latest main
git checkout feature/auth-page
git rebase main
# Interactive rebase - squash, reorder, edit commits
git rebase -i main
# Abort a conflicted merge or rebase
git merge --abort
git rebase --abortWhen to reach for this: Every time you need to integrate changes from one branch into another.
# Update main
git checkout main
git pull origin main
# Merge feature branch
git merge feature/user-profile
# If conflicts occur, resolve them:
# 1. Open conflicted files (marked with <<<<<<< / ======= / >>>>>>>)
# 2. Edit to keep the correct code
# 3. Stage resolved files
git add src/components/UserProfile.tsx
# 4. Complete the merge
git commitWhat this demonstrates:
main before merging<<<<<<<, =======, >>>>>>> markers# Start on your feature branch
git checkout feature/dashboard
# Rebase onto latest main
git fetch origin
git rebase origin/main
# If conflicts occur during rebase:
# 1. Resolve the conflict in the file
# 2. Stage the resolved file
git add src/app/dashboard/page.tsx
# 3. Continue the rebase
git rebase --continue
# Force push after rebase (required since history changed)
git push --force-with-leaseWhat this demonstrates:
git fetch + git rebase origin/main avoids needing to checkout main first--force-with-lease is safer than --force - it refuses to push if the remote has commits you haven't seen| Merge | Rebase | |
|---|---|---|
| History | Preserves all commits and creates a merge commit | Rewrites commits for a linear history |
| Conflicts | Resolve once in the merge commit | Resolve per-commit during replay |
| Shared branches | Safe - never rewrites published history | Dangerous on shared branches - rewrites commits others may have based work on |
| Best for | Main branch integrations, release branches | Keeping feature branches up to date, cleaning up before PR |
Rule of thumb: Rebase your own feature branches. Merge into shared branches.
Interactive rebase lets you clean up commits before merging a PR.
# Rebase the last 4 commits
git rebase -i HEAD~4This opens an editor with your commits:
pick abc1234 feat: add dashboard layout
pick def5678 fix: typo in dashboard
pick ghi9012 feat: add chart component
pick jkl3456 fix: chart responsive issue
Common operations:
# Squash a fix into the previous commit
pick abc1234 feat: add dashboard layout
squash def5678 fix: typo in dashboard
pick ghi9012 feat: add chart component
squash jkl3456 fix: chart responsive issue
# Reword a commit message
reword abc1234 feat: add dashboard layout
# Reorder commits (just move lines)
pick ghi9012 feat: add chart component
pick abc1234 feat: add dashboard layout
# Drop a commit entirely
drop def5678 fix: typo in dashboard
When Git cannot auto-merge, it marks the file:
<<<<<<< HEAD
export function Header({ title }: { title: string }) {
return <h1 className="text-2xl font-bold">{title}</h1>;
=======
export function Header({ title, subtitle }: HeaderProps) {
return (
<header>
<h1 className="text-3xl font-bold">{title}</h1>
<p className="text-muted-foreground">{subtitle}</p>
</header>
);
>>>>>>> feature/header-redesign
}Steps to resolve:
<<<<<<<, =======, >>>>>>>)# Fast-forward merge (no merge commit, linear history)
git merge --ff-only feature/small-fix
# Force a merge commit even when fast-forward is possible
git merge --no-ff feature/auth-pageThings that will bite you. Each gotcha includes what goes wrong, why it happens, and the fix.
Rebasing a shared branch - If others have based work on your branch and you force-push a rebase, their history diverges. Fix: Only rebase branches that are yours alone. Use merge for shared branches.
Force push destroying work - git push --force overwrites the remote unconditionally. Fix: Always use git push --force-with-lease which refuses if someone else pushed since your last fetch.
Conflict fatigue during rebase - A rebase across many commits can surface the same conflict repeatedly. Fix: Use git rerere (reuse recorded resolution) to auto-resolve repeated conflicts: git config rerere.enabled true.
Lost commits after rebase - Commits seem to disappear after a botched rebase. Fix: git reflog shows all recent HEAD positions. Find the commit hash and git checkout -b recovery <hash>.
Merge commit noise - Frequent merge commits from pulling main into your feature branch clutter the history. Fix: Use git pull --rebase instead, or configure it globally: git config pull.rebase true.
Other ways to solve the same problem - and when each is the better choice.
| Alternative | Use When | Don't Use When |
|---|---|---|
git cherry-pick | You need one specific commit from another branch | You need all changes from a branch |
Squash merge (gh pr merge --squash) | Feature branch has messy WIP commits | You want to preserve individual commit history |
git merge --squash | Combine all changes into one commit locally | The branch has meaningful atomic commits worth keeping |
mainmain or develop--force-with-lease refuses to push if the remote branch has commits you haven't fetched--force overwrites the remote unconditionally, potentially destroying teammates' work--force-with-lease after a rebasegit rebase -i HEAD~4
# Change "pick" to "squash" (or "s") on the commits you want to fold in
# Save and close the editor, then edit the combined commit message<<<<<<< HEAD marks the start of your current branch's version======= separates the two conflicting versions>>>>>>> branch-name marks the end of the incoming branch's versiongit rerere to auto-resolve repeated conflicts:git config --global rerere.enabled true--ff-only moves the branch pointer forward without a merge commit (linear history)--no-ff always creates a merge commit, preserving that a feature branch existed--ff-only fails if branches have divergedgit reflog
# Find the commit hash before the rebase started
git checkout -b recovery <hash>git fetch origin then git reset --hard origin/branch-name (if they have no local-only commits)git merge --abort
# or
git rebase --abortmain, making git bisect easiergit pull = git fetch + git merge (creates a merge commit if branches diverged)git pull --rebase = git fetch + git rebase (replays your local commits on top of remote)--rebase to avoid noisy merge commits when updating a feature branch.ts file and combine both type definitions if they are compatiblenpx tsc --noEmit after resolving to verify the merged types compileReviewed by Chris St. John·Last updated Jul 10, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥