React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

gitmergerebasebranches

Merging & Rebasing

Two strategies for combining branches — merge preserves history as-is, rebase rewrites it for a linear timeline.

Recipe

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 --abort

When to reach for this: Every time you need to integrate changes from one branch into another.

Working Example

Merge Workflow

# 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 commit

What this demonstrates:

  • Always pull the latest main before merging
  • Git marks conflicts with <<<<<<<, =======, >>>>>>> markers
  • After resolving conflicts, stage and commit to complete the merge

Rebase Workflow

# 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-lease

What this demonstrates:

  • git fetch + git rebase origin/main avoids needing to checkout main first
  • Rebase replays your commits one at a time on top of the target branch
  • --force-with-lease is safer than --force — it refuses to push if the remote has commits you haven't seen

Deep Dive

Merge vs Rebase — When to Use Each

MergeRebase
HistoryPreserves all commits and creates a merge commitRewrites commits for a linear history
ConflictsResolve once in the merge commitResolve per-commit during replay
Shared branchesSafe — never rewrites published historyDangerous on shared branches — rewrites commits others may have based work on
Best forMain branch integrations, release branchesKeeping feature branches up to date, cleaning up before PR

Rule of thumb: Rebase your own feature branches. Merge into shared branches.

Interactive Rebase

Interactive rebase lets you clean up commits before merging a PR.

# Rebase the last 4 commits
git rebase -i HEAD~4

This 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

Resolving Merge Conflicts

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:

  1. Remove all conflict markers (<<<<<<<, =======, >>>>>>>)
  2. Keep the code you want (or combine both changes)
  3. Stage the file and continue the merge/rebase

Fast-Forward vs No-Fast-Forward

# 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-page
  • Fast-forward moves the branch pointer forward — no merge commit, clean history. Only possible when there are no divergent commits on the target branch.
  • No-fast-forward always creates a merge commit — useful for preserving the fact that a feature branch existed.

Gotchas

Things 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 workgit 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.

Alternatives

Other ways to solve the same problem — and when each is the better choice.

AlternativeUse WhenDon't Use When
git cherry-pickYou need one specific commit from another branchYou need all changes from a branch
Squash merge (gh pr merge --squash)Feature branch has messy WIP commitsYou want to preserve individual commit history
git merge --squashCombine all changes into one commit locallyThe branch has meaningful atomic commits worth keeping

FAQs

When should I use merge vs rebase?
  • Rebase your own feature branches to stay up to date with main
  • Merge into shared branches like main or develop
  • Never rebase a branch that others have based work on
What does --force-with-lease do, and why is it safer than --force?
  • --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
  • Always use --force-with-lease after a rebase
How do I squash multiple commits into one using interactive rebase?
git 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
What do the <<<<<<<, =======, and >>>>>>> markers mean?
  • <<<<<<< 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 version
  • Delete all three markers and keep the code you want
Gotcha: Why do I keep resolving the same conflict during a rebase?
  • Rebase replays commits one at a time, so the same conflict can appear multiple times
  • Enable git rerere to auto-resolve repeated conflicts:
git config --global rerere.enabled true
What is the difference between --ff-only and --no-ff merges?
  • --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 diverged
Can I recover commits lost after a bad rebase?
git reflog
# Find the commit hash before the rebase started
git checkout -b recovery <hash>
  • The reflog keeps a record of all HEAD movements for ~90 days
Gotcha: I rebased a shared branch and my teammate's history diverged. What now?
  • Your teammate should run git fetch origin then git reset --hard origin/branch-name (if they have no local-only commits)
  • Going forward, only rebase branches that are yours alone
How do I abort a merge or rebase that has gone wrong?
git merge --abort
# or
git rebase --abort
  • Both commands return your branch to the state before the operation started
In a TypeScript monorepo, should I squash merge feature PRs?
  • Squash merge is ideal when the feature branch has many WIP or fix-up commits
  • It produces one clean commit on main, making git bisect easier
  • Avoid squash if individual commits carry meaningful, atomic changes worth preserving
How does git pull --rebase differ from a regular git pull?
  • git 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)
  • Use --rebase to avoid noisy merge commits when updating a feature branch
How do I resolve a TypeScript conflict where both branches added different type definitions?
  • Open the conflicted .ts file and combine both type definitions if they are compatible
  • Run npx tsc --noEmit after resolving to verify the merged types compile
  • Stage the file and continue the merge or rebase