Git guide · Last updated

Git Rebase vs Merge: What's the Difference and When to Use Each

git merge and git rebase both bring the changes from one branch into another, but they record history differently. Merge preserves exactly what happened and ties the two lines of work together with a merge commit. Rebase rewrites your commits so they appear to have been written on top of the latest code, giving a straight, linear history. Neither is better in general: rebase is ideal for tidying and updating your own unpublished feature branch, and merge is the safe way to combine shared branches. This guide shows both with history diagrams, explains the golden rule of rebasing, and covers interactive rebase, git pull --rebase, squash merges and how conflict resolution differs.

The starting point

Imagine you branched feature off main at commit B and made two commits, while a teammate pushed two commits to main. The branches have diverged. Both commands solve the same problem — getting D and E into the same history as F and G — in different ways.

text
          D---E   feature
         /
A---B---F---G     main
Diverged branches before integrating.

How git merge works

Running git merge main while on feature (or merging feature into main) creates a new merge commit M with two parents. Every original commit keeps its hash, date and parent, so the history is a true record of parallel work. Nothing is rewritten, which is why merging is always safe on shared branches.

The cost is a noisier graph. On a busy repository where everyone merges main into their branch every day, git log --graph turns into a tangle of merge commits that say little more than "Merge branch 'main' into feature". When the target branch has not moved, Git performs a fast-forward instead and simply moves the pointer with no merge commit; --no-ff forces a merge commit anyway, which some teams use to keep a visible record of each feature.

text
          D---E---M   feature  (after: git switch feature && git merge main)
         /       /
A---B---F---G----     main
bash
git switch feature
git merge main                  # bring main's new commits into feature

git switch main
git merge --no-ff feature       # merge the finished feature with a merge commit

How git rebase works

Running git rebase main while on feature temporarily removes D and E, moves the branch to the tip of main, and replays your changes one by one as brand-new commits D' and E'. They contain the same changes but have new hashes, new parents and, often, slightly different content if conflicts were resolved along the way.

The result is a linear history that reads as if you started your work today. Merging feature into main afterwards is a fast-forward, and tools like git bisect and git log are easier to follow. The price is that you have rewritten history: the old D and E still exist in anyone else's clone that fetched them.

text
                  D'--E'  feature  (after: git switch feature && git rebase main)
                 /
A---B---F---G----         main
bash
git switch feature
git fetch origin
git rebase origin/main

# If feature was already pushed, update it safely
git push --force-with-lease

Rebase vs merge: key differences

The practical differences come down to what history looks like and who is affected:

  • History shape: merge keeps a branching graph with merge commits; rebase produces a straight line.
  • Commit hashes: merge never changes existing commits; rebase creates new commits for everything it replays.
  • Safety on shared branches: merge is always safe; rebase on shared branches forces everyone else to recover.
  • Conflicts: merge asks you to resolve all conflicts once; rebase may ask you to resolve conflicts once per replayed commit.
  • Traceability: merge shows when a feature was integrated; rebase shows a cleaner story but hides when work happened in parallel.
  • Pushing: after a merge a normal git push works; after rebasing a pushed branch you need git push --force-with-lease.

The golden rule of rebasing

Never rebase commits that exist outside your repository and that other people may have based work on. In practice: do not rebase main, develop or any branch your teammates pull from. If you rebase a shared branch and force-push it, everyone who had the old commits now has a diverged history; their next git pull will try to merge the old and new versions together and produce duplicate commits.

Rebasing your own feature branch is fine, even after pushing it for backup or for a draft pull request, as long as nobody else commits to it. Use --force-with-lease when you push, so the push is rejected if someone did add a commit in the meantime.

Warning

If you rebased the wrong branch, ORIG_HEAD points to where the branch was before the rebase: git reset --hard ORIG_HEAD undoes it immediately. Later, find the old tip with git reflog.

When to rebase and when to merge

A common, low-drama workflow combines both: rebase locally, merge publicly.

  • Rebase your feature branch onto the latest main before opening or updating a pull request, so reviewers see only your changes on top of current code.
  • Use interactive rebase to clean up "wip" and "fix typo" commits before review.
  • Use git pull --rebase to avoid pointless merge commits when syncing a branch that only you push to.
  • Merge when integrating a finished feature into a shared branch, when several people work on the same branch, or when you need an exact audit trail.
  • Merge long-lived branches (release into main, main into release) instead of rebasing them.

Interactive rebase: clean up commits before review

git rebase -i opens an editor listing your commits, oldest first, and lets you decide what happens to each one. Change the word at the start of a line, save and close, and Git rewrites the commits accordingly. pick keeps a commit, reword changes its message, edit stops so you can amend it, squash combines it with the previous commit and merges the messages, fixup does the same but discards its message, and drop deletes it. You can also reorder lines.

For a smoother flow, create fix-up commits as you work with git commit --fixup <sha> and let --autosquash place and mark them automatically.

bash
git rebase -i HEAD~4          # or: git rebase -i origin/main
text
pick   a1b2c3d Add user model
fixup  d4e5f6a Fix typo in user model
reword 0718aa2 Add signup endpoint
drop   9c3b2e1 Temporary debug logging
Edit the commands, save and close the editor.
bash
git commit --fixup a1b2c3d
git rebase -i --autosquash origin/main

git pull --rebase

By default git pull is git fetch followed by git merge. If you made local commits while someone else pushed, that creates a merge commit every time you pull. git pull --rebase fetches and then rebases your unpushed local commits on top of the remote ones instead, which keeps the history linear. It only rewrites commits you have not pushed yet, so it does not break the golden rule.

Recent Git versions ask you to choose a default when a pull cannot fast-forward. Set it once.

bash
git pull --rebase

# Make it the default
git config --global pull.rebase true
# Or refuse anything except fast-forwards and decide manually
git config --global pull.ff only

Squash merges

A squash merge takes every commit on a branch and combines them into one new commit on the target branch. On GitHub and GitLab it is a merge button option; locally it is git merge --squash feature followed by git commit. The result is linear like a rebase, with one commit per pull request, and contributors do not need to tidy their commits.

The trade-off is that individual commits are lost from the main history, and Git no longer recognises the feature branch as merged, so git branch -d refuses to delete it and you need -D.

bash
git switch main
git merge --squash feature
git commit -m "Add signup flow (#142)"
git branch -D feature

Resolving conflicts in a merge vs a rebase

In a merge, Git combines both sides at once. If files conflict, it stops, you edit the marked sections, git add the files and run git commit (or git merge --continue). git merge --abort returns you to the state before the merge.

In a rebase, Git replays one commit at a time, so you may hit conflicts several times, once for each commit that touches the conflicting lines. Resolve, git add, then git rebase --continue. git rebase --skip drops the current commit, and git rebase --abort restores the branch exactly as it was. Note that during a rebase "ours" refers to the branch you are rebasing onto and "theirs" to your own commits, the opposite of a merge. If you resolve the same conflicts repeatedly, enable rerere so Git remembers your resolutions.

bash
# Merge conflict
git merge main
# ...edit files...
git add src/app.ts
git commit                     # or: git merge --continue

# Rebase conflict
git rebase main
# ...edit files...
git add src/app.ts
git rebase --continue          # repeat for each conflicting commit
git rebase --abort             # bail out completely

git config --global rerere.enabled true

Rebase vs merge: FAQ

Quick answers to common questions

Is git rebase better than git merge?

Neither is better overall. Rebase gives a cleaner linear history and is ideal for updating your own feature branch. Merge preserves history exactly and is the safe choice for shared branches.

What is the golden rule of git rebase?

Do not rebase commits that have been pushed to a branch other people use. Rebasing creates new commits, and anyone who based work on the old ones will end up with duplicate or diverged history.

Should I use git pull --rebase?

For most day-to-day work, yes. It rebases only your unpushed commits onto the remote branch, avoiding meaningless merge commits. Set it as the default with git config --global pull.rebase true.

What is the difference between squash merge and rebase?

A squash merge collapses all of a branch's commits into one commit on the target branch. A rebase keeps each commit but replays them on top of the target branch. Both produce linear history.

How do I undo a git rebase?

Immediately after the rebase, run git reset --hard ORIG_HEAD. Later, find the branch's previous tip in git reflog and reset to it. During a rebase in progress, git rebase --abort cancels it.

Related tools and references

More developer guides