Using git bisect to spot borked yarn.lock update
Published: 2020-05-14 11:49:33

## The scenario

While we were moving all our Frontend apps into a [monorepo](https://en.wikipedia.org/wiki/Monorepo), which is backed by [yarn workspaces](https://classic.yarnpkg.com/en/docs/cli/workspaces/) we encountered a somewhat unexpected problem of misbehaving code in one of the deep dependencies.

This happened because monorepo has only one, top-level [yarn.lock](https://classic.yarnpkg.com/en/docs/yarn-lock/). So when moving the code from `application-A` to the monorepo we had to drop the `application-A/yarn.lock` file and run `yarn` to get all the `application-A` dependencies written in `monorepo/yarn.lock`.

If you ever worked on even a little big bigger project using `yarn` you will know that this can easily mean _tens of thousands_ of changed lines.

In theory, this should be fine as [semver](https://semver.org/) should guarantee that nothing should break as long as we're pinning packages in a proper way. _In theory_. Realistically speaking this broke some tests and our CI was red.

And now the _£10000_ question is: How do you find out *which* dependency is the problematic one? One of my coworkers had a great idea to hunt for the flaw in the `application-A` repo instead of the `monorepo` so that we can see the _diff_ of the packages that changed.

## The fix:

1) Go to `application-A` repo
2) Remove `yarn.lock`
3) Run `yarn`
4) Run following shell one-liner:
```sh
until git diff --quiet; do echo "yq" | git add -p; git commit -m 'hunk'; done
```

Let's break this down:
- [until](https://www.tutorialspoint.com/unix/until-loop.htm) - Like a `while` loop, but with inverted condition
- [git diff --quiet](https://git-scm.com/docs/git-diff) - This makes the output less noisy and also implies `--exit-code`, which make the program exit with `1` if there were differences and `0` when there are none
- `echo "yq"` - sends letters `y` and `q` to the next command
- [git add -p](https://git-scm.com/docs/git-add#Documentation/git-add.txt-patch) - Enter git staging in interactive mode. The letters from the previous command will make it to: `y` = stage this (one, first) hunk; `q` = quit; do not stage this hunk or any of the remaining ones
- [git commit -m](https://git-scm.com/docs/git-commit) - Commit the hunk we just staged

And now used [git bisect](https://git-scm.com/docs/git-bisect) to do all the hard work!

If you don't know `git bisect` the tl;dr is: Mark one commit as "good", one commit as "bad" and create a script that can run a test to determine whether a given commit is "good" or "bad". Git will then run a [bisect algorithm](https://en.wikipedia.org/wiki/Bisection_(software_engineering)) to automatically find the first "bad" commit for you automatically.

So let's create the _test script_ named `check.sh`:

```sh
#!/bin/bash

# Install current dependencies.
yarn 

# Run your test that will tell whether the commit is "good" or "bad".
# The example here is running EmberJS test suite.
ember test -f 'failing test' 

# Capture the exit code of our test.
e=$? 

# Make sure yarn did not pollute working copy.
git checkout yarn.lock 

# Exit the script with the exit code of our test.
exit $e 
```

And now the cool part:

1) Run `git bisect start`
2) Checkout the _latest commit_ in your branch and run `git bisect bad`
3) Checkout the _first commit before_ your series of "hunk" commits and run `git bisect good`
4) Execute `git bisect run ./check.sh`
5) Make yourself a coffee ☕️
6) When the script is done, look at the first "bad" commit and write the _old version_ of the offending package into your `monorepo/package.json` [resolutions](https://classic.yarnpkg.com/en/docs/selective-version-resolutions/) section
6) Victory dance 🕺🏻

![Git bisect example in Fork App](https://dev-to-uploads.s3.amazonaws.com/i/ghyt9mso86ezqlqbnqxb.png)

The `resolutions` section in our case was:

```json
  // monorepo/package.json
  ...
  "resolutions": {
    "fake-xml-http-request": "2.0.0"
  }
  ...
```

## Conclusion

This gave us a "pixel-perfect" pointer to the problematic part in a huge diff of one file. I believe this technique can be leveraged in other scenarios as well.

As long as you can write test script for `git bisect`, it should work for you.

The trick of dividing one big diff to multiple commits is the "new" part here. And I think the one-liner above can work in many different situations.