How Git Rebase Works — Rewriting History

How Git Rebase Works — Rewriting History

2026-03-24

Merge preserves history as it happened. Rebase rewrites history to look like it happened in a straight line. Both integrate changes from one branch into another, but they produce different commit graphs. Understanding what rebase actually does — and why it changes commit hashes — prevents the most common Git disasters.

What Rebase Does

git rebase main while on a feature branch does the following:

  1. Find the common ancestor (merge base) of feature and main.
  2. Compute the diff for each commit on feature since the merge base.
  3. Reset feature to the tip of main.
  4. Replay each diff as a new commit on top of main.

The result: feature's commits appear after main's latest commit, as if the branch was created from the current tip of main. History is linear.

rebase: replay feature commits onto main

BEFORE

c1 c2 M1 F1 F2

main feature

AFTER

c1 c2 M1 F1' F2'

main feature

F1' and F2' are new commits (different hashes)

Why Rebase Changes Commit Hashes

A Git commit's SHA-1 hash is computed from its contents: the tree, the parent pointer(s), the author, the timestamp, and the message. When rebase replays a commit onto a new base, the parent changes — the rebased commit now points to a different parent commit.

Different parent means different input to the hash function. Different input means different hash. F1 and F1' have the same diff, the same message, and the same author, but they are different commit objects because their parents differ.

This is not a bug — it is a direct consequence of the content-addressable object model. The hash guarantees that if two commits have the same SHA-1, they have the same content AND the same history. Changing the history must change the hash.

Interactive Rebase

git rebase -i main opens an editor with a list of commits:

pick a1b2c3 Add login form
pick d4e5f6 Fix typo in login form
pick 789abc Add logout button

You can reorder, edit, combine, or drop commits:

  • pick — keep the commit as-is.
  • squash — combine with the previous commit, keeping both messages.
  • fixup — combine with the previous commit, discarding this commit's message.
  • edit — pause the rebase at this commit so you can amend it.
  • drop — delete the commit entirely.

Interactive rebase is the most powerful history editing tool in Git. Common uses:

  • Squash "fix typo" commits into the original commit before merging to main.
  • Reorder commits so related changes are adjacent.
  • Split a large commit into smaller logical commits (using edit).
  • Drop experimental commits that are no longer needed.

Every modified commit gets a new hash, and every descendant of a modified commit also gets a new hash (because its parent changed).

When to Rebase vs Merge

Rebase produces linear history. One straight line of commits, easy to read with git log. No merge commits. git bisect walks a single chain.

Merge preserves the actual development topology. You can see that a feature branch existed, when it was started, and when it was integrated. The history is accurate but more complex.

Practical guidelines:

  • Rebase your local feature branch onto main before merging. This gives you linear history on main while you developed on a branch.
  • Never rebase commits that have been pushed to a shared branch. Other developers have those commit hashes in their history. Rebasing creates new hashes, causing their history to diverge from the remote.
  • Merge for integration branches (main, develop). Merge commits mark integration points.
  • Rebase for cleanup — squashing, reordering, and tidying commits before they become public.

The Golden Rule

Never rebase commits that exist outside your local repository.

If you push commits to a shared branch and then rebase them, you create a fork in history. Your rebased commits have different hashes from the ones other developers have already fetched. When they pull, Git sees two divergent histories and either creates a confusing merge or forces them to reconcile manually.

This means:

  • Rebase your unpushed commits freely.
  • Once you push, use merge to integrate changes.
  • If you must rebase after pushing (on a personal branch that nobody else uses), you need git push --force-with-lease.

--force-with-lease is safer than --force — it refuses to push if someone else has pushed to the same branch since your last fetch.

Rebase Conflicts

Rebase replays commits one at a time. Each replay is a cherry-pick — applying the diff of that commit onto the current base. If the diff conflicts with the new base, Git pauses and asks you to resolve the conflict, just like during a merge.

The difference: in a merge, you resolve all conflicts once. In a rebase, you may resolve conflicts at each replayed commit. Rebasing a branch with 10 commits can mean resolving conflicts 10 times if the same region keeps changing.

This is another reason to keep branches short-lived — fewer commits to replay means fewer potential conflicts.

After resolving a conflict during rebase:

git add resolved-file.rs
git rebase --continue

Or to abort the entire rebase and return to the state before it started:

git rebase --abort

Next Steps