← back to the section

merge combines branches, honestly preserving everything as it was. rebase solves the same problem differently: it moves your commits onto a new base, rewriting them. The result is a straight, readable history without diamonds and merge commits. The price is a set of safety rules you need to know cold. Let's go through it without the "merge vs rebase" religious wars.

git rebase main main a1b2c3d4before 9f074be1after feature same changes, new identifiers

The feature branch left main before other commits landed there. Rebase re-applies its changes on top of the fresh tip: the content stays the same, but the commits are new — a1b2 and c3d4 are replaced by 9f07 and 4be1. The feature bookmark moves onto the new chain; the old one is left aside with no branch pointing at it and eventually disappears.

What rebase does

You've been working in a branch, and main has moved ahead. Instead of merging main into your branch (a merge commit in your branch), let's replant your branch onto the fresh main:

git switch feature-export
git rebase main

Git "re-applies" your commits one by one on top of the tip of main. The changes look the same from the outside, but the history reads as if you started your work from the fresh main. Important to understand: these are new commits — with new identifiers; the old ones are replaced. Conflicts are possible, just like with merge, and are resolved the same way — except they can show up on every commit being moved, one at a time: sort it out, git add, git rebase --continue; changed your mind halfway through — git rebase --abort puts the branch back the way it was.

The same mechanism powers the everyday git pull --rebase: your local commits are replayed on top of the fresh server ones — without junk merge commits like "Merge branch main into main".

Interactive rebase: cleanup before review

Real work produces a draft history: "wip", "fixed a typo", "fix again". Before a pull request you can turn it into a tidy one:

git rebase -i main

A list of your commits opens up, with commands:

pick   a1b2c3 feat: export orders to CSV
squash d4e5f6 wip
fixup  g7h8i9 fixed a typo
reword j0k1l2 added export tests
  • pick — keep as is; reword — edit the message;
  • squash — fold into the previous commit (messages are combined); fixup — fold in, discarding the message;
  • lines can be reordered and deleted (drop).

Result: four lines turn into two commits — the drafts are folded into the meaningful one, and the last one gets a better message. If your team merges via squash — no need to go overboard; if via rebase/merge — this cleanup makes your history a gift to future readers.

The golden rule

Never rewrite history you have already pushed to a shared branch. Rebase creates new commits — colleagues who already pulled the old ones will end up with a history that diverges from yours, and untangling it will be painful.

The practical safety formula: you may rewrite your own branch before it's merged (even if already pushed — then git push --force-with-lease; it's your personal branch); you may not touch main, develop, or any branch others work from. This also answers "merge or rebase": in a personal branch — rebase to your heart's content; shared branches are combined with merge. --force-with-lease differs from --force in that it refuses to overwrite the branch if someone has pushed to it since your last fetch.

cherry-pick: move a single commit

Sometimes you need not the whole branch but a single commit from it — the classic: a bugfix from main needs to be delivered to the release 2.3 branch.

git switch release-2.3
git cherry-pick a1b2c3

Git applies the changes of the specified commit to the current branch (as a separate new commit). Here the commit atomicity from the history article pays off literally: an atomic bugfix moves over with one command, while "a fix buried inside a dumping-ground commit" — only by hand.

The transplanted commit is a new one too: the same changes, a different identifier. When the branches are merged together later, those changes meet twice — usually silently, sometimes as a conflict.

In short

  • Rebase moves your commits onto a new base: the history is straight, but the commits are new. Conflicts are resolved as in merge (--continue/--abort).
  • pull --rebase — clean everyday synchronization without junk merge commits.
  • rebase -i — branch cleanup before review: squash/fixup/reword/drop.
  • The golden rule: never rewrite what's been pushed to shared branches. Your own branch — fine (--force-with-lease); main — never.
  • cherry-pick moves an individual commit — atomic commits are what make this possible.