← 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.

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 — continue with git rebase --continue, cancel with --abort.

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: three draft commits became one meaningful one. 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.

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.

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.
  • Undo and recovery — reset, revert, reflog: the safety net if a rebase goes sideways.
  • Pull requests and code review — where history cleanup meets the team process.
  • Branching models — the series finale: how all of this comes together into a process.