Level 14

Rebase

Transfer a whole branch of commits onto a new base commit, and learn why that rewrites history.

In Level 14 we mix things up a bit. As you have probably gathered, in Git there are often several ways to do something. Back in Level 8 you used git merge to combine branches, and you saw the result depends on whether the branches had diverged (a merge commit) or not (a fast-forward).

But just wait, my little sapling. There is more. A totally separate command can rearrange branches: git rebase.

Step 1 of 15
0 xp

What a base is

Fundamentally, rebasing changes the base commit of a branch. A base commit is the starting point where two branches split off from one another. So re-base literally means moving all of a branch's commits onto a new base commit. Get it? RE-BASE.

In scrapbook terms, it is like transferring all the pages in one section to the front of a different section. But before you can change the base of a branch, you need a branch to change.

A third section

Create it:

Type this into the terminal below
git branch feature

You created a branch called feature. Three green markers now stand on the same page. dev is still the active branch, so before switching to the new one, you will make one more commit on dev.

safe Lists or creates branches; nothing at risk.

dev moves on

It is STILL your birthday, apparently. You update the print statement in birthday.py to:

print("It's STILL mah birfday!")

Stage it and commit it, as usual.

Stage it

To the mat.

Type this into the terminal below
git add birthday.py
safe 'add' stages files; nothing is discarded.

Commit on dev

Commit on dev:

Type this into the terminal below
git commit -m "Still my birthday"

HEAD and the dev marker moved forward to the new page, because dev was the active branch. main and feature stayed behind on the old page.

safe Creates a new commit; nothing at risk.

Over to feature

Switch to the new branch:

Type this into the terminal below
git switch feature

HEAD moved back to sit on the feature marker on the prior page. feature is checked out; it is the active branch now. Your files roll back to that page too, so the STILL-birthday edit is not on your table. It is safe on dev.

safe Switches to 'feature'.

A cool new feature

Now create two new files to hold the code for the new feature. In feature.py:

print("This is a cool new feature!")

And in feature.html:

<title>A cool new feature</title>

Normally you could stage and commit both at once. For the sake of this exercise, stage and commit each file separately, so there are two pages to move.

Stage the first file

Just the Python file for now.

Type this into the terminal below
git add feature.py
safe 'add' stages files; nothing is discarded.

First feature page

One page on feature.

Type this into the terminal below
git commit -m "Add feature.py"
safe Creates a new commit; nothing at risk.

Stage the second file

And now the HTML file.

Type this into the terminal below
git add feature.html
safe 'add' stages files; nothing is discarded.

Second feature page

Second page:

Type this into the terminal below
git commit -m "Add feature.html"

Awesome stuff. That is one heck of a feature. Note how the two commits on feature have diverged from dev: two pages on feature that dev does not have, and one page on dev that feature does not have.

safe Creates a new commit; nothing at risk.

Diverged, again

Let's pretend the feature is complete. Your next task is to integrate the two new feature pages into dev, so you can share the feature with your team.

As you learned in Level 8, you could use git merge for this. But what if you want to avoid that good-for-nothing, UGLY merge commit? This is where git rebase comes in. It brings your two feature pages onto a new base commit: the tip of dev. In scrapbook terms, it moves all the pages of the feature section to the front of the dev section.

git rebase <new-base>

Your turn: move the pages

You are on feature. Which command transfers its two pages onto the tip of dev with no merge commit?

Rewritten

Here is the catch, and it is not all acorns and berries. Rebasing REWRITES those commits onto a new base. You told Git: change the parent of the first feature page to the tip of dev. News flash: a commit's ID depends on the ID of its parent. So the first feature page got a new ID. That page is the parent of the second, so the second got a new ID too. And so on, until every page in the section has been rewritten.

The old pages are still in the binder for a while, faded, unreachable. The identities of the rebased commits were quite literally recalculated to move them.

When rewriting is trouble

So why is this bad? It depends. If the commits you rebased have not been shared by pushing them to a remote, meaning they are still LOCAL, there is usually no problem with rewriting them. But if you already pushed them, their IDs are in other people's repositories. Rebase now, and sending them to the remote again gets... complicated to reconcile.

Rule of thumb for commands that rewrite history: only use them on LOCAL commits that have not been pushed, unless you really know what you are doing.

One more option worth knowing: git rebase -i <commit> runs an interactive rebase, which lets you tell Git what to do with each commit: keep it, modify it, combine it with others, or delete it. Advanced, very powerful, and worth looking into once you have mastered the basics.

git-sim
Initial CommitDevlands

The graph appears here when the first command runs. The simulations are drawn by git-sim from a real repository.

The scrapbook · one picture for every Git idea

The scrapbook picture appears as the story is told.