Both answer the same question — "bring feature's changes into main" — but they record the answer completely differently:
git merge feature (run from main) creates a new merge commit with two parents, joining both histories exactly as they happened. You can always see when and how the branches diverged and rejoined, because that information is preserved permanently in the graph.git rebase main (run from feature) rewrites feature's commits one by one on top of main's latest commit, producing a straight line — as if you'd started the feature branch now instead of when you actually did. The original commits are replaced with brand-new ones (different hashes, even though the actual code changes are identical), and the old commits become unreachable from feature once the rebase completes.# Merge: preserves exact history, adds a merge commit
git switch main
git merge feature
# Rebase: rewrites feature's commits onto the tip of main
git switch feature
git rebase main
| Merge | Rebase | |
|---|---|---|
| History shape | Branching, with merge commits showing where things joined | Linear — looks as if development happened sequentially |
| Original commit hashes | Preserved | Replaced (new hashes, same content) |
| Safe on already-pushed/shared commits | Yes | No — see the next chapter |
git log --graph readability | Shows true concurrent development | Reads as if there was never more than one line of work |
| Typical use | Integrating a finished feature branch into main | Cleaning up your own local commits before sharing them, or keeping a long-lived branch current with main without a merge commit each time |
Neither is universally "correct" — many teams use rebase for keeping a personal feature branch current with main during development, then merge (often with --no-ff to force a merge commit even when a fast-forward would be possible) when the feature is actually done, to leave a clear record in history of when it landed.