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 -i HEAD~1
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:
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:
Did you get Stuck in Vim?
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!
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:
On the next screen, I will pick the commit message I want to keep. I’m going to remove the Finish removing name
message by commenting it out, the file will look like this:
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
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 orange
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:
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:
$> 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:
When we check the contents of the poem.txt
file, we see that we called JavaScript “orange” instead of “yellow.” So we mark this one as bad with 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.
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~1 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 poem.txt
and make sure JavaScript is yellow again:
$> 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:
And now we know how to use bisect
to locate bugs and rebase
to get rid of them!
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 ewhiting@erikscode.space or hitting me up on Twitter, @ErikWhiting4. Thanks, and see you next time!