- 1. Getting geeky with Git #1. Remotes and upstream branches
- 2. Getting geeky with Git #2. Building blocks of a commit
- 3. Getting geeky with Git #3. The branch is a reference
- 4. Getting geeky with Git #4. Fast-forward merge and merge strategies
- 5. Getting geeky with Git #5. Improving merge workflow with rebase
- 6. Getting geeky with Git #6. Interactive Rebase
- 7. Getting geeky with Git #7. Cherry Pick with Reflog
- 8. Getting geeky with Git #8. Improving our debugging flow with Bisect and Worktree
The debugging process is not always easy, and the bugs are sometimes difficult to track down. Fortunately, Git provides us with tools that might make this process easier. In this article, we learn both the worktree and bisect tools.
Sometimes, when we focus on a new feature, a bug pops up in production. Unfortunately, this might require us to switch to a different branch. This might prove to be quite a hassle because we don’t want to lose our current work. The more we improve this process, the easier it will be for us to come back to our original focus.
When we initialize a new repository, Git creates a working tree for us. It is essentially one local copy of our repository. We refer to it as the main working tree.
The interesting thing is that we can have multiple working trees. By doing so, we can check out more than one branch simultaneously.
git checkout -b feature-a
npm install jquery
echo "console.log('I am switching to jquery!');" >> ./feature-a.js
If we now want to switch to the master branch, we would either need to stash our current changes or create a commit. To avoid it, we can create a linked working tree instead.
git worktree add ../master-branch master
We create a new directory on our drive and link it to the master branch with the above. Let’s list all of our working trees.
git worktree list
/home/marcin/Documents/Projects/bugs 786f0ea [feature-a]
/home/marcin/Documents/Projects/master-branch f256fa2 [master]
Please note that the directory created when creating a linked working tree does not include any files listed in our .gitignore file. This means that we probably need to install dependencies and add environment variables to our project’s new copy.
Since the branch we currently work on has different dependencies than the master branch, we can run the npm install inside the new directory.
npm install ../master-branch
If you feel like that might not be necessary and will take too much time, you can copy your existing node_modules directory.
cp -r ./node_modules/ ../master-branch/node_modules
Cleaning up after the linked worktree
An important thing to realize is that Git is aware of all of the linked working trees. In fact, it prevents you from checking out the same branch twice.
git checkout master
fatal: ‘master’ is already checked out at ‘/home/marcin/Documents/Projects/master-branch’
There are two ways to clean up if we don’t need our linked worktree anymore. The most straightforward is to use the git worktree remove command.
git worktree remove ../master-branch/
At some point, we might have removed the directory that contains our linked worked tree. In this case, we can use the git worktree prune command to remove the information about the linked worktree.
rm -rf ../master-branch/
git worktree prune
When looking into an issue, we are sometimes not sure what lines of code caused it. A viable strategy is to check out every commit one by one to see which change caused the bug. This is not the most elegant solution, though. Fortunately, Git has a bisect tool that can do just that. Its purpose is to help us find a commit that introduced a bug to our application.
Let’s say that our app’s current state contains the bug, and we know that it worked properly ten commits ago. If that’s the case, the regression probably was introduced somewhere along with those ten commits. The bisect tool starts checks out a commit right in the middle of a known good and bad state. Doing so repeatedly reducers the number of commits each time by half.
To start the bisect process, we need to run git bisect start. Now, we need to point to a state that we know does not contain the bug. To do that, we need to provide the hash of a commit.
git bisect good 266c5209
We also need to tell Git when the application contained the bug. If we don’t provide the hash, Git assumes that the application’s current state has the bug.
git bisect bad
Doing the above starts the process of a binary search. Git checks out a commit right in the middle of a known good and bad state. Our job is to test the application to determine if the bug is still there.
After doing that, we either run git bisect good, or git bisect bad. Every time we do it, Git checks out into a commit in the middle. After a few times, Git can pinpoint the specific commit that introduced the bug.
You can see a very good video representation of this process in this video
The smaller and more specific commits we have, the easier it is to find the issue using the bisect tool. Also, squashing many commits into one will decrease the effectiveness of bisect.
Instead of running git bisect good or git bisect bad, we can also use git bisect skip if we can’t test the commit for some reason. Unfortunately, this might prevent Git from finding the exact commit that introduced the issue.
You can also use the Bisect run functionality to automate the whole process. This is possible only if you can write a script that can determine if the application’s given state is valid.
With linked worktrees, we can create additional local versions of our repository more easily than with git clone. It might prove to be useful if we need to switch our focus for some period of time. Using linked worktrees also allows us to run a few instances of our application and compare them if we need to.
Once you have set up a working tree, you can use the bisect tool to determine the exact commit that introduced the issue. Even though the first thing might be to use it for looking for bugs, it doesn’t have to be the case. For example, we could use it to find the first time a feature was introduced.