bisect: introduce first-parent flag

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.

Signed-off-by: Aaron Lipman <alipman88@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Aaron Lipman 2020-08-07 17:58:37 -04:00 committed by Junio C Hamano
parent be5fe2000d
commit e8861ffc20
4 changed files with 42 additions and 3 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

@ -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;
@ -460,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 argv_array *array) static void read_bisect_paths(struct argv_array *array)
@ -998,7 +1000,7 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix)
struct object_id *bisect_rev; struct object_id *bisect_rev;
char *steps_msg; char *steps_msg;
int no_checkout = ref_exists("BISECT_HEAD"); int no_checkout = ref_exists("BISECT_HEAD");
int first_parent_only = 0; /* TODO: pass --first-parent flag from git bisect start */ int first_parent_only = file_exists(git_path_bisect_first_parent());
read_bisect_terms(&term_bad, &term_good); read_bisect_terms(&term_bad, &term_good);
if (read_bisect_refs()) if (read_bisect_refs())
@ -1142,6 +1144,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

@ -17,6 +17,7 @@ static GIT_PATH_FUNC(git_path_bisect_head, "BISECT_HEAD")
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"), N_("git bisect--helper --next-all"),
@ -28,7 +29,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
}; };
@ -424,6 +425,7 @@ finish:
static int bisect_start(struct bisect_terms *terms, const char **argv, int argc) static int bisect_start(struct bisect_terms *terms, const char **argv, int argc)
{ {
int no_checkout = 0; 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;
@ -453,6 +455,8 @@ static int bisect_start(struct bisect_terms *terms, const char **argv, int argc)
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++;
@ -577,6 +581,9 @@ static int bisect_start(struct bisect_terms *terms, const char **argv, int argc)
*/ */
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);

View File

@ -448,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 &&