[PATCH v15 15/27] bisect--helper: `bisect_next` and `bisect_auto_next` shell function in C
Show changes to 3 files +285 −107
bisect.c, builtin/bisect--helper.c, git-bisect.sh
diff --git a/bisect.c b/bisect.c
index 45d598d..7c97e85 100644
--- a/bisect.c
+++ b/bisect.c
@@ -618,6 +618,12 @@ static void bisect_rev_setup(struct rev_info *revs, const char *prefix,
struct argv_array rev_argv = ARGV_ARRAY_INIT;
int i;
+ /*
+ * Since the code is slowly being converted to C, there might be
+ * instances where the revisions were initialized before. Thus
+ * we first need to reset it.
+ */
+ reset_revision_walk();
init_revisions(revs, prefix);
revs->abbrev = 0;
revs->commit_format = CMIT_FMT_UNSPECIFIED;
@@ -638,17 +644,22 @@ static void bisect_rev_setup(struct rev_info *revs, const char *prefix,
static void bisect_common(struct rev_info *revs)
{
+ /*
+ * We don't want to clean the bisection state
+ * as we need to get back to where we started
+ * by using `git bisect reset`.
+ */
if (prepare_revision_walk(revs))
die("revision walk setup failed");
if (revs->tree_objects)
mark_edges_uninteresting(revs, NULL);
}
-static void exit_if_skipped_commits(struct commit_list *tried,
+static int exit_if_skipped_commits(struct commit_list *tried,
const struct object_id *bad)
{
if (!tried)
- return;
+ return 0;
printf("There are only 'skip'ped commits left to test.\n"
"The first %s commit could be any of:\n", term_bad);
@@ -659,7 +670,13 @@ static void exit_if_skipped_commits(struct commit_list *tried,
if (bad)
printf("%s\n", oid_to_hex(bad));
printf(_("We cannot bisect more!\n"));
- exit(2);
+
+ /*
+ * We don't want to clean the bisection state
+ * as we need to get back to where we started
+ * by using `git bisect reset`.
+ */
+ return 2;
}
static int is_expected_rev(const struct object_id *oid)
@@ -700,7 +717,7 @@ static int bisect_checkout(const unsigned char *bisect_rev, int no_checkout)
int res;
res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
if (res)
- exit(res);
+ return res;
}
argv_show_branch[1] = bisect_rev_hex;
@@ -729,7 +746,7 @@ static struct commit **get_bad_and_good_commits(int *rev_nr)
return rev;
}
-static void handle_bad_merge_base(void)
+static int handle_bad_merge_base(void)
{
if (is_expected_rev(current_bad_oid)) {
char *bad_hex = oid_to_hex(current_bad_oid);
@@ -750,17 +767,23 @@ static void handle_bad_merge_base(void)
"between %s and [%s].\n"),
bad_hex, term_bad, term_good, bad_hex, good_hex);
}
- exit(3);
+ /*
+ * We don't want to clean the bisection state
+ * as we need to get back to where we started
+ * by using `git bisect reset`.
+ */
+ return 3;
}
fprintf(stderr, _("Some %s revs are not ancestor of the %s rev.\n"
"git bisect cannot work properly in this case.\n"
"Maybe you mistook %s and %s revs?\n"),
term_good, term_bad, term_good, term_bad);
- exit(1);
+ bisect_clean_state();
+ return 1;
}
-static void handle_skipped_merge_base(const unsigned char *mb)
+static int handle_skipped_merge_base(const unsigned char *mb)
{
char *mb_hex = sha1_to_hex(mb);
char *bad_hex = oid_to_hex(current_bad_oid);
@@ -773,21 +796,22 @@ static void handle_skipped_merge_base(const unsigned char *mb)
"We continue anyway."),
bad_hex, good_hex, term_bad, mb_hex, bad_hex);
free(good_hex);
+ return 0;
}
/*
* "check_merge_bases" checks that merge bases are not "bad" (or "new").
*
* - If one is "bad" (or "new"), it means the user assumed something wrong
- * and we must exit with a non 0 error code.
+ * and we must return error with a non 0 error code.
* - If one is "good" (or "old"), that's good, we have nothing to do.
* - If one is "skipped", we can't know but we should warn.
* - If we don't know, we should check it out and ask the user to test.
*/
-static void check_merge_bases(int no_checkout)
+static int check_merge_bases(int no_checkout)
{
struct commit_list *result;
- int rev_nr;
+ int rev_nr, res = 0;
struct commit **rev = get_bad_and_good_commits(&rev_nr);
result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1);
@@ -795,19 +819,30 @@ static void check_merge_bases(int no_checkout)
for (; result; result = result->next) {
const unsigned char *mb = result->item->object.oid.hash;
if (!hashcmp(mb, current_bad_oid->hash)) {
- handle_bad_merge_base();
+ res = handle_bad_merge_base();
+ break;
} else if (0 <= sha1_array_lookup(&good_revs, mb)) {
continue;
} else if (0 <= sha1_array_lookup(&skipped_revs, mb)) {
- handle_skipped_merge_base(mb);
+ res = handle_skipped_merge_base(mb);
+ break;
} else {
printf(_("Bisecting: a merge base must be tested\n"));
- exit(bisect_checkout(mb, no_checkout));
+ res = bisect_checkout(mb, no_checkout);
+ /*
+ * We don't want to clean the bisection state
+ * as we need to get back to where we started
+ * by using `git bisect reset`.
+ */
+ if (!res)
+ exit(0);
+ break;
}
}
free(rev);
free_commit_list(result);
+ return res;
}
static int check_ancestors(const char *prefix)
@@ -843,16 +878,21 @@ static int check_ancestors(const char *prefix)
*
* If that's not the case, we need to check the merge bases.
* If a merge base must be tested by the user, its source code will be
- * checked out to be tested by the user and we will exit.
+ * checked out to be tested by the user and we will return.
*/
-static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout)
+static int check_good_are_ancestors_of_bad(const char *prefix, int no_checkout)
{
char *filename = git_pathdup("BISECT_ANCESTORS_OK");
struct stat st;
- int fd;
+ int fd, res = 0;
+ /*
+ * We don't want to clean the bisection state
+ * as we need to get back to where we started
+ * by using `git bisect reset`.
+ */
if (!current_bad_oid)
- die(_("a %s revision is needed"), term_bad);
+ error(_("a %s revision is needed"), term_bad);
/* Check if file BISECT_ANCESTORS_OK exists. */
if (!stat(filename, &st) && S_ISREG(st.st_mode))
@@ -864,7 +904,10 @@ static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout)
/* Check if all good revs are ancestor of the bad rev. */
if (check_ancestors(prefix))
- check_merge_bases(no_checkout);
+ res = check_merge_bases(no_checkout);
+
+ if (res)
+ goto done;
/* Create file BISECT_ANCESTORS_OK. */
fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
@@ -873,8 +916,11 @@ static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout)
filename);
else
close(fd);
+
+ goto done;
done:
free(filename);
+ return res;
}
/*
@@ -933,7 +979,7 @@ void read_bisect_terms(const char **read_bad, const char **read_good)
}
/*
- * We use the convention that exiting with an exit code 10 means that
+ * We use the convention to return with an return code 10 means that
* the bisection process finished successfully.
* In this case the calling shell script should exit 0.
*
@@ -944,7 +990,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
{
struct rev_info revs;
struct commit_list *tried;
- int reaches = 0, all = 0, nr, steps;
+ int reaches = 0, all = 0, nr, steps, res;
const unsigned char *bisect_rev;
char steps_msg[32];
@@ -952,7 +998,9 @@ int bisect_next_all(const char *prefix, int no_checkout)
if (read_bisect_refs())
die(_("reading bisect refs failed"));
- check_good_are_ancestors_of_bad(prefix, no_checkout);
+ res = check_good_are_ancestors_of_bad(prefix, no_checkout);
+ if (res)
+ return res;
bisect_rev_setup(&revs, prefix, "%s", "^%s", 1);
revs.limited = 1;
@@ -964,34 +1012,52 @@ int bisect_next_all(const char *prefix, int no_checkout)
revs.commits = managed_skipped(revs.commits, &tried);
if (!revs.commits) {
+ int res;
/*
- * We should exit here only if the "bad"
+ * We should return error here only if the "bad"
* commit is also a "skip" commit.
*/
- exit_if_skipped_commits(tried, NULL);
+ res = exit_if_skipped_commits(tried, NULL);
+ if (res)
+ return res;
printf(_("%s was both %s and %s\n"),
oid_to_hex(current_bad_oid),
term_good,
term_bad);
- exit(1);
+
+ /*
+ * We don't want to clean the bisection state
+ * as we need to get back to where we started
+ * by using `git bisect reset`.
+ */
+ return 1;
}
if (!all) {
fprintf(stderr, _("No testable commit found.\n"
"Maybe you started with bad path parameters?\n"));
- exit(4);
+
+ /*
+ * We don't want to clean the bisection state
+ * as we need to get back to where we started
+ * by using `git bisect reset`.
+ */
+ return 4;
}
bisect_rev = revs.commits->item->object.oid.hash;
if (!hashcmp(bisect_rev, current_bad_oid->hash)) {
- exit_if_skipped_commits(tried, current_bad_oid);
+ res = exit_if_skipped_commits(tried, current_bad_oid);
+ if (res)
+ return res;
+
printf("%s is the first %s commit\n", sha1_to_hex(bisect_rev),
term_bad);
show_diff_tree(prefix, revs.commits->item);
/* This means the bisection process succeeded. */
- exit(10);
+ return 10;
}
nr = all - reaches - 1;
@@ -1005,7 +1071,11 @@ int bisect_next_all(const char *prefix, int no_checkout)
"Bisecting: %d revisions left to test after this %s\n",
nr), nr, steps_msg);
- return bisect_checkout(bisect_rev, no_checkout);
+ res = bisect_checkout(bisect_rev, no_checkout);
+ if (res)
+ bisect_clean_state();
+
+ return res;
}
static inline int log2i(int n)
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index 1d3e17f..fcd7574 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -8,6 +8,7 @@
#include "run-command.h"
#include "prompt.h"
#include "quote.h"
+#include "revision.h"
static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
@@ -29,6 +30,8 @@ static const char * const git_bisect_helper_usage[] = {
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>]"
"[--no-checkout] [<bad> [<good>...]] [--] [<paths>...]"),
+ N_("git bisect--helper --bisect-next"),
+ N_("git bisect--helper --bisect-auto-next"),
NULL
};
@@ -408,6 +411,136 @@ static int bisect_terms(struct bisect_terms *terms, const char **argv, int argc)
return 0;
}
+static int register_good_ref(const char *refname,
+ const struct object_id *oid, int flags,
+ void *cb_data)
+{
+ struct string_list *good_refs = cb_data;
+ string_list_append(good_refs, oid_to_hex(oid));
+ return 0;
+}
+
+static int bisect_next(struct bisect_terms *terms, const char *prefix)
+{
+ int res, no_checkout;
+
+ /*
+ * In case of mistaken revs or checkout error, or signals received,
+ * "bisect_auto_next" below may exit or misbehave.
+ * We have to trap this to be able to clean up using
+ * "bisect_clean_state".
+ */
+ if (bisect_next_check(terms, terms->term_good))
+ return -1;
+
+ no_checkout = !is_empty_or_missing_file(git_path_bisect_head());
+
+ /* Perform all bisection computation, display and checkout */
+ res = bisect_next_all(prefix , no_checkout);
+
+ if (res == 10) {
+ FILE *fp = NULL;
+ unsigned char sha1[20];
+ struct commit *commit;
+ struct pretty_print_context pp = {0};
+ struct strbuf commit_name = STRBUF_INIT;
+ char *bad_ref = xstrfmt("refs/bisect/%s",
+ terms->term_bad);
+ int retval = 0;
+
+ read_ref(bad_ref, sha1);
+ commit = lookup_commit_reference(sha1);
+ format_commit_message(commit, "%s", &commit_name, &pp);
+ fp = fopen(git_path_bisect_log(), "a");
+ if (!fp) {
+ retval = -1;
+ goto finish_10;
+ }
+ if (fprintf(fp, "# first %s commit: [%s] %s\n",
+ terms->term_bad, sha1_to_hex(sha1),
+ commit_name.buf) < 1){
+ retval = -1;
+ goto finish_10;
+ }
+ goto finish_10;
+ finish_10:
+ if (fp)
+ fclose(fp);
+ strbuf_release(&commit_name);
+ free(bad_ref);
+ return retval;
+ }
+ else if (res == 2) {
+ FILE *fp = NULL;
+ struct rev_info revs;
+ struct argv_array rev_argv = ARGV_ARRAY_INIT;
+ struct string_list good_revs = STRING_LIST_INIT_DUP;
+ struct pretty_print_context pp = {0};
+ struct commit *commit;
+ char *term_good = xstrfmt("%s-*", terms->term_good);
+ int i, retval = 0;
+
+ fp = fopen(git_path_bisect_log(), "a");
+ if (!fp) {
+ retval = -1;
+ goto finish_2;
+ }
+ if (fprintf(fp, "# only skipped commits left to test\n") < 1) {
+ retval = -1;
+ goto finish_2;
+ }
+ for_each_glob_ref_in(register_good_ref, term_good,
+ "refs/bisect/", (void *) &good_revs);
+
+ argv_array_pushl(&rev_argv, "skipped_commits", "refs/bisect/bad", "--not", NULL);
+ for (i = 0; i < good_revs.nr; i++)
+ argv_array_push(&rev_argv, good_revs.items[i].string);
+
+ /* It is important to reset the flags used by revision walks
+ * as the previous call to bisect_next_all() in turn
+ * setups a revision walk.
+ */
+ reset_revision_walk();
+ init_revisions(&revs, NULL);
+ rev_argv.argc = setup_revisions(rev_argv.argc, rev_argv.argv, &revs, NULL);
+ argv_array_clear(&rev_argv);
+ string_list_clear(&good_revs, 0);
+ if (prepare_revision_walk(&revs))
+ die(_("revision walk setup failed\n"));
+
+ while ((commit = get_revision(&revs)) != NULL) {
+ struct strbuf commit_name = STRBUF_INIT;
+ format_commit_message(commit, "%s",
+ &commit_name, &pp);
+ fprintf(fp, "# possible first %s commit: "
+ "[%s] %s\n", terms->term_bad,
+ oid_to_hex(&commit->object.oid),
+ commit_name.buf);
+ strbuf_release(&commit_name);
+ }
+ goto finish_2;
+ finish_2:
+ if (fp)
+ fclose(fp);
+ string_list_clear(&good_revs, 0);
+ argv_array_clear(&rev_argv);
+ free(term_good);
+ if (retval)
+ return retval;
+ else
+ return res;
+ }
+ return res;
+}
+
+static int bisect_auto_next(struct bisect_terms *terms, const char *prefix)
+{
+ if (!bisect_next_check(terms, NULL))
+ return bisect_next(terms, prefix);
+
+ return 0;
+}
+
static int bisect_start(struct bisect_terms *terms, int no_checkout,
const char **argv, int argc)
{
@@ -427,15 +560,24 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
no_checkout = 1;
for (i = 0; i < argc; i++) {
- if (!strcmp(argv[i], "--")) {
+ const char *arg;
+ if (starts_with(argv[i], "'"))
+ arg = sq_dequote(xstrdup(argv[i]));
+ else
+ arg = argv[i];
+ if (!strcmp(arg, "--")) {
has_double_dash = 1;
break;
}
}
for (i = 0; i < argc; i++) {
- const char *commit_id = xstrfmt("%s^{commit}", argv[i]);
- const char *arg = argv[i];
+ const char *arg, *commit_id;
+ if (starts_with(argv[i], "'"))
+ arg = sq_dequote(xstrdup(argv[i]));
+ else
+ arg = argv[i];
+ commit_id = xstrfmt("%s^{commit}", arg);
if (!strcmp(argv[i], "--")) {
has_double_dash = 1;
break;
@@ -443,24 +585,31 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
no_checkout = 1;
} else if (!strcmp(arg, "--term-good") ||
!strcmp(arg, "--term-old")) {
+ if (starts_with(argv[++i], "'"))
+ terms->term_good = sq_dequote(xstrdup(argv[i]));
+ else
+ terms->term_good = xstrdup(argv[i]);
must_write_terms = 1;
- terms->term_good = xstrdup(argv[++i]);
} else if (skip_prefix(arg, "--term-good=", &arg)) {
must_write_terms = 1;
- terms->term_good = xstrdup(arg);
+ terms->term_good = arg;
} else if (skip_prefix(arg, "--term-old=", &arg)) {
must_write_terms = 1;
- terms->term_good = xstrdup(arg);
+ terms->term_good = arg;
} else if (!strcmp(arg, "--term-bad") ||
!strcmp(arg, "--term-new")) {
+ const char *next_arg;
+ if (starts_with(argv[++i], "'"))
+ terms->term_bad = sq_dequote(xstrdup(argv[i]));
+ else
+ terms->term_bad = xstrdup(argv[i]);
must_write_terms = 1;
- terms->term_bad = xstrdup(argv[++i]);
} else if (skip_prefix(arg, "--term-bad=", &arg)) {
must_write_terms = 1;
- terms->term_bad = xstrdup(arg);
+ terms->term_bad = arg;
} else if (skip_prefix(arg, "--term-new=", &arg)) {
must_write_terms = 1;
- terms->term_good = xstrdup(arg);
+ terms->term_good = arg;
} else if (starts_with(arg, "--") &&
!one_of(arg, "--term-good", "--term-bad", NULL)) {
die(_("unrecognised option: '%s'"), arg);
@@ -604,7 +753,7 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
strbuf_release(&start_head);
strbuf_release(&bisect_names);
strbuf_release(&orig_args);
- return retval;
+ return retval || bisect_auto_next(terms, NULL);
}
int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
@@ -619,7 +768,9 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
CHECK_AND_SET_TERMS,
BISECT_NEXT_CHECK,
BISECT_TERMS,
- BISECT_START
+ BISECT_START,
+ BISECT_NEXT,
+ BISECT_AUTO_NEXT,
} cmdmode = 0;
int no_checkout = 0, res = 0;
struct option options[] = {
@@ -643,6 +794,10 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
N_("print out the bisect terms"), BISECT_TERMS),
OPT_CMDMODE(0, "bisect-start", &cmdmode,
N_("start the bisect session"), BISECT_START),
+ OPT_CMDMODE(0, "bisect-next", &cmdmode,
+ N_("find the next bisection commit"), BISECT_NEXT),
+ OPT_CMDMODE(0, "bisect-auto-next", &cmdmode,
+ N_("verify the next bisection state then find the next bisection state"), BISECT_AUTO_NEXT),
OPT_BOOL(0, "no-checkout", &no_checkout,
N_("update BISECT_HEAD instead of checking out the current commit")),
OPT_END()
@@ -710,6 +865,18 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
terms.term_bad = "bad";
res = bisect_start(&terms, no_checkout, argv, argc);
break;
+ case BISECT_NEXT:
+ if (argc)
+ die(_("--bisect-next requires 0 arguments"));
+ get_terms(&terms);
+ res = bisect_next(&terms, prefix);
+ break;
+ case BISECT_AUTO_NEXT:
+ if (argc)
+ die(_("--bisect-auto-next requires 0 arguments"));
+ get_terms(&terms);
+ res = bisect_auto_next(&terms, prefix);
+ break;
default:
die("BUG: unknown subcommand '%d'", cmdmode);
}
diff --git a/git-bisect.sh b/git-bisect.sh
index f0896b3..d574c44 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -63,34 +63,13 @@ bisect_autostart() {
[Nn]*)
exit ;;
esac
- bisect_start
+ git bisect--helper --bisect-start
else
exit 1
fi
}
}
-bisect_start() {
- git bisect--helper --bisect-start $@ || exit
-
- #
- # Change state.
- # In case of mistaken revs or checkout error, or signals received,
- # "bisect_auto_next" below may exit or misbehave.
- # We have to trap this to be able to clean up using
- # "bisect_clean_state".
- #
- trap 'git bisect--helper --bisect-clean-state' 0
- trap 'exit 255' 1 2 3 15
- #
- # Check if we can proceed to the next bisect state.
- #
- get_terms
- bisect_auto_next
-
- trap '-' 0
-}
-
bisect_skip() {
all=''
for arg in "$@"
@@ -109,6 +88,7 @@ bisect_skip() {
bisect_state() {
bisect_autostart
state=$1
+ get_terms
git bisect--helper --check-and-set-terms $state $TERM_GOOD $TERM_BAD || exit
get_terms
case "$#,$state" in
@@ -139,45 +119,7 @@ bisect_state() {
*)
usage ;;
esac
- bisect_auto_next
-}
-
-bisect_auto_next() {
- git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD && bisect_next || :
-}
-
-bisect_next() {
- case "$#" in 0) ;; *) usage ;; esac
- bisect_autostart
- git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD $TERM_GOOD|| exit
-
- # Perform all bisection computation, display and checkout
- git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout)
- res=$?
-
- # Check if we should exit because bisection is finished
- if test $res -eq 10
- then
- bad_rev=$(git show-ref --hash --verify refs/bisect/$TERM_BAD)
- bad_commit=$(git show-branch $bad_rev)
- echo "# first $TERM_BAD commit: $bad_commit" >>"$GIT_DIR/BISECT_LOG"
- exit 0
- elif test $res -eq 2
- then
- echo "# only skipped commits left to test" >>"$GIT_DIR/BISECT_LOG"
- good_revs=$(git for-each-ref --format="%(objectname)" "refs/bisect/$TERM_GOOD-*")
- for skipped in $(git rev-list refs/bisect/$TERM_BAD --not $good_revs)
- do
- skipped_commit=$(git show-branch $skipped)
- echo "# possible first $TERM_BAD commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG"
- done
- exit $res
- fi
-
- # Check for an error in the bisection process
- test $res -ne 0 && exit $res
-
- return 0
+ git bisect--helper --bisect-auto-next || exit
}
bisect_visualize() {
@@ -221,8 +163,7 @@ bisect_replay () {
get_terms
case "$command" in
start)
- cmd="bisect_start $rev"
- eval "$cmd" ;;
+ eval "git bisect--helper --bisect-start $rev" ;;
"$TERM_GOOD"|"$TERM_BAD"|skip)
git bisect--helper --bisect-write "$command" "$rev" "$TERM_GOOD" "$TERM_BAD" || exit;;
terms)
@@ -231,7 +172,7 @@ bisect_replay () {
die "$(gettext "?? what are you talking about?")" ;;
esac
done <"$file"
- bisect_auto_next
+ git bisect--helper --bisect-auto-next
}
bisect_run () {
@@ -319,14 +260,15 @@ case "$#" in
help)
git bisect -h ;;
start)
- bisect_start "$@" ;;
+ git bisect--helper --bisect-start "$@" ;;
bad|good|new|old|"$TERM_BAD"|"$TERM_GOOD")
bisect_state "$cmd" "$@" ;;
skip)
bisect_skip "$@" ;;
next)
# Not sure we want "next" at the UI level anymore.
- bisect_next "$@" ;;
+ get_terms
+ git bisect--helper --bisect-next "$@" || exit ;;
visualize|view)
bisect_visualize "$@" ;;
reset)
--
https://github.com/git/git/pull/287