Git’s rebase feature is a powerful and advanced tool that can help you convert a messy commit history into useful notes for future developers. Sometimes that future developer might even be you!
More precisely, the interactive rebase feature can be used to re-write history to make it appear like you never made a mistake and wrote all of your perfect code in one try. Yes, it is sorcery. No, you do not need a Hogwarts degree perform it.
So you’ve done your work in your feature branch and, hopefully, have been generating frequent, isolated commits for your work in progress. When you run $ git log
your history may look something like this:
commit 28f0fed0fbefab20436e9d0927d0e07241b1db05
Author: Matt Langan <matt.langan@austintexas.gov>
Date: Wed Jun 28 13:25:05 2017 -0500
wip workflow
commit 88c7675ef37489056ff0550cd3216d6c7599a99f
Author: Matt Langan <matt.langan@austintexas.gov>
Date: Wed Jun 28 13:24:51 2017 -0500
wip setup
commit cdf3bf1fde1c3c138eb73a020c375b70c7a22b65
Author: Matt Langan <matt.langan@austintexas.gov>
Date: Wed Jun 28 13:00:27 2017 -0500
wip local setup
commit 70adbee56a3b0a8e766e0eba120dc390813895ee
Author: Matt Langan <matt.langan@austintexas.gov>
Date: Wed Jun 28 13:00:03 2017 -0500
wip unified guides
commit 69bcb81dd6e1749560a51775e66a7befd6c6fd5e
Author: Matt Langan <matt.langan@austintexas.gov>
Date: Wed Jun 28 08:35:43 2017 -0500
wip aggregate all guides outside of workflow
So you have a bunch of “wip” (work-in-progress) commits that you are now ready to do 2 things with:
workflow.md
should be grouped into one single commit that tells the story of one cohesive re-write of the file.First, you’ll probably need to get out of your log history by typing q
in front of the :
at the bottom of your shell. It will look something like this:
commit 610e2a16ffca9665aabe9f58055f3b2bc64a4895
Author: Matt Langan <matt.langan@austintexas.gov>
Date: Wed Jul 5 10:25:58 2017 -0500
wip workflow
:
Now that you’re back in your command prompt you need to tell Git 2 things:
The first step is handled by a simple Git command $ git rebase -i
, and the second part is handled by appending additional context to that command. There are 2 ways to perform that second step.
If you know exactly which commit you want to go back to you can copy its hash and append it to the rebase command, so your whole command would look like:
$ git rebase -i 610e2a16ffca9665aabe9f58055f3b2bc64a4895
That will tell Git that you’re interested in rewriting the history starting at your most recent commit, going all the way back to the commit for the hash you provided.
The other option is simply tell Git that you want to go a set number of commits back from your latest commit. Let’s say you only wanted to rewrite your most recent commit. In that case you would run:
$ git rebase -i HEAD~1
Want to rewrite your last 7 commits?
$ git rebase -i HEAD~7
If your machine is properly configured then your default Git editor should open up a window that consists of your commit log at the top and rebase instructions at the bottom.
pick 6c6fd5e wip aggregate all guides outside of workflow
pick 13895ee wip unified guides
pick 7a22b65 wip local setup
pick 599a99f wip setup
pick 1b1db05 wip workflow
# Rebase 1b1db05..6c6fd5e onto e77065b (5 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
Note that the display order is the opposite from what you see when you run $ git log
. The most recent commit is at the bottom, and the oldest is at the top.
It’s critical that you treat this file with caution, as the work done here will be permanent. For example, if you delete one of the commit lines that commit will be lost. For this reason it is recommended that your local copy be pushed to the remote before performing the rebase. That way, if you make a mistake you can’t recover from during the rebase, you can always delete your local feature branch, re-pull it from the remote, and start over.
You’ll notice the list of commands available, starting with pick
and ending with drop
. By default, all of the commits are set to pick
which, as you can see, simply tells Git to use the commit as-is. This retains the code change, the title, the commit body, and its order in the log. If you leave all actions as pick
and close this window then the rebase will run and the history will not be changed.
Once you’ve selected your commands you need to save the file and close the window. If you’re using Atom you need to close the the entire Atom application window. Once you’ve done that you should be redirected to your shell, where you will see messages from Git about its attempt to run your commands.
If the rebase fails or hits a conflict then you will see an error message. You can always run $ git rebase —abort
to cancel the rebase and return to the commit history you had before attempting the rebase.
The simplest change to make in a rebase is the reword
. This lets you edit a commit’s title and body, but makes no other changes to the history or code. To reword a commit, replace pick
with r
beside the commit hash.
When Git runs your reword
command it will once again launch your default editor, this time with a file open for just that commit. Supply your commit title and message, then save and close the file to apply the commit. Remember to insert an empty line between your title and commit body!
Refer to writing-a-commit.md for guidelines on how to author your title and body.
As with the rebase log, if you’re using Atom then both the file and application windows must be closed in order for Git to continue performing the rebase.
This is the command to use to combine multiple sets of changes into a single commit, while only retaining the commit title and message of the parent (top) commit. For example, if we applied the following changes to the rebase file…
pick 6c6fd5e wip aggregate all guides outside of workflow
f 13895ee wip unified guides
pick 7a22b65 wip local setup
pick 599a99f wip setup
pick 1b1db05 wip workflow
… then the code changes in 13895ee
would be combined into the commit above it (6c6fd5e
), and the commit title and body from 13895ee
would be destroyed. If that rebase were to be completed successfully then the next time you brought up the rebase window you would see
pick 6c6fd5e wip aggregate all guides outside of workflow
pick 7a22b65 wip local setup
pick 599a99f wip setup
pick 1b1db05 wip workflow
This will remove the commit and your history will appear as though it was never created in the first place.
You can move lines around to change the order in which they will be reflected in the log. This is also useful when combined with a fixup. Let’s take another look at our messy history and work through a reorder and fixup strategy. In this case, let’s say that the we wanted to combine our most recent commit 1b1db05
with the first commit in our rebase history 6c6fd5e
.
Our original commit history looks like this:
pick 6c6fd5e wip aggregate all guides outside of workflow
pick 13895ee wip unified guides
pick 7a22b65 wip local setup
pick 599a99f wip setup
pick 1b1db05 wip workflow
First, we would move pick 1b1db05 wip workflow
from line 5 to line 2, resulting in:
pick 6c6fd5e wip aggregate all guides outside of workflow
pick 1b1db05 wip workflow
pick 13895ee wip unified guides
pick 7a22b65 wip local setup
pick 599a99f wip setup
Next, we apply the fixup
to the more recent commit in order to combine it with its intended parent commit:
pick 6c6fd5e wip aggregate all guides outside of workflow
f 1b1db05 wip workflow
pick 13895ee wip unified guides
pick 7a22b65 wip local setup
pick 599a99f wip setup
Now when we save and close the commit file Git will apply the code changes from 1b1db05
to 6c6fd5e
.