Skip to content

Commit b2ae9b8

Browse files
HaraldNordgrengitster
authored andcommitted
bisect: add --auto-reset to leave when done
When a bisection finishes, "git bisect" reports the first bad commit but leaves the session active until "git bisect reset" is run by hand. Add an "--auto-reset[=<where>]" option, accepted by both "git bisect start" and "git bisect run", that resets as soon as the first bad commit is found. The "original" value returns to the commit checked out before "git bisect start", while "found" leaves the first bad commit checked out; omitting the value defaults to "original". Persist the selected target in a BISECT_AUTO_RESET state file and perform the reset quietly. Reject this option together with "--no-checkout", since that mode must not check out either target. Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent e9fd5fe commit b2ae9b8

4 files changed

Lines changed: 219 additions & 4 deletions

File tree

Documentation/git-bisect.adoc

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ SYNOPSIS
1010
--------
1111
[synopsis]
1212
git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]
13-
[--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]
13+
[--no-checkout] [--first-parent] [--auto-reset[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]
1414
git bisect (bad|new|<term-new>) [<rev>]
1515
git bisect (good|old|<term-old>) [<rev>...]
1616
git bisect terms [--term-(good|old) | --term-(bad|new)]
@@ -20,7 +20,7 @@ git bisect reset [<commit>]
2020
git bisect (visualize|view)
2121
git bisect replay <logfile>
2222
git bisect log
23-
git bisect run <cmd> [<arg>...]
23+
git bisect run [--auto-reset[=<where>]] <cmd> [<arg>...]
2424
git bisect help
2525

2626
DESCRIPTION
@@ -385,6 +385,16 @@ ignored.
385385
This option is particularly useful in avoiding false positives when a merged
386386
branch contained broken or non-buildable commits, but the merge itself was OK.
387387

388+
`--auto-reset[=<where>]`::
389+
Once the first bad commit is found, report it and clean up the
390+
bisection state. `<where>` may be `original` to return to the commit
391+
checked out before `git bisect start`, or `found` to leave the first
392+
bad commit checked out. If `<where>` is omitted, it defaults to
393+
`original`.
394+
+
395+
This option may be given to `git bisect start` or to `git bisect run`. It
396+
cannot be used for a bisection started with `--no-checkout`.
397+
388398
EXAMPLES
389399
--------
390400

bisect.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@ static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
488488
static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
489489
static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
490490
static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
491+
static GIT_PATH_FUNC(git_path_bisect_auto_reset, "BISECT_AUTO_RESET")
491492

492493
static void read_bisect_paths(struct strvec *array)
493494
{
@@ -1211,6 +1212,7 @@ int bisect_clean_state(void)
12111212
unlink_or_warn(git_path_bisect_run());
12121213
unlink_or_warn(git_path_bisect_terms());
12131214
unlink_or_warn(git_path_bisect_first_parent());
1215+
unlink_or_warn(git_path_bisect_auto_reset());
12141216
/*
12151217
* Cleanup BISECT_START last to support the --no-checkout option
12161218
* introduced in the commit 4796e823a.

builtin/bisect.c

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
2424
static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
2525
static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
2626
static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
27+
static GIT_PATH_FUNC(git_path_bisect_auto_reset, "BISECT_AUTO_RESET")
2728
static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
2829

2930
#define BUILTIN_GIT_BISECT_START_USAGE \
3031
N_("git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\n" \
31-
" [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]")
32+
" [--no-checkout] [--first-parent] [--auto-reset[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]")
3233
#define BUILTIN_GIT_BISECT_BAD_USAGE \
3334
N_("git bisect (bad|new|<term-new>) [<rev>]")
3435
#define BUILTIN_GIT_BISECT_GOOD_USAGE \
@@ -48,7 +49,7 @@ static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
4849
#define BUILTIN_GIT_BISECT_LOG_USAGE \
4950
"git bisect log"
5051
#define BUILTIN_GIT_BISECT_RUN_USAGE \
51-
N_("git bisect run <cmd> [<arg>...]")
52+
N_("git bisect run [--auto-reset[=<where>]] <cmd> [<arg>...]")
5253
#define BUILTIN_GIT_BISECT_HELP_USAGE \
5354
"git bisect help"
5455

@@ -68,6 +69,12 @@ static const char * const git_bisect_usage[] = {
6869
NULL
6970
};
7071

72+
enum auto_reset_mode {
73+
AUTO_RESET_NONE,
74+
AUTO_RESET_ORIGINAL,
75+
AUTO_RESET_FOUND,
76+
};
77+
7178
struct add_bisect_ref_data {
7279
struct rev_info *revs;
7380
unsigned int object_flags;
@@ -268,6 +275,59 @@ static int bisect_reset(const char *commit, int quiet)
268275
return bisect_clean_state();
269276
}
270277

278+
static int parse_auto_reset(const char *value, enum auto_reset_mode *mode)
279+
{
280+
if (!strcmp(value, "original"))
281+
*mode = AUTO_RESET_ORIGINAL;
282+
else if (!strcmp(value, "found"))
283+
*mode = AUTO_RESET_FOUND;
284+
else
285+
return error(_("invalid value for '--auto-reset': '%s'"), value);
286+
287+
return 0;
288+
}
289+
290+
static const char *auto_reset_mode_name(enum auto_reset_mode mode)
291+
{
292+
switch (mode) {
293+
case AUTO_RESET_ORIGINAL:
294+
return "original";
295+
case AUTO_RESET_FOUND:
296+
return "found";
297+
case AUTO_RESET_NONE:
298+
BUG("no name for unset auto-reset mode");
299+
}
300+
BUG("unknown auto-reset mode %d", mode);
301+
}
302+
303+
static int bisect_auto_reset(struct bisect_terms *terms)
304+
{
305+
struct strbuf value = STRBUF_INIT;
306+
enum auto_reset_mode mode;
307+
char *commit = NULL;
308+
int res;
309+
310+
if (strbuf_read_file(&value, git_path_bisect_auto_reset(), 0) < 0) {
311+
res = error_errno(_("could not read '%s'"),
312+
git_path_bisect_auto_reset());
313+
goto cleanup;
314+
}
315+
strbuf_trim(&value);
316+
if (parse_auto_reset(value.buf, &mode)) {
317+
res = -1;
318+
goto cleanup;
319+
}
320+
321+
if (mode == AUTO_RESET_FOUND)
322+
commit = xstrfmt("refs/bisect/%s", terms->term_bad);
323+
res = bisect_reset(commit, 1);
324+
325+
cleanup:
326+
free(commit);
327+
strbuf_release(&value);
328+
return res;
329+
}
330+
271331
static void log_commit(FILE *fp,
272332
const char *fmt, const char *state,
273333
struct commit *commit)
@@ -688,6 +748,8 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre
688748

689749
if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {
690750
res = bisect_successful(terms);
751+
if (!res && !is_empty_or_missing_file(git_path_bisect_auto_reset()))
752+
res = bisect_auto_reset(terms);
691753
return res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;
692754
} else if (res == BISECT_ONLY_SKIPPED_LEFT) {
693755
res = bisect_skipped_commits(terms);
@@ -711,6 +773,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
711773
{
712774
int no_checkout = 0;
713775
int first_parent_only = 0;
776+
enum auto_reset_mode auto_reset = AUTO_RESET_NONE;
714777
int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;
715778
int flags, pathspec_pos;
716779
enum bisect_error res = BISECT_OK;
@@ -743,6 +806,13 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
743806
no_checkout = 1;
744807
} else if (!strcmp(arg, "--first-parent")) {
745808
first_parent_only = 1;
809+
} else if (!strcmp(arg, "--auto-reset")) {
810+
auto_reset = AUTO_RESET_ORIGINAL;
811+
} else if (skip_prefix(arg, "--auto-reset=", &arg)) {
812+
if (parse_auto_reset(arg, &auto_reset)) {
813+
res = BISECT_FAILED;
814+
goto finish;
815+
}
746816
} else if (!strcmp(arg, "--term-good") ||
747817
!strcmp(arg, "--term-old")) {
748818
i++;
@@ -780,6 +850,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
780850
break;
781851
}
782852
}
853+
if (auto_reset != AUTO_RESET_NONE && no_checkout) {
854+
res = error(_("'--auto-reset' cannot be used with '--no-checkout'"));
855+
goto finish;
856+
}
783857
pathspec_pos = i;
784858

785859
/*
@@ -857,6 +931,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
857931
if (first_parent_only)
858932
write_file(git_path_bisect_first_parent(), "\n");
859933

934+
if (auto_reset != AUTO_RESET_NONE)
935+
write_file(git_path_bisect_auto_reset(), "%s\n",
936+
auto_reset_mode_name(auto_reset));
937+
860938
if (no_checkout) {
861939
if (repo_get_oid(the_repository, start_head.buf, &oid) < 0) {
862940
res = error(_("invalid ref: '%s'"), start_head.buf);
@@ -1235,13 +1313,31 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
12351313
{
12361314
int res = BISECT_OK;
12371315
struct strbuf command = STRBUF_INIT;
1316+
enum auto_reset_mode auto_reset = AUTO_RESET_NONE;
1317+
const char *auto_reset_arg;
12381318
const char *new_state;
12391319
int temporary_stdout_fd, saved_stdout;
12401320
int is_first_run = 1;
12411321

12421322
if (bisect_next_check(terms, NULL))
12431323
return BISECT_FAILED;
12441324

1325+
if (argc && !strcmp(argv[0], "--auto-reset"))
1326+
auto_reset = AUTO_RESET_ORIGINAL;
1327+
else if (argc && skip_prefix(argv[0], "--auto-reset=", &auto_reset_arg)) {
1328+
if (parse_auto_reset(auto_reset_arg, &auto_reset))
1329+
return BISECT_FAILED;
1330+
}
1331+
1332+
if (auto_reset != AUTO_RESET_NONE) {
1333+
if (refs_ref_exists(get_main_ref_store(the_repository), "BISECT_HEAD"))
1334+
return error(_("'--auto-reset' cannot be used with '--no-checkout'"));
1335+
write_file(git_path_bisect_auto_reset(), "%s\n",
1336+
auto_reset_mode_name(auto_reset));
1337+
argc--;
1338+
argv++;
1339+
}
1340+
12451341
if (!argc) {
12461342
error(_("bisect run failed: no command provided."));
12471343
return BISECT_FAILED;

t/t6030-bisect-porcelain.sh

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,37 @@ test_bisect_usage () {
4343
test_cmp expect actual
4444
}
4545

46+
test_bisect_state_file () {
47+
test_path_is_file "$(git rev-parse --git-path "$1")"
48+
}
49+
50+
test_bisect_state_missing () {
51+
test_path_is_missing "$(git rev-parse --git-path "$1")"
52+
}
53+
54+
bisect_start_and_finish () {
55+
git bisect start "$1" $HASH4 $HASH2 &&
56+
git bisect bad
57+
}
58+
59+
bisect_run_auto_reset () {
60+
write_script test_script.sh <<-\EOF &&
61+
! grep Another hello >/dev/null
62+
EOF
63+
git bisect start $HASH4 $HASH2 &&
64+
git bisect run "$1" ./test_script.sh >my_bisect_log.txt &&
65+
test_grep "$HASH3 is the first .bad. commit" my_bisect_log.txt
66+
}
67+
68+
test_auto_reset_fails () {
69+
local pattern="$1" &&
70+
local state_file="$2" &&
71+
shift 2 &&
72+
test_must_fail "$@" 2>err &&
73+
test_grep -- "$pattern" err &&
74+
test_bisect_state_missing "$state_file"
75+
}
76+
4677
test_expect_success 'bisect usage' "
4778
test_bisect_usage 1 git bisect reset extra1 extra2 <<-\EOF &&
4879
error: 'git bisect reset' requires either no argument or a commit
@@ -453,6 +484,82 @@ test_expect_success '"git bisect run" simple case' '
453484
git bisect reset
454485
'
455486

487+
test_expect_success '"git bisect start --auto-reset" defaults to original' '
488+
test_when_finished "git bisect reset; git checkout main" &&
489+
git checkout main &&
490+
bisect_start_and_finish --auto-reset &&
491+
test "$HASH4" = "$(git rev-parse HEAD)" &&
492+
test main = "$(git branch --show-current)" &&
493+
test_bisect_state_missing BISECT_START &&
494+
495+
bisect_start_and_finish --auto-reset=original &&
496+
test "$HASH4" = "$(git rev-parse HEAD)" &&
497+
test main = "$(git branch --show-current)" &&
498+
test_bisect_state_missing BISECT_START
499+
'
500+
501+
test_expect_success '"git bisect start --auto-reset=found" leaves first bad checked out' '
502+
test_when_finished "git bisect reset; git checkout main" &&
503+
bisect_start_and_finish --auto-reset=found &&
504+
test "$HASH3" = "$(git rev-parse HEAD)" &&
505+
test_bisect_state_missing BISECT_START
506+
'
507+
508+
test_expect_success '"git bisect run --auto-reset" defaults to original' '
509+
test_when_finished "git bisect reset; git checkout main" &&
510+
bisect_run_auto_reset --auto-reset &&
511+
test "$HASH4" = "$(git rev-parse HEAD)" &&
512+
test main = "$(git branch --show-current)" &&
513+
test_bisect_state_missing BISECT_START
514+
'
515+
516+
test_expect_success '"git bisect run --auto-reset=found" leaves first bad checked out' '
517+
test_when_finished "git bisect reset; git checkout main" &&
518+
bisect_run_auto_reset --auto-reset=found &&
519+
test "$HASH3" = "$(git rev-parse HEAD)" &&
520+
test_bisect_state_missing BISECT_START
521+
'
522+
523+
test_expect_success '--auto-reset rejects an unknown reset target' '
524+
test_when_finished "git bisect reset; git checkout main" &&
525+
test_auto_reset_fails \
526+
"invalid value for.*--auto-reset.*unknown" BISECT_START \
527+
git bisect start --auto-reset=unknown $HASH4 $HASH2 &&
528+
529+
git bisect start $HASH4 $HASH2 &&
530+
test_auto_reset_fails \
531+
"invalid value for.*--auto-reset.*unknown" BISECT_AUTO_RESET \
532+
git bisect run --auto-reset=unknown true
533+
'
534+
535+
test_expect_success '--auto-reset cannot be used with --no-checkout' '
536+
test_when_finished "git bisect reset" &&
537+
test_auto_reset_fails \
538+
"cannot be used with.*--no-checkout" BISECT_START \
539+
git bisect start --auto-reset=original --no-checkout $HASH4 $HASH2 &&
540+
541+
git bisect start --no-checkout $HASH4 $HASH2 &&
542+
test_auto_reset_fails \
543+
"cannot be used with.*--no-checkout" BISECT_AUTO_RESET \
544+
git bisect run --auto-reset=found true
545+
'
546+
547+
test_expect_success 'without --auto-reset the bisection state is kept' '
548+
test_when_finished "git bisect reset" &&
549+
git bisect start $HASH4 $HASH2 &&
550+
git bisect bad &&
551+
test_bisect_state_file BISECT_START
552+
'
553+
554+
test_expect_success '--auto-reset does not leak into a later bisection' '
555+
test_when_finished "git bisect reset; git checkout main" &&
556+
bisect_start_and_finish --auto-reset &&
557+
558+
git bisect start $HASH4 $HASH2 &&
559+
git bisect bad &&
560+
test_bisect_state_file BISECT_START
561+
'
562+
456563
# We want to automatically find the commit that
457564
# added "Ciao" into hello.
458565
test_expect_success '"git bisect run" with more complex "git bisect start"' '

0 commit comments

Comments
 (0)