Every Git tutorial in 2026 still triggers the same argument: do I rebase or merge? The honest answer is that both are correct for different situations, and the team conflict comes from people applying one tool everywhere. This guide gives you a mental model in one paragraph, then walks through five concrete scenarios with branch diagrams before and after the operation, the exact commands to run, and how to resolve conflicts when they happen. By the end you should know which command to type without thinking about it, and which one to convince your team to standardise on for shared branches.
Contents
- The mental model in one paragraph
- Scenario 1: solo feature branch, short-lived
- Scenario 2: long-lived feature branch with messy commits
- Scenario 3: pulling updates from main into your branch
- Scenario 4: cleaning up before a pull request
- Scenario 5: collaborative branch with multiple authors
- Interactive rebase and squash, decoded
- Conflict resolution in each mode
- A team convention that actually works
- FAQ
The mental model in one paragraph
Merge preserves history. Rebase rewrites history. Merging joins two branches with a new commit that has two parents, so you can always see exactly when the branch was integrated and what state it was in. Rebasing takes your commits and replays them on top of a different base, producing a linear history that pretends the branch was always written against the latest main. Merge is honest about what happened. Rebase is honest about what the final state should look like. Both ship working code; the difference is what your git log looks like six months later when you are bisecting a bug.
Solo feature branch, short-lived
Either works You are the only one on the branch, you have one or two commits, the PR will be reviewed and merged within a few hours.
Either rebase onto main before merging (linear history) or just merge through the PR button (preserves branch context). On a small team where the PR is small, the difference is academic. Pick the team default and stick with it.
# Either option, both fine git checkout feature git rebase main # or just push and let GitHub merge git checkout main git merge feature # or "git merge --no-ff feature" if you want the commit graph
Long-lived feature branch with messy commits
Rebase wins You have been on this branch for two weeks, you have 14 commits with messages like “wip”, “fix typo”, “actually fix it”, and you need to ship a clean change.
Interactive rebase: squash the noise into one logical commit per concern, rewrite the message, then merge. The reviewer reads three meaningful commits instead of fourteen wips. Your future self bisecting a bug six months from now will thank you.
git checkout feature git rebase -i main # In the editor: # pick a1b2c3d Implement core logic # squash e4f5g6h wip # squash 1a2b3c4 fix typo # pick 5d6e7f8 Add tests # squash 9g0h1i2 actually fix it # Save and write the cleaned commit messages.
Pulling updates from main into your branch
Rebase wins Main has moved forward while you were working. You want your branch to catch up.
Rebasing keeps your branch linear and removes “merge main into branch” garbage commits. The alternative (merging main into your feature) creates merge commits that pollute the PR and make it hard for the reviewer to see what you actually changed.
git checkout feature git fetch origin git rebase origin/main # Resolve any conflicts (see section 8) git push --force-with-lease
The --force-with-lease flag is critical here: it refuses to overwrite the remote if someone else has pushed to your branch since you last fetched. Use it instead of --force for safety.
Cleaning up before a pull request
Rebase + squash Your branch is ready to ship but the commit history is messy. You want one clean commit per logical change before opening the PR.
git checkout feature git rebase -i origin/main # Reorder, squash, edit messages to tell the story: # pickRefactor auth middleware # pick Add login rate limiting # pick Update auth tests git push --force-with-lease
The reviewer now reads three commits, each one a self-contained change with a meaningful message. The bisect-friendliness of the resulting history is worth the five minutes of interactive rebase.
Collaborative branch with multiple authors
Merge wins Two or three engineers are pushing commits to the same shared feature branch.
Never rebase a shared branch. Rebasing rewrites SHAs, and the next person who pulls gets a confusing divergent history with duplicate commits. Use merges to integrate main into the shared branch:
git checkout shared-feature git fetch origin git merge origin/main # Resolve conflicts, commit, push (regular push, no force) git push
The merge commits in the shared branch are noise you accept as the cost of safe collaboration. When the shared branch is finally ready, the team lead can squash-merge to main if they want a clean final history.
Interactive rebase and squash, decoded
Interactive rebase (git rebase -i) opens an editor with a list of your commits and a verb next to each one. The verbs you use 95 percent of the time:
pick— keep this commit as-is.squash(ors) — merge this commit into the previous pick. Combines the diffs, lets you edit the combined message.fixup(orf) — same as squash but throws away this commit’s message and keeps the previous one’s. Use for “wip” or “fix typo” commits.reword(orr) — keep the commit but rewrite the message in the next editor.drop(ord) — discard the commit entirely. Useful for accidentally committed debug logs.
Reordering is free: drag a line up or down in the editor before saving and Git will replay them in the new order. The replay can fail if you reorder commits that touch the same lines, in which case you resolve the conflict and continue with git rebase --continue.
Conflict resolution in each mode
Merge conflicts
When a merge can’t combine two changes automatically, Git pauses with conflict markers in the affected files. Fix the files, git add them, then git commit to complete the merge. The resulting merge commit’s message is auto-generated; you can edit it.
Rebase conflicts
Rebasing replays commits one by one, so conflicts can happen on each replay. The pattern: fix the files, git add, then git rebase --continue (not commit). To bail out and return to where you started, git rebase --abort.
# During a rebase conflict: # 1) Look at what is conflicting git status # 2) Edit the files, remove the conflict markers # 3) Mark as resolved git add path/to/file # 4) Continue with the next commit git rebase --continue # Or bail out if it gets too messy git rebase --abort
A team convention that actually works
Pick one of these two and write it in your contributing guide. Both ship clean histories; the choice is taste, not technical correctness.
Convention A: rebase for personal, merge for shared
- Personal feature branch: rebase onto main before opening PR. Force-push to your branch as needed.
- PR merge: squash-merge or rebase-merge in the GitHub/GitLab UI. Linear main history.
- Shared feature branch: merge main in, regular merge commits. Squash-merge to main when ready.
Convention B: always merge, never rebase
- Personal feature branch: don’t touch history. Merge main into your branch when you need to catch up.
- PR merge: regular merge with –no-ff. Branches show up as bubbles in the graph.
- Pro: zero risk of accidentally losing commits through bad rebases. Con: harder to bisect.
Decoding a tricky git error?
Our Developer Error Fix Hub covers EACCES, ENOENT, npm peer dep, JSON parse errors and other daily debugging pain — curated and tested.
FAQ
Can I rebase a branch that has already been pushed to a remote?
Yes, if you are the only one working on it. Use git push --force-with-lease (not plain --force) to avoid overwriting commits that someone else may have pushed. If you are working on a shared branch with other people, never rebase it — use merge instead.
My rebase is in a mess and I want to bail out. How?
During an interactive rebase or a rebase-with-conflicts session, run git rebase --abort. This rewinds your branch to the state it was in before the rebase started. The original commits are still there; nothing is lost.
What is the difference between squash-merge and rebase-merge in GitHub?
Squash-merge combines every commit in the PR into a single commit on main with a synthesized message — short PRs become one line in the log. Rebase-merge replays each commit individually on main, preserving granular history. Pick squash for tiny fix PRs, rebase for substantial features where each commit is a real step.
When do I use git pull versus git fetch then merge?
They do the same thing. git pull is git fetch followed by git merge FETCH_HEAD. Many teams prefer git fetch then explicit git rebase origin/main because it lets them see what changed before integrating. Configure git config --global pull.rebase true if you want git pull to use rebase by default.
Will rebase break my tests?
Indirectly, yes. If your commits A, B, C each pass tests on their own but A on top of main fails because main has a new conflict, the rebased A’ might fail too. Re-run the test suite after every rebase. CI usually does this automatically when you force-push.
How do I find a commit I accidentally lost during a bad rebase?
Use git reflog. It shows every position HEAD has been at, including the state before your rebase started. Find the SHA you want, then git reset --hard to jump back. The reflog keeps 90 days of history by default.













