Merge branch 'al/bisect-first-parent'

"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
  cmd_bisect__helper: defer parsing no-checkout flag
  rev-list: allow bisect and first-parent flags
  t6030: modernize "git bisect run" tests
This commit is contained in:
Junio C Hamano 2020-08-17 17:02:45 -07:00
commit 47f0f94bc7
11 changed files with 195 additions and 103 deletions

View File

@ -17,7 +17,7 @@ The command takes various subcommands, and different options depending
on the subcommand: on the subcommand:
git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>] git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
[--no-checkout] [<bad> [<good>...]] [--] [<paths>...] [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]
git bisect (bad|new|<term-new>) [<rev>] git bisect (bad|new|<term-new>) [<rev>]
git bisect (good|old|<term-old>) [<rev>...] git bisect (good|old|<term-old>) [<rev>...]
git bisect terms [--term-good | --term-bad] git bisect terms [--term-good | --term-bad]
@ -365,6 +365,17 @@ does not require a checked out tree.
+ +
If the repository is bare, `--no-checkout` is assumed. If the repository is bare, `--no-checkout` is assumed.
--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.
EXAMPLES EXAMPLES
-------- --------

View File

@ -128,8 +128,7 @@ parents) and `--max-parents=-1` (negative numbers denote no upper limit).
because merges into a topic branch tend to be only about because merges into a topic branch tend to be only about
adjusting to updated upstream from time to time, and adjusting to updated upstream from time to time, and
this option allows you to ignore the individual commits this option allows you to ignore the individual commits
brought in to your history by such a merge. Cannot be brought in to your history by such a merge.
combined with --bisect.
--not:: --not::
Reverses the meaning of the '{caret}' prefix (or lack thereof) Reverses the meaning of the '{caret}' prefix (or lack thereof)
@ -207,7 +206,7 @@ ifndef::git-rev-list[]
Pretend as if the bad bisection ref `refs/bisect/bad` Pretend as if the bad bisection ref `refs/bisect/bad`
was listed and as if it was followed by `--not` and the good was listed and as if it was followed by `--not` and the good
bisection refs `refs/bisect/good-*` on the command bisection refs `refs/bisect/good-*` on the command
line. Cannot be combined with --first-parent. line.
endif::git-rev-list[] endif::git-rev-list[]
--stdin:: --stdin::
@ -743,7 +742,7 @@ outputs 'midpoint', the output of the two commands
would be of roughly the same length. Finding the change which would be of roughly the same length. Finding the change which
introduces a regression is thus reduced to a binary search: repeatedly introduces a regression is thus reduced to a binary search: repeatedly
generate and test new 'midpoint's until the commit chain is of length generate and test new 'midpoint's until the commit chain is of length
one. Cannot be combined with --first-parent. one.
--bisect-vars:: --bisect-vars::
This calculates the same as `--bisect`, except that refs in This calculates the same as `--bisect`, except that refs in

View File

@ -15,6 +15,7 @@
#include "commit-slab.h" #include "commit-slab.h"
#include "commit-reach.h" #include "commit-reach.h"
#include "object-store.h" #include "object-store.h"
#include "dir.h"
static struct oid_array good_revs; static struct oid_array good_revs;
static struct oid_array skipped_revs; static struct oid_array skipped_revs;
@ -88,15 +89,16 @@ static inline void weight_set(struct commit_list *elem, int weight)
**commit_weight_at(&commit_weight, elem->item) = weight; **commit_weight_at(&commit_weight, elem->item) = weight;
} }
static int count_interesting_parents(struct commit *commit) static int count_interesting_parents(struct commit *commit, unsigned bisect_flags)
{ {
struct commit_list *p; struct commit_list *p;
int count; int count;
for (count = 0, p = commit->parents; p; p = p->next) { for (count = 0, p = commit->parents; p; p = p->next) {
if (p->item->object.flags & UNINTERESTING) if (!(p->item->object.flags & UNINTERESTING))
continue; count++;
count++; if (bisect_flags & FIND_BISECTION_FIRST_PARENT_ONLY)
break;
} }
return count; return count;
} }
@ -135,7 +137,7 @@ static void show_list(const char *debug, int counted, int nr,
for (p = list; p; p = p->next) { for (p = list; p; p = p->next) {
struct commit_list *pp; struct commit_list *pp;
struct commit *commit = p->item; struct commit *commit = p->item;
unsigned flags = commit->object.flags; unsigned commit_flags = commit->object.flags;
enum object_type type; enum object_type type;
unsigned long size; unsigned long size;
char *buf = read_object_file(&commit->object.oid, &type, char *buf = read_object_file(&commit->object.oid, &type,
@ -144,9 +146,9 @@ static void show_list(const char *debug, int counted, int nr,
int subject_len; int subject_len;
fprintf(stderr, "%c%c%c ", fprintf(stderr, "%c%c%c ",
(flags & TREESAME) ? ' ' : 'T', (commit_flags & TREESAME) ? ' ' : 'T',
(flags & UNINTERESTING) ? 'U' : ' ', (commit_flags & UNINTERESTING) ? 'U' : ' ',
(flags & COUNTED) ? 'C' : ' '); (commit_flags & COUNTED) ? 'C' : ' ');
if (*commit_weight_at(&commit_weight, p->item)) if (*commit_weight_at(&commit_weight, p->item))
fprintf(stderr, "%3d", weight(p)); fprintf(stderr, "%3d", weight(p));
else else
@ -171,9 +173,9 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr)
best = list; best = list;
for (p = list; p; p = p->next) { for (p = list; p; p = p->next) {
int distance; int distance;
unsigned flags = p->item->object.flags; unsigned commit_flags = p->item->object.flags;
if (flags & TREESAME) if (commit_flags & TREESAME)
continue; continue;
distance = weight(p); distance = weight(p);
if (nr - distance < distance) if (nr - distance < distance)
@ -212,9 +214,9 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list, int n
for (p = list, cnt = 0; p; p = p->next) { for (p = list, cnt = 0; p; p = p->next) {
int distance; int distance;
unsigned flags = p->item->object.flags; unsigned commit_flags = p->item->object.flags;
if (flags & TREESAME) if (commit_flags & TREESAME)
continue; continue;
distance = weight(p); distance = weight(p);
if (nr - distance < distance) if (nr - distance < distance)
@ -259,7 +261,7 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list, int n
*/ */
static struct commit_list *do_find_bisection(struct commit_list *list, static struct commit_list *do_find_bisection(struct commit_list *list,
int nr, int *weights, int nr, int *weights,
int find_all) unsigned bisect_flags)
{ {
int n, counted; int n, counted;
struct commit_list *p; struct commit_list *p;
@ -268,12 +270,12 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
for (n = 0, p = list; p; p = p->next) { for (n = 0, p = list; p; p = p->next) {
struct commit *commit = p->item; struct commit *commit = p->item;
unsigned flags = commit->object.flags; unsigned commit_flags = commit->object.flags;
*commit_weight_at(&commit_weight, p->item) = &weights[n++]; *commit_weight_at(&commit_weight, p->item) = &weights[n++];
switch (count_interesting_parents(commit)) { switch (count_interesting_parents(commit, bisect_flags)) {
case 0: case 0:
if (!(flags & TREESAME)) { if (!(commit_flags & TREESAME)) {
weight_set(p, 1); weight_set(p, 1);
counted++; counted++;
show_list("bisection 2 count one", show_list("bisection 2 count one",
@ -314,11 +316,13 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
continue; continue;
if (weight(p) != -2) if (weight(p) != -2)
continue; continue;
if (bisect_flags & FIND_BISECTION_FIRST_PARENT_ONLY)
BUG("shouldn't be calling count-distance in fp mode");
weight_set(p, count_distance(p)); weight_set(p, count_distance(p));
clear_distance(list); clear_distance(list);
/* Does it happen to be at exactly half-way? */ /* Does it happen to be at exactly half-way? */
if (!find_all && halfway(p, nr)) if (!(bisect_flags & FIND_BISECTION_ALL) && halfway(p, nr))
return p; return p;
counted++; counted++;
} }
@ -328,11 +332,14 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
while (counted < nr) { while (counted < nr) {
for (p = list; p; p = p->next) { for (p = list; p; p = p->next) {
struct commit_list *q; struct commit_list *q;
unsigned flags = p->item->object.flags; unsigned commit_flags = p->item->object.flags;
if (0 <= weight(p)) if (0 <= weight(p))
continue; continue;
for (q = p->item->parents; q; q = q->next) {
for (q = p->item->parents;
q;
q = bisect_flags & FIND_BISECTION_FIRST_PARENT_ONLY ? NULL : q->next) {
if (q->item->object.flags & UNINTERESTING) if (q->item->object.flags & UNINTERESTING)
continue; continue;
if (0 <= weight(q)) if (0 <= weight(q))
@ -346,7 +353,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
* add one for p itself if p is to be counted, * add one for p itself if p is to be counted,
* otherwise inherit it from q directly. * otherwise inherit it from q directly.
*/ */
if (!(flags & TREESAME)) { if (!(commit_flags & TREESAME)) {
weight_set(p, weight(q)+1); weight_set(p, weight(q)+1);
counted++; counted++;
show_list("bisection 2 count one", show_list("bisection 2 count one",
@ -356,21 +363,21 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
weight_set(p, weight(q)); weight_set(p, weight(q));
/* Does it happen to be at exactly half-way? */ /* Does it happen to be at exactly half-way? */
if (!find_all && halfway(p, nr)) if (!(bisect_flags & FIND_BISECTION_ALL) && halfway(p, nr))
return p; return p;
} }
} }
show_list("bisection 2 counted all", counted, nr, list); show_list("bisection 2 counted all", counted, nr, list);
if (!find_all) if (!(bisect_flags & FIND_BISECTION_ALL))
return best_bisection(list, nr); return best_bisection(list, nr);
else else
return best_bisection_sorted(list, nr); return best_bisection_sorted(list, nr);
} }
void find_bisection(struct commit_list **commit_list, int *reaches, void find_bisection(struct commit_list **commit_list, int *reaches,
int *all, int find_all) int *all, unsigned bisect_flags)
{ {
int nr, on_list; int nr, on_list;
struct commit_list *list, *p, *best, *next, *last; struct commit_list *list, *p, *best, *next, *last;
@ -386,16 +393,16 @@ void find_bisection(struct commit_list **commit_list, int *reaches,
for (nr = on_list = 0, last = NULL, p = *commit_list; for (nr = on_list = 0, last = NULL, p = *commit_list;
p; p;
p = next) { p = next) {
unsigned flags = p->item->object.flags; unsigned commit_flags = p->item->object.flags;
next = p->next; next = p->next;
if (flags & UNINTERESTING) { if (commit_flags & UNINTERESTING) {
free(p); free(p);
continue; continue;
} }
p->next = last; p->next = last;
last = p; last = p;
if (!(flags & TREESAME)) if (!(commit_flags & TREESAME))
nr++; nr++;
on_list++; on_list++;
} }
@ -406,9 +413,9 @@ void find_bisection(struct commit_list **commit_list, int *reaches,
weights = xcalloc(on_list, sizeof(*weights)); weights = xcalloc(on_list, sizeof(*weights));
/* Do the real work of finding bisection commit. */ /* Do the real work of finding bisection commit. */
best = do_find_bisection(list, nr, weights, find_all); best = do_find_bisection(list, nr, weights, bisect_flags);
if (best) { if (best) {
if (!find_all) { if (!(bisect_flags & FIND_BISECTION_ALL)) {
list->item = best->item; list->item = best->item;
free_commit_list(list->next); free_commit_list(list->next);
best = list; best = list;
@ -454,6 +461,7 @@ static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START") static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG") static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS") static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
static GIT_PATH_FUNC(git_path_head_name, "head-name") static GIT_PATH_FUNC(git_path_head_name, "head-name")
static void read_bisect_paths(struct strvec *array) static void read_bisect_paths(struct strvec *array)
@ -983,7 +991,7 @@ void read_bisect_terms(const char **read_bad, const char **read_good)
* If no_checkout is non-zero, the bisection process does not * If no_checkout is non-zero, the bisection process does not
* checkout the trial commit but instead simply updates BISECT_HEAD. * checkout the trial commit but instead simply updates BISECT_HEAD.
*/ */
enum bisect_error bisect_next_all(struct repository *r, const char *prefix, int no_checkout) enum bisect_error bisect_next_all(struct repository *r, const char *prefix)
{ {
struct rev_info revs; struct rev_info revs;
struct commit_list *tried; struct commit_list *tried;
@ -991,21 +999,31 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix, int
enum bisect_error res = BISECT_OK; enum bisect_error res = BISECT_OK;
struct object_id *bisect_rev; struct object_id *bisect_rev;
char *steps_msg; char *steps_msg;
int no_checkout = ref_exists("BISECT_HEAD");
unsigned bisect_flags = 0;
read_bisect_terms(&term_bad, &term_good); read_bisect_terms(&term_bad, &term_good);
if (read_bisect_refs()) if (read_bisect_refs())
die(_("reading bisect refs failed")); die(_("reading bisect refs failed"));
if (file_exists(git_path_bisect_first_parent()))
bisect_flags |= FIND_BISECTION_FIRST_PARENT_ONLY;
if (skipped_revs.nr)
bisect_flags |= FIND_BISECTION_ALL;
res = check_good_are_ancestors_of_bad(r, prefix, no_checkout); res = check_good_are_ancestors_of_bad(r, prefix, no_checkout);
if (res) if (res)
return res; return res;
bisect_rev_setup(r, &revs, prefix, "%s", "^%s", 1); bisect_rev_setup(r, &revs, prefix, "%s", "^%s", 1);
revs.first_parent_only = !!(bisect_flags & FIND_BISECTION_FIRST_PARENT_ONLY);
revs.limited = 1; revs.limited = 1;
bisect_common(&revs); bisect_common(&revs);
find_bisection(&revs.commits, &reaches, &all, !!skipped_revs.nr); find_bisection(&revs.commits, &reaches, &all, bisect_flags);
revs.commits = managed_skipped(revs.commits, &tried); revs.commits = managed_skipped(revs.commits, &tried);
if (!revs.commits) { if (!revs.commits) {
@ -1133,6 +1151,7 @@ int bisect_clean_state(void)
unlink_or_warn(git_path_bisect_names()); unlink_or_warn(git_path_bisect_names());
unlink_or_warn(git_path_bisect_run()); unlink_or_warn(git_path_bisect_run());
unlink_or_warn(git_path_bisect_terms()); unlink_or_warn(git_path_bisect_terms());
unlink_or_warn(git_path_bisect_first_parent());
/* Cleanup head-name if it got left by an old version of git-bisect */ /* Cleanup head-name if it got left by an old version of git-bisect */
unlink_or_warn(git_path_head_name()); unlink_or_warn(git_path_head_name());
/* /*

View File

@ -12,7 +12,7 @@ struct repository;
* best commit, as chosen by `find_all`. * best commit, as chosen by `find_all`.
*/ */
void find_bisection(struct commit_list **list, int *reaches, int *all, void find_bisection(struct commit_list **list, int *reaches, int *all,
int find_all); unsigned bisect_flags);
struct commit_list *filter_skipped(struct commit_list *list, struct commit_list *filter_skipped(struct commit_list *list,
struct commit_list **tried, struct commit_list **tried,
@ -23,6 +23,9 @@ struct commit_list *filter_skipped(struct commit_list *list,
#define BISECT_SHOW_ALL (1<<0) #define BISECT_SHOW_ALL (1<<0)
#define REV_LIST_QUIET (1<<1) #define REV_LIST_QUIET (1<<1)
#define FIND_BISECTION_ALL (1u<<0)
#define FIND_BISECTION_FIRST_PARENT_ONLY (1u<<1)
struct rev_list_info { struct rev_list_info {
struct rev_info *revs; struct rev_info *revs;
int flags; int flags;
@ -58,9 +61,7 @@ enum bisect_error {
BISECT_INTERNAL_SUCCESS_MERGE_BASE = -11 BISECT_INTERNAL_SUCCESS_MERGE_BASE = -11
}; };
enum bisect_error bisect_next_all(struct repository *r, enum bisect_error bisect_next_all(struct repository *r, const char *prefix);
const char *prefix,
int no_checkout);
int estimate_bisect_steps(int all); int estimate_bisect_steps(int all);

View File

@ -16,9 +16,10 @@ static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG") static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
static GIT_PATH_FUNC(git_path_head_name, "head-name") static GIT_PATH_FUNC(git_path_head_name, "head-name")
static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES") static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
static const char * const git_bisect_helper_usage[] = { static const char * const git_bisect_helper_usage[] = {
N_("git bisect--helper --next-all [--no-checkout]"), N_("git bisect--helper --next-all"),
N_("git bisect--helper --write-terms <bad_term> <good_term>"), N_("git bisect--helper --write-terms <bad_term> <good_term>"),
N_("git bisect--helper --bisect-clean-state"), N_("git bisect--helper --bisect-clean-state"),
N_("git bisect--helper --bisect-reset [<commit>]"), N_("git bisect--helper --bisect-reset [<commit>]"),
@ -27,7 +28,7 @@ static const char * const git_bisect_helper_usage[] = {
N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"), N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"),
N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"), N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"),
N_("git bisect--helper --bisect-start [--term-{old,good}=<term> --term-{new,bad}=<term>]" N_("git bisect--helper --bisect-start [--term-{old,good}=<term> --term-{new,bad}=<term>]"
"[--no-checkout] [<bad> [<good>...]] [--] [<paths>...]"), " [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]"),
NULL NULL
}; };
@ -420,9 +421,10 @@ finish:
return res; return res;
} }
static int bisect_start(struct bisect_terms *terms, int no_checkout, static int bisect_start(struct bisect_terms *terms, const char **argv, int argc)
const char **argv, int argc)
{ {
int no_checkout = 0;
int first_parent_only = 0;
int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0; int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;
int flags, pathspec_pos, res = 0; int flags, pathspec_pos, res = 0;
struct string_list revs = STRING_LIST_INIT_DUP; struct string_list revs = STRING_LIST_INIT_DUP;
@ -452,6 +454,8 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
break; break;
} else if (!strcmp(arg, "--no-checkout")) { } else if (!strcmp(arg, "--no-checkout")) {
no_checkout = 1; no_checkout = 1;
} else if (!strcmp(arg, "--first-parent")) {
first_parent_only = 1;
} else if (!strcmp(arg, "--term-good") || } else if (!strcmp(arg, "--term-good") ||
!strcmp(arg, "--term-old")) { !strcmp(arg, "--term-old")) {
i++; i++;
@ -576,6 +580,9 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
*/ */
write_file(git_path_bisect_start(), "%s\n", start_head.buf); write_file(git_path_bisect_start(), "%s\n", start_head.buf);
if (first_parent_only)
write_file(git_path_bisect_first_parent(), "\n");
if (no_checkout) { if (no_checkout) {
if (get_oid(start_head.buf, &oid) < 0) { if (get_oid(start_head.buf, &oid) < 0) {
res = error(_("invalid ref: '%s'"), start_head.buf); res = error(_("invalid ref: '%s'"), start_head.buf);
@ -631,7 +638,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
BISECT_TERMS, BISECT_TERMS,
BISECT_START BISECT_START
} cmdmode = 0; } cmdmode = 0;
int no_checkout = 0, res = 0, nolog = 0; int res = 0, nolog = 0;
struct option options[] = { struct option options[] = {
OPT_CMDMODE(0, "next-all", &cmdmode, OPT_CMDMODE(0, "next-all", &cmdmode,
N_("perform 'git bisect next'"), NEXT_ALL), N_("perform 'git bisect next'"), NEXT_ALL),
@ -653,8 +660,6 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
N_("print out the bisect terms"), BISECT_TERMS), N_("print out the bisect terms"), BISECT_TERMS),
OPT_CMDMODE(0, "bisect-start", &cmdmode, OPT_CMDMODE(0, "bisect-start", &cmdmode,
N_("start the bisect session"), BISECT_START), N_("start the bisect session"), BISECT_START),
OPT_BOOL(0, "no-checkout", &no_checkout,
N_("update BISECT_HEAD instead of checking out the current commit")),
OPT_BOOL(0, "no-log", &nolog, OPT_BOOL(0, "no-log", &nolog,
N_("no log for BISECT_WRITE")), N_("no log for BISECT_WRITE")),
OPT_END() OPT_END()
@ -670,7 +675,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
switch (cmdmode) { switch (cmdmode) {
case NEXT_ALL: case NEXT_ALL:
res = bisect_next_all(the_repository, prefix, no_checkout); res = bisect_next_all(the_repository, prefix);
break; break;
case WRITE_TERMS: case WRITE_TERMS:
if (argc != 2) if (argc != 2)
@ -712,7 +717,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
break; break;
case BISECT_START: case BISECT_START:
set_terms(&terms, "bad", "good"); set_terms(&terms, "bad", "good");
res = bisect_start(&terms, no_checkout, argv, argc); res = bisect_start(&terms, argv, argc);
break; break;
default: default:
return error("BUG: unknown subcommand '%d'", cmdmode); return error("BUG: unknown subcommand '%d'", cmdmode);

View File

@ -637,8 +637,15 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (bisect_list) { if (bisect_list) {
int reaches, all; int reaches, all;
unsigned bisect_flags = 0;
find_bisection(&revs.commits, &reaches, &all, bisect_find_all); if (bisect_find_all)
bisect_flags |= FIND_BISECTION_ALL;
if (revs.first_parent_only)
bisect_flags |= FIND_BISECTION_FIRST_PARENT_ONLY;
find_bisection(&revs.commits, &reaches, &all, bisect_flags);
if (bisect_show_vars) if (bisect_show_vars)
return show_bisect_vars(&info, reaches, all); return show_bisect_vars(&info, reaches, all);

View File

@ -153,7 +153,7 @@ bisect_next() {
git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD $TERM_GOOD|| exit git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD $TERM_GOOD|| exit
# Perform all bisection computation, display and checkout # Perform all bisection computation, display and checkout
git bisect--helper --next-all $(git rev-parse --verify -q BISECT_HEAD > /dev/null && echo --no-checkout) git bisect--helper --next-all
res=$? res=$?
# Check if we should exit because bisection is finished # Check if we should exit because bisection is finished

View File

@ -2889,9 +2889,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
if (!revs->reflog_info && revs->grep_filter.use_reflog_filter) if (!revs->reflog_info && revs->grep_filter.use_reflog_filter)
die("cannot use --grep-reflog without --walk-reflogs"); die("cannot use --grep-reflog without --walk-reflogs");
if (revs->first_parent_only && revs->bisect)
die(_("--first-parent is incompatible with --bisect"));
if (revs->line_level_traverse && if (revs->line_level_traverse &&
(revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT))) (revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT)))
die(_("-L does not yet support diff formats besides -p and -s")); die(_("-L does not yet support diff formats besides -p and -s"));

View File

@ -128,8 +128,8 @@ test_expect_success 'rev-list can negate index objects' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_success '--bisect and --first-parent can not be combined' ' test_expect_success '--bisect and --first-parent can be combined' '
test_must_fail git rev-list --bisect --first-parent HEAD git rev-list --bisect --first-parent HEAD
' '
test_expect_success '--header shows a NUL after each commit' ' test_expect_success '--header shows a NUL after each commit' '

View File

@ -263,4 +263,49 @@ test_expect_success 'rev-parse --bisect can default to good/bad refs' '
test_cmp expect.sorted actual.sorted test_cmp expect.sorted actual.sorted
' '
test_output_expect_success '--bisect --first-parent' 'git rev-list --bisect --first-parent E ^F' <<EOF
e4
EOF
test_output_expect_success '--first-parent' 'git rev-list --first-parent E ^F' <<EOF
E
e1
e2
e3
e4
e5
e6
e7
e8
EOF
test_output_expect_success '--bisect-vars --first-parent' 'git rev-list --bisect-vars --first-parent E ^F' <<EOF
bisect_rev='e5'
bisect_nr=4
bisect_good=4
bisect_bad=3
bisect_all=9
bisect_steps=2
EOF
test_expect_success '--bisect-all --first-parent' '
cat >expect.unsorted <<-EOF &&
$(git rev-parse E) (tag: E, dist=0)
$(git rev-parse e1) (tag: e1, dist=1)
$(git rev-parse e2) (tag: e2, dist=2)
$(git rev-parse e3) (tag: e3, dist=3)
$(git rev-parse e4) (tag: e4, dist=4)
$(git rev-parse e5) (tag: e5, dist=4)
$(git rev-parse e6) (tag: e6, dist=3)
$(git rev-parse e7) (tag: e7, dist=2)
$(git rev-parse e8) (tag: e8, dist=1)
EOF
# expect results to be ordered by distance (descending),
# commit hash (ascending)
sort -k4,4r -k1,1 expect.unsorted >expect &&
git rev-list --bisect-all --first-parent E ^F >actual &&
test_cmp expect actual
'
test_done test_done

View File

@ -243,32 +243,30 @@ test_expect_success 'bisect skip: with commit both bad and skipped' '
' '
# We want to automatically find the commit that # We want to automatically find the commit that
# introduced "Another" into hello. # added "Another" into hello.
test_expect_success \ test_expect_success '"git bisect run" simple case' '
'"git bisect run" simple case' \ write_script test_script.sh <<-\EOF &&
'echo "#"\!"/bin/sh" > test_script.sh && ! grep Another hello >/dev/null
echo "grep Another hello > /dev/null" >> test_script.sh && EOF
echo "test \$? -ne 0" >> test_script.sh && git bisect start &&
chmod +x test_script.sh && git bisect good $HASH1 &&
git bisect start && git bisect bad $HASH4 &&
git bisect good $HASH1 && git bisect run ./test_script.sh >my_bisect_log.txt &&
git bisect bad $HASH4 && grep "$HASH3 is the first bad commit" my_bisect_log.txt &&
git bisect run ./test_script.sh > my_bisect_log.txt && git bisect reset
grep "$HASH3 is the first bad commit" my_bisect_log.txt && '
git bisect reset'
# We want to automatically find the commit that # We want to automatically find the commit that
# introduced "Ciao" into hello. # added "Ciao" into hello.
test_expect_success \ test_expect_success '"git bisect run" with more complex "git bisect start"' '
'"git bisect run" with more complex "git bisect start"' \ write_script test_script.sh <<-\EOF &&
'echo "#"\!"/bin/sh" > test_script.sh && ! grep Ciao hello >/dev/null
echo "grep Ciao hello > /dev/null" >> test_script.sh && EOF
echo "test \$? -ne 0" >> test_script.sh && git bisect start $HASH4 $HASH1 &&
chmod +x test_script.sh && git bisect run ./test_script.sh >my_bisect_log.txt &&
git bisect start $HASH4 $HASH1 && grep "$HASH4 is the first bad commit" my_bisect_log.txt &&
git bisect run ./test_script.sh > my_bisect_log.txt && git bisect reset
grep "$HASH4 is the first bad commit" my_bisect_log.txt && '
git bisect reset'
# $HASH1 is good, $HASH5 is bad, we skip $HASH3 # $HASH1 is good, $HASH5 is bad, we skip $HASH3
# but $HASH4 is good, # but $HASH4 is good,
@ -295,24 +293,17 @@ HASH6=
test_expect_success 'bisect run & skip: cannot tell between 2' ' test_expect_success 'bisect run & skip: cannot tell between 2' '
add_line_into_file "6: Yet a line." hello && add_line_into_file "6: Yet a line." hello &&
HASH6=$(git rev-parse --verify HEAD) && HASH6=$(git rev-parse --verify HEAD) &&
echo "#"\!"/bin/sh" > test_script.sh && write_script test_script.sh <<-\EOF &&
echo "sed -ne \\\$p hello | grep Ciao > /dev/null && exit 125" >> test_script.sh && sed -ne \$p hello | grep Ciao >/dev/null && exit 125
echo "grep line hello > /dev/null" >> test_script.sh && ! grep line hello >/dev/null
echo "test \$? -ne 0" >> test_script.sh && EOF
chmod +x test_script.sh &&
git bisect start $HASH6 $HASH1 && git bisect start $HASH6 $HASH1 &&
if git bisect run ./test_script.sh > my_bisect_log.txt test_expect_code 2 git bisect run ./test_script.sh >my_bisect_log.txt &&
then grep "first bad commit could be any of" my_bisect_log.txt &&
echo Oops, should have failed. ! grep $HASH3 my_bisect_log.txt &&
false ! grep $HASH6 my_bisect_log.txt &&
else grep $HASH4 my_bisect_log.txt &&
test $? -eq 2 && grep $HASH5 my_bisect_log.txt
grep "first bad commit could be any of" my_bisect_log.txt &&
! grep $HASH3 my_bisect_log.txt &&
! grep $HASH6 my_bisect_log.txt &&
grep $HASH4 my_bisect_log.txt &&
grep $HASH5 my_bisect_log.txt
fi
' '
HASH7= HASH7=
@ -320,14 +311,13 @@ test_expect_success 'bisect run & skip: find first bad' '
git bisect reset && git bisect reset &&
add_line_into_file "7: Should be the last line." hello && add_line_into_file "7: Should be the last line." hello &&
HASH7=$(git rev-parse --verify HEAD) && HASH7=$(git rev-parse --verify HEAD) &&
echo "#"\!"/bin/sh" > test_script.sh && write_script test_script.sh <<-\EOF &&
echo "sed -ne \\\$p hello | grep Ciao > /dev/null && exit 125" >> test_script.sh && sed -ne \$p hello | grep Ciao >/dev/null && exit 125
echo "sed -ne \\\$p hello | grep day > /dev/null && exit 125" >> test_script.sh && sed -ne \$p hello | grep day >/dev/null && exit 125
echo "grep Yet hello > /dev/null" >> test_script.sh && ! grep Yet hello >/dev/null
echo "test \$? -ne 0" >> test_script.sh && EOF
chmod +x test_script.sh &&
git bisect start $HASH7 $HASH1 && git bisect start $HASH7 $HASH1 &&
git bisect run ./test_script.sh > my_bisect_log.txt && git bisect run ./test_script.sh >my_bisect_log.txt &&
grep "$HASH6 is the first bad commit" my_bisect_log.txt grep "$HASH6 is the first bad commit" my_bisect_log.txt
' '
@ -458,6 +448,24 @@ test_expect_success 'many merge bases creation' '
grep "$SIDE_HASH5" merge_bases.txt grep "$SIDE_HASH5" merge_bases.txt
' '
# We want to automatically find the merge that
# added "line" into hello.
test_expect_success '"git bisect run --first-parent" simple case' '
git rev-list --first-parent $B_HASH ^$HASH4 >first_parent_chain.txt &&
write_script test_script.sh <<-\EOF &&
grep $(git rev-parse HEAD) first_parent_chain.txt || exit -1
! grep line hello >/dev/null
EOF
git bisect start --first-parent &&
test_path_is_file ".git/BISECT_FIRST_PARENT" &&
git bisect good $HASH4 &&
git bisect bad $B_HASH &&
git bisect run ./test_script.sh >my_bisect_log.txt &&
grep "$B_HASH is the first bad commit" my_bisect_log.txt &&
git bisect reset &&
test_path_is_missing .git/BISECT_FIRST_PARENT
'
test_expect_success 'good merge bases when good and bad are siblings' ' test_expect_success 'good merge bases when good and bad are siblings' '
git bisect start "$B_HASH" "$A_HASH" > my_bisect_log.txt && git bisect start "$B_HASH" "$A_HASH" > my_bisect_log.txt &&
test_i18ngrep "merge base must be tested" my_bisect_log.txt && test_i18ngrep "merge base must be tested" my_bisect_log.txt &&