Lesson 4 of 5 in Rewriting History

Interactive rebase

Story: Before your timeline goes into the archive, tidy it up: no dead ends, no half-finished events, every label spelled right.

While you work on a branch, its history gets untidy. There's a commit that was only for testing, a fix for a typo you committed an hour ago, a message with a spelling mistake. Before you share the branch, you can tidy it up. An interactive rebase lets you decide what happens to each of its commits.

The todo list

git rebase -i main finds the same commits as git rebase main: the ones on your branch that main doesn't have. Instead of replaying them straight away, it opens them in the edit pane as a list, called the todo list:

pick e3a35b5 Add a notes page
pick 7c37ef5 Add debug output
pick 36a03f8 Link the notes page
pick 39dc1a4 Fix a typo in the notes page

# Rebase a4b8d97..39dc1a4 onto a4b8d97 (4 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
...

Each line is one commit: a command, the commit's id and its message. The oldest commit is at the top, the opposite way round from git log. The lines that start with # are comments. They list every command, and git ignores them.

You change the list and save it. git then replays it from the top, onto main, one line at a time. If you save the list as it is, every line is pick, and nothing changes.

What you can do with a commit

Change the word at the start of its line:

  • pick keeps the commit as it is.
  • drop leaves it out. Deleting the line does the same.
  • reword keeps its changes, but git opens the edit pane again so you can write a new message.
  • edit keeps it, then stops the rebase so you can change the commit yourself.
  • squash folds the commit into the one on the line above. git opens the edit pane with both messages, so you can write one message for the combined commit.
  • fixup also folds it into the one above, but keeps only that commit's message, without asking.

You can also move lines to change the order of the commits. That's how you put a fix directly under the commit it fixes, so that fixup can fold it in. The first line can't be squash or fixup, because there's nothing above it to fold into.

Each command has a one-letter short form: p, d, r, e, s and f.

An example

Here's the list above, tidied up:

pick e3a35b5 Add a notes page
fixup 39dc1a4 Fix a typo in the notes page
drop 7c37ef5 Add debug output
pick 36a03f8 Link the notes page

The typo fix has moved up under the commit it fixes, to be folded into it. The debug commit is dropped. Save the list, and git replays it:

$ git rebase -i main
Successfully rebased and updated refs/heads/notes.

notes now has two commits: Add a notes page, with the typo fixed, and Link the notes page.

Stopping to change a commit

When a line says edit, git replays everything up to and including that commit, then stops:

Stopped at e787aa4...  Add a contcat page
You can amend the commit now, with

  git commit --amend

Once you are satisfied with your changes, run

  git rebase --continue

HEAD is detached at that commit, and the lines after it are still to do. Change the commit with git commit --amend, as you learned in Undoing Things. To fix just its message:

git commit --amend -m "Add a contact page"
git rebase --continue

git rebase --continue replays the rest of the list. reword gets you to the same place in one go, by asking for the new message while the rebase runs.

It's still a rebase

Everything from the Rebase Lesson still holds:

  • The new commits are copies with new ids, so the originals fade in the Live Graph.
  • notes@{1} is where notes was before the rebase, so git reset --hard notes@{1} undoes the whole thing.
  • If a line can't be replayed cleanly, the rebase stops with a conflict. Resolve it as before: fix the file, git add it, then git rebase --continue. Moving lines makes this more likely, because a commit can then come before a change it builds on.
  • While the rebase is stopped, git rebase --abort puts everything back as it was.

Which commits are listed

git rebase -i main lists the commits on your branch that main doesn't have, and keeps the result on top of main's newest commit. You can also name a commit on your own branch: git rebase -i HEAD~2 lists only your last two commits and leaves the ones before them alone. Either way, the list starts just after the commit you name.

The Walkthroughs in this Lesson write the list with names such as notes~2 instead of ids. git accepts any name for a commit there.

Tidy before you share

An interactive rebase replaces every commit from the first one you change onwards. Do it while the commits are only in your repository. The next Lesson, Rewriting shared branches, covers a branch you've already pushed.

New commands

  • git rebase -i

Explainer: An interactive rebase tidies a branch

project: your repo, you are here37d7cf7 Add a README37d7cf7ab89288 Add the home pageab89288a4b8d97 Style the headinga4b8d97e3a35b5 Add a notes page (unreachable)e3a35b57c37ef5 Add debug output (unreachable)7c37ef536a03f8 Link the notes page (unreachable)36a03f839dc1a4 Fix a typo in the notes page (unreachable)39dc1a4a18696a Link the notes pagea18696adcaeb34 Add a notes pagedcaeb34mainHEAD → notes
The repository at the end of the Explainer.
Graph as text

project: your repo, you are here

  • HEAD → notes at a18696a "Link the notes page"

Branches and tags

  • main at a4b8d97 "Style the heading"
  • notes at a18696a "Link the notes page"

Commits, newest first

  1. a18696a "Link the notes page"; parent dcaeb34; refs: HEAD → notes
  2. dcaeb34 "Add a notes page"; parent a4b8d97
  3. a4b8d97 "Style the heading"; parent ab89288; refs: main
  4. ab89288 "Add the home page"; parent 37d7cf7
  5. 37d7cf7 "Add a README"; no parent

Unreachable

  • 39dc1a4 "Fix a typo in the notes page"; parent 36a03f8
  • 36a03f8 "Link the notes page"; parent 7c37ef5
  • 7c37ef5 "Add debug output"; parent e3a35b5
  • e3a35b5 "Add a notes page"; parent a4b8d97

Challenge 1 of 4

Story: One event on your timeline was only a test run. Strike it from the record before anyone reads it.

You're on notes, which has three commits that main doesn't. You haven't pushed it yet. The middle commit, Add debug output, was only for you.

Use an interactive rebase to remove that commit from notes, so that notes has just Add a notes page and then Link the notes page.

Unlocks after Remotes & Collaboration and Undoing Things.

Terminal

Unlocks after Remotes & Collaboration and Undoing Things.

This Lesson can be played once Remotes & Collaboration and Undoing Things are complete. Its text and Explainer are open to everyone.