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.
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
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):
You might have to manually press “enter” one more time, but you should now have a four line poem in
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
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!
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"
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>
-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:
Let’s talk about what we’re looking at real quick. This text file is called
git-rebase-todo and lives inside the local
.git directory for this project. The first line is the commit we’ve rolled back to, the one with the typo. Below that is a few lines to help users figure out what to do from here, and is pretty helpful once you get the hang of rebasing. For now though, I’ll just walk you through the next few steps.
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:
Scroll over to
nammee and change it to
name, save, and exit. You’ll be returned to the terminal with a message about successfully rebasing. Run
git log again and what do you notice? The typo from our last commit message is gone and our history is saved! Here’s what mine looks like:
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:
Operating vim is a little weird if you’re not used to it. When it first opens, you’re in “normal” mode and you can’t edit text the way you think you can. To enter “insert” mode, just type the letter
i. Once you’ve done this, you can edit the text like you would in a normal program, using the arrows to maneuver around and the backspace and delete buttons the way you’d expect.
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!
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
squash on the second line:
Notice that I added the
# symbol in front of the line underlined in red. From here, save and exit this file again to return to the terminal. Run
git log once again and what do you notice? We’ve successfully merged the last two commits into a single commit, keeping our history clean!
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 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:
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:
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:
Since I know that the bug currently exists, I’m going to call my most recent commit (
b33c81d) the bad one, and I know that the second line still said “yellow” when I signed my name, so I’m going to call that commit (
00b7e71) the good one.
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:
Git then picks a commit between the good and bad ones. From here, we’re supposed to check and see if the bug still exists, if it does, we tell it with
bad if not, we use
good. So check the file for the bug:
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:
When we check the contents of the
git bisect bad. With this information, git now believes it’s found the commit that introduced the bug. It correctly identifies the commit in which we replaced “yellow” with “orange” (for me, commit
d5ebcc3) as the offending commit.
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.
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:
Since I can see from this diff that nothing other than changing the word “yellow” to “orange” happened in this commit, I can safely assume that the whole commit can be removed from the project’s git history. I will use rebase to expunge any record of that commit ever taking place. To do this, I need to do another interactive rebase, this time to the commit before
d5ebcc3. In order to this, I run the following command:
$> 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):
Save and exit the file and you’ll be returned to the terminal with the message “Successfully rebased and updated refs/heads/main.” First, let’s make sure the bug is fixed.
cat the contents of
Great! The bug has been fixed, now run
git log and note that the offending commit has been stricken from the record:
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 firstname.lastname@example.org or hitting me up on Twitter, @ErikWhiting4. Thanks, and see you next time!