* [PATCH 0/3] bisect: add --auto-reset to leave when done
@ 2026-07-16 5:35 Harald Nordgren via GitGitGadget
2026-07-16 5:35 ` [PATCH 1/3] bisect: read run output from the open descriptor Harald Nordgren via GitGitGadget
` (3 more replies)
0 siblings, 4 replies; 13+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-07-16 5:35 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
Add a --auto-reset option to git bisect start and git bisect run that
returns to the commit checked out before git bisect start as soon as the
first bad commit is reported, instead of leaving the session active until
git bisect reset is run by hand.
Harald Nordgren (3):
bisect: read run output from the open descriptor
bisect: let bisect_reset() optionally check out quietly
bisect: add --auto-reset to leave when done
Documentation/git-bisect.adoc | 12 +++++++--
bisect.c | 2 ++
builtin/bisect.c | 51 ++++++++++++++++++++++-------------
t/t6030-bisect-porcelain.sh | 34 +++++++++++++++++++++++
4 files changed, 78 insertions(+), 21 deletions(-)
base-commit: f60db8d575adb79761d363e026fb49bddf330c73
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2335%2FHaraldNordgren%2Fbisect-auto-reset-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2335/HaraldNordgren/bisect-auto-reset-v1
Pull-Request: https://github.com/git/git/pull/2335
--
gitgitgadget
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 1/3] bisect: read run output from the open descriptor
2026-07-16 5:35 [PATCH 0/3] bisect: add --auto-reset to leave when done Harald Nordgren via GitGitGadget
@ 2026-07-16 5:35 ` Harald Nordgren via GitGitGadget
2026-07-16 5:35 ` [PATCH 2/3] bisect: let bisect_reset() optionally check out quietly Harald Nordgren via GitGitGadget
` (2 subsequent siblings)
3 siblings, 0 replies; 13+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-07-16 5:35 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"git bisect run" redirects each step's output into BISECT_RUN, then
prints it back by reopening the file by name. Read it from the already
open descriptor instead; this behaves the same and no longer needs the
file to be reachable by name.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
builtin/bisect.c | 20 ++++++++------------
1 file changed, 8 insertions(+), 12 deletions(-)
diff --git a/builtin/bisect.c b/builtin/bisect.c
index 798e28f501..69ea14b1b6 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -178,17 +178,13 @@ static int append_to_file(const char *path, const char *format, ...)
return res;
}
-static int print_file_to_stdout(const char *path)
+static int print_fd_to_stdout(int fd)
{
- int fd = open(path, O_RDONLY);
- int ret = 0;
-
- if (fd < 0)
- return error_errno(_("cannot open file '%s' for reading"), path);
+ if (lseek(fd, 0, SEEK_SET) < 0)
+ return error_errno(_("failed to rewind BISECT_RUN output"));
if (copy_fd(fd, 1) < 0)
- ret = error_errno(_("failed to read '%s'"), path);
- close(fd);
- return ret;
+ return error_errno(_("failed to read BISECT_RUN output"));
+ return 0;
}
static int check_term_format(const char *term, const char *orig_term)
@@ -1291,7 +1287,7 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
else
new_state = terms->term_bad;
- temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
+ temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_RDWR | O_TRUNC, 0666);
if (temporary_stdout_fd < 0) {
res = error_errno(_("cannot open file '%s' for writing"), git_path_bisect_run());
@@ -1307,9 +1303,9 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
fflush(stdout);
dup2(saved_stdout, 1);
close(saved_stdout);
- close(temporary_stdout_fd);
- print_file_to_stdout(git_path_bisect_run());
+ print_fd_to_stdout(temporary_stdout_fd);
+ close(temporary_stdout_fd);
if (res == BISECT_ONLY_SKIPPED_LEFT)
error(_("bisect run cannot continue any more"));
--
gitgitgadget
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 2/3] bisect: let bisect_reset() optionally check out quietly
2026-07-16 5:35 [PATCH 0/3] bisect: add --auto-reset to leave when done Harald Nordgren via GitGitGadget
2026-07-16 5:35 ` [PATCH 1/3] bisect: read run output from the open descriptor Harald Nordgren via GitGitGadget
@ 2026-07-16 5:35 ` Harald Nordgren via GitGitGadget
2026-07-16 5:35 ` [PATCH 3/3] bisect: add --auto-reset to leave when done Harald Nordgren via GitGitGadget
2026-07-17 18:27 ` [PATCH v2 0/3] " Harald Nordgren via GitGitGadget
3 siblings, 0 replies; 13+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-07-16 5:35 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Add a "quiet" parameter to bisect_reset() that passes "--quiet" to the
checkout restoring the original HEAD, suppressing its progress and
branch-status output.
No caller sets the flag yet, so behavior is unchanged.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
builtin/bisect.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/builtin/bisect.c b/builtin/bisect.c
index 69ea14b1b6..27d30b549e 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -230,7 +230,7 @@ static int write_terms(const char *bad, const char *good)
return res;
}
-static int bisect_reset(const char *commit)
+static int bisect_reset(const char *commit, int quiet)
{
struct strbuf branch = STRBUF_INIT;
@@ -251,8 +251,10 @@ static int bisect_reset(const char *commit)
struct child_process cmd = CHILD_PROCESS_INIT;
cmd.git_cmd = 1;
- strvec_pushl(&cmd.args, "checkout", "--ignore-other-worktrees",
- branch.buf, "--", NULL);
+ strvec_pushl(&cmd.args, "checkout", "--ignore-other-worktrees", NULL);
+ if (quiet)
+ strvec_push(&cmd.args, "--quiet");
+ strvec_pushl(&cmd.args, branch.buf, "--", NULL);
if (run_command(&cmd)) {
error(_("could not check out original"
" HEAD '%s'. Try 'git bisect"
@@ -1085,7 +1087,7 @@ static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f
if (is_empty_or_missing_file(filename))
return error(_("cannot read file '%s' for replaying"), filename);
- if (bisect_reset(NULL))
+ if (bisect_reset(NULL, 0))
return BISECT_FAILED;
fp = fopen(filename, "r");
@@ -1334,7 +1336,7 @@ static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNU
if (argc > 1)
return error(_("'%s' requires either no argument or a commit"),
"git bisect reset");
- return bisect_reset(argc ? argv[0] : NULL);
+ return bisect_reset(argc ? argv[0] : NULL, 0);
}
static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,
--
gitgitgadget
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 3/3] bisect: add --auto-reset to leave when done
2026-07-16 5:35 [PATCH 0/3] bisect: add --auto-reset to leave when done Harald Nordgren via GitGitGadget
2026-07-16 5:35 ` [PATCH 1/3] bisect: read run output from the open descriptor Harald Nordgren via GitGitGadget
2026-07-16 5:35 ` [PATCH 2/3] bisect: let bisect_reset() optionally check out quietly Harald Nordgren via GitGitGadget
@ 2026-07-16 5:35 ` Harald Nordgren via GitGitGadget
2026-07-16 17:22 ` Junio C Hamano
2026-07-17 18:27 ` [PATCH v2 0/3] " Harald Nordgren via GitGitGadget
3 siblings, 1 reply; 13+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-07-16 5:35 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
When a bisection finished, "git bisect" reported the first bad commit
but left the session active until "git bisect reset" was run by hand.
Add an "--auto-reset" option, accepted by both "git bisect start" and
"git bisect run", that resets as soon as the first bad commit is found,
returning to the commit checked out before "git bisect start". The flag
is persisted in a BISECT_AUTO_RESET state file and the restoring
checkout is done quietly.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
Documentation/git-bisect.adoc | 12 ++++++++++--
bisect.c | 2 ++
builtin/bisect.c | 19 +++++++++++++++++--
t/t6030-bisect-porcelain.sh | 34 ++++++++++++++++++++++++++++++++++
4 files changed, 63 insertions(+), 4 deletions(-)
diff --git a/Documentation/git-bisect.adoc b/Documentation/git-bisect.adoc
index d2115b2990..1b03dbba7a 100644
--- a/Documentation/git-bisect.adoc
+++ b/Documentation/git-bisect.adoc
@@ -10,7 +10,7 @@ SYNOPSIS
--------
[synopsis]
git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]
- [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]
+ [--no-checkout] [--first-parent] [--auto-reset] [<bad> [<good>...]] [--] [<pathspec>...]
git bisect (bad|new|<term-new>) [<rev>]
git bisect (good|old|<term-old>) [<rev>...]
git bisect terms [--term-(good|old) | --term-(bad|new)]
@@ -20,7 +20,7 @@ git bisect reset [<commit>]
git bisect (visualize|view)
git bisect replay <logfile>
git bisect log
-git bisect run <cmd> [<arg>...]
+git bisect run [--auto-reset] <cmd> [<arg>...]
git bisect help
DESCRIPTION
@@ -385,6 +385,14 @@ 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.
+`--auto-reset`::
+ Once the first bad commit is found, clean up the bisection state and
+ return to the commit that was checked out before `git bisect start`,
+ as if `git bisect reset` had been run. The first bad commit is still
+ reported before resetting.
++
+This option may be given to `git bisect start` or to `git bisect run`.
+
EXAMPLES
--------
diff --git a/bisect.c b/bisect.c
index 94c7028d2a..a34309dd35 100644
--- a/bisect.c
+++ b/bisect.c
@@ -488,6 +488,7 @@ 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_terms, "BISECT_TERMS")
static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
+static GIT_PATH_FUNC(git_path_bisect_auto_reset, "BISECT_AUTO_RESET")
static void read_bisect_paths(struct strvec *array)
{
@@ -1211,6 +1212,7 @@ int bisect_clean_state(void)
unlink_or_warn(git_path_bisect_run());
unlink_or_warn(git_path_bisect_terms());
unlink_or_warn(git_path_bisect_first_parent());
+ unlink_or_warn(git_path_bisect_auto_reset());
/*
* Cleanup BISECT_START last to support the --no-checkout option
* introduced in the commit 4796e823a.
diff --git a/builtin/bisect.c b/builtin/bisect.c
index 27d30b549e..b80eccb635 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -24,11 +24,12 @@ 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_names, "BISECT_NAMES")
static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
+static GIT_PATH_FUNC(git_path_bisect_auto_reset, "BISECT_AUTO_RESET")
static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
#define BUILTIN_GIT_BISECT_START_USAGE \
N_("git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\n" \
- " [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]")
+ " [--no-checkout] [--first-parent] [--auto-reset] [<bad> [<good>...]] [--] [<pathspec>...]")
#define BUILTIN_GIT_BISECT_BAD_USAGE \
N_("git bisect (bad|new|<term-new>) [<rev>]")
#define BUILTIN_GIT_BISECT_GOOD_USAGE \
@@ -48,7 +49,7 @@ static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
#define BUILTIN_GIT_BISECT_LOG_USAGE \
"git bisect log"
#define BUILTIN_GIT_BISECT_RUN_USAGE \
- N_("git bisect run <cmd> [<arg>...]")
+ N_("git bisect run [--auto-reset] <cmd> [<arg>...]")
#define BUILTIN_GIT_BISECT_HELP_USAGE \
"git bisect help"
@@ -688,6 +689,8 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre
if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {
res = bisect_successful(terms);
+ if (!res && !is_empty_or_missing_file(git_path_bisect_auto_reset()))
+ res = bisect_reset(NULL, 1);
return res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;
} else if (res == BISECT_ONLY_SKIPPED_LEFT) {
res = bisect_skipped_commits(terms);
@@ -711,6 +714,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
{
int no_checkout = 0;
int first_parent_only = 0;
+ int auto_reset = 0;
int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;
int flags, pathspec_pos;
enum bisect_error res = BISECT_OK;
@@ -743,6 +747,8 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
no_checkout = 1;
} else if (!strcmp(arg, "--first-parent")) {
first_parent_only = 1;
+ } else if (!strcmp(arg, "--auto-reset")) {
+ auto_reset = 1;
} else if (!strcmp(arg, "--term-good") ||
!strcmp(arg, "--term-old")) {
i++;
@@ -857,6 +863,9 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
if (first_parent_only)
write_file(git_path_bisect_first_parent(), "\n");
+ if (auto_reset)
+ write_file(git_path_bisect_auto_reset(), "\n");
+
if (no_checkout) {
if (repo_get_oid(the_repository, start_head.buf, &oid) < 0) {
res = error(_("invalid ref: '%s'"), start_head.buf);
@@ -1242,6 +1251,12 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
if (bisect_next_check(terms, NULL))
return BISECT_FAILED;
+ if (argc && !strcmp(argv[0], "--auto-reset")) {
+ write_file(git_path_bisect_auto_reset(), "\n");
+ argc--;
+ argv++;
+ }
+
if (!argc) {
error(_("bisect run failed: no command provided."));
return BISECT_FAILED;
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index 081116220a..5389ba388c 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -453,6 +453,40 @@ test_expect_success '"git bisect run" simple case' '
git bisect reset
'
+test_expect_success '"git bisect start --auto-reset" leaves the bisection' '
+ test_when_finished "git bisect reset" &&
+ git bisect start --auto-reset $HASH4 $HASH2 &&
+ git bisect bad &&
+ test_path_is_missing "$(git rev-parse --git-path BISECT_START)"
+'
+
+test_expect_success '"git bisect run --auto-reset" leaves the bisection' '
+ test_when_finished "git bisect reset" &&
+ write_script test_script.sh <<-\EOF &&
+ ! grep Another hello >/dev/null
+ EOF
+ git bisect start $HASH4 $HASH2 &&
+ git bisect run --auto-reset ./test_script.sh >my_bisect_log.txt &&
+ grep "$HASH3 is the first .bad. commit" my_bisect_log.txt &&
+ test_path_is_missing "$(git rev-parse --git-path BISECT_START)"
+'
+
+test_expect_success 'without --auto-reset the bisection state is kept' '
+ test_when_finished "git bisect reset" &&
+ git bisect start $HASH4 $HASH2 &&
+ git bisect bad &&
+ test_path_is_file "$(git rev-parse --git-path BISECT_START)"
+'
+
+test_expect_success '--auto-reset does not leak into a later bisection' '
+ test_when_finished "git bisect reset" &&
+ git bisect start --auto-reset $HASH4 $HASH2 &&
+ git bisect bad &&
+ git bisect start $HASH4 $HASH2 &&
+ git bisect bad &&
+ test_path_is_file "$(git rev-parse --git-path BISECT_START)"
+'
+
# We want to automatically find the commit that
# added "Ciao" into hello.
test_expect_success '"git bisect run" with more complex "git bisect start"' '
--
gitgitgadget
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH 3/3] bisect: add --auto-reset to leave when done
2026-07-16 5:35 ` [PATCH 3/3] bisect: add --auto-reset to leave when done Harald Nordgren via GitGitGadget
@ 2026-07-16 17:22 ` Junio C Hamano
2026-07-16 21:22 ` Harald Nordgren
0 siblings, 1 reply; 13+ messages in thread
From: Junio C Hamano @ 2026-07-16 17:22 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget; +Cc: git, Harald Nordgren
"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
> From: Harald Nordgren <haraldnordgren@gmail.com>
>
> When a bisection finished, "git bisect" reported the first bad commit
> but left the session active until "git bisect reset" was run by hand.
If this gives an observation of the behavior of the current code,
please write it in the present tense.
> Add an "--auto-reset" option, accepted by both "git bisect start" and
> "git bisect run", that resets as soon as the first bad commit is found,
> returning to the commit checked out before "git bisect start". The flag
> is persisted in a BISECT_AUTO_RESET state file and the restoring
> checkout is done quietly.
I often find myself, after the culprit is found, running 'git
reset --hard' or 'git bisect reset' to jump to the problematic
commit to investigate further. If '--auto-reset' leaves me
checked out on that bad commit, that would be a very welcome
change. If it only returns me to where I started before the
bisection, well, 'Meh'.
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 3/3] bisect: add --auto-reset to leave when done
2026-07-16 17:22 ` Junio C Hamano
@ 2026-07-16 21:22 ` Harald Nordgren
2026-07-17 5:00 ` Junio C Hamano
0 siblings, 1 reply; 13+ messages in thread
From: Harald Nordgren @ 2026-07-16 21:22 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Harald Nordgren via GitGitGadget, git
> > Add an "--auto-reset" option, accepted by both "git bisect start" and
> > "git bisect run", that resets as soon as the first bad commit is found,
> > returning to the commit checked out before "git bisect start". The flag
> > is persisted in a BISECT_AUTO_RESET state file and the restoring
> > checkout is done quietly.
>
> I often find myself, after the culprit is found, running 'git
> reset --hard' or 'git bisect reset' to jump to the problematic
> commit to investigate further. If '--auto-reset' leaves me
> checked out on that bad commit, that would be a very welcome
> change.
No it's the opposite, returns to where we started before the bisection.
I don't mind changing it assuming no one likes the original idea. I
guess the name shouldn't be '--auto-reset' then.
Harald
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 3/3] bisect: add --auto-reset to leave when done
2026-07-16 21:22 ` Harald Nordgren
@ 2026-07-17 5:00 ` Junio C Hamano
2026-07-17 9:16 ` Harald Nordgren
0 siblings, 1 reply; 13+ messages in thread
From: Junio C Hamano @ 2026-07-17 5:00 UTC (permalink / raw)
To: Harald Nordgren; +Cc: Harald Nordgren via GitGitGadget, git
Harald Nordgren <haraldnordgren@gmail.com> writes:
>> > Add an "--auto-reset" option, accepted by both "git bisect start" and
>> > "git bisect run", that resets as soon as the first bad commit is found,
>> > returning to the commit checked out before "git bisect start". The flag
>> > is persisted in a BISECT_AUTO_RESET state file and the restoring
>> > checkout is done quietly.
>>
>> I often find myself, after the culprit is found, running 'git
>> reset --hard' or 'git bisect reset' to jump to the problematic
>> commit to investigate further. If '--auto-reset' leaves me
>> checked out on that bad commit, that would be a very welcome
>> change.
>
> No it's the opposite, returns to where we started before the bisection.
>
> I don't mind changing it assuming no one likes the original idea. I
> guess the name shouldn't be '--auto-reset' then.
Since "git bisect reset <goto>" is just as common as a plain "git
bisect reset" (which implicitly uses the original branch as the
target), I suspect that an option like "--auto-reset=<where>" with
values like "original" or "found" might be appropriate. And I would
not mind if omitting the value defaulted to "original".
The point I was trying to make is that where to reset depends
more on the situation the user is in, rather than on their
personal preference. I would mind if you changed it to always
reset to the culprit, just as much as I would mind if it always
reset to the original.
Thanks.
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 3/3] bisect: add --auto-reset to leave when done
2026-07-17 5:00 ` Junio C Hamano
@ 2026-07-17 9:16 ` Harald Nordgren
2026-07-17 16:43 ` Junio C Hamano
0 siblings, 1 reply; 13+ messages in thread
From: Harald Nordgren @ 2026-07-17 9:16 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Harald Nordgren via GitGitGadget, git
That's a great idea!
Harald
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 3/3] bisect: add --auto-reset to leave when done
2026-07-17 9:16 ` Harald Nordgren
@ 2026-07-17 16:43 ` Junio C Hamano
0 siblings, 0 replies; 13+ messages in thread
From: Junio C Hamano @ 2026-07-17 16:43 UTC (permalink / raw)
To: Harald Nordgren; +Cc: Harald Nordgren via GitGitGadget, git
Harald Nordgren <haraldnordgren@gmail.com> writes:
> That's a great idea!
May not be, though. Depending on what the bisect session finds,
where I want to reset to may probably be different. But that merely
means that in such a case, I cannot use "bisect start --autoreset".
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v2 0/3] bisect: add --auto-reset to leave when done
2026-07-16 5:35 [PATCH 0/3] bisect: add --auto-reset to leave when done Harald Nordgren via GitGitGadget
` (2 preceding siblings ...)
2026-07-16 5:35 ` [PATCH 3/3] bisect: add --auto-reset to leave when done Harald Nordgren via GitGitGadget
@ 2026-07-17 18:27 ` Harald Nordgren via GitGitGadget
2026-07-17 18:27 ` [PATCH v2 1/3] bisect: read run output from the open descriptor Harald Nordgren via GitGitGadget
` (2 more replies)
3 siblings, 3 replies; 13+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-07-17 18:27 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
Add a --auto-reset option to git bisect that resets the bisect session when
culprit is found.
Changes in v2:
* Add option --auto-reset[=<where>] with option to go to final commit as
well as original.
* Refactored tests.
Harald Nordgren (3):
bisect: read run output from the open descriptor
bisect: let bisect_reset() optionally check out quietly
bisect: add --auto-reset to leave when done
Documentation/git-bisect.adoc | 14 +++-
bisect.c | 2 +
builtin/bisect.c | 132 +++++++++++++++++++++++++++++-----
t/t6030-bisect-porcelain.sh | 107 +++++++++++++++++++++++++++
4 files changed, 234 insertions(+), 21 deletions(-)
base-commit: 44de1520f08d1dfebc3ab2d9f644208eaa5ac925
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2335%2FHaraldNordgren%2Fbisect-auto-reset-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2335/HaraldNordgren/bisect-auto-reset-v2
Pull-Request: https://github.com/git/git/pull/2335
Range-diff vs v1:
1: 3fd365835d = 1: 0de8b12f65 bisect: read run output from the open descriptor
2: a7670baafc = 2: 8a2dcdf305 bisect: let bisect_reset() optionally check out quietly
3: a9194b1d00 ! 3: 5b3704fbd4 bisect: add --auto-reset to leave when done
@@ Metadata
## Commit message ##
bisect: add --auto-reset to leave when done
- When a bisection finished, "git bisect" reported the first bad commit
- but left the session active until "git bisect reset" was run by hand.
+ 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" option, accepted by both "git bisect start" and
- "git bisect run", that resets as soon as the first bad commit is found,
- returning to the commit checked out before "git bisect start". The flag
- is persisted in a BISECT_AUTO_RESET state file and the restoring
- checkout is done quietly.
+ 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>
@@ Documentation/git-bisect.adoc: SYNOPSIS
[synopsis]
git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]
- [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]
-+ [--no-checkout] [--first-parent] [--auto-reset] [<bad> [<good>...]] [--] [<pathspec>...]
++ [--no-checkout] [--first-parent] [--auto-reset[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]
git bisect (bad|new|<term-new>) [<rev>]
git bisect (good|old|<term-old>) [<rev>...]
git bisect terms [--term-(good|old) | --term-(bad|new)]
@@ Documentation/git-bisect.adoc: git bisect reset [<commit>]
git bisect replay <logfile>
git bisect log
-git bisect run <cmd> [<arg>...]
-+git bisect run [--auto-reset] <cmd> [<arg>...]
++git bisect run [--auto-reset[=<where>]] <cmd> [<arg>...]
git bisect help
DESCRIPTION
@@ Documentation/git-bisect.adoc: 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.
-+`--auto-reset`::
-+ Once the first bad commit is found, clean up the bisection state and
-+ return to the commit that was checked out before `git bisect start`,
-+ as if `git bisect reset` had been run. The first bad commit is still
-+ reported before resetting.
++`--auto-reset[=<where>]`::
++ Once the first bad commit is found, report it and clean up the
++ bisection state. `<where>` may be `original` to return to the commit
++ checked out before `git bisect start`, or `found` to leave the first
++ bad commit checked out. If `<where>` is omitted, it defaults to
++ `original`.
++
-+This option may be given to `git bisect start` or to `git bisect run`.
++This option may be given to `git bisect start` or to `git bisect run`. It
++cannot be used for a bisection started with `--no-checkout`.
+
EXAMPLES
--------
@@ builtin/bisect.c: static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
#define BUILTIN_GIT_BISECT_START_USAGE \
N_("git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\n" \
- " [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]")
-+ " [--no-checkout] [--first-parent] [--auto-reset] [<bad> [<good>...]] [--] [<pathspec>...]")
++ " [--no-checkout] [--first-parent] [--auto-reset[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]")
#define BUILTIN_GIT_BISECT_BAD_USAGE \
N_("git bisect (bad|new|<term-new>) [<rev>]")
#define BUILTIN_GIT_BISECT_GOOD_USAGE \
@@ builtin/bisect.c: static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
"git bisect log"
#define BUILTIN_GIT_BISECT_RUN_USAGE \
- N_("git bisect run <cmd> [<arg>...]")
-+ N_("git bisect run [--auto-reset] <cmd> [<arg>...]")
++ N_("git bisect run [--auto-reset[=<where>]] <cmd> [<arg>...]")
#define BUILTIN_GIT_BISECT_HELP_USAGE \
"git bisect help"
+@@ builtin/bisect.c: static const char * const git_bisect_usage[] = {
+ NULL
+ };
+
++enum auto_reset_mode {
++ AUTO_RESET_NONE,
++ AUTO_RESET_ORIGINAL,
++ AUTO_RESET_FOUND,
++};
++
+ struct add_bisect_ref_data {
+ struct rev_info *revs;
+ unsigned int object_flags;
+@@ builtin/bisect.c: static int bisect_reset(const char *commit, int quiet)
+ return bisect_clean_state();
+ }
+
++static int parse_auto_reset(const char *value, enum auto_reset_mode *mode)
++{
++ if (!strcmp(value, "original"))
++ *mode = AUTO_RESET_ORIGINAL;
++ else if (!strcmp(value, "found"))
++ *mode = AUTO_RESET_FOUND;
++ else
++ return error(_("invalid value for '--auto-reset': '%s'"), value);
++
++ return 0;
++}
++
++static const char *auto_reset_mode_name(enum auto_reset_mode mode)
++{
++ switch (mode) {
++ case AUTO_RESET_ORIGINAL:
++ return "original";
++ case AUTO_RESET_FOUND:
++ return "found";
++ case AUTO_RESET_NONE:
++ BUG("no name for unset auto-reset mode");
++ }
++ BUG("unknown auto-reset mode %d", mode);
++}
++
++static int bisect_auto_reset(struct bisect_terms *terms)
++{
++ struct strbuf value = STRBUF_INIT;
++ enum auto_reset_mode mode;
++ char *commit = NULL;
++ int res;
++
++ if (strbuf_read_file(&value, git_path_bisect_auto_reset(), 0) < 0) {
++ res = error_errno(_("could not read '%s'"),
++ git_path_bisect_auto_reset());
++ goto cleanup;
++ }
++ strbuf_trim(&value);
++ if (parse_auto_reset(value.buf, &mode)) {
++ res = -1;
++ goto cleanup;
++ }
++
++ if (mode == AUTO_RESET_FOUND)
++ commit = xstrfmt("refs/bisect/%s", terms->term_bad);
++ res = bisect_reset(commit, 1);
++
++cleanup:
++ free(commit);
++ strbuf_release(&value);
++ return res;
++}
++
+ static void log_commit(FILE *fp,
+ const char *fmt, const char *state,
+ struct commit *commit)
@@ builtin/bisect.c: static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre
if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {
res = bisect_successful(terms);
+ if (!res && !is_empty_or_missing_file(git_path_bisect_auto_reset()))
-+ res = bisect_reset(NULL, 1);
++ res = bisect_auto_reset(terms);
return res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;
} else if (res == BISECT_ONLY_SKIPPED_LEFT) {
res = bisect_skipped_commits(terms);
@@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *ter
{
int no_checkout = 0;
int first_parent_only = 0;
-+ int auto_reset = 0;
++ enum auto_reset_mode auto_reset = AUTO_RESET_NONE;
int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;
int flags, pathspec_pos;
enum bisect_error res = BISECT_OK;
@@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *ter
} else if (!strcmp(arg, "--first-parent")) {
first_parent_only = 1;
+ } else if (!strcmp(arg, "--auto-reset")) {
-+ auto_reset = 1;
++ auto_reset = AUTO_RESET_ORIGINAL;
++ } else if (skip_prefix(arg, "--auto-reset=", &arg)) {
++ if (parse_auto_reset(arg, &auto_reset)) {
++ res = BISECT_FAILED;
++ goto finish;
++ }
} else if (!strcmp(arg, "--term-good") ||
!strcmp(arg, "--term-old")) {
i++;
+@@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
+ break;
+ }
+ }
++ if (auto_reset != AUTO_RESET_NONE && no_checkout) {
++ res = error(_("'--auto-reset' cannot be used with '--no-checkout'"));
++ goto finish;
++ }
+ pathspec_pos = i;
+
+ /*
@@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
if (first_parent_only)
write_file(git_path_bisect_first_parent(), "\n");
-+ if (auto_reset)
-+ write_file(git_path_bisect_auto_reset(), "\n");
++ if (auto_reset != AUTO_RESET_NONE)
++ write_file(git_path_bisect_auto_reset(), "%s\n",
++ auto_reset_mode_name(auto_reset));
+
if (no_checkout) {
if (repo_get_oid(the_repository, start_head.buf, &oid) < 0) {
res = error(_("invalid ref: '%s'"), start_head.buf);
+@@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
+ {
+ int res = BISECT_OK;
+ struct strbuf command = STRBUF_INIT;
++ enum auto_reset_mode auto_reset = AUTO_RESET_NONE;
++ const char *auto_reset_arg;
+ const char *new_state;
+ int temporary_stdout_fd, saved_stdout;
+ int is_first_run = 1;
@@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
if (bisect_next_check(terms, NULL))
return BISECT_FAILED;
-+ if (argc && !strcmp(argv[0], "--auto-reset")) {
-+ write_file(git_path_bisect_auto_reset(), "\n");
++ if (argc && !strcmp(argv[0], "--auto-reset"))
++ auto_reset = AUTO_RESET_ORIGINAL;
++ else if (argc && skip_prefix(argv[0], "--auto-reset=", &auto_reset_arg)) {
++ if (parse_auto_reset(auto_reset_arg, &auto_reset))
++ return BISECT_FAILED;
++ }
++
++ if (auto_reset != AUTO_RESET_NONE) {
++ if (refs_ref_exists(get_main_ref_store(the_repository), "BISECT_HEAD"))
++ return error(_("'--auto-reset' cannot be used with '--no-checkout'"));
++ write_file(git_path_bisect_auto_reset(), "%s\n",
++ auto_reset_mode_name(auto_reset));
+ argc--;
+ argv++;
+ }
@@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, co
return BISECT_FAILED;
## t/t6030-bisect-porcelain.sh ##
+@@ t/t6030-bisect-porcelain.sh: test_bisect_usage () {
+ test_cmp expect actual
+ }
+
++test_bisect_state_file () {
++ test_path_is_file "$(git rev-parse --git-path "$1")"
++}
++
++test_bisect_state_missing () {
++ test_path_is_missing "$(git rev-parse --git-path "$1")"
++}
++
++bisect_start_and_finish () {
++ git bisect start "$1" $HASH4 $HASH2 &&
++ git bisect bad
++}
++
++bisect_run_auto_reset () {
++ write_script test_script.sh <<-\EOF &&
++ ! grep Another hello >/dev/null
++ EOF
++ git bisect start $HASH4 $HASH2 &&
++ git bisect run "$1" ./test_script.sh >my_bisect_log.txt &&
++ test_grep "$HASH3 is the first .bad. commit" my_bisect_log.txt
++}
++
++test_auto_reset_fails () {
++ local pattern="$1" &&
++ local state_file="$2" &&
++ shift 2 &&
++ test_must_fail "$@" 2>err &&
++ test_grep -- "$pattern" err &&
++ test_bisect_state_missing "$state_file"
++}
++
+ test_expect_success 'bisect usage' "
+ test_bisect_usage 1 git bisect reset extra1 extra2 <<-\EOF &&
+ error: 'git bisect reset' requires either no argument or a commit
@@ t/t6030-bisect-porcelain.sh: test_expect_success '"git bisect run" simple case' '
git bisect reset
'
-+test_expect_success '"git bisect start --auto-reset" leaves the bisection' '
-+ test_when_finished "git bisect reset" &&
-+ git bisect start --auto-reset $HASH4 $HASH2 &&
-+ git bisect bad &&
-+ test_path_is_missing "$(git rev-parse --git-path BISECT_START)"
++test_expect_success '"git bisect start --auto-reset" defaults to original' '
++ test_when_finished "git bisect reset; git checkout main" &&
++ git checkout main &&
++ bisect_start_and_finish --auto-reset &&
++ test "$HASH4" = "$(git rev-parse HEAD)" &&
++ test main = "$(git branch --show-current)" &&
++ test_bisect_state_missing BISECT_START &&
++
++ bisect_start_and_finish --auto-reset=original &&
++ test "$HASH4" = "$(git rev-parse HEAD)" &&
++ test main = "$(git branch --show-current)" &&
++ test_bisect_state_missing BISECT_START
+'
+
-+test_expect_success '"git bisect run --auto-reset" leaves the bisection' '
-+ test_when_finished "git bisect reset" &&
-+ write_script test_script.sh <<-\EOF &&
-+ ! grep Another hello >/dev/null
-+ EOF
++test_expect_success '"git bisect start --auto-reset=found" leaves first bad checked out' '
++ test_when_finished "git bisect reset; git checkout main" &&
++ bisect_start_and_finish --auto-reset=found &&
++ test "$HASH3" = "$(git rev-parse HEAD)" &&
++ test_bisect_state_missing BISECT_START
++'
++
++test_expect_success '"git bisect run --auto-reset" defaults to original' '
++ test_when_finished "git bisect reset; git checkout main" &&
++ bisect_run_auto_reset --auto-reset &&
++ test "$HASH4" = "$(git rev-parse HEAD)" &&
++ test main = "$(git branch --show-current)" &&
++ test_bisect_state_missing BISECT_START
++'
++
++test_expect_success '"git bisect run --auto-reset=found" leaves first bad checked out' '
++ test_when_finished "git bisect reset; git checkout main" &&
++ bisect_run_auto_reset --auto-reset=found &&
++ test "$HASH3" = "$(git rev-parse HEAD)" &&
++ test_bisect_state_missing BISECT_START
++'
++
++test_expect_success '--auto-reset rejects an unknown reset target' '
++ test_when_finished "git bisect reset; git checkout main" &&
++ test_auto_reset_fails \
++ "invalid value for.*--auto-reset.*unknown" BISECT_START \
++ git bisect start --auto-reset=unknown $HASH4 $HASH2 &&
++
+ git bisect start $HASH4 $HASH2 &&
-+ git bisect run --auto-reset ./test_script.sh >my_bisect_log.txt &&
-+ grep "$HASH3 is the first .bad. commit" my_bisect_log.txt &&
-+ test_path_is_missing "$(git rev-parse --git-path BISECT_START)"
++ test_auto_reset_fails \
++ "invalid value for.*--auto-reset.*unknown" BISECT_AUTO_RESET \
++ git bisect run --auto-reset=unknown true
++'
++
++test_expect_success '--auto-reset cannot be used with --no-checkout' '
++ test_when_finished "git bisect reset" &&
++ test_auto_reset_fails \
++ "cannot be used with.*--no-checkout" BISECT_START \
++ git bisect start --auto-reset=original --no-checkout $HASH4 $HASH2 &&
++
++ git bisect start --no-checkout $HASH4 $HASH2 &&
++ test_auto_reset_fails \
++ "cannot be used with.*--no-checkout" BISECT_AUTO_RESET \
++ git bisect run --auto-reset=found true
+'
+
+test_expect_success 'without --auto-reset the bisection state is kept' '
+ test_when_finished "git bisect reset" &&
+ git bisect start $HASH4 $HASH2 &&
+ git bisect bad &&
-+ test_path_is_file "$(git rev-parse --git-path BISECT_START)"
++ test_bisect_state_file BISECT_START
+'
+
+test_expect_success '--auto-reset does not leak into a later bisection' '
-+ test_when_finished "git bisect reset" &&
-+ git bisect start --auto-reset $HASH4 $HASH2 &&
-+ git bisect bad &&
++ test_when_finished "git bisect reset; git checkout main" &&
++ bisect_start_and_finish --auto-reset &&
++
+ git bisect start $HASH4 $HASH2 &&
+ git bisect bad &&
-+ test_path_is_file "$(git rev-parse --git-path BISECT_START)"
++ test_bisect_state_file BISECT_START
+'
+
# We want to automatically find the commit that
--
gitgitgadget
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v2 1/3] bisect: read run output from the open descriptor
2026-07-17 18:27 ` [PATCH v2 0/3] " Harald Nordgren via GitGitGadget
@ 2026-07-17 18:27 ` Harald Nordgren via GitGitGadget
2026-07-17 18:27 ` [PATCH v2 2/3] bisect: let bisect_reset() optionally check out quietly Harald Nordgren via GitGitGadget
2026-07-17 18:27 ` [PATCH v2 3/3] bisect: add --auto-reset to leave when done Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 13+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-07-17 18:27 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"git bisect run" redirects each step's output into BISECT_RUN, then
prints it back by reopening the file by name. Read it from the already
open descriptor instead; this behaves the same and no longer needs the
file to be reachable by name.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
builtin/bisect.c | 20 ++++++++------------
1 file changed, 8 insertions(+), 12 deletions(-)
diff --git a/builtin/bisect.c b/builtin/bisect.c
index 798e28f501..69ea14b1b6 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -178,17 +178,13 @@ static int append_to_file(const char *path, const char *format, ...)
return res;
}
-static int print_file_to_stdout(const char *path)
+static int print_fd_to_stdout(int fd)
{
- int fd = open(path, O_RDONLY);
- int ret = 0;
-
- if (fd < 0)
- return error_errno(_("cannot open file '%s' for reading"), path);
+ if (lseek(fd, 0, SEEK_SET) < 0)
+ return error_errno(_("failed to rewind BISECT_RUN output"));
if (copy_fd(fd, 1) < 0)
- ret = error_errno(_("failed to read '%s'"), path);
- close(fd);
- return ret;
+ return error_errno(_("failed to read BISECT_RUN output"));
+ return 0;
}
static int check_term_format(const char *term, const char *orig_term)
@@ -1291,7 +1287,7 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
else
new_state = terms->term_bad;
- temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
+ temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_RDWR | O_TRUNC, 0666);
if (temporary_stdout_fd < 0) {
res = error_errno(_("cannot open file '%s' for writing"), git_path_bisect_run());
@@ -1307,9 +1303,9 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
fflush(stdout);
dup2(saved_stdout, 1);
close(saved_stdout);
- close(temporary_stdout_fd);
- print_file_to_stdout(git_path_bisect_run());
+ print_fd_to_stdout(temporary_stdout_fd);
+ close(temporary_stdout_fd);
if (res == BISECT_ONLY_SKIPPED_LEFT)
error(_("bisect run cannot continue any more"));
--
gitgitgadget
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v2 2/3] bisect: let bisect_reset() optionally check out quietly
2026-07-17 18:27 ` [PATCH v2 0/3] " Harald Nordgren via GitGitGadget
2026-07-17 18:27 ` [PATCH v2 1/3] bisect: read run output from the open descriptor Harald Nordgren via GitGitGadget
@ 2026-07-17 18:27 ` Harald Nordgren via GitGitGadget
2026-07-17 18:27 ` [PATCH v2 3/3] bisect: add --auto-reset to leave when done Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 13+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-07-17 18:27 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Add a "quiet" parameter to bisect_reset() that passes "--quiet" to the
checkout restoring the original HEAD, suppressing its progress and
branch-status output.
No caller sets the flag yet, so behavior is unchanged.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
builtin/bisect.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/builtin/bisect.c b/builtin/bisect.c
index 69ea14b1b6..27d30b549e 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -230,7 +230,7 @@ static int write_terms(const char *bad, const char *good)
return res;
}
-static int bisect_reset(const char *commit)
+static int bisect_reset(const char *commit, int quiet)
{
struct strbuf branch = STRBUF_INIT;
@@ -251,8 +251,10 @@ static int bisect_reset(const char *commit)
struct child_process cmd = CHILD_PROCESS_INIT;
cmd.git_cmd = 1;
- strvec_pushl(&cmd.args, "checkout", "--ignore-other-worktrees",
- branch.buf, "--", NULL);
+ strvec_pushl(&cmd.args, "checkout", "--ignore-other-worktrees", NULL);
+ if (quiet)
+ strvec_push(&cmd.args, "--quiet");
+ strvec_pushl(&cmd.args, branch.buf, "--", NULL);
if (run_command(&cmd)) {
error(_("could not check out original"
" HEAD '%s'. Try 'git bisect"
@@ -1085,7 +1087,7 @@ static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f
if (is_empty_or_missing_file(filename))
return error(_("cannot read file '%s' for replaying"), filename);
- if (bisect_reset(NULL))
+ if (bisect_reset(NULL, 0))
return BISECT_FAILED;
fp = fopen(filename, "r");
@@ -1334,7 +1336,7 @@ static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNU
if (argc > 1)
return error(_("'%s' requires either no argument or a commit"),
"git bisect reset");
- return bisect_reset(argc ? argv[0] : NULL);
+ return bisect_reset(argc ? argv[0] : NULL, 0);
}
static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,
--
gitgitgadget
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v2 3/3] bisect: add --auto-reset to leave when done
2026-07-17 18:27 ` [PATCH v2 0/3] " Harald Nordgren via GitGitGadget
2026-07-17 18:27 ` [PATCH v2 1/3] bisect: read run output from the open descriptor Harald Nordgren via GitGitGadget
2026-07-17 18:27 ` [PATCH v2 2/3] bisect: let bisect_reset() optionally check out quietly Harald Nordgren via GitGitGadget
@ 2026-07-17 18:27 ` Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 13+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-07-17 18:27 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
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>
---
Documentation/git-bisect.adoc | 14 ++++-
bisect.c | 2 +
builtin/bisect.c | 100 ++++++++++++++++++++++++++++++-
t/t6030-bisect-porcelain.sh | 107 ++++++++++++++++++++++++++++++++++
4 files changed, 219 insertions(+), 4 deletions(-)
diff --git a/Documentation/git-bisect.adoc b/Documentation/git-bisect.adoc
index d2115b2990..afae508463 100644
--- a/Documentation/git-bisect.adoc
+++ b/Documentation/git-bisect.adoc
@@ -10,7 +10,7 @@ SYNOPSIS
--------
[synopsis]
git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]
- [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]
+ [--no-checkout] [--first-parent] [--auto-reset[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]
git bisect (bad|new|<term-new>) [<rev>]
git bisect (good|old|<term-old>) [<rev>...]
git bisect terms [--term-(good|old) | --term-(bad|new)]
@@ -20,7 +20,7 @@ git bisect reset [<commit>]
git bisect (visualize|view)
git bisect replay <logfile>
git bisect log
-git bisect run <cmd> [<arg>...]
+git bisect run [--auto-reset[=<where>]] <cmd> [<arg>...]
git bisect help
DESCRIPTION
@@ -385,6 +385,16 @@ 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.
+`--auto-reset[=<where>]`::
+ Once the first bad commit is found, report it and clean up the
+ bisection state. `<where>` may be `original` to return to the commit
+ checked out before `git bisect start`, or `found` to leave the first
+ bad commit checked out. If `<where>` is omitted, it defaults to
+ `original`.
++
+This option may be given to `git bisect start` or to `git bisect run`. It
+cannot be used for a bisection started with `--no-checkout`.
+
EXAMPLES
--------
diff --git a/bisect.c b/bisect.c
index 94c7028d2a..a34309dd35 100644
--- a/bisect.c
+++ b/bisect.c
@@ -488,6 +488,7 @@ 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_terms, "BISECT_TERMS")
static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
+static GIT_PATH_FUNC(git_path_bisect_auto_reset, "BISECT_AUTO_RESET")
static void read_bisect_paths(struct strvec *array)
{
@@ -1211,6 +1212,7 @@ int bisect_clean_state(void)
unlink_or_warn(git_path_bisect_run());
unlink_or_warn(git_path_bisect_terms());
unlink_or_warn(git_path_bisect_first_parent());
+ unlink_or_warn(git_path_bisect_auto_reset());
/*
* Cleanup BISECT_START last to support the --no-checkout option
* introduced in the commit 4796e823a.
diff --git a/builtin/bisect.c b/builtin/bisect.c
index 27d30b549e..6e835b7d2a 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -24,11 +24,12 @@ 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_names, "BISECT_NAMES")
static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
+static GIT_PATH_FUNC(git_path_bisect_auto_reset, "BISECT_AUTO_RESET")
static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
#define BUILTIN_GIT_BISECT_START_USAGE \
N_("git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\n" \
- " [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]")
+ " [--no-checkout] [--first-parent] [--auto-reset[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]")
#define BUILTIN_GIT_BISECT_BAD_USAGE \
N_("git bisect (bad|new|<term-new>) [<rev>]")
#define BUILTIN_GIT_BISECT_GOOD_USAGE \
@@ -48,7 +49,7 @@ static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
#define BUILTIN_GIT_BISECT_LOG_USAGE \
"git bisect log"
#define BUILTIN_GIT_BISECT_RUN_USAGE \
- N_("git bisect run <cmd> [<arg>...]")
+ N_("git bisect run [--auto-reset[=<where>]] <cmd> [<arg>...]")
#define BUILTIN_GIT_BISECT_HELP_USAGE \
"git bisect help"
@@ -68,6 +69,12 @@ static const char * const git_bisect_usage[] = {
NULL
};
+enum auto_reset_mode {
+ AUTO_RESET_NONE,
+ AUTO_RESET_ORIGINAL,
+ AUTO_RESET_FOUND,
+};
+
struct add_bisect_ref_data {
struct rev_info *revs;
unsigned int object_flags;
@@ -268,6 +275,59 @@ static int bisect_reset(const char *commit, int quiet)
return bisect_clean_state();
}
+static int parse_auto_reset(const char *value, enum auto_reset_mode *mode)
+{
+ if (!strcmp(value, "original"))
+ *mode = AUTO_RESET_ORIGINAL;
+ else if (!strcmp(value, "found"))
+ *mode = AUTO_RESET_FOUND;
+ else
+ return error(_("invalid value for '--auto-reset': '%s'"), value);
+
+ return 0;
+}
+
+static const char *auto_reset_mode_name(enum auto_reset_mode mode)
+{
+ switch (mode) {
+ case AUTO_RESET_ORIGINAL:
+ return "original";
+ case AUTO_RESET_FOUND:
+ return "found";
+ case AUTO_RESET_NONE:
+ BUG("no name for unset auto-reset mode");
+ }
+ BUG("unknown auto-reset mode %d", mode);
+}
+
+static int bisect_auto_reset(struct bisect_terms *terms)
+{
+ struct strbuf value = STRBUF_INIT;
+ enum auto_reset_mode mode;
+ char *commit = NULL;
+ int res;
+
+ if (strbuf_read_file(&value, git_path_bisect_auto_reset(), 0) < 0) {
+ res = error_errno(_("could not read '%s'"),
+ git_path_bisect_auto_reset());
+ goto cleanup;
+ }
+ strbuf_trim(&value);
+ if (parse_auto_reset(value.buf, &mode)) {
+ res = -1;
+ goto cleanup;
+ }
+
+ if (mode == AUTO_RESET_FOUND)
+ commit = xstrfmt("refs/bisect/%s", terms->term_bad);
+ res = bisect_reset(commit, 1);
+
+cleanup:
+ free(commit);
+ strbuf_release(&value);
+ return res;
+}
+
static void log_commit(FILE *fp,
const char *fmt, const char *state,
struct commit *commit)
@@ -688,6 +748,8 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre
if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {
res = bisect_successful(terms);
+ if (!res && !is_empty_or_missing_file(git_path_bisect_auto_reset()))
+ res = bisect_auto_reset(terms);
return res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;
} else if (res == BISECT_ONLY_SKIPPED_LEFT) {
res = bisect_skipped_commits(terms);
@@ -711,6 +773,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
{
int no_checkout = 0;
int first_parent_only = 0;
+ enum auto_reset_mode auto_reset = AUTO_RESET_NONE;
int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;
int flags, pathspec_pos;
enum bisect_error res = BISECT_OK;
@@ -743,6 +806,13 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
no_checkout = 1;
} else if (!strcmp(arg, "--first-parent")) {
first_parent_only = 1;
+ } else if (!strcmp(arg, "--auto-reset")) {
+ auto_reset = AUTO_RESET_ORIGINAL;
+ } else if (skip_prefix(arg, "--auto-reset=", &arg)) {
+ if (parse_auto_reset(arg, &auto_reset)) {
+ res = BISECT_FAILED;
+ goto finish;
+ }
} else if (!strcmp(arg, "--term-good") ||
!strcmp(arg, "--term-old")) {
i++;
@@ -780,6 +850,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
break;
}
}
+ if (auto_reset != AUTO_RESET_NONE && no_checkout) {
+ res = error(_("'--auto-reset' cannot be used with '--no-checkout'"));
+ goto finish;
+ }
pathspec_pos = i;
/*
@@ -857,6 +931,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
if (first_parent_only)
write_file(git_path_bisect_first_parent(), "\n");
+ if (auto_reset != AUTO_RESET_NONE)
+ write_file(git_path_bisect_auto_reset(), "%s\n",
+ auto_reset_mode_name(auto_reset));
+
if (no_checkout) {
if (repo_get_oid(the_repository, start_head.buf, &oid) < 0) {
res = error(_("invalid ref: '%s'"), start_head.buf);
@@ -1235,6 +1313,8 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
{
int res = BISECT_OK;
struct strbuf command = STRBUF_INIT;
+ enum auto_reset_mode auto_reset = AUTO_RESET_NONE;
+ const char *auto_reset_arg;
const char *new_state;
int temporary_stdout_fd, saved_stdout;
int is_first_run = 1;
@@ -1242,6 +1322,22 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
if (bisect_next_check(terms, NULL))
return BISECT_FAILED;
+ if (argc && !strcmp(argv[0], "--auto-reset"))
+ auto_reset = AUTO_RESET_ORIGINAL;
+ else if (argc && skip_prefix(argv[0], "--auto-reset=", &auto_reset_arg)) {
+ if (parse_auto_reset(auto_reset_arg, &auto_reset))
+ return BISECT_FAILED;
+ }
+
+ if (auto_reset != AUTO_RESET_NONE) {
+ if (refs_ref_exists(get_main_ref_store(the_repository), "BISECT_HEAD"))
+ return error(_("'--auto-reset' cannot be used with '--no-checkout'"));
+ write_file(git_path_bisect_auto_reset(), "%s\n",
+ auto_reset_mode_name(auto_reset));
+ argc--;
+ argv++;
+ }
+
if (!argc) {
error(_("bisect run failed: no command provided."));
return BISECT_FAILED;
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index 081116220a..826e54efea 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -43,6 +43,37 @@ test_bisect_usage () {
test_cmp expect actual
}
+test_bisect_state_file () {
+ test_path_is_file "$(git rev-parse --git-path "$1")"
+}
+
+test_bisect_state_missing () {
+ test_path_is_missing "$(git rev-parse --git-path "$1")"
+}
+
+bisect_start_and_finish () {
+ git bisect start "$1" $HASH4 $HASH2 &&
+ git bisect bad
+}
+
+bisect_run_auto_reset () {
+ write_script test_script.sh <<-\EOF &&
+ ! grep Another hello >/dev/null
+ EOF
+ git bisect start $HASH4 $HASH2 &&
+ git bisect run "$1" ./test_script.sh >my_bisect_log.txt &&
+ test_grep "$HASH3 is the first .bad. commit" my_bisect_log.txt
+}
+
+test_auto_reset_fails () {
+ local pattern="$1" &&
+ local state_file="$2" &&
+ shift 2 &&
+ test_must_fail "$@" 2>err &&
+ test_grep -- "$pattern" err &&
+ test_bisect_state_missing "$state_file"
+}
+
test_expect_success 'bisect usage' "
test_bisect_usage 1 git bisect reset extra1 extra2 <<-\EOF &&
error: 'git bisect reset' requires either no argument or a commit
@@ -453,6 +484,82 @@ test_expect_success '"git bisect run" simple case' '
git bisect reset
'
+test_expect_success '"git bisect start --auto-reset" defaults to original' '
+ test_when_finished "git bisect reset; git checkout main" &&
+ git checkout main &&
+ bisect_start_and_finish --auto-reset &&
+ test "$HASH4" = "$(git rev-parse HEAD)" &&
+ test main = "$(git branch --show-current)" &&
+ test_bisect_state_missing BISECT_START &&
+
+ bisect_start_and_finish --auto-reset=original &&
+ test "$HASH4" = "$(git rev-parse HEAD)" &&
+ test main = "$(git branch --show-current)" &&
+ test_bisect_state_missing BISECT_START
+'
+
+test_expect_success '"git bisect start --auto-reset=found" leaves first bad checked out' '
+ test_when_finished "git bisect reset; git checkout main" &&
+ bisect_start_and_finish --auto-reset=found &&
+ test "$HASH3" = "$(git rev-parse HEAD)" &&
+ test_bisect_state_missing BISECT_START
+'
+
+test_expect_success '"git bisect run --auto-reset" defaults to original' '
+ test_when_finished "git bisect reset; git checkout main" &&
+ bisect_run_auto_reset --auto-reset &&
+ test "$HASH4" = "$(git rev-parse HEAD)" &&
+ test main = "$(git branch --show-current)" &&
+ test_bisect_state_missing BISECT_START
+'
+
+test_expect_success '"git bisect run --auto-reset=found" leaves first bad checked out' '
+ test_when_finished "git bisect reset; git checkout main" &&
+ bisect_run_auto_reset --auto-reset=found &&
+ test "$HASH3" = "$(git rev-parse HEAD)" &&
+ test_bisect_state_missing BISECT_START
+'
+
+test_expect_success '--auto-reset rejects an unknown reset target' '
+ test_when_finished "git bisect reset; git checkout main" &&
+ test_auto_reset_fails \
+ "invalid value for.*--auto-reset.*unknown" BISECT_START \
+ git bisect start --auto-reset=unknown $HASH4 $HASH2 &&
+
+ git bisect start $HASH4 $HASH2 &&
+ test_auto_reset_fails \
+ "invalid value for.*--auto-reset.*unknown" BISECT_AUTO_RESET \
+ git bisect run --auto-reset=unknown true
+'
+
+test_expect_success '--auto-reset cannot be used with --no-checkout' '
+ test_when_finished "git bisect reset" &&
+ test_auto_reset_fails \
+ "cannot be used with.*--no-checkout" BISECT_START \
+ git bisect start --auto-reset=original --no-checkout $HASH4 $HASH2 &&
+
+ git bisect start --no-checkout $HASH4 $HASH2 &&
+ test_auto_reset_fails \
+ "cannot be used with.*--no-checkout" BISECT_AUTO_RESET \
+ git bisect run --auto-reset=found true
+'
+
+test_expect_success 'without --auto-reset the bisection state is kept' '
+ test_when_finished "git bisect reset" &&
+ git bisect start $HASH4 $HASH2 &&
+ git bisect bad &&
+ test_bisect_state_file BISECT_START
+'
+
+test_expect_success '--auto-reset does not leak into a later bisection' '
+ test_when_finished "git bisect reset; git checkout main" &&
+ bisect_start_and_finish --auto-reset &&
+
+ git bisect start $HASH4 $HASH2 &&
+ git bisect bad &&
+ test_bisect_state_file BISECT_START
+'
+
# We want to automatically find the commit that
# added "Ciao" into hello.
test_expect_success '"git bisect run" with more complex "git bisect start"' '
--
gitgitgadget
^ permalink raw reply related [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-07-17 18:27 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-16 5:35 [PATCH 0/3] bisect: add --auto-reset to leave when done Harald Nordgren via GitGitGadget
2026-07-16 5:35 ` [PATCH 1/3] bisect: read run output from the open descriptor Harald Nordgren via GitGitGadget
2026-07-16 5:35 ` [PATCH 2/3] bisect: let bisect_reset() optionally check out quietly Harald Nordgren via GitGitGadget
2026-07-16 5:35 ` [PATCH 3/3] bisect: add --auto-reset to leave when done Harald Nordgren via GitGitGadget
2026-07-16 17:22 ` Junio C Hamano
2026-07-16 21:22 ` Harald Nordgren
2026-07-17 5:00 ` Junio C Hamano
2026-07-17 9:16 ` Harald Nordgren
2026-07-17 16:43 ` Junio C Hamano
2026-07-17 18:27 ` [PATCH v2 0/3] " Harald Nordgren via GitGitGadget
2026-07-17 18:27 ` [PATCH v2 1/3] bisect: read run output from the open descriptor Harald Nordgren via GitGitGadget
2026-07-17 18:27 ` [PATCH v2 2/3] bisect: let bisect_reset() optionally check out quietly Harald Nordgren via GitGitGadget
2026-07-17 18:27 ` [PATCH v2 3/3] bisect: add --auto-reset to leave when done Harald Nordgren via GitGitGadget
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox