How do you get git bisect to ignore merged branches?
Asked Answered
D

10

47

I'm aware that git bisect is branch-aware by design, so that if between good commit, G, and bad commit, B, you merged in a branch, it needs to take those changes into consideration as well, as the bug may be contained in the branch.

In my case I have a dependency as a side branch and I merge in changes to my main project from time to time. The dependency can be considered a library that has a different way of running, different build-system etc. from my main project, but I still want recent changes from it via merges to the main branch.

The problem is then that while bisecting in this scenario, you end up on non-compilable commits in the commits from the dependency.

I would really just want to consider each branch merge as a single commit while doing the bisection.

A workaround I've found so far is making a list of valid commits G..B with git log --first-parent, and then while bisecting, do git bisect skip if the current commit isn't in that list. That takes a lot of time though (lots of files to checkout/change for each skip).

So the question is: Is there any way of doing --first-parent with git bisect or providing a list of commits i feel are valid to be able to avoid checking out branches I know already are not compilable? How do we only check the commits marked o in the diagram?

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

Edit: added diagram for clarity

Dementia answered 12/4, 2011 at 15:59 Comment(4)
In my opinion you shouldn't put dependencies in branches, but rather you should use submodules or just plain files containing revision ids or version numbers to use. That would avoid this particular problem.Peaked
I agree, in this case they are actually similar projects that share a lot of code. There are probably better ways of organizing that than branches, but I'm still interested in a solution for the given problem.Dementia
Posted a workaround I found myself, but would like to see better solutions.Dementia
git bisect --first-parent will soon be there with Git 2.29. See my answer below.Mencher
M
22

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.

Mencher answered 24/8, 2020 at 20:35 Comment(5)
Has this functionality been tested yet? git 2.29 isn't released yet, and my attempt to build 47f0f94bc7 (the merge commit of that feature branch) on ubuntu 16 results in "git: 'bisect' is not a git command."Crowns
@JoelGibson Yes, it has. And you need to add <installation path to git>/usr/libexec/git-core to your $PATH.Mencher
This is now the best answerPlymouth
Do you need to add --first-parent to every git bisect good and git bisect bad command, or just to git bisect start?Orly
@Orly Only git bisect start: the --first-parent option is only listed for that command.Mencher
D
14

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.

Dementia answered 12/4, 2011 at 15:59 Comment(0)
A
12

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.

Asur answered 12/4, 2011 at 17:12 Comment(2)
At first it felt like this workaround had a lot of manual steps, but realized you just do the same oneliner once after every bisect suggested checkout, so not a bad solution.Dementia
With Git 2.4.0, git rev-list --bisect --first-parent will no longer work: github.com/git/git/commit/…Mencher
I
11

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.

Inhale answered 13/4, 2011 at 14:36 Comment(3)
In our case the merges are only one way, into our main branch, and they happen several times, with possibly many local taskbranches on the sidebranch. In that case it becomes a bit more tricky.Dementia
For this, if you had like 20 merges, you'd have do the whole $ git bisect skip $(git rev-list G ^C) for each merge manually because you have to get the leading second parent commit and exclude out any commits that are reachable from the commit the branch first occurred on. I'm thinking of using Python to create a set of git rev-list --first-parent <good>..<bad>. Call this setA. And another set git rev-list <good>..<bad>. Call this setB. Then setB - setA = the stuff we want skipped.Jereme
I rolled this into a script that I pushed to GitHub: github.com/marczych/git-first-parent-bisect Thanks for the inspiration!Boneset
P
9

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.

Phenobarbitone answered 18/6, 2013 at 9:42 Comment(3)
What version of git were you running at the time you tested this? It doesn't seem to work in v1.7.2.5. Even with the grafts git bisect complains about merge commits, refusing to allow you to skip or mark it as bad.Brainchild
@dpk I don't recall which version I used back then, but I just tried with v1.7.2.5 and it worked just fine. I wonder how you get bisect to complain about merge commits in the first place. It usually handles them just fine and I even ran the test so that I ended up with the merge commit being the bad one. Can you provide instructions to reproduce your problem?Hasheem
How embarrassing, I can't duplicate it now.Brainchild
V
4

Björn Steinbrink's answer works great, but recently started printing this:

hint: Support for /info/grafts is deprecated
hint: and will be removed in a future Git version.
hint: 
hint: Please use "git replace --convert-graft-file"
hint: to convert the grafts into replace refs.
hint: 
hint: Turn this message off by running
hint: "git config advice.graftFileDeprecated false"

Here's a more modern version of his solution using "git replace" instead of grafts:

git rev-list --first-parent --merges --parents HEAD | \
  while read COMMIT PARENT1 PARENT2; do 
    git replace --graft $COMMIT $PARENT1; 
  done

Unfortunately, it is much slower for large repos (around 3 minutes for 150k commits); git replace doesn't seem to have a bulk-mode yet. You might want to restrict the rev-list to only the commits in scope of your bisect.

To remove the replacements when you are done, you can rm .git/refs/replace/* .

Veil answered 24/7, 2018 at 14:23 Comment(0)
D
2

You can instruct git-bisect to only go through the first-parents of the merge commits by running the following command:

git bisect skip $(comm -23 <(git rev-list G | sort) <(git rev-list --first-parent G | sort))
Duarte answered 4/2, 2014 at 7:20 Comment(2)
And, what is G, here?Renitarenitent
G is the commit that is known to be good, as is shown in the chart of the original question.Duarte
P
2

So, the assumption that the first parent of a merge commit is always the same branch isn't always correct. For instance, if you are off on a topic branch and merge master to it to get up to date (so for this merge commit, the first parent is the topic branch) and then checkout master and merge topic back to it, you get a fast forward merge, which just moves master to the merge commit that had first parent as your topic branch. This may seem contrived, but its actually a pretty normal workflow - I always merge master into my branch so that my merge back to master will be a trivial merge (i.e., fast forward-able) (Sorry James, always forget to rebase it).

There is one way that I've found to help figure out which parent is your branch - the merge commit comment itself. By default, git composes a merge commit comment that says which branch was merged and you can use this to deduce which parent is the branch you are interested in, so long as the person doing the merge commit didn't overwrite this merge commit comment.

So I tried this out and it seems to work for me. I wrote a Python script to help do this on github. If you run this script, it will try to trace backwards and follow your branch and emit a list of commit ids that are the tips of the branches that are merged into your branch. With this list, you can give these to "git bisect good" and bisect will then omit all of the commits on the merged branches from your bisection, achieving the desired result.

Presumptuous answered 19/9, 2015 at 23:33 Comment(0)
A
1

I don't see a one-step method, however, based on your current solution: git bisect skip can take a list of commits to skip. git log branchname will list commits on branch branchname. So this should let you specify the list of commits.

If your dependency and your main code live in different filesystem spaces, you can specify the paths to include with git bisect start. Depending on your code layout, that may be the best option. (Almost certainly is if you have a list of files that may contain the bug!)

The man page has details; the see also there is interesting reading, too.

Assemblyman answered 12/4, 2011 at 16:24 Comment(1)
In this case the filesystem spaces are overlapping, but in general specifying paths help a lot.Dementia
P
1

You might be able to use git bisect start --no-checkout to avoid having to actually checkout the commit into the working tree. Then I suspect that you can do git checkout BISECT_HEAD for commits that you actually want to test (i.e. only first parent commits on the main branch). I have not tried this but I hope it would work.

Putman answered 22/10, 2015 at 3:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.