Professional Version Control with Git: Pt 3 – Rebase and Bisect

Hello everyone and welcome to part 3 of the professional Git series here at Erik’s Code Space. In part 1, we learned the basics and got our skills good enough to start version controlling our own projects. In part 2, we learned about the collaboration tools available in git and got our skills good enough to start contributing to open source projects. In this part, we’re going to learn about the rebase and bisect commands, two commands that help us with troubleshooting. Without further ado, let’s begin.

Keeping History Clean

In part 1, we learned about how our commit messages become the official “history” of our projects. The git history is supposed to keep a timeline-like record of the growth and change of our repository, so we try to keep our commit messages accurate and helpful, so then when someone runs a git log command against our repo, they get an accurate idea of how the project has been evolving lately.

Sometimes though, we need to rewrite history, or get rid of it completely. Maybe we made a bad commit, made a typo in our commit message, or need to combine two commits into one. That’s where git rebase comes in! Let’s get started; make a directory called rebase_and_bisect, cd into it, and run the git init command. If you’re using a Linux or Mac terminal, or GitBash for Windows, the following commands will do this:

$> mkdir rebase_and_bisect
$> cd rebase_and_bisect
$> git init

Now make a file called poem.txt and commit it to the repository:

$> touch poem.txt
$> git add .
$> git commit -m "Create file"

Now we’ll populate that file with a beautiful poem I’ve written. We will add one line at a time and commit that changes before writing the next line. I will share the commands to do this below, without the command prompt (the $>) so that you can copy and paste them into your terminal, saving you some time. (Please note, the following commands will probably not work with PowerShell or CMD, please use GitBash if you’re on Windows):

echo "Roses are red" > poem.txt
git commit -am "Add first line"
echo "JavaScript is yellow" >> poem.txt
git commit -am "Add second line"
echo "I'm learning git" >> poem.txt
git commit -am "Add third line"
echo "and I wanna say hello!" >> poem.txt
git commit -am "Add fourth line"

You might have to manually press “enter” one more time, but you should now have a four line poem in poem.txt.

There’s one more piece of housekeeping we have to do before we get on with the rest of this tutorial, setting up our git editor. The git editor is the text processing program that opens when you do things with git that require editing text. I believe the default text editor on GitBash is vim, which isn’t user friendly. Personally, I like nano, so in order to set my default git text editor to nano, I would run the following script:

$> git config --global core.editor "nano -w"

If you want to use a different program, you have to pass the path to that program in place of where I put nano -w. For a more exhaustive list of how to set specific text editors, click this link.

With this out of the way, let’s learn about rebase.

Rebase

We learned in part 1 that commit messages make up the history of our project. When someone runs git log, they see all the commit messages made on the project. Our goal is to write concise yet descriptive messages of the work we’ve done so that the collection of commit messages show an accurate picture of where our project came from and how it’s evolving.

Sometimes though, we make mistakes with our commit messages. We could make a typo, duplicate commits, or commit something we really didn’t mean to. Once we’ve made the commit though, the damage is already done, right? Wrong! With git rebase, we can rewrite history!

Making Bad Commit Messages

One of the main uses of using git rebase is rewriting commit messages. We’ll open up poem.txt in our text editor of choice and sign our name at the bottom. Then we’ll commit the changes but, oh no! We make a typo:

$> git commit -am "Signing my nammee"

Run git log to see the typo in our list of otherwise good commit messages. We’re now going to use rebase to fix this. We’re going to use the interactive version of rebase to do this, and we’re going to go back one commit when rebasing. So the command I want you to run is:

$> git rebase <strong>-i</strong> HEAD<strong>~1</strong>

The -i is for “interactive” and the HEAD~1 means “go back one commit from the latest version.” As soon as you hit enter, you should see something like this:

So the goal here is to fix the typo from our last commit message. That means that we want to “reword” the commit message, so on the first line, delete pick and type reword. Then, save the file and exit it (if you’re using nano, you save by pressing “Ctrl + o” then press enter and exit with “Ctrl + x”).

Once you’ve closed the file, another one will open containing the commit message for that particular commit. Mine looks like this:

If you missed the part about setting your default text editor and your files opened in a text editor that doesn’t seem to make sense, you might be in vim. Don’t panic, I’ll walk you through getting out of this. First, check and see that you really are in vim. If you are, you’ll notice that all the lines below the commented-out portion (the portion preceded by #s) are all tildes (~). In my GitBash, vim looks like this:

Once you’ve changed “pick” to “reword,” you have to go back into “normal” mode by pressing the Esc key. Once you’ve done this, you can save the file by first pressing the key combination for colon (:) which is Shift + ;. You’ll notice now that your cursor is now at the bottom left of the screen. Type wq! and then press enter. This is the command for saving and exiting (or writing and quitting). Repeat the process when it opens the commit message file and you should be good to go!

Consolidate Commits

Sometimes, I forget to do a whole task before I commit it. As an example, say I wanted to remove my name from poem.txt file. I open it up, delete my name, save, the file, then run:

$> git commit -am "Removed name"

But then I realize that I didn’t also delete the empty line my name was on. So I go in and delete that line, which was really part of the name-deleting work so I make a silly commit message like:

$> git commit -am "Finish removing name"

Now my history looks a little weird because I have two commits for deleting my name. There’s nothing really wrong with this except that I want my git history to be clean. In order to do that, I decide I want to put the last two commits together. So I need to do another interactive rebase, this time going back two commits, so I run:

$> git rebase -i HEAD~2

This time, when the git-rebase-todo file opens, I have the last two commits listed. This means I want to squash the most recent commit into the last one, so I change pick to squash on the second line:

There’s a ton more you can do with rebase and I encourage you to read the commented lines detailing the commands in the git-rebase-todo file. Play around with it some to get a feel for what you can do with this useful tool.

Bisect

Git’s bisect command is pretty cool because it helps us locate the source of a bug by searching for the specific commit a bug was introduced. The way it works is we first identify a “bad” commit, or a commit in which a bug exists, then we identify a commit in which the bug does not exist, or a “good” commit. Then, git will checkout old commits between the good and bad ones and ask us if the bug still exists.

Then, by process of elimination, we eventually pinpoint the exact commit that broke introduced the problem. It might sound confusing, so let’s just get hands on with it. I’m going to give you another list of terminal commands to run. One of which will intentionally introduce a bug, which we’ll then pinpoint with the bisect tool. Run the following commands:

echo "This poem is about JavaScript" >> poem.txt
git commit -am "Add description of poem"
sed -i -e 's/yellow/orange/g' poem.txt
git commit -am "Replace 'yellow' with 'orange'"
echo "This is for my git tutorial" >> poem.txt
git commit -am "Add reason for poem"

Now, take a look at the contents of poem.txt and note the bug, “yellow” seems to have been replaced with “orange.” See for yourself:

$> cat poem.txt
Roses are red
JavaScript is <strong>orange</strong>
I'm learning git
and I wanna say hello!
This poem is about JavaScript
This is for my git tutorial

We’re now going to use bisect to identify the commit that introduced this bug. The first thing we have to do is identify a good commit–where the bug doesn’t exist–and a bad commit–where the bug does exist. Run git log to find these commits. Please note that your commit hashes will not match mine, so for the rest of the article, you cannot copy/paste my example commands, you’ll have to use your relevant commit hashes.

My git log looks like this:

Now, I need to start the bisect wizard by running git bisect start, then identify the good and bad commits with git bisect good [hash] and git bisect bad [hash]. Take a look:

$> cat poem.txt
Roses are red
JavaScript is yellow
I'm learning git
and I wanna say hello!
This poem is about JavaScript

The word “yellow” is still there, so we run git bisect good. Git then picks another commit between this one and the commit we marked as bad. Take a look:

We’ll now exit the bisect wizard by running git bisect reset which returns us back to the most recent commit. Now that we know which commit introduced the “yellow -> orange” bug, we’ll use rebase to get rid of it.

Using Rebase to Fix Bug

Since we know the commit that introduced the bug, we should check and see what was going on with that commit. We can compare the commit with it’s previous commit to see what changes were made by using the git diff command and passing in two hashes.

For me, the hash for the bad commit was d5ebcc3. If I want to reference the commit before that without looking up its hash, I can append ~1 to the hash as something of a relative way of referencing the previous commit. In other words, if I want to compare d5ebcc3 and the commit prior to it with diff, I would run the following command:

$> git diff d5ebcc3<strong>~1</strong> d5ebcc3

The output then shows me what changed in that commit, see my screenshot:

$> git rebase -i d5ebcc3~1

This brings us back to git-rebase-todo file we saw earlier. This time, I’m going to drop the offending commit. Not only will that remove that commit’s message from the project’s history, it’ll also get rid of the changes made in that commit. Make your file look like mine (but don’t change the commit hashes):

$> cat poem.txt
Roses are red
JavaScript is yellow
I'm learning git
and I wanna say hello!
This poem is about JavaScript
This is for my git tutorial

Great! The bug has been fixed, now run git log and note that the offending commit has been stricken from the record:

Conclusion

That concludes part 3 of the Professional Version Control with Git series. In part 1, we learned the basics of using git for our own projects, then in part 2 we learned about the tools for collaboration. Finally, in this part, we learned how to use rebase to keep our history clean, and bisect to help us track down bugs. That concludes the Professional Version Control with Git series, but keep an eye out for more content about version control and collaboration tools. Don’t hesitate to contact me with any questions you have by emailing [email protected] or hitting me up on Twitter, @ErikWhiting4. Thanks, and see you next time!

20