Rebase vs Merge
The Debate: Rebase vs Merge
After this page you can pick a default for desk-api and know what you are giving up.
Same trees, different graphs
Both operations can produce a working tree that contains both sides of the work. Merge records a join. Rebase copies a chain.
start after merge
A ← B ← C ← G main A ← B ← C ← G ← M main
↑ ↑ ↑
D ← E feat D ← E feat
start after rebase feat onto main
A ← B ← C ← G main A ← B ← C ← G main
↑ ↖
D ← E feat D' ← E' feat
| Merge | Rebase | |
|---|---|---|
| Hashes of your old commits | Unchanged | New |
| Extra commit | Often a merge commit | None if you fast-forward after |
| Conflicts | Once, at the join | Once per copied commit |
Safe on published main |
Yes | Usually no |
| Safe on your unpushed feature | Yes | Yes, and often cleaner |
A default that scales
- Feature branch you own: rebase onto
main, then fast-forwardmain(or open a pull request that the forge squash-merges). main/ shared long-lived branch: merge or fast-forward only. Do not rebase commits other people have.- Hotfix already on
main: revert (chapter 10), do not rebasemain.
Forges add a third option: squash merge (many commits become one on main). That is a server-side rewrite. Chapter 15 and chapter 31. Locally, rebase is how you squash before you push.
rerere, tests, Linux
Run the test command you actually have after every --continue. On Linux that is whatever desk-api uses (python -m pytest, a shell script, make test). Rebase does not run tests for you.
You can mix
Rebase the feature. Merge into main with --no-ff if the team wants a join node for git log --first-parent. That is Git Flow’s “feature into develop” shape. Trunk-based skips the join and fast-forwards.
There is no global winner. There is a default per branch class. Write it down in the repo (chapter 15). The hands-on page next squashes a messy feature on Linux.