Skip to content

Commit b8f64d8

Browse files
committed
Merge branch 'hn/bisect-auto-reset' into seen
The 'git bisect' command has been taught a '--auto-reset[=<where>]' option that tells the command to automatically run 'git bisect reset' to jump back to the original state or to the found culprit. * hn/bisect-auto-reset: bisect: add --auto-reset to leave when done bisect: let bisect_reset() optionally check out quietly bisect: read run output from the open descriptor
2 parents 2ecc6ab + b2ae9b8 commit b8f64d8

4 files changed

Lines changed: 234 additions & 21 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
{
@@ -1213,6 +1214,7 @@ int bisect_clean_state(void)
12131214
unlink_or_warn(git_path_bisect_run());
12141215
unlink_or_warn(git_path_bisect_terms());
12151216
unlink_or_warn(git_path_bisect_first_parent());
1217+
unlink_or_warn(git_path_bisect_auto_reset());
12161218
/*
12171219
* Cleanup BISECT_START last to support the --no-checkout option
12181220
* introduced in the commit 4796e823a.

builtin/bisect.c

Lines changed: 113 additions & 19 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;
@@ -178,17 +185,13 @@ static int append_to_file(const char *path, const char *format, ...)
178185
return res;
179186
}
180187

181-
static int print_file_to_stdout(const char *path)
188+
static int print_fd_to_stdout(int fd)
182189
{
183-
int fd = open(path, O_RDONLY);
184-
int ret = 0;
185-
186-
if (fd < 0)
187-
return error_errno(_("cannot open file '%s' for reading"), path);
190+
if (lseek(fd, 0, SEEK_SET) < 0)
191+
return error_errno(_("failed to rewind BISECT_RUN output"));
188192
if (copy_fd(fd, 1) < 0)
189-
ret = error_errno(_("failed to read '%s'"), path);
190-
close(fd);
191-
return ret;
193+
return error_errno(_("failed to read BISECT_RUN output"));
194+
return 0;
192195
}
193196

194197
static int check_term_format(const char *term, const char *orig_term)
@@ -234,7 +237,7 @@ static int write_terms(const char *bad, const char *good)
234237
return res;
235238
}
236239

237-
static int bisect_reset(const char *commit)
240+
static int bisect_reset(const char *commit, int quiet)
238241
{
239242
struct strbuf branch = STRBUF_INIT;
240243

@@ -255,8 +258,10 @@ static int bisect_reset(const char *commit)
255258
struct child_process cmd = CHILD_PROCESS_INIT;
256259

257260
cmd.git_cmd = 1;
258-
strvec_pushl(&cmd.args, "checkout", "--ignore-other-worktrees",
259-
branch.buf, "--", NULL);
261+
strvec_pushl(&cmd.args, "checkout", "--ignore-other-worktrees", NULL);
262+
if (quiet)
263+
strvec_push(&cmd.args, "--quiet");
264+
strvec_pushl(&cmd.args, branch.buf, "--", NULL);
260265
if (run_command(&cmd)) {
261266
error(_("could not check out original"
262267
" HEAD '%s'. Try 'git bisect"
@@ -270,6 +275,59 @@ static int bisect_reset(const char *commit)
270275
return bisect_clean_state();
271276
}
272277

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+
273331
static void log_commit(FILE *fp,
274332
const char *fmt, const char *state,
275333
struct commit *commit)
@@ -701,6 +759,8 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre
701759

702760
if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {
703761
res = bisect_successful(terms);
762+
if (!res && !is_empty_or_missing_file(git_path_bisect_auto_reset()))
763+
res = bisect_auto_reset(terms);
704764
return res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;
705765
} else if (res == BISECT_ONLY_SKIPPED_LEFT) {
706766
res = bisect_skipped_commits(terms);
@@ -724,6 +784,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
724784
{
725785
int no_checkout = 0;
726786
int first_parent_only = 0;
787+
enum auto_reset_mode auto_reset = AUTO_RESET_NONE;
727788
int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;
728789
int flags, pathspec_pos;
729790
enum bisect_error res = BISECT_OK;
@@ -756,6 +817,13 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
756817
no_checkout = 1;
757818
} else if (!strcmp(arg, "--first-parent")) {
758819
first_parent_only = 1;
820+
} else if (!strcmp(arg, "--auto-reset")) {
821+
auto_reset = AUTO_RESET_ORIGINAL;
822+
} else if (skip_prefix(arg, "--auto-reset=", &arg)) {
823+
if (parse_auto_reset(arg, &auto_reset)) {
824+
res = BISECT_FAILED;
825+
goto finish;
826+
}
759827
} else if (!strcmp(arg, "--term-good") ||
760828
!strcmp(arg, "--term-old")) {
761829
i++;
@@ -793,6 +861,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
793861
break;
794862
}
795863
}
864+
if (auto_reset != AUTO_RESET_NONE && no_checkout) {
865+
res = error(_("'--auto-reset' cannot be used with '--no-checkout'"));
866+
goto finish;
867+
}
796868
pathspec_pos = i;
797869

798870
/*
@@ -872,6 +944,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
872944
if (first_parent_only)
873945
write_file(git_path_bisect_first_parent(), "\n");
874946

947+
if (auto_reset != AUTO_RESET_NONE)
948+
write_file(git_path_bisect_auto_reset(), "%s\n",
949+
auto_reset_mode_name(auto_reset));
950+
875951
if (no_checkout) {
876952
if (repo_get_oid(the_repository, start_head.buf, &oid) < 0) {
877953
res = error(_("invalid ref: '%s'"), start_head.buf);
@@ -1104,7 +1180,7 @@ static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f
11041180
if (is_empty_or_missing_file(filename))
11051181
return error(_("cannot read file '%s' for replaying"), filename);
11061182

1107-
if (bisect_reset(NULL))
1183+
if (bisect_reset(NULL, 0))
11081184
return BISECT_FAILED;
11091185

11101186
fp = fopen(filename, "r");
@@ -1252,13 +1328,31 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
12521328
{
12531329
int res = BISECT_OK;
12541330
struct strbuf command = STRBUF_INIT;
1331+
enum auto_reset_mode auto_reset = AUTO_RESET_NONE;
1332+
const char *auto_reset_arg;
12551333
const char *new_state;
12561334
int temporary_stdout_fd, saved_stdout;
12571335
int is_first_run = 1;
12581336

12591337
if (bisect_next_check(terms, NULL))
12601338
return BISECT_FAILED;
12611339

1340+
if (argc && !strcmp(argv[0], "--auto-reset"))
1341+
auto_reset = AUTO_RESET_ORIGINAL;
1342+
else if (argc && skip_prefix(argv[0], "--auto-reset=", &auto_reset_arg)) {
1343+
if (parse_auto_reset(auto_reset_arg, &auto_reset))
1344+
return BISECT_FAILED;
1345+
}
1346+
1347+
if (auto_reset != AUTO_RESET_NONE) {
1348+
if (refs_ref_exists(get_main_ref_store(the_repository), "BISECT_HEAD"))
1349+
return error(_("'--auto-reset' cannot be used with '--no-checkout'"));
1350+
write_file(git_path_bisect_auto_reset(), "%s\n",
1351+
auto_reset_mode_name(auto_reset));
1352+
argc--;
1353+
argv++;
1354+
}
1355+
12621356
if (!argc) {
12631357
error(_("bisect run failed: no command provided."));
12641358
return BISECT_FAILED;
@@ -1306,7 +1400,7 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
13061400
else
13071401
new_state = terms->term_bad;
13081402

1309-
temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
1403+
temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_RDWR | O_TRUNC, 0666);
13101404

13111405
if (temporary_stdout_fd < 0) {
13121406
res = error_errno(_("cannot open file '%s' for writing"), git_path_bisect_run());
@@ -1327,9 +1421,9 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
13271421
fflush(stdout);
13281422
dup2(saved_stdout, 1);
13291423
close(saved_stdout);
1330-
close(temporary_stdout_fd);
13311424

1332-
print_file_to_stdout(git_path_bisect_run());
1425+
print_fd_to_stdout(temporary_stdout_fd);
1426+
close(temporary_stdout_fd);
13331427

13341428
if (res == BISECT_ONLY_SKIPPED_LEFT)
13351429
error(_("bisect run cannot continue any more"));
@@ -1358,7 +1452,7 @@ static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNU
13581452
if (argc > 1)
13591453
return error(_("'%s' requires either no argument or a commit"),
13601454
"git bisect reset");
1361-
return bisect_reset(argc ? argv[0] : NULL);
1455+
return bisect_reset(argc ? argv[0] : NULL, 0);
13621456
}
13631457

13641458
static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,

0 commit comments

Comments
 (0)