Skip to main content
ANVISoftware Solutions
Lesson 7 of 15Beginner16 min

Rebasing

By the end of this lesson

Replay commits onto a new base, and know when not to.

Rebasing answers the same question as merging — how do I combine my work with everyone else's — and answers it differently.

A merge joins two lines of history and records the join. A rebase takes your commits off your branch, moves the starting point to the current tip of the other branch, and applies your changes again on top. The result looks as though you had started your work from that newer point all along.

What rebase does to your branch
Text
Before

  origin/main   A - B - C
  feature       A - B - D - E          D and E are your commits

After  git rebase origin/main

  origin/main   A - B - C
  feature       A - B - C - D2 - E2    D2 and E2 are NEW commits

D2 contains the same change as D. It is not the same commit:
its parent is different, so its identifier is different too.
D and E still exist, but nothing points at them any more.
  • Rebase does not move commits. It creates new ones with the same changes and different parents, then moves the branch pointer to the new chain.
  • That is why every identifier on the rebased branch changes. Even a commit whose content is untouched gets a new identifier, because a commit's identifier depends on its parent.
  • The originals are not deleted immediately — they are reachable through the reflog, covered later in this course. But your branch no longer points at them, and they will eventually be cleaned up.
Rebasing your branch onto the latest main
Shell
# Get the latest state of the remote without changing your files
git fetch origin

# Replay your commits on top of the current origin/main
git switch add-expense-export
git rebase origin/main

# If a conflict stops it: fix the files, then stage and continue
git add src/ExpenseExporter.cs
git rebase --continue

# Or change your mind and put everything back as it was
git rebase --abort
  • git fetch updates your knowledge of the remote. It does not touch your working files, so it is safe to run at any time.
  • git rebase replays your commits one at a time. If one of them conflicts, the rebase pauses at that commit rather than failing outright.
  • git rebase --continue resumes after you have resolved and staged the conflict. Do not commit during a rebase — staging is what Git is waiting for.
  • git rebase --abort returns your branch to exactly where it was before you started. Remember this one. It is the reason a rebase gone wrong is recoverable.

The honest comparison. Both are legitimate; they optimise for different things:

 MergeRebase
Existing commitsUntouchedReplaced by new commits with new identifiers
Resulting historyShows the true shape of parallel workA straight line, easy to read top to bottom
Accuracy of the recordAccurate — it happened this wayTidied — it did not actually happen this way
ConflictsResolved once, in the mergePossibly once per replayed commit
Safe on a shared branch?YesNo — it changes commits others may already have
Recovering if it goes wronggit merge --abort, or revert the merge commitgit rebase --abort, or the reflog once it has finished

Summary

  • Rebasing replays your commits onto a new starting point, producing new commits with new identifiers
  • The result is a linear history that reads well but no longer records how the work actually happened
  • Never rebase commits others have pulled; rebasing your own unpushed branch onto an updated main is the safe, common case
  • git rebase --abort restores your branch exactly as it was, which makes rebasing something you can try
  • Merge and rebase are both defensible — decide as a team what the history is for

Practice

Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.

Think about it

Why the identifiers change

You rebase a branch and the content of your files is byte-for-byte identical afterwards. Every commit identifier is different. Why?

Show solution

A commit's identifier is derived from its contents, and its contents include a reference to its parent. Change the parent and you have changed the commit, even if the files did not change.

This is why rebasing is described as rewriting history rather than moving it. The new commits are genuinely new objects that happen to carry the same changes.

It also explains why a rebased branch cannot be pushed normally after it has been pushed once. From the remote's point of view your branch no longer contains the commits it had, which is the situation a force push overrides.

Try it yourself

Rebase, then abort

In a practice repository, create a branch and make two commits. Switch to main, make a commit there, then switch back and run git rebase main.

Compare git log --oneline before and after. Then repeat the exercise, and this time run git rebase --abort while it is paused on a conflict.

Show solution

After the rebase your two commits sit on top of main's new commit, with different identifiers. The history is a straight line.

The abort run is the more valuable half. Knowing that --abort returns you exactly to where you started is what makes rebasing something you can try rather than something you dread.

Shell
git switch -c add-csv-export
git commit -am "Add CSV export for the expense report"
git commit -am "Escape commas in exported description fields"

git switch main
git commit -am "Rename ExpenseReport to ExpenseSummary"

git switch add-csv-export
git log --oneline
git rebase main
git log --oneline

Knowledge check

Nothing is recorded and there is no score. The explanation appears either way.

What does a rebase do to the commits on your branch?
Why should you avoid rebasing a branch that others have already pulled?

Saved in this browser only.