Search code examples
gitgit-bisect

How can git bisect pick a commit that doesn't have the first good commit as an ancestor? (using --first-parent)


I have a repository that uses a "trunk" flow with feature branches merging in and creating merge commits, and am using bisect to try find when issues were introduced. The issue identification process involves a comparison to results from a known-good commit, so I am using git bisect --first-parent (new in 2.29) to skip commits not in the "trunk" branch (identifying the merge commit that caused the issue is enough for me).

However, git bisect --first-parent is picking commits that do not have my first good commit as an ancestor, and I'm unsure how this is possible.

In the below example, commit 2 is good and 4 is bad.

  a 
 / \
1-2-3-4

Without --first-parent, I would expect feature branch commit a to be included in the bisection, however with --first-parent, I would expect it to skip that commit, and only test the merge commit, 3.

I've tested this on a mini-repository, and it behaves how I'd expect, however my much larger, much more complicated repository is not skipping commits that don't have first-good as an ancestor, and I'm struggling to understand why.

My commands are

# both "first-good" and "first-bad" are tags on the "trunk" branch
git bisect start --first-parent
git merge-base --is-ancestor first-good first-bad  # returns TRUE
git merge-base first-good first-bad                # returns first-good
git checkout first-bad 
git bisect bad
git checkout first-good
git bisect good 
git merge-base --is-ancestor first-good HEAD       # returns FALSE - why/how?
git merge-base first-good HEAD                     # returns some other commit - why/how?

Solution

  • In git@47f0f94bc7, the behaviour described in the question is observed if the first-good commit exists in the mainline only as the second parent of merge commit. I guess this is somewhat expected, given the name of the flag, but is does result in confusing behavior if you were relying on the first-good commit for a working build, because not all your bisects will contain that commit.

    For example:

      a - b - c   # "trunk2"
     /   /  
    1 - 2         # "trunk1"
    
    # 2 is the `first-good` commit
    # c is the `first-bad` commit
    

    git bisect --first-parent will pick a as a commit to test, even though it is not an ancestor of the first known good commit, 2. It appears to back-track to a in this case, which is the first commit that is not an ancestor of first-good. It does not test 1, as that is an ancestor of first-good, and can thus be assumed to be also good.

    In this example, the behaviour with or without the --first-parents flag is identical. Other branches starting from and merging back into trunk2 would continue to be skipped.

    While I'm fairly sure this is the "correct" behaviour, and for most use cases, the user won't notice a difference, the man page could use some detailing of this behaviour and/or warning given when the first-good commit only exists as a second parent.

    This happen in my case because we "re-trunked" at some point, where commits were going into both branches simultaneously.