Everyone knows Git as a great file versioning tool. But did you know you can also use Git to debug your code?

In today's Snippet, I'll introduce two tools I use all the time--Git blame and Git bisect--and explain how they can help you find bugs hiding in your code.

Git Blame 

Git blame is a well-known tool that displays which users most recently modified specific lines of code up to the last commit in a document. For example, the simple README.md file code below displays the partial commit hash followed by user, date, time, time zone, line number, and line content information.

$ git blame README.md

406b2818 (Jonathan 2017-12-05 15:49:16 -0200 1) #Git Debugging
406b2818 (Jonathan 2017-12-05 15:49:16 -0200 2)
06c430e2 (Jonathan 2017-12-05 16:14:49 -0200 3) This project is part...Avenue Code Snippets.
d44487fe (Jonathan 2017-12-05 15:57:23 -0200 4)
d44487fe (Jonathan 2017-12-05 15:57:23 -0200 5) ##Project content
d44487fe (Jonathan 2017-12-05 15:57:23 -0200 6)
06c430e2 (Jonathan 2017-12-05 16:14:49 -0200 7) Basic Rest based crud with Spring Boot.

In the code above, we can view 3 commits (406b2818, 06c430e2, d44487fe), followed by committer data and line content information.

Git blame has many uses, which are detailed in full in the links at the end of this article, but one of the most important uses is [-L <range>]. This option allows you to isolate and check a specific set of lines within your file. For example:

$ git blame -L 2,8 README.md

406b2818 (Jonathan     2017-12-05 15:49:16 -0200 2)
06c430e2 (Jonathan     2017-12-05 16:14:49 -0200 3) This project is part...made for Avenue Code Snippets.
d44487fe (Jonathan     2017-12-05 15:57:23 -0200 4)
00000000 (Not Committed Yet 2017-12-05 16:54:00 -0200 5) This is a unstaged change
00000000 (Not Committed Yet 2017-12-05 16:54:00 -0200 6)
d44487fe (Jonathan     2017-12-05 15:57:23 -0200 7) ##Project content
d44487fe (Jonathan     2017-12-05 15:57:23 -0200 8)

Above, we're filtering lines 2 through 8. (Note that 'blame' also shows uncommitted changes.)

Another interesting option is [-C], which is especially useful when you are refactoring a code. [-C] is helpful for finding code blocks that have been transferred from other files. Below, for example, there is a simple endpoint using Java and SpringBoot.

package edu.ac.gitdebugging;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}

	@RequestMapping("/sayHello")
	public String sayHello() {
		return "Hello";
	}

}

If I decide to move the method sayHello to a new file called MainController, I get the following:

package edu.ac.gitdebugging.web;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MainController {

	@RequestMapping("/sayHello")
	public String sayHello() {
		return "Hello";
	}

}

Now, the [-C] option displays a new column, not yet committed, that specifies the code's original location:

$ git blame -C src/main/java/edu/ac/gitdebugging/web/MainController.java
00000000 src/main/java/edu/ac/gitdebugging/web/MainController.java (Not Committed Yet 2017-12-05 18:27:29 -0200 1) package edu.ac.gitdebugging.web; 00000000 src/main/java/edu/ac/gitdebugging/web/MainController.java (Not Committed Yet 2017-12-05 18:27:29 -0200 2) 62394f5c src/main/java/edu/ac/gitdebugging/App.java        (Jonathan     2017-12-05 18:20:27 -0200 3) import org.springframework.web.bind.annotation.RequestMapping; 62394f5c src/main/java/edu/ac/gitdebugging/App.java        (Jonathan     2017-12-05 18:20:27 -0200 4) import org.springframework.web.bind.annotation.RestController; 62394f5c src/main/java/edu/ac/gitdebugging/App.java        (Jonathan     2017-12-05 18:20:27 -0200 5) 00000000 src/main/java/edu/ac/gitdebugging/web/MainController.java (Not Committed Yet 2017-12-05 18:27:29 -0200 6) @RestController 00000000 src/main/java/edu/ac/gitdebugging/web/MainController.java (Not Committed Yet 2017-12-05 18:27:29 -0200 7) public class MainController { 62394f5c src/main/java/edu/ac/gitdebugging/App.java        (Jonathan     2017-12-05 18:20:27 -0200 8)   @RequestMapping("/sayHello") 62394f5c src/main/java/edu/ac/gitdebugging/App.java        (Jonathan     2017-12-05 18:20:27 -0200 9)   public String sayHello() { 62394f5c src/main/java/edu/ac/gitdebugging/App.java        (Jonathan     2017-12-05 18:20:27 -0200 10)       return "Hello"; 62394f5c src/main/java/edu/ac/gitdebugging/App.java        (Jonathan     2017-12-05 18:20:27 -0200 11)   } ^5f1231c src/main/java/edu/ac/gitdebugging/App.java        (Jonathan     2017-12-05 15:39:16 -0200 12) }

IDEs like Eclipse (figure 01 below) and IntelliJ (figure 02 below) have a built-in 'blame' integrated with the code editor. In Eclipse, right click the line number and select 'Show revision information.' In IntelliJ, right click the line number and select 'Annotate.'

Figure 01: Blame In Eclipse

 

Figure 02: Blame in IntelliJ

Git Bisect

Now that we've covered Git blame, let's take a look at Git bisect. We used Git blame for discovering who changed your code, when they changed it, and which commit they used to do so, but if you're still usnure what's breaking your code, you can use Git bisect. Before discussing usage tips, let's review some basics.

"Git bisect" stands for "Git binary search." In a nutshell, Git bisect uses a binary search to find the commit that introduced a bug. Before looking at an example, let's quickly review binary searches. 

First of all, why do we use binary searches instead of searching commits from newest to oldest? Very often, bugs are introduced early in projects, so if you have a code sequence with ten thousand commits and the bug is in the second, you'll waste time reviewing commits chronologically. 

Binary searches simplify this process: within a collection of elements (e.g. ordered integer arrays), we start at the middle element and check if it has a bug. If it does, we check the element halfway through the preceding code; if it doesn't, we check the element halfway through the following code. We continue iterating this process within smaller groups until we identify the element, saving ourselves a lot of labor and time. (If you want to learn more about binary and other search methods, listen to this lecture from MIT Open Courseware: Binary Search, MIT Open Courseware.)

Now let's apply binary searches using Git bisect. When using the binary method to search your commit stack, you'll need to inform Git if the bug is present at each search phase until it identifies the problem commit.  

Let's take an example that starts with the first check in an entire commit stack: 

$ git log --pretty=oneline --abbrev-commit
548cf21 (HEAD -> master, origin/master) start to write git bisect example f848038 bisect snippet start f3851a1 Delete person service 4ed6ac5 Count persons a313218 Get All Persons 7f3ce1e Adding a BUG not covered by tests f2945f7 Bug fix in insert e64e564 Refactoring exception Handler d07ed1e Person Exist validation e70b341 Tests for update and get 7082948 Test base 9a87bc9 Find One and Update cc66f96 Find all and save endpoints 48476e9 Starting git bisect section 260722e Refactoring Hello World 62394f5 Hello World Endpoint 06c430e Adding snippet d44487f Adding spring dependencies 406b281 Readme basic info 5f1231c First commit

As you can see, the commit that added a bug in the project is "7f3ce1e Adding a BUG not covered by tests." Now let's run the Git bisect:

$ git bisect start

Next we need to define the range of the search:

$ git bisect bad HEAD

$ git bisect good e70b

In the first line, we define that the HEAD commit is bad. In other words, we have a bug at this commit. Next, we need to define a commit where the bug isn't present. For this, I often use the last commit of the last release. (Note that tags can help you.)

Now Git will scan an old commit:

Bisecting: 4 revisions left to test after this (roughly 2 steps)

[a3132181523726cbc77caebade9e2ae096c3f04e] Get All Persons

If you do a "Git log," you can check exactly where the Git is, and you can also check some markers:

$ git log --pretty=oneline --abbrev-commit

a313218 (HEAD) Get All Persons
7f3ce1e Adding a BUG not covered by tests
f2945f7 Bug fix in insert
e64e564 Refactoring exception Handler
d07ed1e Person Exist validation
e70b341 (refs/bisect/good-e70b3416e262f4947de25d6e324c45ea301078a8) Tests for update and get
7082948 Test base
9a87bc9 Find One and Update
cc66f96 Find all and save endpoints
48476e9 Starting git bisect section
260722e Refactoring Hello World
62394f5 Hello World Endpoint
06c430e Adding snippet
d44487f Adding spring dependencies
406b281 Readme basic info
5f1231c First commit

Now we need to go to the project and verify if the bug is still there. You can use "Git bisect bad" or "Git bisect good" to inform Git whether the bug is there or not. If the bug is still present, we inform Git accordingly: 

$ git bisect bad

Bisecting: 2 revisions left to test after this (roughly 1 step)

[e64e5646a348da525b17647d3d1e0834f0e4d7bc] Refactoring exception Handler

Then Git moves our HEAD to a commit before the bug. Check the Git log:

$ git log --pretty=oneline --abbrev-commit

e64e564 (HEAD) Refactoring exception Handler
d07ed1e Person Exist validation
e70b341 (refs/bisect/good-e70b3416e262f4947de25d6e324c45ea301078a8) Tests for update and get
7082948 Test base
9a87bc9 Find One and Update
cc66f96 Find all and save endpoints
48476e9 Starting git bisect section
260722e Refactoring Hello World
62394f5 Hello World Endpoint
06c430e Adding snippet
d44487f Adding spring dependencies
406b281 Readme basic info
5f1231c First commit

Next, we execute Git bisect good, and Git moves the HEAD to where the bug is:

$ git bisect good

Bisecting: 0 revisions left to test after this (roughly 1 step)

[7f3ce1edcb4d3632bfe665d125004465a9d6d8e3] Adding a BUG not covered by tests

Checking the log, we get:

$ git log --pretty=oneline --abbrev-commit

7f3ce1e (HEAD) Adding a BUG not covered by tests
f2945f7 Bug fix in insert
e64e564 (refs/bisect/good-e64e5646a348da525b17647d3d1e0834f0e4d7bc) Refactoring exception Handler
d07ed1e Person Exist validation
e70b341 (refs/bisect/good-e70b3416e262f4947de25d6e324c45ea301078a8) Tests for update and get
7082948 Test base
9a87bc9 Find One and Update
cc66f96 Find all and save endpoints
48476e9 Starting git bisect section
260722e Refactoring Hello World
62394f5 Hello World Endpoint
06c430e Adding snippet
d44487f Adding spring dependencies
406b281 Readme basic info
5f1231c First commit

This time, we use "Git bisect bad." Then Git moves the HEAD one last time to verify exactly where the bug was introduced.

$ git bisect bad

Bisecting: 0 revisions left to test after this (roughly 0 steps)

[f2945f726299f5ce878595d0a1b821e6c09712fc] Bug fix in insert

Once more, the log shows the following:

$ git log --pretty=oneline --abbrev-commit

f2945f7 (HEAD) Bug fix in insert
e64e564 (refs/bisect/good-e64e5646a348da525b17647d3d1e0834f0e4d7bc) Refactoring exception Handler
d07ed1e Person Exist validation
e70b341 (refs/bisect/good-e70b3416e262f4947de25d6e324c45ea301078a8) Tests for update and get
7082948 Test base
9a87bc9 Find One and Update
cc66f96 Find all and save endpoints
48476e9 Starting git bisect section
260722e Refactoring Hello World
62394f5 Hello World Endpoint
06c430e Adding snippet
d44487f Adding spring dependencies
406b281 Readme basic info
5f1231c First commit

If you look at the first Git log before the bisect, you'll see that the commit "f2945f7" is the first commit before the bug. Now when we inform Git that the HEAD is good, we'll be able to see the commit that introduced the bug:

$ git bisect good

7f3ce1edcb4d3632bfe665d125004465a9d6d8e3 is the first bad commit
commit 7f3ce1edcb4d3632bfe665d125004465a9d6d8e3
Author: Jonathan <jaraujo@avenuecode.com>
Date:  Fri Jan 12 14:40:00 2018 -0200

  Adding a BUG not covered by tests

:040000 040000 0f61c1015071199f167b1ddaee632f2368c9b99b b0e10337999203236d17756897a7f8fe071d3237 M	src

To leave bisect mode, type:

$ git bisect reset
Bisect with Automated Tests

Now that we've identified our bug location, we can use a Git bisect run command to determine whether the script is good or bad:

$ git bisect run <cmd>

We can use the run command with Maven to generate results more quickly and easily. There is only one thing that we need to set up before running bisect with Maven: if you try to create a commit at the top of a stack, Git bisect will remove commits to find the bug, so you first need a broken test to check if the bug is present. 

Here are two ways I do this:

1. Create the test - commit the test - create an auxiliary branch - reset the auxiliary branch to a past point (usually to the commit where you will use bisect good). Then, we rebase our source branch so that our new branch becomes the next test's first commit.

Let's review this process step by step. First, we create a new branch titled "tempBranch:"

$ git checkout -b tempBranch

Next, we reset the branch to a past point:

$ git reset --hard e70b

Then, we use the interactive rebase to change the order of our commits:

$ git rebase -i HEAD master

Make sure that you move the new commit to the first commit in the interactive rebase: 

pick fc93c46 Test to Check the BUG
pick d07ed1e Person Exist validation
pick e64e564 Refactoring exception Handler
pick f2945f7 Bug fix in insert
pick 7f3ce1e Adding a BUG not covered by tests
pick a313218 Get All Persons
pick 4ed6ac5 Count persons
pick f3851a1 Delete person service
pick f848038 bisect snippet start
pick 548cf21 start to write git bisect example
pick 17444c7 Removing test class
pick 8dc8f8a bisect step by step
pick 663f57f Removing temp file

Check that the commit "fc93c46 Test to Check the BUG" is right after "e70b341 Tests for update and get."

$ git log --pretty=oneline --abbrev-commit

713de85 (HEAD -> master) Removing temp file
fa8c371 bisect step by step
93bc5ea Removing test class
d57a06c start to write git bisect example
21e3530 bisect snippet start
51ac2ed Delete person service
e9c521a Count persons
59dae70 Get All Persons
af7f157 Adding a BUG not covered by tests
cffb531 Bug fix in insert
8172c65 Refactoring exception Handler
5a03476 Person Exist validation
f7c2b72 Test to Check the BUG
e70b341 (tempBranch) Tests for update and get
7082948 Test base
9a87bc9 Find One and Update
cc66f96 Find all and save endpoints
48476e9 Starting git bisect section
260722e Refactoring Hello World
62394f5 Hello World Endpoint
06c430e Adding snippet
d44487f Adding spring dependencies
406b281 Readme basic info
5f1231c First commit

2. Alternatively, we can create a new file, code the test, and keep this new file unversioned. This method is faster, but it's also easier to create a conflicting file name or lose the new test file, depending on what you do after the bisect.

If we start by creating a test that shows Git if the project is good or bad, we can use Maven to execute the next steps for us:

$ git bisect start

$ git bisect bad HEAD

$ git bisect good e70b

$ git bisect run mvn -Dtest=BugFinderTest test

Now we can wait for Maven to complete the process on its own:

$ git bisect run mvn -Dtest=BugFinderTest test

7f3ce1edcb4d3632bfe665d125004465a9d6d8e3 is the first bad commit
commit 7f3ce1edcb4d3632bfe665d125004465a9d6d8e3
Author: Jonathan <jaraujo@avenuecode.com>
Date:  Fri Jan 12 14:40:00 2018 -0200

  Adding a BUG not covered by tests

:040000 040000 0f61c1015071199f167b1ddaee632f2368c9b99b b0e10337999203236d17756897a7f8fe071d3237 M	src

bisect run success

You can view all these steps in the following video:


Now you know how to use Git blame and Git bisect to easily identify bugs in your code!

References:

Git Blame Docs:

Blame

Git Bisect Docs:

Bisect

All examples are based in following repository:

GitHub

 


Author

Jonathan Ohara

Jonathan Ohara is a Java Engineer at Avenue Code.


Debugging Remote/Containerized Apps with VS Code

READ MORE