How do you get git bisect to ignore merged branches?

Solution 1:

I thought of one possible solution, but I'm still hoping to find something more elegant:

Mark all second-parents of all merges into the main branch as good

Marking all remote parents of each merge as good will consider all the commits preceding them as good (and as such skipped by bisect). This solution should also be generic enough to handle multiple merges from multiple branches, leaving only the commits on the main branch.

git rev-list --first-parent --merges --parents GOOD..BAD \
| sed 's/^[^ ][^ ]* [^ ][^ ]* //' \
| xargs git bisect good

(replace GOOD and BAD with the relevant commits)

The regex in sed removes the first two commits of each line; the merge commit itself, and the first parent, leaving the rest of the parents (usually just the second one).

Given the history stated in the question, running the one-liner would give you:

G---o---o---o---o---o---o---B  main project branch
   /       /       / 
  G---x---G---x---G            dependency
           \ /
            x'                 dependency project taskbranch

This would make bisect traverse only the commits on the main branch:

    o---o---o---o---o---o

If any of the merged branches are indirectly the cause of the problem, it will be discovered when you test the merge commit via bisect, which could be reason to investigate further on that branch.

Solution 2:

I've been looking for something like this too. As far as I've got is that git rev-list --bisect --first-parent seems to do what you want to, and the docs for rev-list implies that the --bisect option is what bisect uses internally - but getting git bisect to add that flag to its call(s) to rev-list seems less trivial:

The bisect command is implemented by a shell script git-bisect, which in turn uses a builtin command bisect--helper to actually do the interesting part ("computation, display and checkout" says the comment...), apparently based on a bunch of magic state files in .git/. And it seems to be the rev-list command that is reusing code from bisect--helper rather than the other way around as you might expect.

So, you'd have to extend the bisect--helper code's commit filtering to do it, I think.

As a workaround, something like this might work: after bisect checks something out for you, reset to a different one using git rev-list --bisect --first-parent, test that and mark it good/bad/skip and continue from there.

Solution 3:

Is there any way of doing --first-parent with git bisect

Yes: with Git 2.29 (Q4 2020), "git bisect"(man) learns the "--first-parent" option to find the first breakage along the first-parent chain.

See commit ad464a4, commit e8861ff, commit be5fe20, commit 0fe305a, commit 15a4802 (07 Aug 2020) by Aaron Lipman (alipman88).
(Merged by Junio C Hamano -- gitster -- in commit 47f0f94, 17 Aug 2020)

bisect: introduce first-parent flag

Signed-off-by: Aaron Lipman

Upon seeing a merge commit when bisecting, this option may be used to follow only the first parent.

In detecting regressions introduced through the merging of a branch, the merge commit will be identified as introduction of the bug and its ancestors will be ignored.

This option is particularly useful in avoiding false positives when a merged branch contained broken or non-buildable commits, but the merge itself was OK.

git bisect [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]

git bisect now includes in its man page:

--first-parent

Follow only the first parent commit upon seeing a merge commit.

In detecting regressions introduced through the merging of a branch, the merge commit will be identified as introduction of the bug and its ancestors will be ignored.

This option is particularly useful in avoiding false positives when a merged branch contained broken or non-buildable commits, but the merge itself was OK.

Solution 4:

If the history looks like:

A - B - C - H - I - J - K - L
         \              /
          D - E - F - G

where L is bad, B is good, and you want to ignore the DEFG branch, then running

$ git bisect start
$ git bisect skip $( git rev-list G ^C )
$ git bisect bad L
$ git bisect good B

where B,C,G,and L are the respective shas seems to do what you want.

Solution 5:

You can make git treat you history as linear using grafts. To linearize the whole first parent history you can use:

git rev-list --first-parent --merges --parents HEAD | cut -d' ' -f1,2 > .git/info/grafts

Just drop the grafts file when you're done with the bisection.