Git Merge vs Rebase

Lab
Easy
Medium
Hard

Rebase onto mainMedium

Replay your commits on top of main for a linear history.

How do you integrate feature into main?

featuredrag the branch onto a strategy — or just click one
1 / 8
git rebase
Where we start. main has 2 new commits that feature doesn't have yet.

Commit graph

main line feature orphaned
8829dfd Initial commit8829dfd8529db2 Set up project8529db2932e6e5 Update docs932e6e5942e6fe Fix header bug942e6fe15226f0 Add search UI15226f014226d7 Wire up search API14226d71722723 Add search tests1722723mainHEAD → feature
terminal
| * 1722723 (HEAD -> feature) Add search tests
* | 942e6fe (main) Fix header bug
| * 14226d7 Wire up search API
* | 932e6e5 Update docs
| * 15226f0 Add search UI
* | 8529db2 Set up project
* | 8829dfd Initial commit
MergeRebaseSquash merge
History shapeBranching, with merge commitsPerfectly linearLinear, one commit per feature
Original commitsKept as they wereRewritten (new IDs)Not on main at all
ConflictsFixed oncePossibly once per commitFixed once
Safe on shared branchesYesNo — rewrites historyYes

Learn the theory

Git Merge vs Rebase: how it works

4 min read · Updated

Merge, rebase and squash all do the same job — get the work from one branch into another — but they leave very different history. Merge preserves what happened, rebase rewrites it into a straight line, and squash condenses it into one commit. The right choice depends on whether the branch is shared and how much you value a tidy log. This lab draws the commit graph so you can see exactly what Git does to it.

In short

  • Git history is a graph of commits; a branch is just a movable label pointing at one commit.
  • Merge adds a commit with two parents and keeps every original commit untouched.
  • Rebase replays your commits on a new base, creating new commits with new IDs and a linear history.
  • Squash merge turns the whole branch into a single commit on the target branch.
  • Golden rule: never rebase commits that others have already pulled — it rewrites shared history.

How Git models history

Each commit stores a snapshot plus a pointer to its parent commit(s). Together they form a directed acyclic graph. A branch such as main or feature is nothing more than a name pointing at one commit; HEAD points at the branch (or commit) you currently have checked out. Creating a commit moves the current branch's label forward.

A branch diverges when both it and its base get new commits after the point they split. That's when integrating them needs a real strategy.

Merge

git merge feature (run on main) combines both lines of work in a new merge commit with two parents. Nothing is rewritten: every original commit keeps its ID, and the graph shows exactly when the branch diverged and when it came back.

git checkout main
git merge feature            # creates a merge commit
git merge --no-ff feature    # force a merge commit even if a fast-forward is possible

Rebase

git rebase main (run on feature) takes your commits, sets them aside, moves the branch to the tip of main and replays each commit on top of it. The changes are the same, but each replayed commit has a different parent, so it gets a new commit ID. The old commits become orphaned (still reachable via git reflog for a while).

git checkout feature
git rebase main              # replay feature's commits onto main
git checkout main
git merge feature            # now a pure fast-forward: linear history

Because rebase replays commits one at a time, a conflict may appear once per commit — you resolve it, run git rebase --continue, and Git moves on. With a merge you resolve conflicts once, for the combined result.

Squash merge

git merge --squash feature applies the final result of all feature commits to your working tree without creating a merge commit; you then commit it once. main gets one tidy commit per feature, but Git no longer knows the branch was merged — which is why git branch -d feature refuses to delete it and you use -D (or let the pull-request UI delete it).

  • Pro: a clean, one-commit-per-feature history that's easy to revert.
  • Con: the individual commits and their messages are lost from main, and long-lived branches that squash repeatedly can re-conflict.

Which one should you use?

SituationGood choiceWhy
Updating your private feature branch with the latest mainRebaseLinear history, no noisy “Merge main into feature” commits.
Integrating a finished feature into mainMerge or squashSafe on shared branches; squash keeps main tidy.
Messy WIP commits (“fix”, “fix again”)SquashCollapse them into one meaningful commit.
Public or shared branchMergeNever rewrite history others depend on.
You want to see when work was integratedMerge (--no-ff)The merge commit marks the integration point.

Undoing mistakes

Rebased the wrong thing? Git rarely loses commits immediately. git reflog lists every position HEAD has been in; find the commit from before the rebase and git reset --hard <id> (or create a branch there) to get it back. For a merge, git merge --abort cancels a conflicted merge and git revert -m 1 <merge-commit> undoes a completed one.

Frequently asked questions

What is the difference between git merge and git rebase?
Merge joins two branches with a merge commit and preserves history as it happened. Rebase replays your commits on top of another branch, rewriting them into new commits and producing a linear history.
Why does rebase change commit hashes?
A commit's ID is computed from its content, its metadata and its parent. Replaying a commit onto a new parent changes the parent, so the result is a different commit with a different hash.
When should I not use git rebase?
Don't rebase commits that have been pushed and pulled by others. Rewriting shared history forces everyone else to reconcile diverged branches.
What is a fast-forward merge?
When the target branch hasn't diverged, Git simply moves its pointer to the tip of the other branch. No merge commit is created.
What does git push --force-with-lease do?
It force-pushes only if the remote branch is still where you last saw it. Unlike plain --force, it refuses to overwrite commits a teammate pushed in the meantime.
Is squash merging better than merge commits?
It's a trade-off. Squashing gives a clean, one-commit-per-feature history but discards the branch's individual commits; merge commits preserve everything at the cost of a busier graph.

Keep exploring

More interactive labs

All labs

HTTP Caching Explained

Cache-Control, ETag and 304s, one request at a time.

Networking12 min · Intermediate

JS Event Loop Visualizer

See the call stack, microtasks and timers run, frame by frame.

JavaScript10 min · Intermediate

CORS & Preflight Simulator

Edit the request and the server headers, then see exactly why the browser blocks it.

Networking12 min · Intermediate

What Happens When You Type a URL

DNS, TCP, TLS and HTTP — with a stopwatch.

Networking10 min · Beginner

How Database Indexes Work (B-Tree)

See a B-tree lookup beat a full table scan, and why some queries ignore your index.

Databases12 min · Intermediate

What Happens When 100 Reads Hit the Database

Connection pools, CPU workers, caches and replicas, as 100 requests arrive at once.

Databases12 min · Intermediate

SQL Isolation Levels Lab

Run two transactions side by side and watch dirty reads, phantoms and write skew appear.

Databases15 min · Advanced

Sharding & Replication

Split data across servers, copy it for reads, and see what each choice costs.

Databases15 min · Advanced

Redis Commands & Data Structures

Strings, lists, hashes, sets and sorted sets, one command at a time.

Redis15 min · Beginner

Redis Caching: Cache-Aside, TTL & Eviction

Hit or miss? Watch LRU, LFU, TTLs and a cache stampede decide your database load.

Redis12 min · Intermediate

Webhooks: Delivery, Retries & Signatures

Why you get duplicate deliveries, lost events and forged requests — and how to stop them.

Queues & Jobs12 min · Intermediate

Cron Jobs: Schedules, Overlaps & Missed Runs

Write a cron expression and watch what really happens when jobs overlap or the scheduler is down.

Queues & Jobs10 min · Beginner

Job Queues with BullMQ (Redis)

Watch jobs move through waiting, active, delayed, completed and failed — and see what BullMQ stores in Redis.

Queues & Jobs14 min · Intermediate

RabbitMQ: Exchanges, Queues, Acks & Dead Letters

Direct, topic and fanout routing, competing consumers, prefetch, redelivery and dead-letter queues, drawn.

Queues & Jobs14 min · Intermediate

Git Commands, HEAD & Refs (Visual)

Every core git command drawn with arrows: three areas, commits, branches, HEAD and remotes.

Git15 min · Beginner