AlgoInsight

Free lesson · Git Mastery

What is Rebasing?

Medium8 min readgit

What Is Rebasing

A note from Brandon Carter, Principal SDE at AWS S3, Amazon Bar Raiser.

The Other Way to Integrate

Module 7 covered merging - combining work from two branches by producing a merge commit that has both branches' tips as parents. This module covers rebasing - the alternative approach that produces a linear history without merge commits.

Both operations achieve a similar end goal: integrate work from one branch into another. The mechanics are very different. The resulting history is very different. The cultural pattern around each is very different. Teams that prefer merging produce one kind of history; teams that prefer rebasing produce another. Both are valid; the choice is mostly aesthetic and operational, not technical.

I learned rebase relatively late in my career. At IBM and Oracle, the legacy version control systems (CVS and Perforce) had no equivalent. Branches were so expensive that branching strategy was conservative; what would be a rebase in Git was simply not an operation we'd think about. Joining Stripe in 2018 was the first time I worked on a team that defaulted to rebasing for feature integration. Adjusting took a few months. After that adjustment, the linear history that rebase produces became my preference for daily work.

This topic covers what rebasing is conceptually. Topic 8.2 covers the basic command. The rest of Module 8 covers the variants - interactive rebase, conflict resolution during rebase, the --onto form, squashing, rewording, reordering, and the safety rules around when rewriting history is acceptable.

What Rebase Does

Conceptually: take the commits from one branch and replay them on top of a different base. The branch's history is rewritten - the commits get new parent commits, new hashes, and possibly new contents (if there were conflicts to resolve during the replay). The end state is that the branch now appears to have started from the new base.

flowchart TD
    subgraph "Before rebase"
        A[Commit A<br/>common ancestor] --> B[Commit B<br/>on main]
        A --> C[Commit C<br/>on feature]
        C --> D[Commit D<br/>on feature]
    end

    subgraph "After git rebase main (on feature)"
        E[Commit A] --> F[Commit B<br/>on main]
        F --> G[Commit C'<br/>replayed]
        G --> H[Commit D'<br/>replayed]
    end
The diagram shows what rebase does. Before, the feature branch's commits C and D branched from commit A. After rebasing onto main, the feature branch's commits are replayed on top of main's commit B. The replayed commits are labeled C-prime and D-prime to indicate they are new commit objects with new hashes - their contents may be the same but their place in history is different. The original C and D commits are no longer reachable from the feature branch; they exist in the reflog and will eventually be garbage-collected.

The result is a linear history. From the feature branch's perspective, it now looks like the work was done on top of main's current state. No merge commit is created; the history has no branching visible.

Rebase vs Merge - The Same Information, Different Shapes

Both operations integrate the feature branch's work with main's work. The difference is what the resulting history looks like:

After merging feature into main:

*   c47b2a3 Merge branch 'feature/payment-flow'
|\
| * b9e3f81 Add payment validation
| * a3f2c1d Add payment model
* | d8f3e1c Fix typo in landing page
|/
* e1f4a2b Initial commit

After rebasing feature onto main and fast-forwarding:

* b9e3f81' Add payment validation
* a3f2c1d' Add payment model
* d8f3e1c Fix typo in landing page
* e1f4a2b Initial commit
Both histories contain the same logical work - payment model added, payment validation added, typo fix on landing page. The merge form preserves the branching structure and adds a merge commit. The rebase form produces a linear sequence where the feature's commits appear after main's commits. The merge form is more accurate to what actually happened in time. The rebase form is cleaner to read.

The choice between these two histories is the central question of rebase vs merge. Both contain the same information; they organize it differently.

Why Some Teams Prefer Rebasing

Arguments in favor:

Linear history is easier to read. No merge commits, no branching graph. git log --oneline shows a clean sequence. Tools like git bisect (Topic 4.6) work more predictably on linear histories.

Each commit is self-contained. Because the commits are replayed onto current main, each commit is testable against the current state. With merge, intermediate commits might not pass tests because they don't include main's recent changes.

No "merge bubble" clutter. Long-lived branches that merge in and out repeatedly create complex graph structures. Rebasing keeps everything linear.

Easier code review of incremental commits. When reviewing a feature branch that was kept up-to-date via rebase, each commit applies against current main. With periodic merges from main, the commits intermix.

Why Some Teams Prefer Merging

Arguments against rebase, in favor of merge:

Rebase rewrites history. The original commits don't exist as commits anymore (technically they're in the reflog for a while, but they're not reachable from the branch). Some teams consider this a problem.

Loses the "what was done in parallel" information. The history pretends the feature branch was done on top of current main. In reality, the work happened in parallel. The merge form preserves that fidelity.

Conflicts can be more painful. Rebase replays each commit individually. If multiple commits conflict in the same area, you resolve the same kind of conflict multiple times. Merge resolves the conflict once.

Doesn't work for shared branches. Rebasing a branch that others have pulled creates serious problems for them. Topic 8.4 covers when rewriting history is safe.

The Cultural Split

Different teams default to different patterns. Some examples I've worked in:

Neither pattern is technically superior. Both can produce well-organized history; both can produce messy history. The discipline matters more than the choice.

I personally prefer rebase for my own feature branches but accept whatever the team has standardized on. At S3 I use merge because that's the team convention; in my personal projects I rebase.

When Rebase Is Wrong

A few situations where rebase isn't the right choice:

Shared branches. If anyone else has pulled the branch, rebasing creates divergent history that they'll have trouble reconciling. Topic 8.4 covers this in detail. The short rule: don't rebase pushed commits unless your team has explicitly agreed to allow it.

Branches that capture important context about parallel work. Some merges are meaningful events that should be visible in history. Rebasing erases the parallelism.

When the merge commit message would carry valuable information. Rebase doesn't produce a merge commit; any rationale that would have gone there is lost.

During complex multi-step integration. Rebase replays each commit individually. For large-scale integration with many conflicts, merge is sometimes easier because you resolve the cumulative state once.

The general principle: rebase for local cleanup and feature-branch updates; merge for substantive integration events.

A Pattern at Stripe

The team's convention at Stripe for feature branches:

# Start a feature
git switch -c feature/payment-flow

# Work, commit, work, commit

# Periodically update against main
git fetch origin
git rebase origin/main

# When the feature is ready, push and open a PR
git push --force-with-lease origin feature/payment-flow

# PR review happens; merge through the platform (which often does a final rebase)
The pattern: rebase feature branches onto main periodically rather than merging main in. Push with force dash with dash lease - Topic 8.4 covers this - to update the remote with the rebased version. The result is that the feature branch always appears as a clean linear continuation of main, even though it was developed in parallel. The PR shows clean commits applying to current main.

This is the discipline that makes rebase workflows succeed. Without the regular rebase against main, the same problems that delayed merges produce - accumulating conflicts, drift from current state - happen with rebase too. The integration discipline is what works; the choice of merge vs rebase is the means to that end.

A Specific Story from Stripe Adjustment

When I joined Stripe and started rebasing my feature branches, the first few times I lost work. I'd rebase, hit a conflict, get confused about what state I was in, and accidentally discard commits during the resolution. The recovery via reflog worked but was painful.

After a few iterations, I developed the discipline: before any rebase, create a backup branch (git branch backup-$(date +%s)) that I can return to if the rebase goes wrong. The backup costs nothing (it's a forty-byte pointer file, Topic 6.1) and saves me when things go sideways.

git branch backup-before-rebase   # cheap insurance
git rebase main
# If things go wrong:
git rebase --abort
# Or, if I'd already completed a bad rebase:
git reset --hard backup-before-rebase
The backup branch is recovery insurance. The original commits are preserved at the backup; even if the rebase goes catastrophically wrong, the backup branch can restore them. After a successful rebase, I delete the backup. The discipline takes ten seconds and prevents the painful "where did my work go" moments that newcomers to rebase often have.

I still do this. After hundreds of rebases, the discipline is automatic. The backup branch costs nothing; the protection is real.

What Comes Next

Topic 8.2 covers the basic git rebase command. Topic 8.3 covers interactive rebase. Topic 8.4 covers the rules around when rewriting history is safe. Topic 8.5 covers rebase conflicts. Topic 8.6 covers the --onto form for complex rebases. Topics 8.7, 8.8, 8.9 cover specific operations during interactive rebase (squashing, rewording, reordering).

What Goes Wrong

Warning: Common rebase-mental-model confusions:

Treating rebase and merge as interchangeable. They produce different history shapes; the choice has implications.

Rebasing shared branches. Anyone who pulled the original commits is now in a divergent state. Topic 8.4 covers this rule.

Believing rebase "is better than" merge. Neither is. They serve different goals; teams choose differently.

Skipping the periodic rebase against main. The same drift problems that affect merge workflows affect rebase workflows.

Not creating a backup branch before a complex rebase. The work is recoverable via reflog but the backup is easier.

Mixing rebase and merge inconsistently in the same branch. Pick a strategy and stick with it for a given branch's lifetime.

Try It

Exercise: Build the mental model:

  1. In a practice repository, create a feature branch from main, make commits on both branches. Run git rebase main on the feature. Look at git log --graph --all to see the linear result.
  2. Reset the same situation. This time run git merge main on the feature. Compare the resulting history graph. Note the difference.
  3. Read both histories with git log --oneline. Which one is easier to follow at a glance? (Answer depends on personal preference.)
  4. Set up a backup branch before any rebase: git branch backup-test. Practice deleting it after the rebase succeeds. Build the habit.
  5. Read your team's documentation on whether they prefer rebase or merge for feature integration. Follow the convention.
  6. Look at a high-quality open source project's history (Linux kernel, Git itself). Note the structure - merge-heavy, rebase-heavy, or mixed.

Back to Git Mastery