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:
pickkeeps the commit as it is.dropleaves it out. Deleting the line does the same.rewordkeeps its changes, but git opens the edit pane again so you can write a new message.editkeeps it, then stops the rebase so you can change the commit yourself.squashfolds 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.fixupalso 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 wherenoteswas before the rebase, sogit 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 addit, thengit 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 --abortputs 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
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
- a18696a "Link the notes page"; parent dcaeb34; refs: HEAD → notes
- dcaeb34 "Add a notes page"; parent a4b8d97
- a4b8d97 "Style the heading"; parent ab89288; refs: main
- ab89288 "Add the home page"; parent 37d7cf7
- 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.