Intermediate

Git Debugging Techniques

In the world of software development, you can't get far without knowing how to use Git, the world's most popular and widely adopted version control system. But Git is much more than the c...

Table of Contents

  1. Course Overview
  2. Debug with binary search: git bisect
  1. Debug with file history: git blame
  1. Debug with a string or regular expression: git grep
  1. Summary

1. Course Overview

In the world of software development, you can’t get far without knowing how to use Git, the world’s most popular and widely adopted version control system. But Git is much more than the common day-to-day tasks of adding, committing, and merging your changes into a branch of your Git repository.

But what happens when you need to find a bug, fix a problem, or discover historical information? Often, developers use a graphical interface (UI) to navigate a project’s code base. But did you know that you can use Git to resolve bugs and issues faster by isolating problematic commits? Or collaborate more effectively by understanding code changes and their context in your files’ historical data? Or even efficiently navigate your code base to discover valuable information using Git as a search engine for specific patterns or text in your repositories?

What you will learn

In this course, you will learn to:

  • Become a code detective by using git bisect to identify precisely when and why a bug or regression was introduced, systematically narrowing down commits until you identify the exact change that caused the problem.

  • Become a code historian by leveraging git blame to understand the who, when and why behind every line of code.

  • Become a code explorer by using git grep to search for specific patterns or text in your repositories.

Whether you’re debugging issues, discovering information, or searching for code references, Git has equipped you with the ability to efficiently navigate and analyze your code base without having to leave your command prompt.


2. Debug with binary search: git bisect

2.1 The three Git debugging techniques

Git is indispensable in the world of software development due to its ability to maintain a detailed history of code changes and provide a robust framework for version control. It allows developers to work smoothly on complex projects, experiment with new features in isolated branches, and merge changes with confidence. Thanks to the distributed nature of Git, it provides data redundancy and serves as a reliable backup system. In open-source communities and professional development teams, Git is the backbone that streamlines workflows, builds accountability, and allows developers to build and maintain high-quality software with ease.

Of course, there are plenty of advanced Git users who have a deep understanding of the inner workings of Git and can take this theoretical definition and expand on it, enough to write a book about it — well, they have, a lot of books. But for most developers who use Git, they’ll tell you it’s about branching, making commits, and merging those changes into a target branch using some sort of merge strategy, because that’s what they mostly do on a daily basis. Knowledge of using Git to do things other than commit and merge depends entirely on personal workflows and experience using Git.

But there’s so much more to Git than its fundamental commands. So let’s see how to use Git to do more than just add, commit, and merge your changes into your Git repository.

When debugging becomes necessary

When working on a project, there is a good chance that at some point you will encounter an issue where you need to debug code or search through the code history to find a specific commit or change. You may also want to know who made this change to better understand why it was made. Maybe it was just a bug in the code, or maybe it was a change that you want to find the origin of and contact the author of the commit to find out more.

Of course, if you use a code collaboration tool like GitHub, you could use its user interface and search features to find some of this information. But what if you wanted to do this locally, or if your commit history is really long with years of commits and the UI just isn’t the best place to find some of this information?

The three Git debug commands

Git has a few debugging techniques and a few Git commands — three to be precise — that we’ll cover in this course that the ordinary Git user might not know about, but that are really handy if you know how to use them and can help you debug your code or find particular changes using your commit history.

These bugs or commits in question can be elusive, time-consuming to resolve, and sometimes even frustrating. This is where Git debugging techniques can come into play. Imagine this scenario: you are working on a team project and a bug emerges. Maybe it’s a functionality issue, a performance bottleneck, or even a security vulnerability. Either way, you need to find the problem quickly and accurately to maintain project momentum. This is where git bisect, git blame and git grep can come in handy.

We’ll explore each of these Git commands in this course, their purpose, and why they can be useful. Let’s start by looking at our first debug command: git bisect.


2.2 Use cases of git bisect

It is common for a developer to find a bug by looking at the modification that introduced it. This is most efficiently done by performing a binary search between the last known working commit and the first known broken commit in the commit history. This is where git bisect can help.

git bisect is a command that helps identify the specific commit where a bug or issue was introduced into a code base by performing a binary search of the commit history. It automates the process of narrowing the commit range to quickly identify the source of the problem.

Usage scenarios

1. Identifying the commit that introduced a bug

The most common use of git bisect is to identify the exact commit that introduced a bug or issue into a code base. You can use it to track down when and where a bug was introduced, making it easier to quickly resolve the issue.

2. Regression tests

git bisect is also widely used for regression testing. It can help you automatically identify which commit caused a test or feature to fail, allowing you to detect regressions early in the development process.

3. Performance regression

When optimizing code performance, git bisect can be used to find the commit that led to a performance regression. You can run performance tests or benchmarks at each stage to identify precisely when code performance has degraded.

In all these cases, git bisect simplifies the process of identifying problematic commits by automating binary search in the commit history, saving time and effort in debugging and troubleshooting.


2.3 How to use git bisect

Before you can use git bisect, you need a project with an existing commit history. Understanding how git bisect works is essential before you begin.

git bisect uses binary search to detect specific events in your code from commit history. To do this:

  1. You start the bisect session and specify a good commit and a bad commit in your history.
  2. The good commit is the last known point in your project history where the code was working correctly.
  3. The bad commit is where the problem or bug is present.
  4. Git then finds the midpoint between the good and bad commits for testing and positions itself on that specific commit.
  5. From here you can examine the project code as it was at the time of this commit.
  6. You check if the bug is present and inform Git of the result.
  7. Git does another split of the history commits until it has narrowed down the exact commit that made the change.
  8. Git repeats this process, reducing the commit range with each iteration, until it finds the first bad commit.
  9. This first bad commit is the commit that introduced the change into the code.
  10. Git then displays the commit hash, commit author, and other related information.
  11. Once finished, you can exit the bisect session with git bisect reset.

Basic syntax

# Démarrer une session bisect
git bisect start

# Indiquer un commit connu comme bon
git bisect good <commit-hash>

# Indiquer un commit connu comme mauvais
git bisect bad <commit-hash>

# Démarrage rapide avec good et bad en une seule commande
git bisect start <bad-commit> <good-commit>

# Réinitialiser la session bisect (sortir)
git bisect reset

2.4 git bisect in action

Let’s see git bisect in action with a real-world example using a demo repository. The repository contains a project with an existing commit history and an intentionally introduced error.

Step 1: Clone the repository and view history

# Cloner le dépôt
git clone <repository-url>

# Naviguer dans le dépôt
cd <nom-du-repertoire>

# Consulter l'historique des commits de manière concise
git log --oneline

The git log --oneline command displays a clean view of the history without the timestamp and author information. You will see the shortened commit ID along with all commit messages.

In our example, the history looks like:

b2ba1d0 dernier commit (mauvais)
2c67297 rename index.html to inde.html
...
3715426 commit initial (bon)

Looking at the commit messages, one might suspect that commit 2c67297 with the message “rename index.html to inde.html” is the culprit. But let’s treat this as if we don’t know, and let git bisect guide us to the same conclusion.

Step 2: Identify good and bad commits

We know the most recent commit is bad. Let’s take note of this commit: b2ba1d0.

To find a good commit where index.html was correctly named, let’s check a previous commit:

# Se positionner sur un commit antérieur
git checkout 3715426

# Vérifier les fichiers du projet
ls

HEAD is now in the project state at the time of this commit. We check that index.html was correctly named at this step — this gives us a good starting point.

Step 3: Start the bisect session

# Démarrer la session bisect avec le commit good et bad
git bisect start 3715426 b2ba1d0

Git will find an intermediate commit and position itself there automatically.

Step 4: Evaluate each commit

At each step, Git positions itself on a commit and you must indicate whether it is good or bad:

# Vérifier les fichiers à ce commit
ls

# Si le bug est présent (mauvais commit)
git bisect bad

# Si le bug est absent (bon commit)
git bisect good

Git continues to split the commit range with each response, providing another commit to review. You repeat these steps until you find a good commit. Git then has enough data to identify the commit that introduced the bug.

Step 5: End the bisect session

# Terminer la session bisect et retourner à l'état normal
git bisect reset

In our example, Git identified commit 2c67297 — exactly the one we suspected from the start. This example confirms that git bisect led us to the same solution we would have found manually, but in a systematic and automated way.


2.5 Automate git bisect

With a short commit history, running git bisect will only take a few iterations to find the desired commit. But if you’re working in a codebase with a much larger commit history — which is probably the case — it may take a little longer, but the steps will be the same.

However, git bisect has an option to automate the process instead of telling Git on each bisect commit whether it is good or bad after reviewing the code. You can use git bisect run and pass a script, command, or program that returns an exit code.

Exit Codes

  • Code 0 = success → commit good
  • Non-zero code = failure → commit bad

Note: There are some nuances to this command, so consult the official Git documentation to better understand its use cases.

Syntax of git bisect run

git bisect run <commande-ou-script>

Automation example

# Démarrer la session bisect avec les commits bad et good
git bisect start b2ba1d0 3715426

# Automatiser la recherche avec une commande
git bisect run ls index.html

In our case, since we are just looking for index.html and the first commit that broke the file name, we can just provide ls index.html. This command returns an exit code of 0 if the file exists (good commit) and a non-zero code if the file does not exist (bad commit).

Running this command automates each bisect revision and tests it against the desired result. And git bisect found the first bad commit which changed the name from index.html to indie.html. This is a useful way to automate the process in a much larger code base when looking to identify a specific commit that introduced a bug or issue.

Summary of git bisect commands

OrderDescription
git bisect startStarts a bisect session
git bisect start <bad> <good>Starts with the specified good and bad commits
git bisect good <hash>Mark a commit as good
git bisect bad <hash>Mark a commit as bad
git bisect run <cmd>Automates search with a script
git bisect resetEnds the session and returns to HEAD
git bisect logShows the bisect session log
git bisect skipIgnore a problematic commit

3. Debug with file history: git blame

3.1 Introduction to git blame

Because Git is a version control system, we can do things like go back in time to see our codebase at a specific point in history by checking out a commit to see its contents, run tests, and use useful Git debugging commands like git bisect to help identify the origin of a bug in the codebase introduced by a specific commit.

But unlike git bisect which is used more as a time machine to navigate you through your project history, git blame is another debugging tool. Think of it like a historian who will provide you with the historical records of a file and help you trace the origins of each line in your file, revealing the author, date, and commit responsible for its existence.

Key differences between git bisect and git blame

It is important to note a few key differences:

  • git bisect: examines your entire commit history to find the origin of a bug or problematic code. It operates on the entire project.
  • git blame: Operates only on individual files and therefore requires a file path as input when using the command. The response will be a historical record of that specific file as it exists in its most current state.

The word “blame” — do not interpret it negatively

The name git blame can sometimes have a negative connotation. It’s not just about finding fault and blaming the person who made an edit. It’s about understanding the history and context that brought this code to its current state.


3.2 Use case of git blame

Imagine you are working on a complex code base with multiple contributors. You have a problem and need to know who made the last changes to a specific line of code and when. git blame can quickly provide you with this information. With git blame, you can trace a file’s commit history line by line, helping you identify contributors potentially responsible for this bug or issue.

Usage scenarios

1. Debugging and bug investigation

A common scenario where git blame is useful is when you encounter a bug or unexpected behavior in your code. It’s like having a forensic investigator at your disposal. By running git blame on problematic code, you can trace it back to its origins, identifying the commit and the exact author responsible for introducing it. This information is not only helpful in resolving the issue, but also in understanding the idea behind the change and whether it was intentional or a mistake.

2. Code review and collaboration

git blame is not limited to troubleshooting and bug hunting. It can be a powerful tool for code review, collaboration, and code ownership. In larger projects or teams, it can be difficult to know who is responsible for maintaining certain sections of the code base. git blame allows you to identify the main contributor for a particular section or file. And this knowledge can be crucial for seeking help or code reviews from the right person when making changes.

3. Identification of the expert domain

If you are working on a project where code ownership is not clearly defined, git blame can be very useful. For example, if you were tasked with adding a new feature to a complex codebase, you could use git blame to identify the developer who constantly worked on that part of the project. This helps you to get in touch with the domain expert and get advice.

4. Historical context and evolution of the code

git blame provides historical context for each line of code. By examining the commit messages associated with each change, you can obtain information about the evolution of the code base. This can be particularly useful when trying to understand why a particular design decision was made or when tracking the development of a feature over time. By running git blame on a relevant file, you can trace the path of the code, see how it evolved, and understand the logic behind previous changes, making it easier to extend functionality while maintaining consistency.

In summary

git blame is a powerful tool in Git that adds capabilities for debugging, code analysis, identifying code ownership, and collecting historical context. It can help you navigate and understand the code base efficiently, leading to faster problem resolution, improved collaboration, and enhanced software quality.


3.3 git blame in action

Let’s use the same project used with git bisect to see how git blame works.

Step 1: View file contents

# Afficher le contenu du fichier
cat inde.html

This command displays the contents of the inde.html file in the command prompt. (Note: this Git repository is actually a working Tetris game! If the file was correctly named index.html and deployed, you could play Tetris.)

Step 2: Use git blame on the file

For this example, assume that the game speed set in the game constants section was incorrectly changed. We want to see who changed the speed from 0.6 to 0.5.

# Exécuter git blame sur le fichier
git blame inde.html

Git will display the same file contents we were just looking at, but with historical records of each line in the file, including commit ID, author, and timestamp.

The result looks like:

b2ba1d04 (Auteur    2024-01-15 10:23:45 -0500  67) const SPEED = 0.5;
3715426a (Auteur2   2023-08-10 09:14:22 -0500  68) const LEVEL = 1;
...

Scrolling down to the game constants section, we can see that the speed was recently changed with commit, author and timestamp.

Step 3: Filter by row range with -L option

If you are working in a large file with thousands of lines of code, scrolling to find the line you are looking for is a time-consuming and frustrating task. Use the -L option with a line range:

# git blame avec une plage de lignes spécifique
git blame -L 67,82 inde.html

The syntax is -L <start-line>,<end-line> followed by the file name. This gives a much easier to inspect view for the desired line item, without having to scroll through the entire file.

Step 4: Examine the commit in detail with git show

Once you have identified the suspicious commit via git blame, you can examine it in detail:

# Voir les détails du commit qui a introduit le bug
git show b2ba1d04

In the output, notice that the only change in this commit was the line in question, making this an easy fix. Once you have identified the commit that introduced the change, you can proceed to fix the error. You can simply revert this commit or make another commit with the fix.

Important note: git blame is useful when you know the file that introduced the change. If you are in a situation where you can see the effect of a bug, but you are not sure which part of the code leads to it, git bisect comes to the rescue.

Summary of git blame commands

OrderDescription
git blame <file>Shows the line-by-line history of a file
git blame -L <start>,<end> <file>Limits display to a range of lines
git blame -e <file>Shows authors’ email addresses
git blame -w <file>Ignores whitespace changes
git blame --since=<date> <file>Filter by date
git show <commit-hash>Shows details of a specific commit

Note: As with most Git commands, there are other options available when using git blame. Check out the official Git documentation on git blame to learn more about these common options and how to better filter this historical data.


4. Debug with a string or regular expression: git grep

4.1 Introduction to git grep

Finding your way through lines of code can sometimes feel like a quest for hidden treasure. The treasure, in this case, is knowledge — knowledge of where specific code patterns, functions, or variables reside in your project. We use search engines to navigate websites all the time. It would be nice to have something similar in Git. Well, Git has a command to help you do that.

git grep is a useful tool that allows you to navigate your Git repository with precision. It’s like having a search engine dedicated to exploring and analyzing code, helping you quickly locate and understand specific pieces of code or content in your Git repository. It is useful when you need to find instances of code or when you are working with a large code base and need to find specific information.


4.2 Use cases of git grep

Similar to git bisect and git blame, git grep has several options available when searching your codebase using strings or regular expressions.

Basic syntax

git grep [options] <pattern> [<chemin>...]
  • pattern: the text pattern or regular expression you want to search for in your Git repository.
  • [options]: modifiers to refine the search.
  • [<path>]: Limit the search to specific files or directories.

Common Examples

Simple search for a pattern:

# Rechercher le mot "error" dans toute la base de code
git grep error

Limit search to specific files or directories:

# Rechercher "error" uniquement dans les fichiers .js
git grep error -- *.js

# Rechercher "error" dans un répertoire spécifique
git grep error -- src/

Show line numbers:

# Utiliser l'option -n ou --line-number
git grep -n error
git grep --line-number error

This option is very useful — it’s an option used almost all the time with git grep, because it indicates exactly where in the file each match is located, making it easy to navigate directly to the relevant line.

Use regular expressions:

# Rechercher des lignes contenant un nombre quelconque de chiffres suivi du mot "error"
git grep '[0-9]*error'

# Rechercher des patterns plus complexes
git grep 'function\s\+[a-zA-Z]\+'

Search in specific branches:

# Rechercher dans une branche particulière
git grep <pattern> <nom-de-branche>

# Exemple : rechercher "speed" dans la branche game-instructions
git grep speed game-instructions

Use custom output format:

# Personnaliser la sortie avec --format
git grep --format=<format> <pattern>

# Exemple : afficher uniquement le nom de fichier et la ligne correspondante
git grep -l error  # Affiche uniquement les noms de fichiers

Note: There are several other options and shades available. The official Git documentation is a great place to see available options, examples, and outputs to better understand your specific use case.


4.3 git grep in action

Let’s return to the command prompt to see how git grep would work in this Git repository. We will use git grep to find the speed variable in our game constants. Let’s pretend we don’t know where these variables are defined or maybe we don’t know all the places that reference them.

# Recherche simple du pattern "speed"
git grep speed

With only two slots in our output, the list of options to filter is not long. But let’s say we had a lot of options in different files and we wanted to narrow the scope of our search.

Step 2: Limit the search to a specific file

We think the variable is in the inde.html file (always wrongly named). We can search for speed only in this file:

# Rechercher "speed" dans un fichier spécifique
git grep speed inde.html

This is useful — we see the speed variable to modify and we know that it is in the inde.html file. But we don’t yet know exactly where this line is located in the file. If it was a large file, we wouldn’t want to scroll through the entire file to find this item.

Step 3: Show line numbers

# Ajouter l'option de numéro de ligne
git grep --line-number speed inde.html

# Forme abrégée
git grep -n speed inde.html

With this option one can see exactly where in the file this variable is defined, making it easier to navigate to that file to make the appropriate correction.

The result looks like:

inde.html:72:    const SPEED = 0.5;
inde.html:89:    speed: SPEED,

Step 4: Search specific branches

Let’s say you have several different branches and you want to check the code changes in each of these branches to ensure that the changes are consistent across all workflows. You can simply type the name of the branch you want to check right after the pattern.

For example, with a branch called game-instructions:

# Rechercher dans une branche distante
git grep speed origin/game-instructions

The output gives similar information, but now we see the branch ID, file and line item. On this branch, the speed variable is at 0.6, which is the correct speed instead of 0.5.

The result looks like:

origin/game-instructions:inde.html:72:    const SPEED = 0.6;

Summary of git grep commands

OrderDescription
git grep <pattern>Simple search throughout the repository
git grep <pattern> -- <file>Search in a specific file
git grep <pattern> -- *.jsSearching files with an extension
git grep -n <pattern>Shows line numbers
git grep --line-number <pattern>Shows line numbers (long form)
git grep -i <pattern>Case insensitive search
git grep -l <pattern>Shows only file names
git grep -c <pattern>Shows the number of matches per file
git grep -e <pattern>Use a regular expression
git grep <pattern> <branch>Search in a specific industry
git grep <pattern> origin/<branch>Searching in a remote branch

5. Summary

This course has given you a better understanding of using these three useful Git tools: git bisect, git blame and git grep. Throughout this journey, we’ve covered skills for effectively navigating your codebase, troubleshooting issues, and discovering insights in your Git repositories.

What you learned

You have become a code detective using git bisect to identify precisely when and why a bug was introduced in your code. By systematically reducing commits, you can identify the exact change that caused the problem, saving valuable development time and ensuring code quality.

You have become a code historian, leveraging git blame to understand the who, when and why behind every line of code. This tool allowed you to track changes, explore code evolution, and collaborate effectively within your team, promoting transparency and accountability in your development process.

You have become a code explorer, using git grep to search for specific patterns of text in your own repositories. Whether you’re hunting for code snippets, references, or hidden information, Git has equipped you with the ability to efficiently navigate and analyze your codebase.

Comparison of the three tools

ToolRoleOperates onMain use
git bisectCode DetectiveThe entire depositFind which commit introduced a bug
git blameCode HistorianA specific fileKnow who modified each line and when
git grepCode ExplorerThe entire depositFind patterns or text in code

When to use which tool?

  • Use git bisect when you know a bug exists but don’t know when it was introduced. Best for regressions detected late in a long commit history.

  • Use git blame when you have identified the problematic line of code and want to know who modified it, when and why. Great for understanding the context of a specific change.

  • Use git grep when you want to find where a pattern, function or variable is used in your codebase. Great for exploring code and finding all occurrences of an element.



Search Terms

git · debugging · techniques · ci/cd · devops · bisect · blame · grep · search · commands · debug · action · syntax · binary · cases · commit · history · scenarios · session · show · specific · usage · view

Interested in this course?

Contact us to book it or get a custom training plan for your team.