Rebasing

Rebasing is the other way to bring branches together. Instead of tying two histories with a merge commit, git rebase replays your branch's commits on top of another branch, as if you had started your work from its latest commit. The result is a straight, tidy history.

The situation

Sam has two commits on a soups branch. Meanwhile main has moved on:

$
git log --oneline --graph --all
Output
* 34bcd14 (main) Add chives to omelette
| * c3f01f0 (HEAD -> soups) Add pea soup
| * dc6ea2b Add tomato soup
|/
* 90c0089 Add omelette recipe
* c4f5e47 Add pancake recipe

A merge would join them with a merge commit. A rebase instead moves the soup commits so they start from the tip of main.

Rebasing a branch

While on the branch you want to move, name the branch to rebase onto:

$
git rebase main
git log --oneline --graph --all
Output
Successfully rebased and updated refs/heads/soups.
* 5da11b5 (HEAD -> soups) Add pea soup
* ff51e53 Add tomato soup
* 34bcd14 (main) Add chives to omelette
* 90c0089 Add omelette recipe
* c4f5e47 Add pancake recipe

The history is now a straight line. The soup commits come after "Add chives to omelette", and their hashes have changed — rebasing creates new commits with the same changes and messages but a different parent:

TXT
before:                                   after:
        C ◀── D   soups                                 C' ◀── D'   soups
       /                                               /
  A ◀── B ◀── E   main                    A ◀── B ◀── E   main

Now main can take the soups with a simple fast-forward, and there's no merge commit:

$
git switch main
git merge soups
Output
Switched to branch 'main'
Updating 34bcd14..5da11b5
Fast-forward
 pea-soup.txt    | 1 +
 tomato-soup.txt | 1 +
 2 files changed, 2 insertions(+)
 create mode 100644 pea-soup.txt
 create mode 100644 tomato-soup.txt

Merge or rebase?

MergeRebase
HistoryShows exactly what happened, including parallel workA straight line, as if work happened in sequence
Existing commitsUnchangedReplaced by new copies
ConflictsResolved once, in the merge commitMay need resolving once per replayed commit
Safe on shared branchesYesNo — only on commits you haven't shared

Both are fine choices, and teams pick a style. A common combination: rebase your own feature branch onto the latest main to keep it up to date and tidy, then merge it into main through a pull request.

The golden rule

Don't rebase commits that other people already have. Rebasing replaces commits with new ones. If a teammate built on the old commits, their history and yours no longer match, and sorting that out is confusing and error-prone. Rebase freely on local commits and on your own feature branch; never rebase main or another shared branch.

If you rebase a feature branch you've already pushed (and only you work on), the next push must be forced with git push --force-with-lease (lesson 32).

Conflicts during a rebase

Each commit is replayed one at a time, so a conflict stops the rebase at that commit:

$
git rebase main
Output
Auto-merging tomato-soup.txt
CONFLICT (content): Merge conflict in tomato-soup.txt
error: could not apply 9a7a2d7... Make tomato soup spicy
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 9a7a2d7... Make tomato soup spicy

Resolve it as with a merge conflict: edit the file, then git add it. Instead of committing, tell the rebase to carry on:

$
echo "Tomato soup: tomatoes, onion, stock, basil, chilli" > tomato-soup.txt
git add tomato-soup.txt
GIT_EDITOR=true git rebase --continue
Output
[detached HEAD 6aca843] Make tomato soup spicy
 1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/spicy.

(git rebase --continue opens your editor so you can adjust the commit message; the GIT_EDITOR=true prefix here just accepts it unchanged.) While a rebase is paused you have three choices:

CommandEffect
git rebase --continueCarry on after resolving and staging
git rebase --skipDrop the commit that conflicted and continue
git rebase --abortCancel the whole rebase and go back to how things were

Pulling with rebase

The most everyday use of rebasing is updating a branch from the server. git pull --rebase fetches new commits and replays your local, unpushed commits on top of them, instead of creating a merge commit each time you pull (lesson 34).

Rebasing onto another base

--onto moves a range of commits to a completely different starting point — for example, if you accidentally started a branch from another feature branch instead of main:

$
# move the commits that are on my-feature but not on other-feature onto main
git rebase --onto main other-feature my-feature

Try it yourself

Create a branch with two commits, then add a commit to main. Rebase the branch onto main and check with git log --oneline --graph --all that the history is a straight line. Fast-forward main to include it.