Skip to content

Commit 2a87a35

Browse files
committed
Merge branch 'al/bisect-first-parent' into jch
"git bisect" learns the "--first-parent" option to find the first breakage along the first-parent chain. * al/bisect-first-parent: bisect: combine args passed to find_bisection() bisect: introduce first-parent flag rev-list: allow bisect and first-parent flags
2 parents 4608048 + 7109f10 commit 2a87a35

File tree

10 files changed

+142
-30
lines changed

10 files changed

+142
-30
lines changed

Documentation/git-bisect.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ The command takes various subcommands, and different options depending
1717
on the subcommand:
1818

1919
git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
20-
[--no-checkout] [<bad> [<good>...]] [--] [<paths>...]
20+
[--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]
2121
git bisect (bad|new|<term-new>) [<rev>]
2222
git bisect (good|old|<term-old>) [<rev>...]
2323
git bisect terms [--term-good | --term-bad]
@@ -365,6 +365,17 @@ does not require a checked out tree.
365365
+
366366
If the repository is bare, `--no-checkout` is assumed.
367367

368+
--first-parent::
369+
+
370+
Follow only the first parent commit upon seeing a merge commit.
371+
+
372+
In detecting regressions introduced through the merging of a branch, the merge
373+
commit will be identified as introduction of the bug and its ancestors will be
374+
ignored.
375+
+
376+
This option is particularly useful in avoiding false positives when a merged
377+
branch contained broken or non-buildable commits, but the merge itself was OK.
378+
368379
EXAMPLES
369380
--------
370381

Documentation/rev-list-options.txt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,7 @@ parents) and `--max-parents=-1` (negative numbers denote no upper limit).
128128
because merges into a topic branch tend to be only about
129129
adjusting to updated upstream from time to time, and
130130
this option allows you to ignore the individual commits
131-
brought in to your history by such a merge. Cannot be
132-
combined with --bisect.
131+
brought in to your history by such a merge.
133132

134133
--not::
135134
Reverses the meaning of the '{caret}' prefix (or lack thereof)
@@ -207,7 +206,7 @@ ifndef::git-rev-list[]
207206
Pretend as if the bad bisection ref `refs/bisect/bad`
208207
was listed and as if it was followed by `--not` and the good
209208
bisection refs `refs/bisect/good-*` on the command
210-
line. Cannot be combined with --first-parent.
209+
line.
211210
endif::git-rev-list[]
212211

213212
--stdin::
@@ -743,7 +742,7 @@ outputs 'midpoint', the output of the two commands
743742
would be of roughly the same length. Finding the change which
744743
introduces a regression is thus reduced to a binary search: repeatedly
745744
generate and test new 'midpoint's until the commit chain is of length
746-
one. Cannot be combined with --first-parent.
745+
one.
747746

748747
--bisect-vars::
749748
This calculates the same as `--bisect`, except that refs in

bisect.c

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,16 @@ static inline void weight_set(struct commit_list *elem, int weight)
8888
**commit_weight_at(&commit_weight, elem->item) = weight;
8989
}
9090

91-
static int count_interesting_parents(struct commit *commit)
91+
static int count_interesting_parents(struct commit *commit, unsigned bisect_flags)
9292
{
9393
struct commit_list *p;
9494
int count;
9595

9696
for (count = 0, p = commit->parents; p; p = p->next) {
97-
if (p->item->object.flags & UNINTERESTING)
98-
continue;
99-
count++;
97+
if (!(p->item->object.flags & UNINTERESTING))
98+
count++;
99+
if (bisect_flags & BISECT_FIRST_PARENT)
100+
break;
100101
}
101102
return count;
102103
}
@@ -259,7 +260,7 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list, int n
259260
*/
260261
static struct commit_list *do_find_bisection(struct commit_list *list,
261262
int nr, int *weights,
262-
int find_all)
263+
unsigned bisect_flags)
263264
{
264265
int n, counted;
265266
struct commit_list *p;
@@ -271,7 +272,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
271272
unsigned flags = commit->object.flags;
272273

273274
*commit_weight_at(&commit_weight, p->item) = &weights[n++];
274-
switch (count_interesting_parents(commit)) {
275+
switch (count_interesting_parents(commit, bisect_flags)) {
275276
case 0:
276277
if (!(flags & TREESAME)) {
277278
weight_set(p, 1);
@@ -314,11 +315,13 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
314315
continue;
315316
if (weight(p) != -2)
316317
continue;
318+
if (bisect_flags & BISECT_FIRST_PARENT)
319+
BUG("shouldn't be calling count-distance in fp mode");
317320
weight_set(p, count_distance(p));
318321
clear_distance(list);
319322

320323
/* Does it happen to be at exactly half-way? */
321-
if (!find_all && halfway(p, nr))
324+
if (!(bisect_flags & BISECT_FIND_ALL) && halfway(p, nr))
322325
return p;
323326
counted++;
324327
}
@@ -332,7 +335,10 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
332335

333336
if (0 <= weight(p))
334337
continue;
335-
for (q = p->item->parents; q; q = q->next) {
338+
339+
for (q = p->item->parents;
340+
q;
341+
q = bisect_flags & BISECT_FIRST_PARENT ? NULL : q->next) {
336342
if (q->item->object.flags & UNINTERESTING)
337343
continue;
338344
if (0 <= weight(q))
@@ -356,21 +362,21 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
356362
weight_set(p, weight(q));
357363

358364
/* Does it happen to be at exactly half-way? */
359-
if (!find_all && halfway(p, nr))
365+
if (!(bisect_flags & BISECT_FIND_ALL) && halfway(p, nr))
360366
return p;
361367
}
362368
}
363369

364370
show_list("bisection 2 counted all", counted, nr, list);
365371

366-
if (!find_all)
372+
if (!(bisect_flags & BISECT_FIND_ALL))
367373
return best_bisection(list, nr);
368374
else
369375
return best_bisection_sorted(list, nr);
370376
}
371377

372378
void find_bisection(struct commit_list **commit_list, int *reaches,
373-
int *all, int find_all)
379+
int *all, unsigned bisect_flags)
374380
{
375381
int nr, on_list;
376382
struct commit_list *list, *p, *best, *next, *last;
@@ -406,9 +412,9 @@ void find_bisection(struct commit_list **commit_list, int *reaches,
406412
weights = xcalloc(on_list, sizeof(*weights));
407413

408414
/* Do the real work of finding bisection commit. */
409-
best = do_find_bisection(list, nr, weights, find_all);
415+
best = do_find_bisection(list, nr, weights, bisect_flags);
410416
if (best) {
411-
if (!find_all) {
417+
if (!(bisect_flags & BISECT_FIND_ALL)) {
412418
list->item = best->item;
413419
free_commit_list(list->next);
414420
best = list;
@@ -454,6 +460,7 @@ static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
454460
static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
455461
static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
456462
static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
463+
static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
457464
static GIT_PATH_FUNC(git_path_head_name, "head-name")
458465

459466
static void read_bisect_paths(struct argv_array *array)
@@ -975,6 +982,12 @@ void read_bisect_terms(const char **read_bad, const char **read_good)
975982
fclose(fp);
976983
}
977984

985+
int read_first_parent_option(void)
986+
{
987+
const char *filename = git_path_bisect_first_parent();
988+
return !access(filename, F_OK);
989+
}
990+
978991
/*
979992
* We use the convention that return BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND (-10) means
980993
* the bisection process finished successfully.
@@ -991,21 +1004,30 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix, int
9911004
enum bisect_error res = BISECT_OK;
9921005
struct object_id *bisect_rev;
9931006
char *steps_msg;
1007+
unsigned bisect_flags = 0;
9941008

9951009
read_bisect_terms(&term_bad, &term_good);
9961010
if (read_bisect_refs())
9971011
die(_("reading bisect refs failed"));
9981012

1013+
if (read_first_parent_option())
1014+
bisect_flags |= BISECT_FIRST_PARENT;
1015+
1016+
if (!!skipped_revs.nr)
1017+
bisect_flags |= BISECT_FIND_ALL;
1018+
9991019
res = check_good_are_ancestors_of_bad(r, prefix, no_checkout);
10001020
if (res)
10011021
return res;
10021022

10031023
bisect_rev_setup(r, &revs, prefix, "%s", "^%s", 1);
1024+
1025+
revs.first_parent_only = !!(bisect_flags & BISECT_FIRST_PARENT);
10041026
revs.limited = 1;
10051027

10061028
bisect_common(&revs);
10071029

1008-
find_bisection(&revs.commits, &reaches, &all, !!skipped_revs.nr);
1030+
find_bisection(&revs.commits, &reaches, &all, bisect_flags);
10091031
revs.commits = managed_skipped(revs.commits, &tried);
10101032

10111033
if (!revs.commits) {
@@ -1133,6 +1155,7 @@ int bisect_clean_state(void)
11331155
unlink_or_warn(git_path_bisect_names());
11341156
unlink_or_warn(git_path_bisect_run());
11351157
unlink_or_warn(git_path_bisect_terms());
1158+
unlink_or_warn(git_path_bisect_first_parent());
11361159
/* Cleanup head-name if it got left by an old version of git-bisect */
11371160
unlink_or_warn(git_path_head_name());
11381161
/*

bisect.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ struct repository;
1212
* best commit, as chosen by `find_all`.
1313
*/
1414
void find_bisection(struct commit_list **list, int *reaches, int *all,
15-
int find_all);
15+
unsigned bisect_flags);
1616

1717
struct commit_list *filter_skipped(struct commit_list *list,
1818
struct commit_list **tried,
@@ -23,6 +23,9 @@ struct commit_list *filter_skipped(struct commit_list *list,
2323
#define BISECT_SHOW_ALL (1<<0)
2424
#define REV_LIST_QUIET (1<<1)
2525

26+
#define BISECT_FIND_ALL (1u<<0)
27+
#define BISECT_FIRST_PARENT (1u<<1)
28+
2629
struct rev_list_info {
2730
struct rev_info *revs;
2831
int flags;
@@ -66,6 +69,8 @@ int estimate_bisect_steps(int all);
6669

6770
void read_bisect_terms(const char **bad, const char **good);
6871

72+
int read_first_parent_option(void);
73+
6974
int bisect_clean_state(void);
7075

7176
#endif

builtin/bisect--helper.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
1616
static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
1717
static GIT_PATH_FUNC(git_path_head_name, "head-name")
1818
static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
19+
static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
1920

2021
static const char * const git_bisect_helper_usage[] = {
2122
N_("git bisect--helper --next-all [--no-checkout]"),
@@ -27,7 +28,7 @@ static const char * const git_bisect_helper_usage[] = {
2728
N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"),
2829
N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"),
2930
N_("git bisect--helper --bisect-start [--term-{old,good}=<term> --term-{new,bad}=<term>]"
30-
"[--no-checkout] [<bad> [<good>...]] [--] [<paths>...]"),
31+
" [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]"),
3132
NULL
3233
};
3334

@@ -421,7 +422,7 @@ static int bisect_append_log_quoted(const char **argv)
421422
}
422423

423424
static int bisect_start(struct bisect_terms *terms, int no_checkout,
424-
const char **argv, int argc)
425+
int first_parent_only, const char **argv, int argc)
425426
{
426427
int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;
427428
int flags, pathspec_pos, res = 0;
@@ -452,6 +453,8 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
452453
break;
453454
} else if (!strcmp(arg, "--no-checkout")) {
454455
no_checkout = 1;
456+
} else if (!strcmp(arg, "--first-parent")) {
457+
first_parent_only = 1;
455458
} else if (!strcmp(arg, "--term-good") ||
456459
!strcmp(arg, "--term-old")) {
457460
i++;
@@ -576,6 +579,9 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
576579
*/
577580
write_file(git_path_bisect_start(), "%s\n", start_head.buf);
578581

582+
if (first_parent_only)
583+
write_file(git_path_bisect_first_parent(), "\n");
584+
579585
if (no_checkout) {
580586
if (get_oid(start_head.buf, &oid) < 0) {
581587
res = error(_("invalid ref: '%s'"), start_head.buf);
@@ -631,7 +637,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
631637
BISECT_TERMS,
632638
BISECT_START
633639
} cmdmode = 0;
634-
int no_checkout = 0, res = 0, nolog = 0;
640+
int no_checkout = 0, first_parent_only = 0, res = 0, nolog = 0;
635641
struct option options[] = {
636642
OPT_CMDMODE(0, "next-all", &cmdmode,
637643
N_("perform 'git bisect next'"), NEXT_ALL),
@@ -655,6 +661,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
655661
N_("start the bisect session"), BISECT_START),
656662
OPT_BOOL(0, "no-checkout", &no_checkout,
657663
N_("update BISECT_HEAD instead of checking out the current commit")),
664+
OPT_BOOL(0, "first-parent", &first_parent_only,
665+
N_("only trace the first parent of merge commits")),
658666
OPT_BOOL(0, "no-log", &nolog,
659667
N_("no log for BISECT_WRITE")),
660668
OPT_END()
@@ -712,7 +720,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
712720
break;
713721
case BISECT_START:
714722
set_terms(&terms, "bad", "good");
715-
res = bisect_start(&terms, no_checkout, argv, argc);
723+
res = bisect_start(&terms, no_checkout, first_parent_only, argv, argc);
716724
break;
717725
default:
718726
return error("BUG: unknown subcommand '%d'", cmdmode);

builtin/rev-list.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,8 +637,15 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
637637

638638
if (bisect_list) {
639639
int reaches, all;
640+
unsigned bisect_flags = 0;
640641

641-
find_bisection(&revs.commits, &reaches, &all, bisect_find_all);
642+
if (bisect_find_all)
643+
bisect_flags |= BISECT_FIND_ALL;
644+
645+
if (revs.first_parent_only)
646+
bisect_flags |= BISECT_FIRST_PARENT;
647+
648+
find_bisection(&revs.commits, &reaches, &all, bisect_flags);
642649

643650
if (bisect_show_vars)
644651
return show_bisect_vars(&info, reaches, all);

revision.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2895,9 +2895,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
28952895
if (!revs->reflog_info && revs->grep_filter.use_reflog_filter)
28962896
die("cannot use --grep-reflog without --walk-reflogs");
28972897

2898-
if (revs->first_parent_only && revs->bisect)
2899-
die(_("--first-parent is incompatible with --bisect"));
2900-
29012898
if (revs->line_level_traverse &&
29022899
(revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT)))
29032900
die(_("-L does not yet support diff formats besides -p and -s"));

t/t6000-rev-list-misc.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,8 @@ test_expect_success 'rev-list can negate index objects' '
128128
test_cmp expect actual
129129
'
130130

131-
test_expect_success '--bisect and --first-parent can not be combined' '
132-
test_must_fail git rev-list --bisect --first-parent HEAD
131+
test_expect_success '--bisect and --first-parent can be combined' '
132+
git rev-list --bisect --first-parent HEAD
133133
'
134134

135135
test_expect_success '--header shows a NUL after each commit' '

0 commit comments

Comments
 (0)