Join the discussion

Write your take first — we'll ask for email only when you're ready to publish.

  • Hacker News
  • > People rant about having to learn algorithmic questions for interviews. I get it — interview system is broken, but you ought to learn binary search at least.

    Well, the example of git bisect tells you that you should know of the concept of binary search, but it's not a good argument for having to learn how to implement binary search.

    Also just about any language worth using has binary search in the standard library (or as a third party library) these days. That's saner than writing your own, because getting all the corner cases right is tricky (and writing tests so they stay right, even when people make small changes to the code over time).

    by eru
  • My personal mantra (that I myself cannot uphold 100%) is that every dev should at least do the exercise of implementing binary search from scratch in a language with arbitrary-precision integers (e.g., Python) once in a while. It is the best exercise in invariant-based thinking, useful for software correctness at large
  • Unfortunately I can't find the reference now, but I remember reading that even though binary search was first described in the 1940's, the first bug-free implementation wasn't published until the 1960s.

    The most problematic line that most people seem to miss is in the calculation of the midpoint index. Using `mid = (low + high) / 2` has an overflow bug if you're not using infinite precision, but there are several other potential problems even in the simplest algorithm.

  • git bisect is great when it works; but you will come across things that cannot be found with git bisect.

    I've debugged things like this: a bug was introduced, but with no manifestation. Eventually, many commits later, some unrelated change triggers it. But this comes and goes. Some changes make the manifestation go away, and some changes make it reappear.

    Git bisect is predicated on the bug not existing at the "good" end point and making a single appearance between that and the "bad" end of the range. It has allowance for commits not being testable; you can skip those. If the bad commit is one of the skipped ones, I think it tells you.

    Try not to have any other kind of bug. :)

  • Honestly, after 20 years in the field: optimising the workflow for when you can already reliably reproduce the bug seems misapplied because that's the part that already takes the least amount of time and effort for most projects.
    by rf15
  • Eh not always. If you work in a big codebase with 1000s of devs then it can quite tricky to find the cause of some bug when it’s in some random library someone changed for a different reason.
  • I would add to nixpulvis’s comments that git history may also help you find a repro case, especially if you’ve only found a half-assed repro case that is overly broad.

    Before you find even that, your fire drill strategy is very very important. Is there enough detail in the incident channel and our CD system for coworkers to put their dev sandbox in the same state as production? Is there enough if a clue of what is happening for them to run speculative tests in parallel? Is the data architecture clean enough that your experiments don’t change the outcome of mine? Onboarding docs and deployment process docs, if they are tight, reduce the Amdahl’s Law effect as it applies to figuring out what the bug is and where it is. Which is I. This context also Brooks ‘s Law.

  • Just because you can reproduce it doesn't mean you know what is causing it. Running a bisect to fix which commit introduces it will reduce the area you need to search for the cause.
  • Git has some really good tools for searching code and debugging. A few years ago I wrote a blog post abot them, including bisect, log -L, log -S and blame. You can see it and the discussion here: https://news.ycombinator.com/item?id=39877637
  • `git-bisect` is legit if you have to do the history archaeological digging. Though, there is the open question of how git commit history is maintained, the squash-and-merge vs. just retain all history. With squash-and-merge you're looking at the merged pull-request versus with full history you can find the true code-level inflection point.
  • > with full history you can find the true code-level inflection point.

    "typo fix"

  • `git bisect --first-parent` lets you easily restrict to just the "PR level" if you are using PRs with merge commits (ie, no-fast-forward).
  • Can someone explain why anyone would want non-squashed PRs?

    For the 5% of engineers that diligently split each PR into nice semantic changes, I suppose that's nice. But the vast majority of engineers don't do this. Individual commits in a PR are testing and iteration. You don't want to read though that.

    Unless, of course, you're asking the engineer to squash on their end before making the PR. But what's the value in that ceremony?

    Each PR being squashed to 1 commit is nice and easy to reason about. If you truly care about making more semantic history, split the work into multiple PRs.

    For that matter, why merge? Rebase it on top. It's so much cleaner. It's atomic and hermetic.

  • One place bisect shines is when a flaky test snuck in due to some race condition but you can’t figure out what. If you have to run a test 100000 times to be convinced the bug isn’t present, this can be pretty slow. Bisecting makes it practical to narrow in on the faulty commit, and with the right script you can just leave it running in the background for an hour.
  • We really would benefit from a Bayesian binary search for this purpose, so you can get by with only running the test 1000 times in most cases.
  • I recently used git bisect to help find the root cause of a bug in a fun little jam of mine (a music player/recorder written in Svelte - https://lets-make-sweet-music.com).

    My scenario with the project was:

    - no unit/E2E tests - no error occurring, either from Sentry tracking or in the developer tools console. - Many git commits to check through as GitHub's dependabot alerts had been busy in the meantime.

    I would say git bisect was a lifesaver - I managed to trace the error to my attempt to replace a file I had with the library I extracted for what it did (http://github.com/anephenix/event-emitter).

    It turns out that the file had implemented a feature that I hadn't ported to the library (to be able to attach multiple event names to call the same function).

    I think the other thing that helps is to keep git commits small, so that when you do discover the commit that breaks the app, you can easily find the root cause among the small number of files/code that changed.

    Where it becomes more complex is when the root cause of the error requires evaluating not just one component that can change (in my case a frontend SPA), but also other components like the backend API, as well as the data in the database.

  • I've only needed to use it a couple of times, but sometimes Chrome releases changes that break your website and it's really nice to be able to point to a changeset when you report it. Chrome has its own tool, bisect-builds.py, which you can do this with:

    https://github.com/jay0lee/chrome-bisect

  • Make sure you know about exit code 125 to your test script. You can use it in those terrible cases where the test can't tell, one way or another, whether the failure you seek happened, for example when there is an unrelated build problem.

    I wrote a short post on this:

    https://speechcode.com/blog/git-bisect

    by aag
  • Related advice, in any repo where merge commits represent integration points (such as PRs must build and pass CI tests before merging and PRs produce merge commits) you can use `git bisect --first-parent` to just bisect your high-level merge commits which you know should build. Often knowing "which PR introduced this bug" is as handy as knowing which commit did it. But once you find the PR merge commit you can also run a quick second bisect on just that branch to find the lower level commit.
  • I've used bisect a few times in my life. Most of the time, I already know which files or functions might have introduced a bug.

    Looking at the history of specific files or functions usually gives a quick idea. In modern Git, you can search the history of a specific function.

        >> git log -L :func_name:path/to/a/file.c
    
    You need to have a proper .gitattributes file, though.
  • I use this often, but it is sadly weak when used on C++ code that includes polymorphic methods/functions:

      /* snip */
    
      void
      Object::do_the_thing (int)
      {
      }
    
      void
      Object::do_the_thing (float)
      {
      }
    
      /* snip*/
    
    AFAICT, git log will never be able to be told to review the history of the second version.
  • I use git bisect literally every day. We are clearly different people :)
  • Can you elaborate on the dependent .gitattributes file? Where can I find more information on the necessary content? Sounds super useful!
  • Alternatively if you do not have that set up, `git log -S` helps you find commits whose diff contain a specific string.
  • I used git bisect in anger for the first time recently and it felt like magic.

    Background: We had two functions in the codebase with identical names and nearly identical implementations, the latter having a subtle bug. Somehow both were imported into a particular python script, but the correct one had always overshadowed the incorrect one - that is, until an unrelated effort to apply code formatting standards to the codebase “fixed” the shadowing problem by removing the import of the correct function. Not exactly mind bending - but, we had looked at the change a few times over in GitHub while debugging and couldn’t find a problem with it - not until we knew for sure that was the commit causing the problem did we find the bug.

  • Git bisect was an extremely powerful tool when I worked in a big-ball-of-mud codebase that had no test coverage and terrible abstractions which made it impossible to write meaningful tests in the first place. In that codebase it was far easier to find a bug by finding the commit it was introduced in - simply because it was impossible to reason through the codebase otherwise.

    In any high quality codebase I’ve worked in, git bisect has been totally unnecessary. It doesn’t matter which commit the bug was introduced in when it’s simple to test the components of your code in isolation and you have useful observability to instruct you on where to look and what use inputs to test with.

    This has been my experience working on backend web services - YMMV wildly in different domains.

  • I found a bug in a OSS program where an entry contained a gibberish string. This was C so apparently it was some not-properly initialized variable. I couldn’t hope to trace down where that occurred. But with time to write a bisect script and half an hour to run the bisect session I was able to find the commit.
  • I find git bisect indispensable when tracking down weird kernel bugs.
  • That was my first thought when reading this.

    It sounds like the author doesn't understand the codebase, if you're brute-forcing bug detection by bisecting commit versions to figure out where the issue is, something's already failed. In most cases you should have logs/traces/whatever that give you the info you need to figure out exactly where the problem is.

  • Even if you can reason through a code base a bisect can still be much quicker.

    Instead of understanding the code you only need to understand the bug. Much easier!