{"thread":{"id":"66011","subject":"[PATCH 0/3] bisect: add --auto-reset to leave when done","startedAt":"2026-07-16T05:36:01Z","lastAt":"2026-08-06T17:36:03Z","messageCount":42,"participants":["Harald Nordgren via GitGitGadget","Junio C Hamano","Harald Nordgren","Johannes Sixt"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"548363","messageId":"pull.2335.git.git.1784180159.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":null,"subject":"[PATCH 0/3] bisect: add --auto-reset to leave when done","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-07-16T05:35:56Z","receivedAt":"2026-07-16T05:36:01Z","isPatch":true,"body":"Add a --auto-reset option to git bisect start and git bisect run that\nreturns to the commit checked out before git bisect start as soon as the\nfirst bad commit is reported, instead of leaving the session active until\ngit bisect reset is run by hand.\n\nHarald Nordgren (3):\n  bisect: read run output from the open descriptor\n  bisect: let bisect_reset() optionally check out quietly\n  bisect: add --auto-reset to leave when done\n\n Documentation/git-bisect.adoc | 12 +++++++--\n bisect.c                      |  2 ++\n builtin/bisect.c              | 51 ++++++++++++++++++++++-------------\n t/t6030-bisect-porcelain.sh   | 34 +++++++++++++++++++++++\n 4 files changed, 78 insertions(+), 21 deletions(-)\n\n\nbase-commit: f60db8d575adb79761d363e026fb49bddf330c73\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2335%2FHaraldNordgren%2Fbisect-auto-reset-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2335/HaraldNordgren/bisect-auto-reset-v1\nPull-Request: https://github.com/git/git/pull/2335\n-- \ngitgitgadget\n"},{"id":"548364","messageId":"3fd365835d004c3b1463d8abd18dc7bc171466a0.1784180159.git.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.git.git.1784180159.gitgitgadget@gmail.com","subject":"[PATCH 1/3] bisect: read run output from the open descriptor","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-07-16T05:35:57Z","receivedAt":"2026-07-16T05:36:03Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\n\"git bisect run\" redirects each step's output into BISECT_RUN, then\nprints it back by reopening the file by name. Read it from the already\nopen descriptor instead; this behaves the same and no longer needs the\nfile to be reachable by name.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/bisect.c | 20 ++++++++------------\n 1 file changed, 8 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 798e28f501..69ea14b1b6 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -178,17 +178,13 @@ static int append_to_file(const char *path, const char *format, ...)\n \treturn res;\n }\n \n-static int print_file_to_stdout(const char *path)\n+static int print_fd_to_stdout(int fd)\n {\n-\tint fd = open(path, O_RDONLY);\n-\tint ret = 0;\n-\n-\tif (fd < 0)\n-\t\treturn error_errno(_(\"cannot open file '%s' for reading\"), path);\n+\tif (lseek(fd, 0, SEEK_SET) < 0)\n+\t\treturn error_errno(_(\"failed to rewind BISECT_RUN output\"));\n \tif (copy_fd(fd, 1) < 0)\n-\t\tret = error_errno(_(\"failed to read '%s'\"), path);\n-\tclose(fd);\n-\treturn ret;\n+\t\treturn error_errno(_(\"failed to read BISECT_RUN output\"));\n+\treturn 0;\n }\n \n static int check_term_format(const char *term, const char *orig_term)\n@@ -1291,7 +1287,7 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n \t\telse\n \t\t\tnew_state = terms->term_bad;\n \n-\t\ttemporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_WRONLY | O_TRUNC, 0666);\n+\t\ttemporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_RDWR | O_TRUNC, 0666);\n \n \t\tif (temporary_stdout_fd < 0) {\n \t\t\tres = error_errno(_(\"cannot open file '%s' for writing\"), git_path_bisect_run());\n@@ -1307,9 +1303,9 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n \t\tfflush(stdout);\n \t\tdup2(saved_stdout, 1);\n \t\tclose(saved_stdout);\n-\t\tclose(temporary_stdout_fd);\n \n-\t\tprint_file_to_stdout(git_path_bisect_run());\n+\t\tprint_fd_to_stdout(temporary_stdout_fd);\n+\t\tclose(temporary_stdout_fd);\n \n \t\tif (res == BISECT_ONLY_SKIPPED_LEFT)\n \t\t\terror(_(\"bisect run cannot continue any more\"));\n-- \ngitgitgadget\n\n"},{"id":"548365","messageId":"a7670baafc01a8a016486f634f59a3193560e2bb.1784180159.git.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.git.git.1784180159.gitgitgadget@gmail.com","subject":"[PATCH 2/3] bisect: let bisect_reset() optionally check out quietly","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-07-16T05:35:58Z","receivedAt":"2026-07-16T05:36:04Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a \"quiet\" parameter to bisect_reset() that passes \"--quiet\" to the\ncheckout restoring the original HEAD, suppressing its progress and\nbranch-status output.\n\nNo caller sets the flag yet, so behavior is unchanged.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/bisect.c | 12 +++++++-----\n 1 file changed, 7 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 69ea14b1b6..27d30b549e 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -230,7 +230,7 @@ static int write_terms(const char *bad, const char *good)\n \treturn res;\n }\n \n-static int bisect_reset(const char *commit)\n+static int bisect_reset(const char *commit, int quiet)\n {\n \tstruct strbuf branch = STRBUF_INIT;\n \n@@ -251,8 +251,10 @@ static int bisect_reset(const char *commit)\n \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n \t\tcmd.git_cmd = 1;\n-\t\tstrvec_pushl(&cmd.args, \"checkout\", \"--ignore-other-worktrees\",\n-\t\t\t\tbranch.buf, \"--\", NULL);\n+\t\tstrvec_pushl(&cmd.args, \"checkout\", \"--ignore-other-worktrees\", NULL);\n+\t\tif (quiet)\n+\t\t\tstrvec_push(&cmd.args, \"--quiet\");\n+\t\tstrvec_pushl(&cmd.args, branch.buf, \"--\", NULL);\n \t\tif (run_command(&cmd)) {\n \t\t\terror(_(\"could not check out original\"\n \t\t\t\t\" HEAD '%s'. Try 'git bisect\"\n@@ -1085,7 +1087,7 @@ static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f\n \tif (is_empty_or_missing_file(filename))\n \t\treturn error(_(\"cannot read file '%s' for replaying\"), filename);\n \n-\tif (bisect_reset(NULL))\n+\tif (bisect_reset(NULL, 0))\n \t\treturn BISECT_FAILED;\n \n \tfp = fopen(filename, \"r\");\n@@ -1334,7 +1336,7 @@ static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNU\n \tif (argc > 1)\n \t\treturn error(_(\"'%s' requires either no argument or a commit\"),\n \t\t\t     \"git bisect reset\");\n-\treturn bisect_reset(argc ? argv[0] : NULL);\n+\treturn bisect_reset(argc ? argv[0] : NULL, 0);\n }\n \n static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,\n-- \ngitgitgadget\n\n"},{"id":"548366","messageId":"a9194b1d00b260a7a7852eccec54c872618b5fdf.1784180159.git.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.git.git.1784180159.gitgitgadget@gmail.com","subject":"[PATCH 3/3] bisect: add --auto-reset to leave when done","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-07-16T05:35:59Z","receivedAt":"2026-07-16T05:36:05Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen a bisection finished, \"git bisect\" reported the first bad commit\nbut left the session active until \"git bisect reset\" was run by hand.\n\nAdd an \"--auto-reset\" option, accepted by both \"git bisect start\" and\n\"git bisect run\", that resets as soon as the first bad commit is found,\nreturning to the commit checked out before \"git bisect start\". The flag\nis persisted in a BISECT_AUTO_RESET state file and the restoring\ncheckout is done quietly.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-bisect.adoc | 12 ++++++++++--\n bisect.c                      |  2 ++\n builtin/bisect.c              | 19 +++++++++++++++++--\n t/t6030-bisect-porcelain.sh   | 34 ++++++++++++++++++++++++++++++++++\n 4 files changed, 63 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-bisect.adoc b/Documentation/git-bisect.adoc\nindex d2115b2990..1b03dbba7a 100644\n--- a/Documentation/git-bisect.adoc\n+++ b/Documentation/git-bisect.adoc\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [synopsis]\n git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\n-\t\t [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\n+\t\t [--no-checkout] [--first-parent] [--auto-reset] [<bad> [<good>...]] [--] [<pathspec>...]\n git bisect (bad|new|<term-new>) [<rev>]\n git bisect (good|old|<term-old>) [<rev>...]\n git bisect terms [--term-(good|old) | --term-(bad|new)]\n@@ -20,7 +20,7 @@ git bisect reset [<commit>]\n git bisect (visualize|view)\n git bisect replay <logfile>\n git bisect log\n-git bisect run <cmd> [<arg>...]\n+git bisect run [--auto-reset] <cmd> [<arg>...]\n git bisect help\n \n DESCRIPTION\n@@ -385,6 +385,14 @@ ignored.\n This option is particularly useful in avoiding false positives when a merged\n branch contained broken or non-buildable commits, but the merge itself was OK.\n \n+`--auto-reset`::\n+\tOnce the first bad commit is found, clean up the bisection state and\n+\treturn to the commit that was checked out before `git bisect start`,\n+\tas if `git bisect reset` had been run. The first bad commit is still\n+\treported before resetting.\n++\n+This option may be given to `git bisect start` or to `git bisect run`.\n+\n EXAMPLES\n --------\n \ndiff --git a/bisect.c b/bisect.c\nindex 94c7028d2a..a34309dd35 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -488,6 +488,7 @@ static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n static GIT_PATH_FUNC(git_path_bisect_terms, \"BISECT_TERMS\")\n static GIT_PATH_FUNC(git_path_bisect_first_parent, \"BISECT_FIRST_PARENT\")\n+static GIT_PATH_FUNC(git_path_bisect_auto_reset, \"BISECT_AUTO_RESET\")\n \n static void read_bisect_paths(struct strvec *array)\n {\n@@ -1211,6 +1212,7 @@ int bisect_clean_state(void)\n \tunlink_or_warn(git_path_bisect_run());\n \tunlink_or_warn(git_path_bisect_terms());\n \tunlink_or_warn(git_path_bisect_first_parent());\n+\tunlink_or_warn(git_path_bisect_auto_reset());\n \t/*\n \t * Cleanup BISECT_START last to support the --no-checkout option\n \t * introduced in the commit 4796e823a.\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 27d30b549e..b80eccb635 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -24,11 +24,12 @@ static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n static GIT_PATH_FUNC(git_path_bisect_names, \"BISECT_NAMES\")\n static GIT_PATH_FUNC(git_path_bisect_first_parent, \"BISECT_FIRST_PARENT\")\n+static GIT_PATH_FUNC(git_path_bisect_auto_reset, \"BISECT_AUTO_RESET\")\n static GIT_PATH_FUNC(git_path_bisect_run, \"BISECT_RUN\")\n \n #define BUILTIN_GIT_BISECT_START_USAGE \\\n \tN_(\"git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\\n\" \\\n-\t   \"                 [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\")\n+\t   \"                 [--no-checkout] [--first-parent] [--auto-reset] [<bad> [<good>...]] [--] [<pathspec>...]\")\n #define BUILTIN_GIT_BISECT_BAD_USAGE \\\n \tN_(\"git bisect (bad|new|<term-new>) [<rev>]\")\n #define BUILTIN_GIT_BISECT_GOOD_USAGE \\\n@@ -48,7 +49,7 @@ static GIT_PATH_FUNC(git_path_bisect_run, \"BISECT_RUN\")\n #define BUILTIN_GIT_BISECT_LOG_USAGE \\\n \t\"git bisect log\"\n #define BUILTIN_GIT_BISECT_RUN_USAGE \\\n-\tN_(\"git bisect run <cmd> [<arg>...]\")\n+\tN_(\"git bisect run [--auto-reset] <cmd> [<arg>...]\")\n #define BUILTIN_GIT_BISECT_HELP_USAGE \\\n \t\"git bisect help\"\n \n@@ -688,6 +689,8 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n \n \tif (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n \t\tres = bisect_successful(terms);\n+\t\tif (!res && !is_empty_or_missing_file(git_path_bisect_auto_reset()))\n+\t\t\tres = bisect_reset(NULL, 1);\n \t\treturn res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;\n \t} else if (res == BISECT_ONLY_SKIPPED_LEFT) {\n \t\tres = bisect_skipped_commits(terms);\n@@ -711,6 +714,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n {\n \tint no_checkout = 0;\n \tint first_parent_only = 0;\n+\tint auto_reset = 0;\n \tint i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;\n \tint flags, pathspec_pos;\n \tenum bisect_error res = BISECT_OK;\n@@ -743,6 +747,8 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \t\t\tno_checkout = 1;\n \t\t} else if (!strcmp(arg, \"--first-parent\")) {\n \t\t\tfirst_parent_only = 1;\n+\t\t} else if (!strcmp(arg, \"--auto-reset\")) {\n+\t\t\tauto_reset = 1;\n \t\t} else if (!strcmp(arg, \"--term-good\") ||\n \t\t\t !strcmp(arg, \"--term-old\")) {\n \t\t\ti++;\n@@ -857,6 +863,9 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \tif (first_parent_only)\n \t\twrite_file(git_path_bisect_first_parent(), \"\\n\");\n \n+\tif (auto_reset)\n+\t\twrite_file(git_path_bisect_auto_reset(), \"\\n\");\n+\n \tif (no_checkout) {\n \t\tif (repo_get_oid(the_repository, start_head.buf, &oid) < 0) {\n \t\t\tres = error(_(\"invalid ref: '%s'\"), start_head.buf);\n@@ -1242,6 +1251,12 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n \tif (bisect_next_check(terms, NULL))\n \t\treturn BISECT_FAILED;\n \n+\tif (argc && !strcmp(argv[0], \"--auto-reset\")) {\n+\t\twrite_file(git_path_bisect_auto_reset(), \"\\n\");\n+\t\targc--;\n+\t\targv++;\n+\t}\n+\n \tif (!argc) {\n \t\terror(_(\"bisect run failed: no command provided.\"));\n \t\treturn BISECT_FAILED;\ndiff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh\nindex 081116220a..5389ba388c 100755\n--- a/t/t6030-bisect-porcelain.sh\n+++ b/t/t6030-bisect-porcelain.sh\n@@ -453,6 +453,40 @@ test_expect_success '\"git bisect run\" simple case' '\n \tgit bisect reset\n '\n \n+test_expect_success '\"git bisect start --auto-reset\" leaves the bisection' '\n+\ttest_when_finished \"git bisect reset\" &&\n+\tgit bisect start --auto-reset $HASH4 $HASH2 &&\n+\tgit bisect bad &&\n+\ttest_path_is_missing \"$(git rev-parse --git-path BISECT_START)\"\n+'\n+\n+test_expect_success '\"git bisect run --auto-reset\" leaves the bisection' '\n+\ttest_when_finished \"git bisect reset\" &&\n+\twrite_script test_script.sh <<-\\EOF &&\n+\t! grep Another hello >/dev/null\n+\tEOF\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect run --auto-reset ./test_script.sh >my_bisect_log.txt &&\n+\tgrep \"$HASH3 is the first .bad. commit\" my_bisect_log.txt &&\n+\ttest_path_is_missing \"$(git rev-parse --git-path BISECT_START)\"\n+'\n+\n+test_expect_success 'without --auto-reset the bisection state is kept' '\n+\ttest_when_finished \"git bisect reset\" &&\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect bad &&\n+\ttest_path_is_file \"$(git rev-parse --git-path BISECT_START)\"\n+'\n+\n+test_expect_success '--auto-reset does not leak into a later bisection' '\n+\ttest_when_finished \"git bisect reset\" &&\n+\tgit bisect start --auto-reset $HASH4 $HASH2 &&\n+\tgit bisect bad &&\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect bad &&\n+\ttest_path_is_file \"$(git rev-parse --git-path BISECT_START)\"\n+'\n+\n # We want to automatically find the commit that\n # added \"Ciao\" into hello.\n test_expect_success '\"git bisect run\" with more complex \"git bisect start\"' '\n-- \ngitgitgadget\n"},{"id":"548438","messageId":"xmqqse5ihmsz.fsf@gitster.g","threadId":"66011","inReplyTo":"a9194b1d00b260a7a7852eccec54c872618b5fdf.1784180159.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 3/3] bisect: add --auto-reset to leave when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-16T17:22:20Z","receivedAt":"2026-07-16T17:22:22Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> When a bisection finished, \"git bisect\" reported the first bad commit\n> but left the session active until \"git bisect reset\" was run by hand.\n\nIf this gives an observation of the behavior of the current code,\nplease write it in the present tense.\n\n> Add an \"--auto-reset\" option, accepted by both \"git bisect start\" and\n> \"git bisect run\", that resets as soon as the first bad commit is found,\n> returning to the commit checked out before \"git bisect start\". The flag\n> is persisted in a BISECT_AUTO_RESET state file and the restoring\n> checkout is done quietly.\n\nI often find myself, after the culprit is found, running 'git\nreset --hard' or 'git bisect reset' to jump to the problematic\ncommit to investigate further.  If '--auto-reset' leaves me\nchecked out on that bad commit, that would be a very welcome\nchange.  If it only returns me to where I started before the\nbisection, well, 'Meh'.\n"},{"id":"548460","messageId":"CAHwyqnUFfewFm7tr-Busv1rKP=4Rqnq+vJ7mEdgbaRLKbpbo=g@mail.gmail.com","threadId":"66011","inReplyTo":"xmqqse5ihmsz.fsf@gitster.g","subject":"Re: [PATCH 3/3] bisect: add --auto-reset to leave when done","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-07-16T21:22:24Z","receivedAt":"2026-07-16T21:23:04Z","isPatch":true,"body":"> > Add an \"--auto-reset\" option, accepted by both \"git bisect start\" and\n> > \"git bisect run\", that resets as soon as the first bad commit is found,\n> > returning to the commit checked out before \"git bisect start\". The flag\n> > is persisted in a BISECT_AUTO_RESET state file and the restoring\n> > checkout is done quietly.\n>\n> I often find myself, after the culprit is found, running 'git\n> reset --hard' or 'git bisect reset' to jump to the problematic\n> commit to investigate further.  If '--auto-reset' leaves me\n> checked out on that bad commit, that would be a very welcome\n> change.\n\nNo it's the opposite, returns to where we started before the bisection.\n\nI don't mind changing it assuming no one likes the original idea. I\nguess the name shouldn't be '--auto-reset' then.\n\n\nHarald\n"},{"id":"548468","messageId":"xmqqpl0m9pnq.fsf@gitster.g","threadId":"66011","inReplyTo":"CAHwyqnUFfewFm7tr-Busv1rKP=4Rqnq+vJ7mEdgbaRLKbpbo=g@mail.gmail.com","subject":"Re: [PATCH 3/3] bisect: add --auto-reset to leave when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-17T05:00:09Z","receivedAt":"2026-07-17T05:00:13Z","isPatch":true,"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n>> > Add an \"--auto-reset\" option, accepted by both \"git bisect start\" and\n>> > \"git bisect run\", that resets as soon as the first bad commit is found,\n>> > returning to the commit checked out before \"git bisect start\". The flag\n>> > is persisted in a BISECT_AUTO_RESET state file and the restoring\n>> > checkout is done quietly.\n>>\n>> I often find myself, after the culprit is found, running 'git\n>> reset --hard' or 'git bisect reset' to jump to the problematic\n>> commit to investigate further.  If '--auto-reset' leaves me\n>> checked out on that bad commit, that would be a very welcome\n>> change.\n>\n> No it's the opposite, returns to where we started before the bisection.\n>\n> I don't mind changing it assuming no one likes the original idea. I\n> guess the name shouldn't be '--auto-reset' then.\n\nSince \"git bisect reset <goto>\" is just as common as a plain \"git\nbisect reset\" (which implicitly uses the original branch as the\ntarget), I suspect that an option like \"--auto-reset=<where>\" with\nvalues like \"original\" or \"found\" might be appropriate.  And I would\nnot mind if omitting the value defaulted to \"original\".\n\nThe point I was trying to make is that where to reset depends\nmore on the situation the user is in, rather than on their\npersonal preference.  I would mind if you changed it to always\nreset to the culprit, just as much as I would mind if it always\nreset to the original.\n\nThanks.\n"},{"id":"548484","messageId":"CAHwyqnWOnDoMpMP7gwrQWRmaJqBrSDpdLN0Dp7U6vPU0GKSmEg@mail.gmail.com","threadId":"66011","inReplyTo":"xmqqpl0m9pnq.fsf@gitster.g","subject":"Re: [PATCH 3/3] bisect: add --auto-reset to leave when done","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-07-17T09:16:17Z","receivedAt":"2026-07-17T09:16:56Z","isPatch":true,"body":"That's a great idea!\n\n\nHarald\n"},{"id":"548548","messageId":"xmqqtspx4le8.fsf@gitster.g","threadId":"66011","inReplyTo":"CAHwyqnWOnDoMpMP7gwrQWRmaJqBrSDpdLN0Dp7U6vPU0GKSmEg@mail.gmail.com","subject":"Re: [PATCH 3/3] bisect: add --auto-reset to leave when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-17T16:43:27Z","receivedAt":"2026-07-17T16:43:29Z","isPatch":true,"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n> That's a great idea!\n\nMay not be, though.  Depending on what the bisect session finds,\nwhere I want to reset to may probably be different.  But that merely\nmeans that in such a case, I cannot use \"bisect start --autoreset\".\n\n"},{"id":"548553","messageId":"pull.2335.v2.git.git.1784312854.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.git.git.1784180159.gitgitgadget@gmail.com","subject":"[PATCH v2 0/3] bisect: add --auto-reset to leave when done","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-07-17T18:27:31Z","receivedAt":"2026-07-17T18:27:38Z","isPatch":true,"body":"Add a --auto-reset option to git bisect that resets the bisect session when\nculprit is found.\n\nChanges in v2:\n\n * Add option --auto-reset[=<where>] with option to go to final commit as\n   well as original.\n * Refactored tests.\n\nHarald Nordgren (3):\n  bisect: read run output from the open descriptor\n  bisect: let bisect_reset() optionally check out quietly\n  bisect: add --auto-reset to leave when done\n\n Documentation/git-bisect.adoc |  14 +++-\n bisect.c                      |   2 +\n builtin/bisect.c              | 132 +++++++++++++++++++++++++++++-----\n t/t6030-bisect-porcelain.sh   | 107 +++++++++++++++++++++++++++\n 4 files changed, 234 insertions(+), 21 deletions(-)\n\n\nbase-commit: 44de1520f08d1dfebc3ab2d9f644208eaa5ac925\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2335%2FHaraldNordgren%2Fbisect-auto-reset-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2335/HaraldNordgren/bisect-auto-reset-v2\nPull-Request: https://github.com/git/git/pull/2335\n\nRange-diff vs v1:\n\n 1:  3fd365835d = 1:  0de8b12f65 bisect: read run output from the open descriptor\n 2:  a7670baafc = 2:  8a2dcdf305 bisect: let bisect_reset() optionally check out quietly\n 3:  a9194b1d00 ! 3:  5b3704fbd4 bisect: add --auto-reset to leave when done\n     @@ Metadata\n       ## Commit message ##\n          bisect: add --auto-reset to leave when done\n      \n     -    When a bisection finished, \"git bisect\" reported the first bad commit\n     -    but left the session active until \"git bisect reset\" was run by hand.\n     +    When a bisection finishes, \"git bisect\" reports the first bad commit\n     +    but leaves the session active until \"git bisect reset\" is run by hand.\n      \n     -    Add an \"--auto-reset\" option, accepted by both \"git bisect start\" and\n     -    \"git bisect run\", that resets as soon as the first bad commit is found,\n     -    returning to the commit checked out before \"git bisect start\". The flag\n     -    is persisted in a BISECT_AUTO_RESET state file and the restoring\n     -    checkout is done quietly.\n     +    Add an \"--auto-reset[=<where>]\" option, accepted by both \"git bisect\n     +    start\" and \"git bisect run\", that resets as soon as the first bad commit\n     +    is found. The \"original\" value returns to the commit checked out before\n     +    \"git bisect start\", while \"found\" leaves the first bad commit checked\n     +    out; omitting the value defaults to \"original\".\n     +\n     +    Persist the selected target in a BISECT_AUTO_RESET state file and perform\n     +    the reset quietly. Reject this option together with \"--no-checkout\",\n     +    since that mode must not check out either target.\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n     @@ Documentation/git-bisect.adoc: SYNOPSIS\n       [synopsis]\n       git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\n      -\t\t [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\n     -+\t\t [--no-checkout] [--first-parent] [--auto-reset] [<bad> [<good>...]] [--] [<pathspec>...]\n     ++\t\t [--no-checkout] [--first-parent] [--auto-reset[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\n       git bisect (bad|new|<term-new>) [<rev>]\n       git bisect (good|old|<term-old>) [<rev>...]\n       git bisect terms [--term-(good|old) | --term-(bad|new)]\n     @@ Documentation/git-bisect.adoc: git bisect reset [<commit>]\n       git bisect replay <logfile>\n       git bisect log\n      -git bisect run <cmd> [<arg>...]\n     -+git bisect run [--auto-reset] <cmd> [<arg>...]\n     ++git bisect run [--auto-reset[=<where>]] <cmd> [<arg>...]\n       git bisect help\n       \n       DESCRIPTION\n     @@ Documentation/git-bisect.adoc: ignored.\n       This option is particularly useful in avoiding false positives when a merged\n       branch contained broken or non-buildable commits, but the merge itself was OK.\n       \n     -+`--auto-reset`::\n     -+\tOnce the first bad commit is found, clean up the bisection state and\n     -+\treturn to the commit that was checked out before `git bisect start`,\n     -+\tas if `git bisect reset` had been run. The first bad commit is still\n     -+\treported before resetting.\n     ++`--auto-reset[=<where>]`::\n     ++\tOnce the first bad commit is found, report it and clean up the\n     ++\tbisection state. `<where>` may be `original` to return to the commit\n     ++\tchecked out before `git bisect start`, or `found` to leave the first\n     ++\tbad commit checked out. If `<where>` is omitted, it defaults to\n     ++\t`original`.\n      ++\n     -+This option may be given to `git bisect start` or to `git bisect run`.\n     ++This option may be given to `git bisect start` or to `git bisect run`. It\n     ++cannot be used for a bisection started with `--no-checkout`.\n      +\n       EXAMPLES\n       --------\n     @@ builtin/bisect.c: static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n       #define BUILTIN_GIT_BISECT_START_USAGE \\\n       \tN_(\"git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\\n\" \\\n      -\t   \"                 [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\")\n     -+\t   \"                 [--no-checkout] [--first-parent] [--auto-reset] [<bad> [<good>...]] [--] [<pathspec>...]\")\n     ++\t   \"                 [--no-checkout] [--first-parent] [--auto-reset[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\")\n       #define BUILTIN_GIT_BISECT_BAD_USAGE \\\n       \tN_(\"git bisect (bad|new|<term-new>) [<rev>]\")\n       #define BUILTIN_GIT_BISECT_GOOD_USAGE \\\n     @@ builtin/bisect.c: static GIT_PATH_FUNC(git_path_bisect_run, \"BISECT_RUN\")\n       \t\"git bisect log\"\n       #define BUILTIN_GIT_BISECT_RUN_USAGE \\\n      -\tN_(\"git bisect run <cmd> [<arg>...]\")\n     -+\tN_(\"git bisect run [--auto-reset] <cmd> [<arg>...]\")\n     ++\tN_(\"git bisect run [--auto-reset[=<where>]] <cmd> [<arg>...]\")\n       #define BUILTIN_GIT_BISECT_HELP_USAGE \\\n       \t\"git bisect help\"\n       \n     +@@ builtin/bisect.c: static const char * const git_bisect_usage[] = {\n     + \tNULL\n     + };\n     + \n     ++enum auto_reset_mode {\n     ++\tAUTO_RESET_NONE,\n     ++\tAUTO_RESET_ORIGINAL,\n     ++\tAUTO_RESET_FOUND,\n     ++};\n     ++\n     + struct add_bisect_ref_data {\n     + \tstruct rev_info *revs;\n     + \tunsigned int object_flags;\n     +@@ builtin/bisect.c: static int bisect_reset(const char *commit, int quiet)\n     + \treturn bisect_clean_state();\n     + }\n     + \n     ++static int parse_auto_reset(const char *value, enum auto_reset_mode *mode)\n     ++{\n     ++\tif (!strcmp(value, \"original\"))\n     ++\t\t*mode = AUTO_RESET_ORIGINAL;\n     ++\telse if (!strcmp(value, \"found\"))\n     ++\t\t*mode = AUTO_RESET_FOUND;\n     ++\telse\n     ++\t\treturn error(_(\"invalid value for '--auto-reset': '%s'\"), value);\n     ++\n     ++\treturn 0;\n     ++}\n     ++\n     ++static const char *auto_reset_mode_name(enum auto_reset_mode mode)\n     ++{\n     ++\tswitch (mode) {\n     ++\tcase AUTO_RESET_ORIGINAL:\n     ++\t\treturn \"original\";\n     ++\tcase AUTO_RESET_FOUND:\n     ++\t\treturn \"found\";\n     ++\tcase AUTO_RESET_NONE:\n     ++\t\tBUG(\"no name for unset auto-reset mode\");\n     ++\t}\n     ++\tBUG(\"unknown auto-reset mode %d\", mode);\n     ++}\n     ++\n     ++static int bisect_auto_reset(struct bisect_terms *terms)\n     ++{\n     ++\tstruct strbuf value = STRBUF_INIT;\n     ++\tenum auto_reset_mode mode;\n     ++\tchar *commit = NULL;\n     ++\tint res;\n     ++\n     ++\tif (strbuf_read_file(&value, git_path_bisect_auto_reset(), 0) < 0) {\n     ++\t\tres = error_errno(_(\"could not read '%s'\"),\n     ++\t\t\t\t  git_path_bisect_auto_reset());\n     ++\t\tgoto cleanup;\n     ++\t}\n     ++\tstrbuf_trim(&value);\n     ++\tif (parse_auto_reset(value.buf, &mode)) {\n     ++\t\tres = -1;\n     ++\t\tgoto cleanup;\n     ++\t}\n     ++\n     ++\tif (mode == AUTO_RESET_FOUND)\n     ++\t\tcommit = xstrfmt(\"refs/bisect/%s\", terms->term_bad);\n     ++\tres = bisect_reset(commit, 1);\n     ++\n     ++cleanup:\n     ++\tfree(commit);\n     ++\tstrbuf_release(&value);\n     ++\treturn res;\n     ++}\n     ++\n     + static void log_commit(FILE *fp,\n     + \t\t       const char *fmt, const char *state,\n     + \t\t       struct commit *commit)\n      @@ builtin/bisect.c: static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n       \n       \tif (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n       \t\tres = bisect_successful(terms);\n      +\t\tif (!res && !is_empty_or_missing_file(git_path_bisect_auto_reset()))\n     -+\t\t\tres = bisect_reset(NULL, 1);\n     ++\t\t\tres = bisect_auto_reset(terms);\n       \t\treturn res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;\n       \t} else if (res == BISECT_ONLY_SKIPPED_LEFT) {\n       \t\tres = bisect_skipped_commits(terms);\n     @@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *ter\n       {\n       \tint no_checkout = 0;\n       \tint first_parent_only = 0;\n     -+\tint auto_reset = 0;\n     ++\tenum auto_reset_mode auto_reset = AUTO_RESET_NONE;\n       \tint i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;\n       \tint flags, pathspec_pos;\n       \tenum bisect_error res = BISECT_OK;\n     @@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *ter\n       \t\t} else if (!strcmp(arg, \"--first-parent\")) {\n       \t\t\tfirst_parent_only = 1;\n      +\t\t} else if (!strcmp(arg, \"--auto-reset\")) {\n     -+\t\t\tauto_reset = 1;\n     ++\t\t\tauto_reset = AUTO_RESET_ORIGINAL;\n     ++\t\t} else if (skip_prefix(arg, \"--auto-reset=\", &arg)) {\n     ++\t\t\tif (parse_auto_reset(arg, &auto_reset)) {\n     ++\t\t\t\tres = BISECT_FAILED;\n     ++\t\t\t\tgoto finish;\n     ++\t\t\t}\n       \t\t} else if (!strcmp(arg, \"--term-good\") ||\n       \t\t\t !strcmp(arg, \"--term-old\")) {\n       \t\t\ti++;\n     +@@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n     + \t\t\tbreak;\n     + \t\t}\n     + \t}\n     ++\tif (auto_reset != AUTO_RESET_NONE && no_checkout) {\n     ++\t\tres = error(_(\"'--auto-reset' cannot be used with '--no-checkout'\"));\n     ++\t\tgoto finish;\n     ++\t}\n     + \tpathspec_pos = i;\n     + \n     + \t/*\n      @@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n       \tif (first_parent_only)\n       \t\twrite_file(git_path_bisect_first_parent(), \"\\n\");\n       \n     -+\tif (auto_reset)\n     -+\t\twrite_file(git_path_bisect_auto_reset(), \"\\n\");\n     ++\tif (auto_reset != AUTO_RESET_NONE)\n     ++\t\twrite_file(git_path_bisect_auto_reset(), \"%s\\n\",\n     ++\t\t\t   auto_reset_mode_name(auto_reset));\n      +\n       \tif (no_checkout) {\n       \t\tif (repo_get_oid(the_repository, start_head.buf, &oid) < 0) {\n       \t\t\tres = error(_(\"invalid ref: '%s'\"), start_head.buf);\n     +@@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n     + {\n     + \tint res = BISECT_OK;\n     + \tstruct strbuf command = STRBUF_INIT;\n     ++\tenum auto_reset_mode auto_reset = AUTO_RESET_NONE;\n     ++\tconst char *auto_reset_arg;\n     + \tconst char *new_state;\n     + \tint temporary_stdout_fd, saved_stdout;\n     + \tint is_first_run = 1;\n      @@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n       \tif (bisect_next_check(terms, NULL))\n       \t\treturn BISECT_FAILED;\n       \n     -+\tif (argc && !strcmp(argv[0], \"--auto-reset\")) {\n     -+\t\twrite_file(git_path_bisect_auto_reset(), \"\\n\");\n     ++\tif (argc && !strcmp(argv[0], \"--auto-reset\"))\n     ++\t\tauto_reset = AUTO_RESET_ORIGINAL;\n     ++\telse if (argc && skip_prefix(argv[0], \"--auto-reset=\", &auto_reset_arg)) {\n     ++\t\tif (parse_auto_reset(auto_reset_arg, &auto_reset))\n     ++\t\t\treturn BISECT_FAILED;\n     ++\t}\n     ++\n     ++\tif (auto_reset != AUTO_RESET_NONE) {\n     ++\t\tif (refs_ref_exists(get_main_ref_store(the_repository), \"BISECT_HEAD\"))\n     ++\t\t\treturn error(_(\"'--auto-reset' cannot be used with '--no-checkout'\"));\n     ++\t\twrite_file(git_path_bisect_auto_reset(), \"%s\\n\",\n     ++\t\t\t   auto_reset_mode_name(auto_reset));\n      +\t\targc--;\n      +\t\targv++;\n      +\t}\n     @@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, co\n       \t\treturn BISECT_FAILED;\n      \n       ## t/t6030-bisect-porcelain.sh ##\n     +@@ t/t6030-bisect-porcelain.sh: test_bisect_usage () {\n     + \ttest_cmp expect actual\n     + }\n     + \n     ++test_bisect_state_file () {\n     ++\ttest_path_is_file \"$(git rev-parse --git-path \"$1\")\"\n     ++}\n     ++\n     ++test_bisect_state_missing () {\n     ++\ttest_path_is_missing \"$(git rev-parse --git-path \"$1\")\"\n     ++}\n     ++\n     ++bisect_start_and_finish () {\n     ++\tgit bisect start \"$1\" $HASH4 $HASH2 &&\n     ++\tgit bisect bad\n     ++}\n     ++\n     ++bisect_run_auto_reset () {\n     ++\twrite_script test_script.sh <<-\\EOF &&\n     ++\t! grep Another hello >/dev/null\n     ++\tEOF\n     ++\tgit bisect start $HASH4 $HASH2 &&\n     ++\tgit bisect run \"$1\" ./test_script.sh >my_bisect_log.txt &&\n     ++\ttest_grep \"$HASH3 is the first .bad. commit\" my_bisect_log.txt\n     ++}\n     ++\n     ++test_auto_reset_fails () {\n     ++\tlocal pattern=\"$1\" &&\n     ++\tlocal state_file=\"$2\" &&\n     ++\tshift 2 &&\n     ++\ttest_must_fail \"$@\" 2>err &&\n     ++\ttest_grep -- \"$pattern\" err &&\n     ++\ttest_bisect_state_missing \"$state_file\"\n     ++}\n     ++\n     + test_expect_success 'bisect usage' \"\n     + \ttest_bisect_usage 1 git bisect reset extra1 extra2 <<-\\EOF &&\n     + \terror: 'git bisect reset' requires either no argument or a commit\n      @@ t/t6030-bisect-porcelain.sh: test_expect_success '\"git bisect run\" simple case' '\n       \tgit bisect reset\n       '\n       \n     -+test_expect_success '\"git bisect start --auto-reset\" leaves the bisection' '\n     -+\ttest_when_finished \"git bisect reset\" &&\n     -+\tgit bisect start --auto-reset $HASH4 $HASH2 &&\n     -+\tgit bisect bad &&\n     -+\ttest_path_is_missing \"$(git rev-parse --git-path BISECT_START)\"\n     ++test_expect_success '\"git bisect start --auto-reset\" defaults to original' '\n     ++\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     ++\tgit checkout main &&\n     ++\tbisect_start_and_finish --auto-reset &&\n     ++\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n     ++\ttest main = \"$(git branch --show-current)\" &&\n     ++\ttest_bisect_state_missing BISECT_START &&\n     ++\n     ++\tbisect_start_and_finish --auto-reset=original &&\n     ++\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n     ++\ttest main = \"$(git branch --show-current)\" &&\n     ++\ttest_bisect_state_missing BISECT_START\n      +'\n      +\n     -+test_expect_success '\"git bisect run --auto-reset\" leaves the bisection' '\n     -+\ttest_when_finished \"git bisect reset\" &&\n     -+\twrite_script test_script.sh <<-\\EOF &&\n     -+\t! grep Another hello >/dev/null\n     -+\tEOF\n     ++test_expect_success '\"git bisect start --auto-reset=found\" leaves first bad checked out' '\n     ++\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     ++\tbisect_start_and_finish --auto-reset=found &&\n     ++\ttest \"$HASH3\" = \"$(git rev-parse HEAD)\" &&\n     ++\ttest_bisect_state_missing BISECT_START\n     ++'\n     ++\n     ++test_expect_success '\"git bisect run --auto-reset\" defaults to original' '\n     ++\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     ++\tbisect_run_auto_reset --auto-reset &&\n     ++\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n     ++\ttest main = \"$(git branch --show-current)\" &&\n     ++\ttest_bisect_state_missing BISECT_START\n     ++'\n     ++\n     ++test_expect_success '\"git bisect run --auto-reset=found\" leaves first bad checked out' '\n     ++\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     ++\tbisect_run_auto_reset --auto-reset=found &&\n     ++\ttest \"$HASH3\" = \"$(git rev-parse HEAD)\" &&\n     ++\ttest_bisect_state_missing BISECT_START\n     ++'\n     ++\n     ++test_expect_success '--auto-reset rejects an unknown reset target' '\n     ++\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     ++\ttest_auto_reset_fails \\\n     ++\t\t\"invalid value for.*--auto-reset.*unknown\" BISECT_START \\\n     ++\t\tgit bisect start --auto-reset=unknown $HASH4 $HASH2 &&\n     ++\n      +\tgit bisect start $HASH4 $HASH2 &&\n     -+\tgit bisect run --auto-reset ./test_script.sh >my_bisect_log.txt &&\n     -+\tgrep \"$HASH3 is the first .bad. commit\" my_bisect_log.txt &&\n     -+\ttest_path_is_missing \"$(git rev-parse --git-path BISECT_START)\"\n     ++\ttest_auto_reset_fails \\\n     ++\t\t\"invalid value for.*--auto-reset.*unknown\" BISECT_AUTO_RESET \\\n     ++\t\tgit bisect run --auto-reset=unknown true\n     ++'\n     ++\n     ++test_expect_success '--auto-reset cannot be used with --no-checkout' '\n     ++\ttest_when_finished \"git bisect reset\" &&\n     ++\ttest_auto_reset_fails \\\n     ++\t\t\"cannot be used with.*--no-checkout\" BISECT_START \\\n     ++\t\tgit bisect start --auto-reset=original --no-checkout $HASH4 $HASH2 &&\n     ++\n     ++\tgit bisect start --no-checkout $HASH4 $HASH2 &&\n     ++\ttest_auto_reset_fails \\\n     ++\t\t\"cannot be used with.*--no-checkout\" BISECT_AUTO_RESET \\\n     ++\t\tgit bisect run --auto-reset=found true\n      +'\n      +\n      +test_expect_success 'without --auto-reset the bisection state is kept' '\n      +\ttest_when_finished \"git bisect reset\" &&\n      +\tgit bisect start $HASH4 $HASH2 &&\n      +\tgit bisect bad &&\n     -+\ttest_path_is_file \"$(git rev-parse --git-path BISECT_START)\"\n     ++\ttest_bisect_state_file BISECT_START\n      +'\n      +\n      +test_expect_success '--auto-reset does not leak into a later bisection' '\n     -+\ttest_when_finished \"git bisect reset\" &&\n     -+\tgit bisect start --auto-reset $HASH4 $HASH2 &&\n     -+\tgit bisect bad &&\n     ++\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     ++\tbisect_start_and_finish --auto-reset &&\n     ++\n      +\tgit bisect start $HASH4 $HASH2 &&\n      +\tgit bisect bad &&\n     -+\ttest_path_is_file \"$(git rev-parse --git-path BISECT_START)\"\n     ++\ttest_bisect_state_file BISECT_START\n      +'\n      +\n       # We want to automatically find the commit that\n\n-- \ngitgitgadget\n"},{"id":"548552","messageId":"0de8b12f65530497320b6a4bca395dfd0556c959.1784312854.git.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.v2.git.git.1784312854.gitgitgadget@gmail.com","subject":"[PATCH v2 1/3] bisect: read run output from the open descriptor","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-07-17T18:27:32Z","receivedAt":"2026-07-17T18:27:39Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\n\"git bisect run\" redirects each step's output into BISECT_RUN, then\nprints it back by reopening the file by name. Read it from the already\nopen descriptor instead; this behaves the same and no longer needs the\nfile to be reachable by name.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/bisect.c | 20 ++++++++------------\n 1 file changed, 8 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 798e28f501..69ea14b1b6 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -178,17 +178,13 @@ static int append_to_file(const char *path, const char *format, ...)\n \treturn res;\n }\n \n-static int print_file_to_stdout(const char *path)\n+static int print_fd_to_stdout(int fd)\n {\n-\tint fd = open(path, O_RDONLY);\n-\tint ret = 0;\n-\n-\tif (fd < 0)\n-\t\treturn error_errno(_(\"cannot open file '%s' for reading\"), path);\n+\tif (lseek(fd, 0, SEEK_SET) < 0)\n+\t\treturn error_errno(_(\"failed to rewind BISECT_RUN output\"));\n \tif (copy_fd(fd, 1) < 0)\n-\t\tret = error_errno(_(\"failed to read '%s'\"), path);\n-\tclose(fd);\n-\treturn ret;\n+\t\treturn error_errno(_(\"failed to read BISECT_RUN output\"));\n+\treturn 0;\n }\n \n static int check_term_format(const char *term, const char *orig_term)\n@@ -1291,7 +1287,7 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n \t\telse\n \t\t\tnew_state = terms->term_bad;\n \n-\t\ttemporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_WRONLY | O_TRUNC, 0666);\n+\t\ttemporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_RDWR | O_TRUNC, 0666);\n \n \t\tif (temporary_stdout_fd < 0) {\n \t\t\tres = error_errno(_(\"cannot open file '%s' for writing\"), git_path_bisect_run());\n@@ -1307,9 +1303,9 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n \t\tfflush(stdout);\n \t\tdup2(saved_stdout, 1);\n \t\tclose(saved_stdout);\n-\t\tclose(temporary_stdout_fd);\n \n-\t\tprint_file_to_stdout(git_path_bisect_run());\n+\t\tprint_fd_to_stdout(temporary_stdout_fd);\n+\t\tclose(temporary_stdout_fd);\n \n \t\tif (res == BISECT_ONLY_SKIPPED_LEFT)\n \t\t\terror(_(\"bisect run cannot continue any more\"));\n-- \ngitgitgadget\n\n"},{"id":"548554","messageId":"8a2dcdf305437fc71b85d9dcb00600b68a39631d.1784312854.git.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.v2.git.git.1784312854.gitgitgadget@gmail.com","subject":"[PATCH v2 2/3] bisect: let bisect_reset() optionally check out quietly","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-07-17T18:27:33Z","receivedAt":"2026-07-17T18:27:40Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a \"quiet\" parameter to bisect_reset() that passes \"--quiet\" to the\ncheckout restoring the original HEAD, suppressing its progress and\nbranch-status output.\n\nNo caller sets the flag yet, so behavior is unchanged.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/bisect.c | 12 +++++++-----\n 1 file changed, 7 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 69ea14b1b6..27d30b549e 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -230,7 +230,7 @@ static int write_terms(const char *bad, const char *good)\n \treturn res;\n }\n \n-static int bisect_reset(const char *commit)\n+static int bisect_reset(const char *commit, int quiet)\n {\n \tstruct strbuf branch = STRBUF_INIT;\n \n@@ -251,8 +251,10 @@ static int bisect_reset(const char *commit)\n \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n \t\tcmd.git_cmd = 1;\n-\t\tstrvec_pushl(&cmd.args, \"checkout\", \"--ignore-other-worktrees\",\n-\t\t\t\tbranch.buf, \"--\", NULL);\n+\t\tstrvec_pushl(&cmd.args, \"checkout\", \"--ignore-other-worktrees\", NULL);\n+\t\tif (quiet)\n+\t\t\tstrvec_push(&cmd.args, \"--quiet\");\n+\t\tstrvec_pushl(&cmd.args, branch.buf, \"--\", NULL);\n \t\tif (run_command(&cmd)) {\n \t\t\terror(_(\"could not check out original\"\n \t\t\t\t\" HEAD '%s'. Try 'git bisect\"\n@@ -1085,7 +1087,7 @@ static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f\n \tif (is_empty_or_missing_file(filename))\n \t\treturn error(_(\"cannot read file '%s' for replaying\"), filename);\n \n-\tif (bisect_reset(NULL))\n+\tif (bisect_reset(NULL, 0))\n \t\treturn BISECT_FAILED;\n \n \tfp = fopen(filename, \"r\");\n@@ -1334,7 +1336,7 @@ static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNU\n \tif (argc > 1)\n \t\treturn error(_(\"'%s' requires either no argument or a commit\"),\n \t\t\t     \"git bisect reset\");\n-\treturn bisect_reset(argc ? argv[0] : NULL);\n+\treturn bisect_reset(argc ? argv[0] : NULL, 0);\n }\n \n static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,\n-- \ngitgitgadget\n\n"},{"id":"548555","messageId":"5b3704fbd4129e6bf742fe9b38998d5c952c6f21.1784312854.git.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.v2.git.git.1784312854.gitgitgadget@gmail.com","subject":"[PATCH v2 3/3] bisect: add --auto-reset to leave when done","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-07-17T18:27:34Z","receivedAt":"2026-07-17T18:27:42Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen a bisection finishes, \"git bisect\" reports the first bad commit\nbut leaves the session active until \"git bisect reset\" is run by hand.\n\nAdd an \"--auto-reset[=<where>]\" option, accepted by both \"git bisect\nstart\" and \"git bisect run\", that resets as soon as the first bad commit\nis found. The \"original\" value returns to the commit checked out before\n\"git bisect start\", while \"found\" leaves the first bad commit checked\nout; omitting the value defaults to \"original\".\n\nPersist the selected target in a BISECT_AUTO_RESET state file and perform\nthe reset quietly. Reject this option together with \"--no-checkout\",\nsince that mode must not check out either target.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-bisect.adoc |  14 ++++-\n bisect.c                      |   2 +\n builtin/bisect.c              | 100 ++++++++++++++++++++++++++++++-\n t/t6030-bisect-porcelain.sh   | 107 ++++++++++++++++++++++++++++++++++\n 4 files changed, 219 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-bisect.adoc b/Documentation/git-bisect.adoc\nindex d2115b2990..afae508463 100644\n--- a/Documentation/git-bisect.adoc\n+++ b/Documentation/git-bisect.adoc\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [synopsis]\n git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\n-\t\t [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\n+\t\t [--no-checkout] [--first-parent] [--auto-reset[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\n git bisect (bad|new|<term-new>) [<rev>]\n git bisect (good|old|<term-old>) [<rev>...]\n git bisect terms [--term-(good|old) | --term-(bad|new)]\n@@ -20,7 +20,7 @@ git bisect reset [<commit>]\n git bisect (visualize|view)\n git bisect replay <logfile>\n git bisect log\n-git bisect run <cmd> [<arg>...]\n+git bisect run [--auto-reset[=<where>]] <cmd> [<arg>...]\n git bisect help\n \n DESCRIPTION\n@@ -385,6 +385,16 @@ ignored.\n This option is particularly useful in avoiding false positives when a merged\n branch contained broken or non-buildable commits, but the merge itself was OK.\n \n+`--auto-reset[=<where>]`::\n+\tOnce the first bad commit is found, report it and clean up the\n+\tbisection state. `<where>` may be `original` to return to the commit\n+\tchecked out before `git bisect start`, or `found` to leave the first\n+\tbad commit checked out. If `<where>` is omitted, it defaults to\n+\t`original`.\n++\n+This option may be given to `git bisect start` or to `git bisect run`. It\n+cannot be used for a bisection started with `--no-checkout`.\n+\n EXAMPLES\n --------\n \ndiff --git a/bisect.c b/bisect.c\nindex 94c7028d2a..a34309dd35 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -488,6 +488,7 @@ static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n static GIT_PATH_FUNC(git_path_bisect_terms, \"BISECT_TERMS\")\n static GIT_PATH_FUNC(git_path_bisect_first_parent, \"BISECT_FIRST_PARENT\")\n+static GIT_PATH_FUNC(git_path_bisect_auto_reset, \"BISECT_AUTO_RESET\")\n \n static void read_bisect_paths(struct strvec *array)\n {\n@@ -1211,6 +1212,7 @@ int bisect_clean_state(void)\n \tunlink_or_warn(git_path_bisect_run());\n \tunlink_or_warn(git_path_bisect_terms());\n \tunlink_or_warn(git_path_bisect_first_parent());\n+\tunlink_or_warn(git_path_bisect_auto_reset());\n \t/*\n \t * Cleanup BISECT_START last to support the --no-checkout option\n \t * introduced in the commit 4796e823a.\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 27d30b549e..6e835b7d2a 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -24,11 +24,12 @@ static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n static GIT_PATH_FUNC(git_path_bisect_names, \"BISECT_NAMES\")\n static GIT_PATH_FUNC(git_path_bisect_first_parent, \"BISECT_FIRST_PARENT\")\n+static GIT_PATH_FUNC(git_path_bisect_auto_reset, \"BISECT_AUTO_RESET\")\n static GIT_PATH_FUNC(git_path_bisect_run, \"BISECT_RUN\")\n \n #define BUILTIN_GIT_BISECT_START_USAGE \\\n \tN_(\"git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\\n\" \\\n-\t   \"                 [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\")\n+\t   \"                 [--no-checkout] [--first-parent] [--auto-reset[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\")\n #define BUILTIN_GIT_BISECT_BAD_USAGE \\\n \tN_(\"git bisect (bad|new|<term-new>) [<rev>]\")\n #define BUILTIN_GIT_BISECT_GOOD_USAGE \\\n@@ -48,7 +49,7 @@ static GIT_PATH_FUNC(git_path_bisect_run, \"BISECT_RUN\")\n #define BUILTIN_GIT_BISECT_LOG_USAGE \\\n \t\"git bisect log\"\n #define BUILTIN_GIT_BISECT_RUN_USAGE \\\n-\tN_(\"git bisect run <cmd> [<arg>...]\")\n+\tN_(\"git bisect run [--auto-reset[=<where>]] <cmd> [<arg>...]\")\n #define BUILTIN_GIT_BISECT_HELP_USAGE \\\n \t\"git bisect help\"\n \n@@ -68,6 +69,12 @@ static const char * const git_bisect_usage[] = {\n \tNULL\n };\n \n+enum auto_reset_mode {\n+\tAUTO_RESET_NONE,\n+\tAUTO_RESET_ORIGINAL,\n+\tAUTO_RESET_FOUND,\n+};\n+\n struct add_bisect_ref_data {\n \tstruct rev_info *revs;\n \tunsigned int object_flags;\n@@ -268,6 +275,59 @@ static int bisect_reset(const char *commit, int quiet)\n \treturn bisect_clean_state();\n }\n \n+static int parse_auto_reset(const char *value, enum auto_reset_mode *mode)\n+{\n+\tif (!strcmp(value, \"original\"))\n+\t\t*mode = AUTO_RESET_ORIGINAL;\n+\telse if (!strcmp(value, \"found\"))\n+\t\t*mode = AUTO_RESET_FOUND;\n+\telse\n+\t\treturn error(_(\"invalid value for '--auto-reset': '%s'\"), value);\n+\n+\treturn 0;\n+}\n+\n+static const char *auto_reset_mode_name(enum auto_reset_mode mode)\n+{\n+\tswitch (mode) {\n+\tcase AUTO_RESET_ORIGINAL:\n+\t\treturn \"original\";\n+\tcase AUTO_RESET_FOUND:\n+\t\treturn \"found\";\n+\tcase AUTO_RESET_NONE:\n+\t\tBUG(\"no name for unset auto-reset mode\");\n+\t}\n+\tBUG(\"unknown auto-reset mode %d\", mode);\n+}\n+\n+static int bisect_auto_reset(struct bisect_terms *terms)\n+{\n+\tstruct strbuf value = STRBUF_INIT;\n+\tenum auto_reset_mode mode;\n+\tchar *commit = NULL;\n+\tint res;\n+\n+\tif (strbuf_read_file(&value, git_path_bisect_auto_reset(), 0) < 0) {\n+\t\tres = error_errno(_(\"could not read '%s'\"),\n+\t\t\t\t  git_path_bisect_auto_reset());\n+\t\tgoto cleanup;\n+\t}\n+\tstrbuf_trim(&value);\n+\tif (parse_auto_reset(value.buf, &mode)) {\n+\t\tres = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tif (mode == AUTO_RESET_FOUND)\n+\t\tcommit = xstrfmt(\"refs/bisect/%s\", terms->term_bad);\n+\tres = bisect_reset(commit, 1);\n+\n+cleanup:\n+\tfree(commit);\n+\tstrbuf_release(&value);\n+\treturn res;\n+}\n+\n static void log_commit(FILE *fp,\n \t\t       const char *fmt, const char *state,\n \t\t       struct commit *commit)\n@@ -688,6 +748,8 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n \n \tif (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n \t\tres = bisect_successful(terms);\n+\t\tif (!res && !is_empty_or_missing_file(git_path_bisect_auto_reset()))\n+\t\t\tres = bisect_auto_reset(terms);\n \t\treturn res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;\n \t} else if (res == BISECT_ONLY_SKIPPED_LEFT) {\n \t\tres = bisect_skipped_commits(terms);\n@@ -711,6 +773,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n {\n \tint no_checkout = 0;\n \tint first_parent_only = 0;\n+\tenum auto_reset_mode auto_reset = AUTO_RESET_NONE;\n \tint i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;\n \tint flags, pathspec_pos;\n \tenum bisect_error res = BISECT_OK;\n@@ -743,6 +806,13 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \t\t\tno_checkout = 1;\n \t\t} else if (!strcmp(arg, \"--first-parent\")) {\n \t\t\tfirst_parent_only = 1;\n+\t\t} else if (!strcmp(arg, \"--auto-reset\")) {\n+\t\t\tauto_reset = AUTO_RESET_ORIGINAL;\n+\t\t} else if (skip_prefix(arg, \"--auto-reset=\", &arg)) {\n+\t\t\tif (parse_auto_reset(arg, &auto_reset)) {\n+\t\t\t\tres = BISECT_FAILED;\n+\t\t\t\tgoto finish;\n+\t\t\t}\n \t\t} else if (!strcmp(arg, \"--term-good\") ||\n \t\t\t !strcmp(arg, \"--term-old\")) {\n \t\t\ti++;\n@@ -780,6 +850,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \t\t\tbreak;\n \t\t}\n \t}\n+\tif (auto_reset != AUTO_RESET_NONE && no_checkout) {\n+\t\tres = error(_(\"'--auto-reset' cannot be used with '--no-checkout'\"));\n+\t\tgoto finish;\n+\t}\n \tpathspec_pos = i;\n \n \t/*\n@@ -857,6 +931,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \tif (first_parent_only)\n \t\twrite_file(git_path_bisect_first_parent(), \"\\n\");\n \n+\tif (auto_reset != AUTO_RESET_NONE)\n+\t\twrite_file(git_path_bisect_auto_reset(), \"%s\\n\",\n+\t\t\t   auto_reset_mode_name(auto_reset));\n+\n \tif (no_checkout) {\n \t\tif (repo_get_oid(the_repository, start_head.buf, &oid) < 0) {\n \t\t\tres = error(_(\"invalid ref: '%s'\"), start_head.buf);\n@@ -1235,6 +1313,8 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n {\n \tint res = BISECT_OK;\n \tstruct strbuf command = STRBUF_INIT;\n+\tenum auto_reset_mode auto_reset = AUTO_RESET_NONE;\n+\tconst char *auto_reset_arg;\n \tconst char *new_state;\n \tint temporary_stdout_fd, saved_stdout;\n \tint is_first_run = 1;\n@@ -1242,6 +1322,22 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n \tif (bisect_next_check(terms, NULL))\n \t\treturn BISECT_FAILED;\n \n+\tif (argc && !strcmp(argv[0], \"--auto-reset\"))\n+\t\tauto_reset = AUTO_RESET_ORIGINAL;\n+\telse if (argc && skip_prefix(argv[0], \"--auto-reset=\", &auto_reset_arg)) {\n+\t\tif (parse_auto_reset(auto_reset_arg, &auto_reset))\n+\t\t\treturn BISECT_FAILED;\n+\t}\n+\n+\tif (auto_reset != AUTO_RESET_NONE) {\n+\t\tif (refs_ref_exists(get_main_ref_store(the_repository), \"BISECT_HEAD\"))\n+\t\t\treturn error(_(\"'--auto-reset' cannot be used with '--no-checkout'\"));\n+\t\twrite_file(git_path_bisect_auto_reset(), \"%s\\n\",\n+\t\t\t   auto_reset_mode_name(auto_reset));\n+\t\targc--;\n+\t\targv++;\n+\t}\n+\n \tif (!argc) {\n \t\terror(_(\"bisect run failed: no command provided.\"));\n \t\treturn BISECT_FAILED;\ndiff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh\nindex 081116220a..826e54efea 100755\n--- a/t/t6030-bisect-porcelain.sh\n+++ b/t/t6030-bisect-porcelain.sh\n@@ -43,6 +43,37 @@ test_bisect_usage () {\n \ttest_cmp expect actual\n }\n \n+test_bisect_state_file () {\n+\ttest_path_is_file \"$(git rev-parse --git-path \"$1\")\"\n+}\n+\n+test_bisect_state_missing () {\n+\ttest_path_is_missing \"$(git rev-parse --git-path \"$1\")\"\n+}\n+\n+bisect_start_and_finish () {\n+\tgit bisect start \"$1\" $HASH4 $HASH2 &&\n+\tgit bisect bad\n+}\n+\n+bisect_run_auto_reset () {\n+\twrite_script test_script.sh <<-\\EOF &&\n+\t! grep Another hello >/dev/null\n+\tEOF\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect run \"$1\" ./test_script.sh >my_bisect_log.txt &&\n+\ttest_grep \"$HASH3 is the first .bad. commit\" my_bisect_log.txt\n+}\n+\n+test_auto_reset_fails () {\n+\tlocal pattern=\"$1\" &&\n+\tlocal state_file=\"$2\" &&\n+\tshift 2 &&\n+\ttest_must_fail \"$@\" 2>err &&\n+\ttest_grep -- \"$pattern\" err &&\n+\ttest_bisect_state_missing \"$state_file\"\n+}\n+\n test_expect_success 'bisect usage' \"\n \ttest_bisect_usage 1 git bisect reset extra1 extra2 <<-\\EOF &&\n \terror: 'git bisect reset' requires either no argument or a commit\n@@ -453,6 +484,82 @@ test_expect_success '\"git bisect run\" simple case' '\n \tgit bisect reset\n '\n \n+test_expect_success '\"git bisect start --auto-reset\" defaults to original' '\n+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n+\tgit checkout main &&\n+\tbisect_start_and_finish --auto-reset &&\n+\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n+\ttest main = \"$(git branch --show-current)\" &&\n+\ttest_bisect_state_missing BISECT_START &&\n+\n+\tbisect_start_and_finish --auto-reset=original &&\n+\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n+\ttest main = \"$(git branch --show-current)\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '\"git bisect start --auto-reset=found\" leaves first bad checked out' '\n+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n+\tbisect_start_and_finish --auto-reset=found &&\n+\ttest \"$HASH3\" = \"$(git rev-parse HEAD)\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '\"git bisect run --auto-reset\" defaults to original' '\n+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n+\tbisect_run_auto_reset --auto-reset &&\n+\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n+\ttest main = \"$(git branch --show-current)\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '\"git bisect run --auto-reset=found\" leaves first bad checked out' '\n+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n+\tbisect_run_auto_reset --auto-reset=found &&\n+\ttest \"$HASH3\" = \"$(git rev-parse HEAD)\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '--auto-reset rejects an unknown reset target' '\n+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n+\ttest_auto_reset_fails \\\n+\t\t\"invalid value for.*--auto-reset.*unknown\" BISECT_START \\\n+\t\tgit bisect start --auto-reset=unknown $HASH4 $HASH2 &&\n+\n+\tgit bisect start $HASH4 $HASH2 &&\n+\ttest_auto_reset_fails \\\n+\t\t\"invalid value for.*--auto-reset.*unknown\" BISECT_AUTO_RESET \\\n+\t\tgit bisect run --auto-reset=unknown true\n+'\n+\n+test_expect_success '--auto-reset cannot be used with --no-checkout' '\n+\ttest_when_finished \"git bisect reset\" &&\n+\ttest_auto_reset_fails \\\n+\t\t\"cannot be used with.*--no-checkout\" BISECT_START \\\n+\t\tgit bisect start --auto-reset=original --no-checkout $HASH4 $HASH2 &&\n+\n+\tgit bisect start --no-checkout $HASH4 $HASH2 &&\n+\ttest_auto_reset_fails \\\n+\t\t\"cannot be used with.*--no-checkout\" BISECT_AUTO_RESET \\\n+\t\tgit bisect run --auto-reset=found true\n+'\n+\n+test_expect_success 'without --auto-reset the bisection state is kept' '\n+\ttest_when_finished \"git bisect reset\" &&\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect bad &&\n+\ttest_bisect_state_file BISECT_START\n+'\n+\n+test_expect_success '--auto-reset does not leak into a later bisection' '\n+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n+\tbisect_start_and_finish --auto-reset &&\n+\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect bad &&\n+\ttest_bisect_state_file BISECT_START\n+'\n+\n # We want to automatically find the commit that\n # added \"Ciao\" into hello.\n test_expect_success '\"git bisect run\" with more complex \"git bisect start\"' '\n-- \ngitgitgadget\n"},{"id":"548561","messageId":"xmqqpl0l1bm5.fsf@gitster.g","threadId":"66011","inReplyTo":"0de8b12f65530497320b6a4bca395dfd0556c959.1784312854.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/3] bisect: read run output from the open descriptor","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-17T22:42:58Z","receivedAt":"2026-07-17T22:43:01Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> \"git bisect run\" redirects each step's output into BISECT_RUN, then\n> prints it back by reopening the file by name. Read it from the already\n> open descriptor instead; this behaves the same and no longer needs the\n> file to be reachable by name.\n>\n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> ---\n>  builtin/bisect.c | 20 ++++++++------------\n>  1 file changed, 8 insertions(+), 12 deletions(-)\n>\n> diff --git a/builtin/bisect.c b/builtin/bisect.c\n> index 798e28f501..69ea14b1b6 100644\n> --- a/builtin/bisect.c\n> +++ b/builtin/bisect.c\n> @@ -178,17 +178,13 @@ static int append_to_file(const char *path, const char *format, ...)\n>  \treturn res;\n>  }\n>  \n> -static int print_file_to_stdout(const char *path)\n> +static int print_fd_to_stdout(int fd)\n>  {\n> -\tint fd = open(path, O_RDONLY);\n> -\tint ret = 0;\n> -\n> -\tif (fd < 0)\n> -\t\treturn error_errno(_(\"cannot open file '%s' for reading\"), path);\n> +\tif (lseek(fd, 0, SEEK_SET) < 0)\n> +\t\treturn error_errno(_(\"failed to rewind BISECT_RUN output\"));\n\nOK.  So, instead of the usual 'O_CREAT | O_WRONLY', you use 'O_RDWR'\ninstead, so that we can switch from writing to reading at this\npoint.  That makes sense.\n\nI wonder if there are cases where we somehow fail to seek, and\nyet are still able to open the path for reading and copy the\ndata successfully.  If such a case is common, this change\nwould be a regression, but I cannot offhand think of a\nscenario where that would occur.\n\nWill queue.\n\nThanks.\n\n"},{"id":"548587","messageId":"b79a479b-d279-4ac9-a368-6eb8edfed937@kdbg.org","threadId":"66011","inReplyTo":"5b3704fbd4129e6bf742fe9b38998d5c952c6f21.1784312854.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 3/3] bisect: add --auto-reset to leave when done","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-07-18T16:18:54Z","receivedAt":"2026-07-18T16:19:13Z","isPatch":true,"body":"Am 17.07.26 um 20:27 schrieb Harald Nordgren via GitGitGadget:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> When a bisection finishes, \"git bisect\" reports the first bad commit\n> but leaves the session active until \"git bisect reset\" is run by hand.\n> \n> Add an \"--auto-reset[=<where>]\" option, accepted by both \"git bisect\n> start\" and \"git bisect run\", that resets as soon as the first bad commit\n> is found. The \"original\" value returns to the commit checked out before\n> \"git bisect start\", while \"found\" leaves the first bad commit checked\n> out; omitting the value defaults to \"original\".\n\nIMHO, --auto-reset is too generic and doesn't give a clue what is\nautomatic about it. How about --reset-when-found?\n\n-- Hannes\n\n"},{"id":"548588","messageId":"1139ae20-f08b-4cf2-b779-42328831e13e@kdbg.org","threadId":"66011","inReplyTo":"0de8b12f65530497320b6a4bca395dfd0556c959.1784312854.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/3] bisect: read run output from the open descriptor","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-07-18T16:24:04Z","receivedAt":"2026-07-18T16:24:08Z","isPatch":true,"body":"Am 17.07.26 um 20:27 schrieb Harald Nordgren via GitGitGadget:\n> \"git bisect run\" redirects each step's output into BISECT_RUN, then\n> prints it back by reopening the file by name. Read it from the already\n> open descriptor instead; this behaves the same and no longer needs the\n> file to be reachable by name.\n\nThis rings alarm bells. Why would an open file not be reachable by name\nanymore? Are we already progressed sufficiently in our Windows port that\nit is possible to remove open files? Because it is (was?) the case that\non Windows, open files cannot be remove, so this shouldn't happen\nanywhere in the code in general.\n\n-- Hannes\n\n"},{"id":"548643","messageId":"xmqqik6ajwcf.fsf@gitster.g","threadId":"66011","inReplyTo":"b79a479b-d279-4ac9-a368-6eb8edfed937@kdbg.org","subject":"Re: [PATCH v2 3/3] bisect: add --auto-reset to leave when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-20T01:14:40Z","receivedAt":"2026-07-20T01:14:43Z","isPatch":true,"body":"Johannes Sixt <j6t@kdbg.org> writes:\n\n> IMHO, --auto-reset is too generic and doesn't give a clue what is\n> automatic about it. How about --reset-when-found?\n\nI like it already.\n\nOr you can generalize to have --exec-at-end=\"git bisect reset\"\nperhaps (ducks and runs)?\n\n;-).\n"},{"id":"548659","messageId":"pull.2335.v3.git.git.1784538619.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.v2.git.git.1784312854.gitgitgadget@gmail.com","subject":"[PATCH v3 0/2] bisect: add --auto-reset to leave when done","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-07-20T09:10:17Z","receivedAt":"2026-07-20T09:10:22Z","isPatch":true,"body":"Add a --reset-when-found option to git bisect that resets the bisect session\nwhen culprit is found.\n\nChanges in v3:\n\n * Rename --auto-reset to --reset-when-found, including internal names.\n * Defer git bisect run cleanup until captured output is printed and\n   BISECT_RUN is closed. Drop the open-descriptor preparatory change,\n   retaining the existing filename-based output handling.\n\nChanges in v2:\n\n * Add option --auto-reset[=<where>] with option to go to final commit as\n   well as original.\n * Refactored tests.\n\nHarald Nordgren (2):\n  bisect: let bisect_reset() optionally check out quietly\n  bisect: add --reset-when-found to leave when done\n\n Documentation/git-bisect.adoc |  14 +++-\n bisect.c                      |   2 +\n builtin/bisect.c              | 146 +++++++++++++++++++++++++++++-----\n t/t6030-bisect-porcelain.sh   | 109 +++++++++++++++++++++++++\n 4 files changed, 250 insertions(+), 21 deletions(-)\n\n\nbase-commit: 41365c2a9ba347870b80881c0d67454edd22fd49\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2335%2FHaraldNordgren%2Fbisect-auto-reset-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2335/HaraldNordgren/bisect-auto-reset-v3\nPull-Request: https://github.com/git/git/pull/2335\n\nRange-diff vs v2:\n\n 1:  0de8b12f65 < -:  ---------- bisect: read run output from the open descriptor\n 2:  8a2dcdf305 = 1:  59920c51ae bisect: let bisect_reset() optionally check out quietly\n 3:  5b3704fbd4 ! 2:  542f4b2c80 bisect: add --auto-reset to leave when done\n     @@ Metadata\n      Author: Harald Nordgren <haraldnordgren@gmail.com>\n      \n       ## Commit message ##\n     -    bisect: add --auto-reset to leave when done\n     +    bisect: add --reset-when-found to leave when done\n      \n          When a bisection finishes, \"git bisect\" reports the first bad commit\n          but leaves the session active until \"git bisect reset\" is run by hand.\n      \n     -    Add an \"--auto-reset[=<where>]\" option, accepted by both \"git bisect\n     -    start\" and \"git bisect run\", that resets as soon as the first bad commit\n     -    is found. The \"original\" value returns to the commit checked out before\n     -    \"git bisect start\", while \"found\" leaves the first bad commit checked\n     -    out; omitting the value defaults to \"original\".\n     +    Add a \"--reset-when-found[=<where>]\" option, accepted by both \"git\n     +    bisect start\" and \"git bisect run\", that resets as soon as the first\n     +    bad commit is found. The \"original\" value returns to the commit checked\n     +    out before \"git bisect start\", while \"found\" leaves the first bad commit\n     +    checked out; omitting the value defaults to \"original\".\n      \n     -    Persist the selected target in a BISECT_AUTO_RESET state file and perform\n     -    the reset quietly. Reject this option together with \"--no-checkout\",\n     -    since that mode must not check out either target.\n     +    Persist the selected target in a BISECT_RESET_WHEN_FOUND state file\n     +    and perform the reset quietly.\n     +\n     +    For \"git bisect run\", defer the reset until after the captured output\n     +    is printed and BISECT_RUN is closed. This lets cleanup remove the file\n     +    on systems that cannot unlink an open file.\n     +\n     +    Reject this option together with \"--no-checkout\", since that mode must\n     +    not check out either target.\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n     @@ Documentation/git-bisect.adoc: SYNOPSIS\n       [synopsis]\n       git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\n      -\t\t [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\n     -+\t\t [--no-checkout] [--first-parent] [--auto-reset[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\n     ++\t\t [--no-checkout] [--first-parent] [--reset-when-found[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\n       git bisect (bad|new|<term-new>) [<rev>]\n       git bisect (good|old|<term-old>) [<rev>...]\n       git bisect terms [--term-(good|old) | --term-(bad|new)]\n     @@ Documentation/git-bisect.adoc: git bisect reset [<commit>]\n       git bisect replay <logfile>\n       git bisect log\n      -git bisect run <cmd> [<arg>...]\n     -+git bisect run [--auto-reset[=<where>]] <cmd> [<arg>...]\n     ++git bisect run [--reset-when-found[=<where>]] <cmd> [<arg>...]\n       git bisect help\n       \n       DESCRIPTION\n     @@ Documentation/git-bisect.adoc: ignored.\n       This option is particularly useful in avoiding false positives when a merged\n       branch contained broken or non-buildable commits, but the merge itself was OK.\n       \n     -+`--auto-reset[=<where>]`::\n     ++`--reset-when-found[=<where>]`::\n      +\tOnce the first bad commit is found, report it and clean up the\n      +\tbisection state. `<where>` may be `original` to return to the commit\n      +\tchecked out before `git bisect start`, or `found` to leave the first\n     @@ bisect.c: static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n       static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n       static GIT_PATH_FUNC(git_path_bisect_terms, \"BISECT_TERMS\")\n       static GIT_PATH_FUNC(git_path_bisect_first_parent, \"BISECT_FIRST_PARENT\")\n     -+static GIT_PATH_FUNC(git_path_bisect_auto_reset, \"BISECT_AUTO_RESET\")\n     ++static GIT_PATH_FUNC(git_path_bisect_reset_when_found, \"BISECT_RESET_WHEN_FOUND\")\n       \n       static void read_bisect_paths(struct strvec *array)\n       {\n     @@ bisect.c: int bisect_clean_state(void)\n       \tunlink_or_warn(git_path_bisect_run());\n       \tunlink_or_warn(git_path_bisect_terms());\n       \tunlink_or_warn(git_path_bisect_first_parent());\n     -+\tunlink_or_warn(git_path_bisect_auto_reset());\n     ++\tunlink_or_warn(git_path_bisect_reset_when_found());\n       \t/*\n       \t * Cleanup BISECT_START last to support the --no-checkout option\n       \t * introduced in the commit 4796e823a.\n     @@ builtin/bisect.c: static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n       static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n       static GIT_PATH_FUNC(git_path_bisect_names, \"BISECT_NAMES\")\n       static GIT_PATH_FUNC(git_path_bisect_first_parent, \"BISECT_FIRST_PARENT\")\n     -+static GIT_PATH_FUNC(git_path_bisect_auto_reset, \"BISECT_AUTO_RESET\")\n     ++static GIT_PATH_FUNC(git_path_bisect_reset_when_found, \"BISECT_RESET_WHEN_FOUND\")\n       static GIT_PATH_FUNC(git_path_bisect_run, \"BISECT_RUN\")\n       \n       #define BUILTIN_GIT_BISECT_START_USAGE \\\n       \tN_(\"git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\\n\" \\\n      -\t   \"                 [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\")\n     -+\t   \"                 [--no-checkout] [--first-parent] [--auto-reset[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\")\n     ++\t   \"                 [--no-checkout] [--first-parent] [--reset-when-found[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\")\n       #define BUILTIN_GIT_BISECT_BAD_USAGE \\\n       \tN_(\"git bisect (bad|new|<term-new>) [<rev>]\")\n       #define BUILTIN_GIT_BISECT_GOOD_USAGE \\\n     @@ builtin/bisect.c: static GIT_PATH_FUNC(git_path_bisect_run, \"BISECT_RUN\")\n       \t\"git bisect log\"\n       #define BUILTIN_GIT_BISECT_RUN_USAGE \\\n      -\tN_(\"git bisect run <cmd> [<arg>...]\")\n     -+\tN_(\"git bisect run [--auto-reset[=<where>]] <cmd> [<arg>...]\")\n     ++\tN_(\"git bisect run [--reset-when-found[=<where>]] <cmd> [<arg>...]\")\n       #define BUILTIN_GIT_BISECT_HELP_USAGE \\\n       \t\"git bisect help\"\n       \n     @@ builtin/bisect.c: static const char * const git_bisect_usage[] = {\n       \tNULL\n       };\n       \n     -+enum auto_reset_mode {\n     -+\tAUTO_RESET_NONE,\n     -+\tAUTO_RESET_ORIGINAL,\n     -+\tAUTO_RESET_FOUND,\n     ++enum reset_when_found_mode {\n     ++\tRESET_WHEN_FOUND_NONE,\n     ++\tRESET_WHEN_FOUND_TO_ORIGINAL,\n     ++\tRESET_WHEN_FOUND_TO_FOUND,\n      +};\n      +\n       struct add_bisect_ref_data {\n     @@ builtin/bisect.c: static int bisect_reset(const char *commit, int quiet)\n       \treturn bisect_clean_state();\n       }\n       \n     -+static int parse_auto_reset(const char *value, enum auto_reset_mode *mode)\n     ++static int parse_reset_when_found(const char *value,\n     ++\t\t\t\t  enum reset_when_found_mode *mode)\n      +{\n      +\tif (!strcmp(value, \"original\"))\n     -+\t\t*mode = AUTO_RESET_ORIGINAL;\n     ++\t\t*mode = RESET_WHEN_FOUND_TO_ORIGINAL;\n      +\telse if (!strcmp(value, \"found\"))\n     -+\t\t*mode = AUTO_RESET_FOUND;\n     ++\t\t*mode = RESET_WHEN_FOUND_TO_FOUND;\n      +\telse\n     -+\t\treturn error(_(\"invalid value for '--auto-reset': '%s'\"), value);\n     ++\t\treturn error(_(\"invalid value for '--reset-when-found': '%s'\"),\n     ++\t\t\t     value);\n      +\n      +\treturn 0;\n      +}\n      +\n     -+static const char *auto_reset_mode_name(enum auto_reset_mode mode)\n     ++static const char *reset_when_found_mode_name(enum reset_when_found_mode mode)\n      +{\n      +\tswitch (mode) {\n     -+\tcase AUTO_RESET_ORIGINAL:\n     ++\tcase RESET_WHEN_FOUND_TO_ORIGINAL:\n      +\t\treturn \"original\";\n     -+\tcase AUTO_RESET_FOUND:\n     ++\tcase RESET_WHEN_FOUND_TO_FOUND:\n      +\t\treturn \"found\";\n     -+\tcase AUTO_RESET_NONE:\n     -+\t\tBUG(\"no name for unset auto-reset mode\");\n     ++\tcase RESET_WHEN_FOUND_NONE:\n     ++\t\tBUG(\"no name for unset reset-when-found mode\");\n      +\t}\n     -+\tBUG(\"unknown auto-reset mode %d\", mode);\n     ++\tBUG(\"unknown reset-when-found mode %d\", mode);\n      +}\n      +\n     -+static int bisect_auto_reset(struct bisect_terms *terms)\n     ++static int bisect_reset_when_found(struct bisect_terms *terms)\n      +{\n      +\tstruct strbuf value = STRBUF_INIT;\n     -+\tenum auto_reset_mode mode;\n     ++\tenum reset_when_found_mode mode;\n      +\tchar *commit = NULL;\n      +\tint res;\n      +\n     -+\tif (strbuf_read_file(&value, git_path_bisect_auto_reset(), 0) < 0) {\n     ++\tif (strbuf_read_file(&value, git_path_bisect_reset_when_found(), 0) < 0) {\n      +\t\tres = error_errno(_(\"could not read '%s'\"),\n     -+\t\t\t\t  git_path_bisect_auto_reset());\n     ++\t\t\t\t  git_path_bisect_reset_when_found());\n      +\t\tgoto cleanup;\n      +\t}\n      +\tstrbuf_trim(&value);\n     -+\tif (parse_auto_reset(value.buf, &mode)) {\n     ++\tif (parse_reset_when_found(value.buf, &mode)) {\n      +\t\tres = -1;\n      +\t\tgoto cleanup;\n      +\t}\n      +\n     -+\tif (mode == AUTO_RESET_FOUND)\n     ++\tif (mode == RESET_WHEN_FOUND_TO_FOUND)\n      +\t\tcommit = xstrfmt(\"refs/bisect/%s\", terms->term_bad);\n      +\tres = bisect_reset(commit, 1);\n      +\n     @@ builtin/bisect.c: static int bisect_reset(const char *commit, int quiet)\n       static void log_commit(FILE *fp,\n       \t\t       const char *fmt, const char *state,\n       \t\t       struct commit *commit)\n     +@@ builtin/bisect.c: static int bisect_successful(struct bisect_terms *terms)\n     + \treturn res;\n     + }\n     + \n     +-static enum bisect_error bisect_next(struct bisect_terms *terms, const char *prefix)\n     ++static enum bisect_error bisect_next(struct bisect_terms *terms,\n     ++\t\t\t\t     const char *prefix, bool defer_reset)\n     + {\n     + \tenum bisect_error res;\n     + \n      @@ builtin/bisect.c: static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n       \n       \tif (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n       \t\tres = bisect_successful(terms);\n     -+\t\tif (!res && !is_empty_or_missing_file(git_path_bisect_auto_reset()))\n     -+\t\t\tres = bisect_auto_reset(terms);\n     ++\t\tif (!res && !defer_reset &&\n     ++\t\t    !is_empty_or_missing_file(git_path_bisect_reset_when_found()))\n     ++\t\t\tres = bisect_reset_when_found(terms);\n       \t\treturn res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;\n       \t} else if (res == BISECT_ONLY_SKIPPED_LEFT) {\n       \t\tres = bisect_skipped_commits(terms);\n     +@@ builtin/bisect.c: static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n     + \treturn res;\n     + }\n     + \n     +-static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix)\n     ++static enum bisect_error bisect_auto_next(struct bisect_terms *terms,\n     ++\t\t\t\t\t  const char *prefix, bool defer_reset)\n     + {\n     + \tif (bisect_next_check(terms, NULL)) {\n     + \t\tbisect_print_status(terms);\n     + \t\treturn BISECT_OK;\n     + \t}\n     + \n     +-\treturn bisect_next(terms, prefix);\n     ++\treturn bisect_next(terms, prefix, defer_reset);\n     + }\n     + \n     + static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n      @@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n       {\n       \tint no_checkout = 0;\n       \tint first_parent_only = 0;\n     -+\tenum auto_reset_mode auto_reset = AUTO_RESET_NONE;\n     ++\tenum reset_when_found_mode reset_when_found = RESET_WHEN_FOUND_NONE;\n       \tint i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;\n       \tint flags, pathspec_pos;\n       \tenum bisect_error res = BISECT_OK;\n     @@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *ter\n       \t\t\tno_checkout = 1;\n       \t\t} else if (!strcmp(arg, \"--first-parent\")) {\n       \t\t\tfirst_parent_only = 1;\n     -+\t\t} else if (!strcmp(arg, \"--auto-reset\")) {\n     -+\t\t\tauto_reset = AUTO_RESET_ORIGINAL;\n     -+\t\t} else if (skip_prefix(arg, \"--auto-reset=\", &arg)) {\n     -+\t\t\tif (parse_auto_reset(arg, &auto_reset)) {\n     ++\t\t} else if (!strcmp(arg, \"--reset-when-found\")) {\n     ++\t\t\treset_when_found = RESET_WHEN_FOUND_TO_ORIGINAL;\n     ++\t\t} else if (skip_prefix(arg, \"--reset-when-found=\", &arg)) {\n     ++\t\t\tif (parse_reset_when_found(arg, &reset_when_found)) {\n      +\t\t\t\tres = BISECT_FAILED;\n      +\t\t\t\tgoto finish;\n      +\t\t\t}\n     @@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *ter\n       \t\t\tbreak;\n       \t\t}\n       \t}\n     -+\tif (auto_reset != AUTO_RESET_NONE && no_checkout) {\n     -+\t\tres = error(_(\"'--auto-reset' cannot be used with '--no-checkout'\"));\n     ++\tif (reset_when_found != RESET_WHEN_FOUND_NONE && no_checkout) {\n     ++\t\tres = error(_(\"'--reset-when-found' cannot be used with '--no-checkout'\"));\n      +\t\tgoto finish;\n      +\t}\n       \tpathspec_pos = i;\n     @@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *ter\n       \tif (first_parent_only)\n       \t\twrite_file(git_path_bisect_first_parent(), \"\\n\");\n       \n     -+\tif (auto_reset != AUTO_RESET_NONE)\n     -+\t\twrite_file(git_path_bisect_auto_reset(), \"%s\\n\",\n     -+\t\t\t   auto_reset_mode_name(auto_reset));\n     ++\tif (reset_when_found != RESET_WHEN_FOUND_NONE)\n     ++\t\twrite_file(git_path_bisect_reset_when_found(), \"%s\\n\",\n     ++\t\t\t   reset_when_found_mode_name(reset_when_found));\n      +\n       \tif (no_checkout) {\n       \t\tif (repo_get_oid(the_repository, start_head.buf, &oid) < 0) {\n       \t\t\tres = error(_(\"invalid ref: '%s'\"), start_head.buf);\n     +@@ builtin/bisect.c: finish:\n     + \tif (res)\n     + \t\treturn res;\n     + \n     +-\tres = bisect_auto_next(terms, NULL);\n     ++\tres = bisect_auto_next(terms, NULL, false);\n     + \tif (!is_bisect_success(res))\n     + \t\tbisect_clean_state();\n     + \treturn res;\n     +@@ builtin/bisect.c: static int bisect_autostart(struct bisect_terms *terms)\n     + }\n     + \n     + static enum bisect_error bisect_state(struct bisect_terms *terms, int argc,\n     +-\t\t\t\t      const char **argv)\n     ++\t\t\t\t      const char **argv, bool defer_reset)\n     + {\n     + \tconst char *state;\n     + \tint i, verify_expected = 1;\n     +@@ builtin/bisect.c: static enum bisect_error bisect_state(struct bisect_terms *terms, int argc,\n     + \t}\n     + \n     + \toid_array_clear(&revs);\n     +-\treturn bisect_auto_next(terms, NULL);\n     ++\treturn bisect_auto_next(terms, NULL, defer_reset);\n     + }\n     + \n     + static enum bisect_error bisect_log(void)\n     +@@ builtin/bisect.c: static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f\n     + \tif (res)\n     + \t\treturn BISECT_FAILED;\n     + \n     +-\treturn bisect_auto_next(terms, NULL);\n     ++\treturn bisect_auto_next(terms, NULL, false);\n     + }\n     + \n     + static enum bisect_error bisect_skip(struct bisect_terms *terms, int argc,\n     +@@ builtin/bisect.c: static enum bisect_error bisect_skip(struct bisect_terms *terms, int argc,\n     + \t\t\tstrvec_push(&argv_state, argv[i]);\n     + \t\t}\n     + \t}\n     +-\tres = bisect_state(terms, argv_state.nr, argv_state.v);\n     ++\tres = bisect_state(terms, argv_state.nr, argv_state.v, false);\n     + \n     + \tstrvec_clear(&argv_state);\n     + \treturn res;\n      @@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n       {\n       \tint res = BISECT_OK;\n       \tstruct strbuf command = STRBUF_INIT;\n     -+\tenum auto_reset_mode auto_reset = AUTO_RESET_NONE;\n     -+\tconst char *auto_reset_arg;\n     ++\tenum reset_when_found_mode reset_when_found = RESET_WHEN_FOUND_NONE;\n     ++\tconst char *reset_when_found_arg;\n       \tconst char *new_state;\n       \tint temporary_stdout_fd, saved_stdout;\n       \tint is_first_run = 1;\n     @@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, co\n       \tif (bisect_next_check(terms, NULL))\n       \t\treturn BISECT_FAILED;\n       \n     -+\tif (argc && !strcmp(argv[0], \"--auto-reset\"))\n     -+\t\tauto_reset = AUTO_RESET_ORIGINAL;\n     -+\telse if (argc && skip_prefix(argv[0], \"--auto-reset=\", &auto_reset_arg)) {\n     -+\t\tif (parse_auto_reset(auto_reset_arg, &auto_reset))\n     ++\tif (argc && !strcmp(argv[0], \"--reset-when-found\"))\n     ++\t\treset_when_found = RESET_WHEN_FOUND_TO_ORIGINAL;\n     ++\telse if (argc && skip_prefix(argv[0], \"--reset-when-found=\",\n     ++\t\t\t\t    &reset_when_found_arg)) {\n     ++\t\tif (parse_reset_when_found(reset_when_found_arg, &reset_when_found))\n      +\t\t\treturn BISECT_FAILED;\n      +\t}\n      +\n     -+\tif (auto_reset != AUTO_RESET_NONE) {\n     ++\tif (reset_when_found != RESET_WHEN_FOUND_NONE) {\n      +\t\tif (refs_ref_exists(get_main_ref_store(the_repository), \"BISECT_HEAD\"))\n     -+\t\t\treturn error(_(\"'--auto-reset' cannot be used with '--no-checkout'\"));\n     -+\t\twrite_file(git_path_bisect_auto_reset(), \"%s\\n\",\n     -+\t\t\t   auto_reset_mode_name(auto_reset));\n     ++\t\t\treturn error(_(\"'--reset-when-found' cannot be used with '--no-checkout'\"));\n     ++\t\twrite_file(git_path_bisect_reset_when_found(), \"%s\\n\",\n     ++\t\t\t   reset_when_found_mode_name(reset_when_found));\n      +\t\targc--;\n      +\t\targv++;\n      +\t}\n     @@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, co\n       \tif (!argc) {\n       \t\terror(_(\"bisect run failed: no command provided.\"));\n       \t\treturn BISECT_FAILED;\n     +@@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n     + \t\tsaved_stdout = dup(1);\n     + \t\tdup2(temporary_stdout_fd, 1);\n     + \n     +-\t\tres = bisect_state(terms, 1, &new_state);\n     ++\t\tres = bisect_state(terms, 1, &new_state, true);\n     + \n     + \t\tfflush(stdout);\n     + \t\tdup2(saved_stdout, 1);\n     +@@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n     + \t\t\tres = BISECT_OK;\n     + \t\t} else if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n     + \t\t\tprintf(_(\"bisect found first '%s' commit\\n\"), terms->term_bad);\n     +-\t\t\tres = BISECT_OK;\n     ++\t\t\tif (!is_empty_or_missing_file(git_path_bisect_reset_when_found()) &&\n     ++\t\t\t    bisect_reset_when_found(terms))\n     ++\t\t\t\tres = BISECT_FAILED;\n     ++\t\t\telse\n     ++\t\t\t\tres = BISECT_OK;\n     + \t\t} else if (res) {\n     + \t\t\terror(_(\"bisect run failed: 'git bisect %s'\"\n     + \t\t\t\t\" exited with error code %d\"), new_state, res);\n     +@@ builtin/bisect.c: static int cmd_bisect__next(int argc, const char **argv UNUSED, const char *pref\n     + \t\treturn error(_(\"'%s' requires 0 arguments\"),\n     + \t\t\t     \"git bisect next\");\n     + \tget_terms(&terms);\n     +-\tres = bisect_next(&terms, prefix);\n     ++\tres = bisect_next(&terms, prefix, false);\n     + \tfree_terms(&terms);\n     + \treturn res;\n     + }\n     +@@ builtin/bisect.c: int cmd_bisect(int argc,\n     + \t\t    !one_of(argv[0], terms.term_good, terms.term_bad, NULL))\n     + \t\t\tusage_msg_optf(_(\"unknown command: '%s'\"), git_bisect_usage,\n     + \t\t\t\t       options, argv[0]);\n     +-\t\tres = bisect_state(&terms, argc, argv);\n     ++\t\tres = bisect_state(&terms, argc, argv, false);\n     + \t\tfree_terms(&terms);\n     + \t} else {\n     + \t\targc--;\n      \n       ## t/t6030-bisect-porcelain.sh ##\n      @@ t/t6030-bisect-porcelain.sh: test_bisect_usage () {\n     @@ t/t6030-bisect-porcelain.sh: test_bisect_usage () {\n      +\tgit bisect bad\n      +}\n      +\n     -+bisect_run_auto_reset () {\n     ++bisect_run_reset_when_found () {\n      +\twrite_script test_script.sh <<-\\EOF &&\n      +\t! grep Another hello >/dev/null\n      +\tEOF\n      +\tgit bisect start $HASH4 $HASH2 &&\n      +\tgit bisect run \"$1\" ./test_script.sh >my_bisect_log.txt &&\n     -+\ttest_grep \"$HASH3 is the first .bad. commit\" my_bisect_log.txt\n     ++\ttest_grep \"$HASH3 is the first .bad. commit\" my_bisect_log.txt &&\n     ++\ttest_bisect_state_missing BISECT_RUN\n      +}\n      +\n     -+test_auto_reset_fails () {\n     ++test_reset_when_found_fails () {\n      +\tlocal pattern=\"$1\" &&\n      +\tlocal state_file=\"$2\" &&\n      +\tshift 2 &&\n     @@ t/t6030-bisect-porcelain.sh: test_expect_success '\"git bisect run\" simple case'\n       \tgit bisect reset\n       '\n       \n     -+test_expect_success '\"git bisect start --auto-reset\" defaults to original' '\n     ++test_expect_success '\"git bisect start --reset-when-found\" defaults to original' '\n      +\ttest_when_finished \"git bisect reset; git checkout main\" &&\n      +\tgit checkout main &&\n     -+\tbisect_start_and_finish --auto-reset &&\n     ++\tbisect_start_and_finish --reset-when-found &&\n      +\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n      +\ttest main = \"$(git branch --show-current)\" &&\n      +\ttest_bisect_state_missing BISECT_START &&\n      +\n     -+\tbisect_start_and_finish --auto-reset=original &&\n     ++\tbisect_start_and_finish --reset-when-found=original &&\n      +\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n      +\ttest main = \"$(git branch --show-current)\" &&\n      +\ttest_bisect_state_missing BISECT_START\n      +'\n      +\n     -+test_expect_success '\"git bisect start --auto-reset=found\" leaves first bad checked out' '\n     ++test_expect_success '\"git bisect start --reset-when-found=found\" leaves first bad checked out' '\n      +\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     -+\tbisect_start_and_finish --auto-reset=found &&\n     ++\tbisect_start_and_finish --reset-when-found=found &&\n      +\ttest \"$HASH3\" = \"$(git rev-parse HEAD)\" &&\n      +\ttest_bisect_state_missing BISECT_START\n      +'\n      +\n     -+test_expect_success '\"git bisect run --auto-reset\" defaults to original' '\n     ++test_expect_success '\"git bisect run --reset-when-found\" defaults to original' '\n      +\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     -+\tbisect_run_auto_reset --auto-reset &&\n     ++\tbisect_run_reset_when_found --reset-when-found &&\n      +\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n      +\ttest main = \"$(git branch --show-current)\" &&\n      +\ttest_bisect_state_missing BISECT_START\n      +'\n      +\n     -+test_expect_success '\"git bisect run --auto-reset=found\" leaves first bad checked out' '\n     ++test_expect_success '\"git bisect run --reset-when-found=found\" leaves first bad checked out' '\n      +\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     -+\tbisect_run_auto_reset --auto-reset=found &&\n     ++\tbisect_run_reset_when_found --reset-when-found=found &&\n      +\ttest \"$HASH3\" = \"$(git rev-parse HEAD)\" &&\n      +\ttest_bisect_state_missing BISECT_START\n      +'\n      +\n     -+test_expect_success '--auto-reset rejects an unknown reset target' '\n     ++test_expect_success '--reset-when-found rejects an unknown reset target' '\n      +\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     -+\ttest_auto_reset_fails \\\n     -+\t\t\"invalid value for.*--auto-reset.*unknown\" BISECT_START \\\n     -+\t\tgit bisect start --auto-reset=unknown $HASH4 $HASH2 &&\n     ++\ttest_reset_when_found_fails \\\n     ++\t\t\"invalid value for.*--reset-when-found.*unknown\" BISECT_START \\\n     ++\t\tgit bisect start --reset-when-found=unknown $HASH4 $HASH2 &&\n      +\n      +\tgit bisect start $HASH4 $HASH2 &&\n     -+\ttest_auto_reset_fails \\\n     -+\t\t\"invalid value for.*--auto-reset.*unknown\" BISECT_AUTO_RESET \\\n     -+\t\tgit bisect run --auto-reset=unknown true\n     ++\ttest_reset_when_found_fails \\\n     ++\t\t\"invalid value for.*--reset-when-found.*unknown\" \\\n     ++\t\tBISECT_RESET_WHEN_FOUND \\\n     ++\t\tgit bisect run --reset-when-found=unknown true\n      +'\n      +\n     -+test_expect_success '--auto-reset cannot be used with --no-checkout' '\n     ++test_expect_success '--reset-when-found cannot be used with --no-checkout' '\n      +\ttest_when_finished \"git bisect reset\" &&\n     -+\ttest_auto_reset_fails \\\n     ++\ttest_reset_when_found_fails \\\n      +\t\t\"cannot be used with.*--no-checkout\" BISECT_START \\\n     -+\t\tgit bisect start --auto-reset=original --no-checkout $HASH4 $HASH2 &&\n     ++\t\tgit bisect start --reset-when-found=original --no-checkout $HASH4 $HASH2 &&\n      +\n      +\tgit bisect start --no-checkout $HASH4 $HASH2 &&\n     -+\ttest_auto_reset_fails \\\n     -+\t\t\"cannot be used with.*--no-checkout\" BISECT_AUTO_RESET \\\n     -+\t\tgit bisect run --auto-reset=found true\n     ++\ttest_reset_when_found_fails \\\n     ++\t\t\"cannot be used with.*--no-checkout\" BISECT_RESET_WHEN_FOUND \\\n     ++\t\tgit bisect run --reset-when-found=found true\n      +'\n      +\n     -+test_expect_success 'without --auto-reset the bisection state is kept' '\n     ++test_expect_success 'without --reset-when-found the bisection state is kept' '\n      +\ttest_when_finished \"git bisect reset\" &&\n      +\tgit bisect start $HASH4 $HASH2 &&\n      +\tgit bisect bad &&\n      +\ttest_bisect_state_file BISECT_START\n      +'\n      +\n     -+test_expect_success '--auto-reset does not leak into a later bisection' '\n     ++test_expect_success '--reset-when-found does not leak into a later bisection' '\n      +\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     -+\tbisect_start_and_finish --auto-reset &&\n     ++\tbisect_start_and_finish --reset-when-found &&\n      +\n      +\tgit bisect start $HASH4 $HASH2 &&\n      +\tgit bisect bad &&\n\n-- \ngitgitgadget\n"},{"id":"548660","messageId":"59920c51ae813f1ca0e95746c88b8b9017252c10.1784538619.git.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.v3.git.git.1784538619.gitgitgadget@gmail.com","subject":"[PATCH v3 1/2] bisect: let bisect_reset() optionally check out quietly","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-07-20T09:10:18Z","receivedAt":"2026-07-20T09:10:23Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a \"quiet\" parameter to bisect_reset() that passes \"--quiet\" to the\ncheckout restoring the original HEAD, suppressing its progress and\nbranch-status output.\n\nNo caller sets the flag yet, so behavior is unchanged.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/bisect.c | 12 +++++++-----\n 1 file changed, 7 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 798e28f501..0e49ca23ae 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -234,7 +234,7 @@ static int write_terms(const char *bad, const char *good)\n \treturn res;\n }\n \n-static int bisect_reset(const char *commit)\n+static int bisect_reset(const char *commit, int quiet)\n {\n \tstruct strbuf branch = STRBUF_INIT;\n \n@@ -255,8 +255,10 @@ static int bisect_reset(const char *commit)\n \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n \t\tcmd.git_cmd = 1;\n-\t\tstrvec_pushl(&cmd.args, \"checkout\", \"--ignore-other-worktrees\",\n-\t\t\t\tbranch.buf, \"--\", NULL);\n+\t\tstrvec_pushl(&cmd.args, \"checkout\", \"--ignore-other-worktrees\", NULL);\n+\t\tif (quiet)\n+\t\t\tstrvec_push(&cmd.args, \"--quiet\");\n+\t\tstrvec_pushl(&cmd.args, branch.buf, \"--\", NULL);\n \t\tif (run_command(&cmd)) {\n \t\t\terror(_(\"could not check out original\"\n \t\t\t\t\" HEAD '%s'. Try 'git bisect\"\n@@ -1089,7 +1091,7 @@ static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f\n \tif (is_empty_or_missing_file(filename))\n \t\treturn error(_(\"cannot read file '%s' for replaying\"), filename);\n \n-\tif (bisect_reset(NULL))\n+\tif (bisect_reset(NULL, 0))\n \t\treturn BISECT_FAILED;\n \n \tfp = fopen(filename, \"r\");\n@@ -1338,7 +1340,7 @@ static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNU\n \tif (argc > 1)\n \t\treturn error(_(\"'%s' requires either no argument or a commit\"),\n \t\t\t     \"git bisect reset\");\n-\treturn bisect_reset(argc ? argv[0] : NULL);\n+\treturn bisect_reset(argc ? argv[0] : NULL, 0);\n }\n \n static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,\n-- \ngitgitgadget\n\n"},{"id":"548662","messageId":"542f4b2c8065818b887437add90130d2090fa0f2.1784538619.git.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.v3.git.git.1784538619.gitgitgadget@gmail.com","subject":"[PATCH v3 2/2] bisect: add --reset-when-found to leave when done","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-07-20T09:10:19Z","receivedAt":"2026-07-20T09:10:26Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen a bisection finishes, \"git bisect\" reports the first bad commit\nbut leaves the session active until \"git bisect reset\" is run by hand.\n\nAdd a \"--reset-when-found[=<where>]\" option, accepted by both \"git\nbisect start\" and \"git bisect run\", that resets as soon as the first\nbad commit is found. The \"original\" value returns to the commit checked\nout before \"git bisect start\", while \"found\" leaves the first bad commit\nchecked out; omitting the value defaults to \"original\".\n\nPersist the selected target in a BISECT_RESET_WHEN_FOUND state file\nand perform the reset quietly.\n\nFor \"git bisect run\", defer the reset until after the captured output\nis printed and BISECT_RUN is closed. This lets cleanup remove the file\non systems that cannot unlink an open file.\n\nReject this option together with \"--no-checkout\", since that mode must\nnot check out either target.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-bisect.adoc |  14 +++-\n bisect.c                      |   2 +\n builtin/bisect.c              | 134 ++++++++++++++++++++++++++++++----\n t/t6030-bisect-porcelain.sh   | 109 +++++++++++++++++++++++++++\n 4 files changed, 243 insertions(+), 16 deletions(-)\n\ndiff --git a/Documentation/git-bisect.adoc b/Documentation/git-bisect.adoc\nindex d2115b2990..aabddd42ca 100644\n--- a/Documentation/git-bisect.adoc\n+++ b/Documentation/git-bisect.adoc\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [synopsis]\n git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\n-\t\t [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\n+\t\t [--no-checkout] [--first-parent] [--reset-when-found[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\n git bisect (bad|new|<term-new>) [<rev>]\n git bisect (good|old|<term-old>) [<rev>...]\n git bisect terms [--term-(good|old) | --term-(bad|new)]\n@@ -20,7 +20,7 @@ git bisect reset [<commit>]\n git bisect (visualize|view)\n git bisect replay <logfile>\n git bisect log\n-git bisect run <cmd> [<arg>...]\n+git bisect run [--reset-when-found[=<where>]] <cmd> [<arg>...]\n git bisect help\n \n DESCRIPTION\n@@ -385,6 +385,16 @@ ignored.\n This option is particularly useful in avoiding false positives when a merged\n branch contained broken or non-buildable commits, but the merge itself was OK.\n \n+`--reset-when-found[=<where>]`::\n+\tOnce the first bad commit is found, report it and clean up the\n+\tbisection state. `<where>` may be `original` to return to the commit\n+\tchecked out before `git bisect start`, or `found` to leave the first\n+\tbad commit checked out. If `<where>` is omitted, it defaults to\n+\t`original`.\n++\n+This option may be given to `git bisect start` or to `git bisect run`. It\n+cannot be used for a bisection started with `--no-checkout`.\n+\n EXAMPLES\n --------\n \ndiff --git a/bisect.c b/bisect.c\nindex 94c7028d2a..d426fcd5a9 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -488,6 +488,7 @@ static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n static GIT_PATH_FUNC(git_path_bisect_terms, \"BISECT_TERMS\")\n static GIT_PATH_FUNC(git_path_bisect_first_parent, \"BISECT_FIRST_PARENT\")\n+static GIT_PATH_FUNC(git_path_bisect_reset_when_found, \"BISECT_RESET_WHEN_FOUND\")\n \n static void read_bisect_paths(struct strvec *array)\n {\n@@ -1211,6 +1212,7 @@ int bisect_clean_state(void)\n \tunlink_or_warn(git_path_bisect_run());\n \tunlink_or_warn(git_path_bisect_terms());\n \tunlink_or_warn(git_path_bisect_first_parent());\n+\tunlink_or_warn(git_path_bisect_reset_when_found());\n \t/*\n \t * Cleanup BISECT_START last to support the --no-checkout option\n \t * introduced in the commit 4796e823a.\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 0e49ca23ae..de13f22f8a 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -24,11 +24,12 @@ static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n static GIT_PATH_FUNC(git_path_bisect_names, \"BISECT_NAMES\")\n static GIT_PATH_FUNC(git_path_bisect_first_parent, \"BISECT_FIRST_PARENT\")\n+static GIT_PATH_FUNC(git_path_bisect_reset_when_found, \"BISECT_RESET_WHEN_FOUND\")\n static GIT_PATH_FUNC(git_path_bisect_run, \"BISECT_RUN\")\n \n #define BUILTIN_GIT_BISECT_START_USAGE \\\n \tN_(\"git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\\n\" \\\n-\t   \"                 [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\")\n+\t   \"                 [--no-checkout] [--first-parent] [--reset-when-found[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\")\n #define BUILTIN_GIT_BISECT_BAD_USAGE \\\n \tN_(\"git bisect (bad|new|<term-new>) [<rev>]\")\n #define BUILTIN_GIT_BISECT_GOOD_USAGE \\\n@@ -48,7 +49,7 @@ static GIT_PATH_FUNC(git_path_bisect_run, \"BISECT_RUN\")\n #define BUILTIN_GIT_BISECT_LOG_USAGE \\\n \t\"git bisect log\"\n #define BUILTIN_GIT_BISECT_RUN_USAGE \\\n-\tN_(\"git bisect run <cmd> [<arg>...]\")\n+\tN_(\"git bisect run [--reset-when-found[=<where>]] <cmd> [<arg>...]\")\n #define BUILTIN_GIT_BISECT_HELP_USAGE \\\n \t\"git bisect help\"\n \n@@ -68,6 +69,12 @@ static const char * const git_bisect_usage[] = {\n \tNULL\n };\n \n+enum reset_when_found_mode {\n+\tRESET_WHEN_FOUND_NONE,\n+\tRESET_WHEN_FOUND_TO_ORIGINAL,\n+\tRESET_WHEN_FOUND_TO_FOUND,\n+};\n+\n struct add_bisect_ref_data {\n \tstruct rev_info *revs;\n \tunsigned int object_flags;\n@@ -272,6 +279,61 @@ static int bisect_reset(const char *commit, int quiet)\n \treturn bisect_clean_state();\n }\n \n+static int parse_reset_when_found(const char *value,\n+\t\t\t\t  enum reset_when_found_mode *mode)\n+{\n+\tif (!strcmp(value, \"original\"))\n+\t\t*mode = RESET_WHEN_FOUND_TO_ORIGINAL;\n+\telse if (!strcmp(value, \"found\"))\n+\t\t*mode = RESET_WHEN_FOUND_TO_FOUND;\n+\telse\n+\t\treturn error(_(\"invalid value for '--reset-when-found': '%s'\"),\n+\t\t\t     value);\n+\n+\treturn 0;\n+}\n+\n+static const char *reset_when_found_mode_name(enum reset_when_found_mode mode)\n+{\n+\tswitch (mode) {\n+\tcase RESET_WHEN_FOUND_TO_ORIGINAL:\n+\t\treturn \"original\";\n+\tcase RESET_WHEN_FOUND_TO_FOUND:\n+\t\treturn \"found\";\n+\tcase RESET_WHEN_FOUND_NONE:\n+\t\tBUG(\"no name for unset reset-when-found mode\");\n+\t}\n+\tBUG(\"unknown reset-when-found mode %d\", mode);\n+}\n+\n+static int bisect_reset_when_found(struct bisect_terms *terms)\n+{\n+\tstruct strbuf value = STRBUF_INIT;\n+\tenum reset_when_found_mode mode;\n+\tchar *commit = NULL;\n+\tint res;\n+\n+\tif (strbuf_read_file(&value, git_path_bisect_reset_when_found(), 0) < 0) {\n+\t\tres = error_errno(_(\"could not read '%s'\"),\n+\t\t\t\t  git_path_bisect_reset_when_found());\n+\t\tgoto cleanup;\n+\t}\n+\tstrbuf_trim(&value);\n+\tif (parse_reset_when_found(value.buf, &mode)) {\n+\t\tres = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tif (mode == RESET_WHEN_FOUND_TO_FOUND)\n+\t\tcommit = xstrfmt(\"refs/bisect/%s\", terms->term_bad);\n+\tres = bisect_reset(commit, 1);\n+\n+cleanup:\n+\tfree(commit);\n+\tstrbuf_release(&value);\n+\treturn res;\n+}\n+\n static void log_commit(FILE *fp,\n \t\t       const char *fmt, const char *state,\n \t\t       struct commit *commit)\n@@ -677,7 +739,8 @@ static int bisect_successful(struct bisect_terms *terms)\n \treturn res;\n }\n \n-static enum bisect_error bisect_next(struct bisect_terms *terms, const char *prefix)\n+static enum bisect_error bisect_next(struct bisect_terms *terms,\n+\t\t\t\t     const char *prefix, bool defer_reset)\n {\n \tenum bisect_error res;\n \n@@ -692,6 +755,9 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n \n \tif (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n \t\tres = bisect_successful(terms);\n+\t\tif (!res && !defer_reset &&\n+\t\t    !is_empty_or_missing_file(git_path_bisect_reset_when_found()))\n+\t\t\tres = bisect_reset_when_found(terms);\n \t\treturn res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;\n \t} else if (res == BISECT_ONLY_SKIPPED_LEFT) {\n \t\tres = bisect_skipped_commits(terms);\n@@ -700,14 +766,15 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n \treturn res;\n }\n \n-static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix)\n+static enum bisect_error bisect_auto_next(struct bisect_terms *terms,\n+\t\t\t\t\t  const char *prefix, bool defer_reset)\n {\n \tif (bisect_next_check(terms, NULL)) {\n \t\tbisect_print_status(terms);\n \t\treturn BISECT_OK;\n \t}\n \n-\treturn bisect_next(terms, prefix);\n+\treturn bisect_next(terms, prefix, defer_reset);\n }\n \n static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n@@ -715,6 +782,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n {\n \tint no_checkout = 0;\n \tint first_parent_only = 0;\n+\tenum reset_when_found_mode reset_when_found = RESET_WHEN_FOUND_NONE;\n \tint i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;\n \tint flags, pathspec_pos;\n \tenum bisect_error res = BISECT_OK;\n@@ -747,6 +815,13 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \t\t\tno_checkout = 1;\n \t\t} else if (!strcmp(arg, \"--first-parent\")) {\n \t\t\tfirst_parent_only = 1;\n+\t\t} else if (!strcmp(arg, \"--reset-when-found\")) {\n+\t\t\treset_when_found = RESET_WHEN_FOUND_TO_ORIGINAL;\n+\t\t} else if (skip_prefix(arg, \"--reset-when-found=\", &arg)) {\n+\t\t\tif (parse_reset_when_found(arg, &reset_when_found)) {\n+\t\t\t\tres = BISECT_FAILED;\n+\t\t\t\tgoto finish;\n+\t\t\t}\n \t\t} else if (!strcmp(arg, \"--term-good\") ||\n \t\t\t !strcmp(arg, \"--term-old\")) {\n \t\t\ti++;\n@@ -784,6 +859,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \t\t\tbreak;\n \t\t}\n \t}\n+\tif (reset_when_found != RESET_WHEN_FOUND_NONE && no_checkout) {\n+\t\tres = error(_(\"'--reset-when-found' cannot be used with '--no-checkout'\"));\n+\t\tgoto finish;\n+\t}\n \tpathspec_pos = i;\n \n \t/*\n@@ -861,6 +940,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \tif (first_parent_only)\n \t\twrite_file(git_path_bisect_first_parent(), \"\\n\");\n \n+\tif (reset_when_found != RESET_WHEN_FOUND_NONE)\n+\t\twrite_file(git_path_bisect_reset_when_found(), \"%s\\n\",\n+\t\t\t   reset_when_found_mode_name(reset_when_found));\n+\n \tif (no_checkout) {\n \t\tif (repo_get_oid(the_repository, start_head.buf, &oid) < 0) {\n \t\t\tres = error(_(\"invalid ref: '%s'\"), start_head.buf);\n@@ -902,7 +985,7 @@ finish:\n \tif (res)\n \t\treturn res;\n \n-\tres = bisect_auto_next(terms, NULL);\n+\tres = bisect_auto_next(terms, NULL, false);\n \tif (!is_bisect_success(res))\n \t\tbisect_clean_state();\n \treturn res;\n@@ -941,7 +1024,7 @@ static int bisect_autostart(struct bisect_terms *terms)\n }\n \n static enum bisect_error bisect_state(struct bisect_terms *terms, int argc,\n-\t\t\t\t      const char **argv)\n+\t\t\t\t      const char **argv, bool defer_reset)\n {\n \tconst char *state;\n \tint i, verify_expected = 1;\n@@ -1018,7 +1101,7 @@ static enum bisect_error bisect_state(struct bisect_terms *terms, int argc,\n \t}\n \n \toid_array_clear(&revs);\n-\treturn bisect_auto_next(terms, NULL);\n+\treturn bisect_auto_next(terms, NULL, defer_reset);\n }\n \n static enum bisect_error bisect_log(void)\n@@ -1107,7 +1190,7 @@ static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f\n \tif (res)\n \t\treturn BISECT_FAILED;\n \n-\treturn bisect_auto_next(terms, NULL);\n+\treturn bisect_auto_next(terms, NULL, false);\n }\n \n static enum bisect_error bisect_skip(struct bisect_terms *terms, int argc,\n@@ -1141,7 +1224,7 @@ static enum bisect_error bisect_skip(struct bisect_terms *terms, int argc,\n \t\t\tstrvec_push(&argv_state, argv[i]);\n \t\t}\n \t}\n-\tres = bisect_state(terms, argv_state.nr, argv_state.v);\n+\tres = bisect_state(terms, argv_state.nr, argv_state.v, false);\n \n \tstrvec_clear(&argv_state);\n \treturn res;\n@@ -1239,6 +1322,8 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n {\n \tint res = BISECT_OK;\n \tstruct strbuf command = STRBUF_INIT;\n+\tenum reset_when_found_mode reset_when_found = RESET_WHEN_FOUND_NONE;\n+\tconst char *reset_when_found_arg;\n \tconst char *new_state;\n \tint temporary_stdout_fd, saved_stdout;\n \tint is_first_run = 1;\n@@ -1246,6 +1331,23 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n \tif (bisect_next_check(terms, NULL))\n \t\treturn BISECT_FAILED;\n \n+\tif (argc && !strcmp(argv[0], \"--reset-when-found\"))\n+\t\treset_when_found = RESET_WHEN_FOUND_TO_ORIGINAL;\n+\telse if (argc && skip_prefix(argv[0], \"--reset-when-found=\",\n+\t\t\t\t    &reset_when_found_arg)) {\n+\t\tif (parse_reset_when_found(reset_when_found_arg, &reset_when_found))\n+\t\t\treturn BISECT_FAILED;\n+\t}\n+\n+\tif (reset_when_found != RESET_WHEN_FOUND_NONE) {\n+\t\tif (refs_ref_exists(get_main_ref_store(the_repository), \"BISECT_HEAD\"))\n+\t\t\treturn error(_(\"'--reset-when-found' cannot be used with '--no-checkout'\"));\n+\t\twrite_file(git_path_bisect_reset_when_found(), \"%s\\n\",\n+\t\t\t   reset_when_found_mode_name(reset_when_found));\n+\t\targc--;\n+\t\targv++;\n+\t}\n+\n \tif (!argc) {\n \t\terror(_(\"bisect run failed: no command provided.\"));\n \t\treturn BISECT_FAILED;\n@@ -1304,7 +1406,7 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n \t\tsaved_stdout = dup(1);\n \t\tdup2(temporary_stdout_fd, 1);\n \n-\t\tres = bisect_state(terms, 1, &new_state);\n+\t\tres = bisect_state(terms, 1, &new_state, true);\n \n \t\tfflush(stdout);\n \t\tdup2(saved_stdout, 1);\n@@ -1320,7 +1422,11 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n \t\t\tres = BISECT_OK;\n \t\t} else if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n \t\t\tprintf(_(\"bisect found first '%s' commit\\n\"), terms->term_bad);\n-\t\t\tres = BISECT_OK;\n+\t\t\tif (!is_empty_or_missing_file(git_path_bisect_reset_when_found()) &&\n+\t\t\t    bisect_reset_when_found(terms))\n+\t\t\t\tres = BISECT_FAILED;\n+\t\t\telse\n+\t\t\t\tres = BISECT_OK;\n \t\t} else if (res) {\n \t\t\terror(_(\"bisect run failed: 'git bisect %s'\"\n \t\t\t\t\" exited with error code %d\"), new_state, res);\n@@ -1379,7 +1485,7 @@ static int cmd_bisect__next(int argc, const char **argv UNUSED, const char *pref\n \t\treturn error(_(\"'%s' requires 0 arguments\"),\n \t\t\t     \"git bisect next\");\n \tget_terms(&terms);\n-\tres = bisect_next(&terms, prefix);\n+\tres = bisect_next(&terms, prefix, false);\n \tfree_terms(&terms);\n \treturn res;\n }\n@@ -1482,7 +1588,7 @@ int cmd_bisect(int argc,\n \t\t    !one_of(argv[0], terms.term_good, terms.term_bad, NULL))\n \t\t\tusage_msg_optf(_(\"unknown command: '%s'\"), git_bisect_usage,\n \t\t\t\t       options, argv[0]);\n-\t\tres = bisect_state(&terms, argc, argv);\n+\t\tres = bisect_state(&terms, argc, argv, false);\n \t\tfree_terms(&terms);\n \t} else {\n \t\targc--;\ndiff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh\nindex 081116220a..7dfb871ab9 100755\n--- a/t/t6030-bisect-porcelain.sh\n+++ b/t/t6030-bisect-porcelain.sh\n@@ -43,6 +43,38 @@ test_bisect_usage () {\n \ttest_cmp expect actual\n }\n \n+test_bisect_state_file () {\n+\ttest_path_is_file \"$(git rev-parse --git-path \"$1\")\"\n+}\n+\n+test_bisect_state_missing () {\n+\ttest_path_is_missing \"$(git rev-parse --git-path \"$1\")\"\n+}\n+\n+bisect_start_and_finish () {\n+\tgit bisect start \"$1\" $HASH4 $HASH2 &&\n+\tgit bisect bad\n+}\n+\n+bisect_run_reset_when_found () {\n+\twrite_script test_script.sh <<-\\EOF &&\n+\t! grep Another hello >/dev/null\n+\tEOF\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect run \"$1\" ./test_script.sh >my_bisect_log.txt &&\n+\ttest_grep \"$HASH3 is the first .bad. commit\" my_bisect_log.txt &&\n+\ttest_bisect_state_missing BISECT_RUN\n+}\n+\n+test_reset_when_found_fails () {\n+\tlocal pattern=\"$1\" &&\n+\tlocal state_file=\"$2\" &&\n+\tshift 2 &&\n+\ttest_must_fail \"$@\" 2>err &&\n+\ttest_grep -- \"$pattern\" err &&\n+\ttest_bisect_state_missing \"$state_file\"\n+}\n+\n test_expect_success 'bisect usage' \"\n \ttest_bisect_usage 1 git bisect reset extra1 extra2 <<-\\EOF &&\n \terror: 'git bisect reset' requires either no argument or a commit\n@@ -453,6 +485,83 @@ test_expect_success '\"git bisect run\" simple case' '\n \tgit bisect reset\n '\n \n+test_expect_success '\"git bisect start --reset-when-found\" defaults to original' '\n+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n+\tgit checkout main &&\n+\tbisect_start_and_finish --reset-when-found &&\n+\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n+\ttest main = \"$(git branch --show-current)\" &&\n+\ttest_bisect_state_missing BISECT_START &&\n+\n+\tbisect_start_and_finish --reset-when-found=original &&\n+\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n+\ttest main = \"$(git branch --show-current)\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '\"git bisect start --reset-when-found=found\" leaves first bad checked out' '\n+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n+\tbisect_start_and_finish --reset-when-found=found &&\n+\ttest \"$HASH3\" = \"$(git rev-parse HEAD)\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '\"git bisect run --reset-when-found\" defaults to original' '\n+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n+\tbisect_run_reset_when_found --reset-when-found &&\n+\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n+\ttest main = \"$(git branch --show-current)\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '\"git bisect run --reset-when-found=found\" leaves first bad checked out' '\n+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n+\tbisect_run_reset_when_found --reset-when-found=found &&\n+\ttest \"$HASH3\" = \"$(git rev-parse HEAD)\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '--reset-when-found rejects an unknown reset target' '\n+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"invalid value for.*--reset-when-found.*unknown\" BISECT_START \\\n+\t\tgit bisect start --reset-when-found=unknown $HASH4 $HASH2 &&\n+\n+\tgit bisect start $HASH4 $HASH2 &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"invalid value for.*--reset-when-found.*unknown\" \\\n+\t\tBISECT_RESET_WHEN_FOUND \\\n+\t\tgit bisect run --reset-when-found=unknown true\n+'\n+\n+test_expect_success '--reset-when-found cannot be used with --no-checkout' '\n+\ttest_when_finished \"git bisect reset\" &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"cannot be used with.*--no-checkout\" BISECT_START \\\n+\t\tgit bisect start --reset-when-found=original --no-checkout $HASH4 $HASH2 &&\n+\n+\tgit bisect start --no-checkout $HASH4 $HASH2 &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"cannot be used with.*--no-checkout\" BISECT_RESET_WHEN_FOUND \\\n+\t\tgit bisect run --reset-when-found=found true\n+'\n+\n+test_expect_success 'without --reset-when-found the bisection state is kept' '\n+\ttest_when_finished \"git bisect reset\" &&\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect bad &&\n+\ttest_bisect_state_file BISECT_START\n+'\n+\n+test_expect_success '--reset-when-found does not leak into a later bisection' '\n+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n+\tbisect_start_and_finish --reset-when-found &&\n+\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect bad &&\n+\ttest_bisect_state_file BISECT_START\n+'\n+\n # We want to automatically find the commit that\n # added \"Ciao\" into hello.\n test_expect_success '\"git bisect run\" with more complex \"git bisect start\"' '\n-- \ngitgitgadget\n"},{"id":"548686","messageId":"xmqqldb5d1d9.fsf@gitster.g","threadId":"66011","inReplyTo":"pull.2335.v3.git.git.1784538619.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 0/2] bisect: add --auto-reset to leave when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-20T17:20:18Z","receivedAt":"2026-07-20T17:20:22Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Add a --reset-when-found option to git bisect that resets the bisect session\n> when culprit is found.\n>\n> Changes in v3:\n>\n>  * Rename --auto-reset to --reset-when-found, including internal names.\n>  * Defer git bisect run cleanup until captured output is printed and\n>    BISECT_RUN is closed. Drop the open-descriptor preparatory change,\n>    retaining the existing filename-based output handling.\n\nThe range-diff looks very busy, but it mostly looks like a fallout\ncaused by the renaming of the option and internal functions and enum\nto match the updated name.\n\nLooking good.  Is everybody happy with this version?  If so I'll\nmark the topic for 'next' soonish.\n\nThanks.\n"},{"id":"548804","messageId":"faa22968-54ac-4e4f-8324-3326ffb00c5b@kdbg.org","threadId":"66011","inReplyTo":"542f4b2c8065818b887437add90130d2090fa0f2.1784538619.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 2/2] bisect: add --reset-when-found to leave when done","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-07-23T09:17:28Z","receivedAt":"2026-07-23T09:17:38Z","isPatch":true,"body":"Am 20.07.26 um 11:10 schrieb Harald Nordgren via GitGitGadget:\n> @@ -784,6 +859,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n>  \t\t\tbreak;\n>  \t\t}\n>  \t}\n> +\tif (reset_when_found != RESET_WHEN_FOUND_NONE && no_checkout) {\n> +\t\tres = error(_(\"'--reset-when-found' cannot be used with '--no-checkout'\"));\n\nWe have a boilerplate text for this kind of error that saves a translation:\n\n\t\tres = error(_(\"options '%s' and '%s' cannot be used together\"),\n\"--reset-when-found\", \"--no-checkout\");\n\n> +\t\tgoto finish;\n> +\t}\n>  \tpathspec_pos = i;\n>  \n>  \t/*\n\n> @@ -1246,6 +1331,23 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n>  \tif (bisect_next_check(terms, NULL))\n>  \t\treturn BISECT_FAILED;\n>  \n> +\tif (argc && !strcmp(argv[0], \"--reset-when-found\"))\n> +\t\treset_when_found = RESET_WHEN_FOUND_TO_ORIGINAL;\n> +\telse if (argc && skip_prefix(argv[0], \"--reset-when-found=\",\n> +\t\t\t\t    &reset_when_found_arg)) {\n> +\t\tif (parse_reset_when_found(reset_when_found_arg, &reset_when_found))\n> +\t\t\treturn BISECT_FAILED;\n> +\t}\n> +\n> +\tif (reset_when_found != RESET_WHEN_FOUND_NONE) {\n> +\t\tif (refs_ref_exists(get_main_ref_store(the_repository), \"BISECT_HEAD\"))\n> +\t\t\treturn error(_(\"'--reset-when-found' cannot be used with '--no-checkout'\"));\n\nDitto.\n\n> +\t\twrite_file(git_path_bisect_reset_when_found(), \"%s\\n\",\n> +\t\t\t   reset_when_found_mode_name(reset_when_found));\n> +\t\targc--;\n> +\t\targv++;\n> +\t}\n> +\n>  \tif (!argc) {\n>  \t\terror(_(\"bisect run failed: no command provided.\"));\n>  \t\treturn BISECT_FAILED;\n\n> diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh\n> index 081116220a..7dfb871ab9 100755\n> --- a/t/t6030-bisect-porcelain.sh\n> +++ b/t/t6030-bisect-porcelain.sh\n> @@ -43,6 +43,38 @@ test_bisect_usage () {\n>  \ttest_cmp expect actual\n>  }\n>  \n> +test_bisect_state_file () {\n> +\ttest_path_is_file \"$(git rev-parse --git-path \"$1\")\"\n> +}\n> +\n> +test_bisect_state_missing () {\n> +\ttest_path_is_missing \"$(git rev-parse --git-path \"$1\")\"\n> +}\n\nThese should not use `git` in a $( ) subshell to avoid a case of \"ignore\nfailure in upstream of pipe\". Note that\n\n\tlocal file=$(git rev-parse ...) &&\n\ttest_path...\n\nwould be wrong, too, for the same reason. But\n\n\tlocal file\n\tfile=$(git rev-parse ...) &&\n\ttest_path...\n\nworks as desired.\n\n> +test_expect_success '\"git bisect start --reset-when-found\" defaults to original' '\n> +\ttest_when_finished \"git bisect reset; git checkout main\" &&\n\nLooking at other cases where more than one git command is invoked by\ntest_when_finished, it seems that they are chained with '&&'. `git grepc\n\"&& git checkout main\"` does find a few hits.\n\n> +\tgit checkout main &&\n> +\tbisect_start_and_finish --reset-when-found &&\n> +\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n> +\ttest main = \"$(git branch --show-current)\" &&\n> +\ttest_bisect_state_missing BISECT_START &&\n> +\n> +\tbisect_start_and_finish --reset-when-found=original &&\n> +\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n> +\ttest main = \"$(git branch --show-current)\" &&\n> +\ttest_bisect_state_missing BISECT_START\n> +'\n\nMore cases of `git` in a subshell above and below. I notice that you are\nmimicking existing practice in this file. I'm torn whether to change\nthis or not. After all, there are also a lot of cases in the file that\nuses the correct pattern where the subshell is in a variable assignment.\n\n> +\n> +test_expect_success '\"git bisect start --reset-when-found=found\" leaves first bad checked out' '\n> +\ttest_when_finished \"git bisect reset; git checkout main\" &&\n> +\tbisect_start_and_finish --reset-when-found=found &&\n> +\ttest \"$HASH3\" = \"$(git rev-parse HEAD)\" &&\n> +\ttest_bisect_state_missing BISECT_START\n> +'\n> +\n> +test_expect_success '\"git bisect run --reset-when-found\" defaults to original' '\n> +\ttest_when_finished \"git bisect reset; git checkout main\" &&\n> +\tbisect_run_reset_when_found --reset-when-found &&\n> +\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n> +\ttest main = \"$(git branch --show-current)\" &&\n> +\ttest_bisect_state_missing BISECT_START\n> +'\n> +\n> +test_expect_success '\"git bisect run --reset-when-found=found\" leaves first bad checked out' '\n> +\ttest_when_finished \"git bisect reset; git checkout main\" &&\n> +\tbisect_run_reset_when_found --reset-when-found=found &&\n> +\ttest \"$HASH3\" = \"$(git rev-parse HEAD)\" &&\n> +\ttest_bisect_state_missing BISECT_START\n> +'\n-- Hannes\n\n"},{"id":"548812","messageId":"xmqq33x9n5ms.fsf@gitster.g","threadId":"66011","inReplyTo":"faa22968-54ac-4e4f-8324-3326ffb00c5b@kdbg.org","subject":"Re: [PATCH v3 2/2] bisect: add --reset-when-found to leave when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-23T14:27:07Z","receivedAt":"2026-07-23T14:27:10Z","isPatch":true,"body":"Johannes Sixt <j6t@kdbg.org> writes:\n\n> Am 20.07.26 um 11:10 schrieb Harald Nordgren via GitGitGadget:\n>> @@ -784,6 +859,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n>>  \t\t\tbreak;\n>>  \t\t}\n>>  \t}\n>> +\tif (reset_when_found != RESET_WHEN_FOUND_NONE && no_checkout) {\n>> +\t\tres = error(_(\"'--reset-when-found' cannot be used with '--no-checkout'\"));\n>\n> We have a boilerplate text for this kind of error that saves a translation:\n>\n> \t\tres = error(_(\"options '%s' and '%s' cannot be used together\"),\n> \"--reset-when-found\", \"--no-checkout\");\n\nI wonder if we should add a set of helper functions\nthat return an error instead of dying, to complement\nthe die_for_incompatible_optX() family of functions.\n\nAre there many other places that would benefit from this?\n\nThanks.\n\n\n\n"},{"id":"549376","messageId":"CAHwyqnXxkqr6EeT-zpSo5KdJR8KsqDiLM1Jj_Q-G6wmVtsXDBw@mail.gmail.com","threadId":"66011","inReplyTo":"faa22968-54ac-4e4f-8324-3326ffb00c5b@kdbg.org","subject":"Re: [PATCH v3 2/2] bisect: add --reset-when-found to leave when done","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-08-01T06:51:10Z","receivedAt":"2026-08-01T06:51:49Z","isPatch":true,"body":"> Am 20.07.26 um 11:10 schrieb Harald Nordgren via GitGitGadget:\n> > @@ -784,6 +859,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n> >                       break;\n> >               }\n> >       }\n> > +     if (reset_when_found != RESET_WHEN_FOUND_NONE && no_checkout) {\n> > +             res = error(_(\"'--reset-when-found' cannot be used with '--no-checkout'\"));\n>\n> We have a boilerplate text for this kind of error that saves a translation:\n\nGood point, will fix both.\n\n> > diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh\n> > index 081116220a..7dfb871ab9 100755\n> > --- a/t/t6030-bisect-porcelain.sh\n> > +++ b/t/t6030-bisect-porcelain.sh\n> > @@ -43,6 +43,38 @@ test_bisect_usage () {\n> >       test_cmp expect actual\n> >  }\n> >\n> > +test_bisect_state_file () {\n> > +     test_path_is_file \"$(git rev-parse --git-path \"$1\")\"\n> > +}\n> > +\n> > +test_bisect_state_missing () {\n> > +     test_path_is_missing \"$(git rev-parse --git-path \"$1\")\"\n> > +}\n>\n> These should not use `git` in a $( ) subshell to avoid a case of \"ignore\n> failure in upstream of pipe\". Note that\n>\n>         local file=$(git rev-parse ...) &&\n>         test_path...\n>\n> would be wrong, too, for the same reason. But\n>\n>         local file\n>         file=$(git rev-parse ...) &&\n>         test_path...\n>\n> works as desired.\n\nWill update.\n\n\nHarald\n"},{"id":"549385","messageId":"pull.2335.v4.git.git.1785577445.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.v3.git.git.1784538619.gitgitgadget@gmail.com","subject":"[PATCH v4 0/2] bisect: add --reset-when-found to leave when done","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-08-01T09:44:03Z","receivedAt":"2026-08-01T09:44:09Z","isPatch":true,"body":"Add a --reset-when-found option to git bisect that resets the bisect session\nwhen culprit is found.\n\nChanges in v4:\n\n * Simplify translation calls.\n * Avoid git subshell calls in tests, that can bury errors.\n\nChanges in v3:\n\n * Rename --auto-reset to --reset-when-found, including internal names.\n * Defer git bisect run cleanup until captured output is printed and\n   BISECT_RUN is closed. Drop the open-descriptor preparatory change,\n   retaining the existing filename-based output handling.\n\nChanges in v2:\n\n * Add option --auto-reset[=<where>] with option to go to final commit as\n   well as original.\n * Refactored tests.\n\nHarald Nordgren (2):\n  bisect: let bisect_reset() optionally check out quietly\n  bisect: add --reset-when-found to leave when done\n\n Documentation/git-bisect.adoc |  14 +++-\n bisect.c                      |   2 +\n builtin/bisect.c              | 148 +++++++++++++++++++++++++++++-----\n t/t6030-bisect-porcelain.sh   | 121 +++++++++++++++++++++++++++\n 4 files changed, 264 insertions(+), 21 deletions(-)\n\n\nbase-commit: a97fcc37c2bc6340a8d7ce78dedf227aac4e9aa7\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2335%2FHaraldNordgren%2Fbisect-auto-reset-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2335/HaraldNordgren/bisect-auto-reset-v4\nPull-Request: https://github.com/git/git/pull/2335\n\nRange-diff vs v3:\n\n 1:  59920c51ae = 1:  e39670edf4 bisect: let bisect_reset() optionally check out quietly\n 2:  542f4b2c80 ! 2:  f5f370df1b bisect: add --reset-when-found to leave when done\n     @@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *ter\n       \t\t}\n       \t}\n      +\tif (reset_when_found != RESET_WHEN_FOUND_NONE && no_checkout) {\n     -+\t\tres = error(_(\"'--reset-when-found' cannot be used with '--no-checkout'\"));\n     ++\t\tres = error(_(\"options '%s' and '%s' cannot be used together\"),\n     ++\t\t\t    \"--reset-when-found\", \"--no-checkout\");\n      +\t\tgoto finish;\n      +\t}\n       \tpathspec_pos = i;\n     @@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, co\n      +\n      +\tif (reset_when_found != RESET_WHEN_FOUND_NONE) {\n      +\t\tif (refs_ref_exists(get_main_ref_store(the_repository), \"BISECT_HEAD\"))\n     -+\t\t\treturn error(_(\"'--reset-when-found' cannot be used with '--no-checkout'\"));\n     ++\t\t\treturn error(_(\"options '%s' and '%s' cannot be used together\"),\n     ++\t\t\t\t     \"--reset-when-found\", \"--no-checkout\");\n      +\t\twrite_file(git_path_bisect_reset_when_found(), \"%s\\n\",\n      +\t\t\t   reset_when_found_mode_name(reset_when_found));\n      +\t\targc--;\n     @@ t/t6030-bisect-porcelain.sh: test_bisect_usage () {\n       }\n       \n      +test_bisect_state_file () {\n     -+\ttest_path_is_file \"$(git rev-parse --git-path \"$1\")\"\n     ++\tlocal file &&\n     ++\tfile=$(git rev-parse --git-path \"$1\") &&\n     ++\ttest_path_is_file \"$file\"\n      +}\n      +\n      +test_bisect_state_missing () {\n     -+\ttest_path_is_missing \"$(git rev-parse --git-path \"$1\")\"\n     ++\tlocal file &&\n     ++\tfile=$(git rev-parse --git-path \"$1\") &&\n     ++\ttest_path_is_missing \"$file\"\n      +}\n      +\n      +bisect_start_and_finish () {\n     @@ t/t6030-bisect-porcelain.sh: test_expect_success '\"git bisect run\" simple case'\n       '\n       \n      +test_expect_success '\"git bisect start --reset-when-found\" defaults to original' '\n     -+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     ++\ttest_when_finished \"git bisect reset && git checkout main\" &&\n      +\tgit checkout main &&\n      +\tbisect_start_and_finish --reset-when-found &&\n     -+\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n     -+\ttest main = \"$(git branch --show-current)\" &&\n     ++\tactual=$(git rev-parse HEAD) &&\n     ++\ttest \"$HASH4\" = \"$actual\" &&\n     ++\tactual=$(git branch --show-current) &&\n     ++\ttest main = \"$actual\" &&\n      +\ttest_bisect_state_missing BISECT_START &&\n      +\n      +\tbisect_start_and_finish --reset-when-found=original &&\n     -+\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n     -+\ttest main = \"$(git branch --show-current)\" &&\n     ++\tactual=$(git rev-parse HEAD) &&\n     ++\ttest \"$HASH4\" = \"$actual\" &&\n     ++\tactual=$(git branch --show-current) &&\n     ++\ttest main = \"$actual\" &&\n      +\ttest_bisect_state_missing BISECT_START\n      +'\n      +\n      +test_expect_success '\"git bisect start --reset-when-found=found\" leaves first bad checked out' '\n     -+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     ++\ttest_when_finished \"git bisect reset && git checkout main\" &&\n      +\tbisect_start_and_finish --reset-when-found=found &&\n     -+\ttest \"$HASH3\" = \"$(git rev-parse HEAD)\" &&\n     ++\tactual=$(git rev-parse HEAD) &&\n     ++\ttest \"$HASH3\" = \"$actual\" &&\n      +\ttest_bisect_state_missing BISECT_START\n      +'\n      +\n      +test_expect_success '\"git bisect run --reset-when-found\" defaults to original' '\n     -+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     ++\ttest_when_finished \"git bisect reset && git checkout main\" &&\n      +\tbisect_run_reset_when_found --reset-when-found &&\n     -+\ttest \"$HASH4\" = \"$(git rev-parse HEAD)\" &&\n     -+\ttest main = \"$(git branch --show-current)\" &&\n     ++\tactual=$(git rev-parse HEAD) &&\n     ++\ttest \"$HASH4\" = \"$actual\" &&\n     ++\tactual=$(git branch --show-current) &&\n     ++\ttest main = \"$actual\" &&\n      +\ttest_bisect_state_missing BISECT_START\n      +'\n      +\n      +test_expect_success '\"git bisect run --reset-when-found=found\" leaves first bad checked out' '\n     -+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     ++\ttest_when_finished \"git bisect reset && git checkout main\" &&\n      +\tbisect_run_reset_when_found --reset-when-found=found &&\n     -+\ttest \"$HASH3\" = \"$(git rev-parse HEAD)\" &&\n     ++\tactual=$(git rev-parse HEAD) &&\n     ++\ttest \"$HASH3\" = \"$actual\" &&\n      +\ttest_bisect_state_missing BISECT_START\n      +'\n      +\n      +test_expect_success '--reset-when-found rejects an unknown reset target' '\n     -+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     ++\ttest_when_finished \"git bisect reset && git checkout main\" &&\n      +\ttest_reset_when_found_fails \\\n      +\t\t\"invalid value for.*--reset-when-found.*unknown\" BISECT_START \\\n      +\t\tgit bisect start --reset-when-found=unknown $HASH4 $HASH2 &&\n     @@ t/t6030-bisect-porcelain.sh: test_expect_success '\"git bisect run\" simple case'\n      +test_expect_success '--reset-when-found cannot be used with --no-checkout' '\n      +\ttest_when_finished \"git bisect reset\" &&\n      +\ttest_reset_when_found_fails \\\n     -+\t\t\"cannot be used with.*--no-checkout\" BISECT_START \\\n     ++\t\t\"options .*--reset-when-found.* and .*--no-checkout.* cannot be used together\" BISECT_START \\\n      +\t\tgit bisect start --reset-when-found=original --no-checkout $HASH4 $HASH2 &&\n      +\n      +\tgit bisect start --no-checkout $HASH4 $HASH2 &&\n      +\ttest_reset_when_found_fails \\\n     -+\t\t\"cannot be used with.*--no-checkout\" BISECT_RESET_WHEN_FOUND \\\n     ++\t\t\"options .*--reset-when-found.* and .*--no-checkout.* cannot be used together\" BISECT_RESET_WHEN_FOUND \\\n      +\t\tgit bisect run --reset-when-found=found true\n      +'\n      +\n     @@ t/t6030-bisect-porcelain.sh: test_expect_success '\"git bisect run\" simple case'\n      +'\n      +\n      +test_expect_success '--reset-when-found does not leak into a later bisection' '\n     -+\ttest_when_finished \"git bisect reset; git checkout main\" &&\n     ++\ttest_when_finished \"git bisect reset && git checkout main\" &&\n      +\tbisect_start_and_finish --reset-when-found &&\n      +\n      +\tgit bisect start $HASH4 $HASH2 &&\n\n-- \ngitgitgadget\n"},{"id":"549386","messageId":"e39670edf4be8bc917a985666f200a88880212ce.1785577445.git.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.v4.git.git.1785577445.gitgitgadget@gmail.com","subject":"[PATCH v4 1/2] bisect: let bisect_reset() optionally check out quietly","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-08-01T09:44:04Z","receivedAt":"2026-08-01T09:44:11Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a \"quiet\" parameter to bisect_reset() that passes \"--quiet\" to the\ncheckout restoring the original HEAD, suppressing its progress and\nbranch-status output.\n\nNo caller sets the flag yet, so behavior is unchanged.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/bisect.c | 12 +++++++-----\n 1 file changed, 7 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 3264e2da54..1e0c043249 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -234,7 +234,7 @@ static int write_terms(const char *bad, const char *good)\n \treturn res;\n }\n \n-static int bisect_reset(const char *commit)\n+static int bisect_reset(const char *commit, int quiet)\n {\n \tstruct strbuf branch = STRBUF_INIT;\n \n@@ -255,8 +255,10 @@ static int bisect_reset(const char *commit)\n \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n \t\tcmd.git_cmd = 1;\n-\t\tstrvec_pushl(&cmd.args, \"checkout\", \"--ignore-other-worktrees\",\n-\t\t\t\tbranch.buf, \"--\", NULL);\n+\t\tstrvec_pushl(&cmd.args, \"checkout\", \"--ignore-other-worktrees\", NULL);\n+\t\tif (quiet)\n+\t\t\tstrvec_push(&cmd.args, \"--quiet\");\n+\t\tstrvec_pushl(&cmd.args, branch.buf, \"--\", NULL);\n \t\tif (run_command(&cmd)) {\n \t\t\terror(_(\"could not check out original\"\n \t\t\t\t\" HEAD '%s'. Try 'git bisect\"\n@@ -1096,7 +1098,7 @@ static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f\n \tif (is_empty_or_missing_file(filename))\n \t\treturn error(_(\"cannot read file '%s' for replaying\"), filename);\n \n-\tif (bisect_reset(NULL))\n+\tif (bisect_reset(NULL, 0))\n \t\treturn BISECT_FAILED;\n \n \tfp = fopen(filename, \"r\");\n@@ -1345,7 +1347,7 @@ static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNU\n \tif (argc > 1)\n \t\treturn error(_(\"'%s' requires either no argument or a commit\"),\n \t\t\t     \"git bisect reset\");\n-\treturn bisect_reset(argc ? argv[0] : NULL);\n+\treturn bisect_reset(argc ? argv[0] : NULL, 0);\n }\n \n static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,\n-- \ngitgitgadget\n\n"},{"id":"549387","messageId":"f5f370df1bab91872e32398386935d71d48a831b.1785577445.git.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.v4.git.git.1785577445.gitgitgadget@gmail.com","subject":"[PATCH v4 2/2] bisect: add --reset-when-found to leave when done","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-08-01T09:44:05Z","receivedAt":"2026-08-01T09:44:13Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen a bisection finishes, \"git bisect\" reports the first bad commit\nbut leaves the session active until \"git bisect reset\" is run by hand.\n\nAdd a \"--reset-when-found[=<where>]\" option, accepted by both \"git\nbisect start\" and \"git bisect run\", that resets as soon as the first\nbad commit is found. The \"original\" value returns to the commit checked\nout before \"git bisect start\", while \"found\" leaves the first bad commit\nchecked out; omitting the value defaults to \"original\".\n\nPersist the selected target in a BISECT_RESET_WHEN_FOUND state file\nand perform the reset quietly.\n\nFor \"git bisect run\", defer the reset until after the captured output\nis printed and BISECT_RUN is closed. This lets cleanup remove the file\non systems that cannot unlink an open file.\n\nReject this option together with \"--no-checkout\", since that mode must\nnot check out either target.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-bisect.adoc |  14 +++-\n bisect.c                      |   2 +\n builtin/bisect.c              | 136 ++++++++++++++++++++++++++++++----\n t/t6030-bisect-porcelain.sh   | 121 ++++++++++++++++++++++++++++++\n 4 files changed, 257 insertions(+), 16 deletions(-)\n\ndiff --git a/Documentation/git-bisect.adoc b/Documentation/git-bisect.adoc\nindex d2115b2990..aabddd42ca 100644\n--- a/Documentation/git-bisect.adoc\n+++ b/Documentation/git-bisect.adoc\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [synopsis]\n git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\n-\t\t [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\n+\t\t [--no-checkout] [--first-parent] [--reset-when-found[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\n git bisect (bad|new|<term-new>) [<rev>]\n git bisect (good|old|<term-old>) [<rev>...]\n git bisect terms [--term-(good|old) | --term-(bad|new)]\n@@ -20,7 +20,7 @@ git bisect reset [<commit>]\n git bisect (visualize|view)\n git bisect replay <logfile>\n git bisect log\n-git bisect run <cmd> [<arg>...]\n+git bisect run [--reset-when-found[=<where>]] <cmd> [<arg>...]\n git bisect help\n \n DESCRIPTION\n@@ -385,6 +385,16 @@ ignored.\n This option is particularly useful in avoiding false positives when a merged\n branch contained broken or non-buildable commits, but the merge itself was OK.\n \n+`--reset-when-found[=<where>]`::\n+\tOnce the first bad commit is found, report it and clean up the\n+\tbisection state. `<where>` may be `original` to return to the commit\n+\tchecked out before `git bisect start`, or `found` to leave the first\n+\tbad commit checked out. If `<where>` is omitted, it defaults to\n+\t`original`.\n++\n+This option may be given to `git bisect start` or to `git bisect run`. It\n+cannot be used for a bisection started with `--no-checkout`.\n+\n EXAMPLES\n --------\n \ndiff --git a/bisect.c b/bisect.c\nindex 94c7028d2a..d426fcd5a9 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -488,6 +488,7 @@ static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n static GIT_PATH_FUNC(git_path_bisect_terms, \"BISECT_TERMS\")\n static GIT_PATH_FUNC(git_path_bisect_first_parent, \"BISECT_FIRST_PARENT\")\n+static GIT_PATH_FUNC(git_path_bisect_reset_when_found, \"BISECT_RESET_WHEN_FOUND\")\n \n static void read_bisect_paths(struct strvec *array)\n {\n@@ -1211,6 +1212,7 @@ int bisect_clean_state(void)\n \tunlink_or_warn(git_path_bisect_run());\n \tunlink_or_warn(git_path_bisect_terms());\n \tunlink_or_warn(git_path_bisect_first_parent());\n+\tunlink_or_warn(git_path_bisect_reset_when_found());\n \t/*\n \t * Cleanup BISECT_START last to support the --no-checkout option\n \t * introduced in the commit 4796e823a.\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 1e0c043249..3267380edc 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -24,11 +24,12 @@ static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n static GIT_PATH_FUNC(git_path_bisect_names, \"BISECT_NAMES\")\n static GIT_PATH_FUNC(git_path_bisect_first_parent, \"BISECT_FIRST_PARENT\")\n+static GIT_PATH_FUNC(git_path_bisect_reset_when_found, \"BISECT_RESET_WHEN_FOUND\")\n static GIT_PATH_FUNC(git_path_bisect_run, \"BISECT_RUN\")\n \n #define BUILTIN_GIT_BISECT_START_USAGE \\\n \tN_(\"git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\\n\" \\\n-\t   \"                 [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\")\n+\t   \"                 [--no-checkout] [--first-parent] [--reset-when-found[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\")\n #define BUILTIN_GIT_BISECT_BAD_USAGE \\\n \tN_(\"git bisect (bad|new|<term-new>) [<rev>]\")\n #define BUILTIN_GIT_BISECT_GOOD_USAGE \\\n@@ -48,7 +49,7 @@ static GIT_PATH_FUNC(git_path_bisect_run, \"BISECT_RUN\")\n #define BUILTIN_GIT_BISECT_LOG_USAGE \\\n \t\"git bisect log\"\n #define BUILTIN_GIT_BISECT_RUN_USAGE \\\n-\tN_(\"git bisect run <cmd> [<arg>...]\")\n+\tN_(\"git bisect run [--reset-when-found[=<where>]] <cmd> [<arg>...]\")\n #define BUILTIN_GIT_BISECT_HELP_USAGE \\\n \t\"git bisect help\"\n \n@@ -68,6 +69,12 @@ static const char * const git_bisect_usage[] = {\n \tNULL\n };\n \n+enum reset_when_found_mode {\n+\tRESET_WHEN_FOUND_NONE,\n+\tRESET_WHEN_FOUND_TO_ORIGINAL,\n+\tRESET_WHEN_FOUND_TO_FOUND,\n+};\n+\n struct add_bisect_ref_data {\n \tstruct rev_info *revs;\n \tunsigned int object_flags;\n@@ -272,6 +279,61 @@ static int bisect_reset(const char *commit, int quiet)\n \treturn bisect_clean_state();\n }\n \n+static int parse_reset_when_found(const char *value,\n+\t\t\t\t  enum reset_when_found_mode *mode)\n+{\n+\tif (!strcmp(value, \"original\"))\n+\t\t*mode = RESET_WHEN_FOUND_TO_ORIGINAL;\n+\telse if (!strcmp(value, \"found\"))\n+\t\t*mode = RESET_WHEN_FOUND_TO_FOUND;\n+\telse\n+\t\treturn error(_(\"invalid value for '--reset-when-found': '%s'\"),\n+\t\t\t     value);\n+\n+\treturn 0;\n+}\n+\n+static const char *reset_when_found_mode_name(enum reset_when_found_mode mode)\n+{\n+\tswitch (mode) {\n+\tcase RESET_WHEN_FOUND_TO_ORIGINAL:\n+\t\treturn \"original\";\n+\tcase RESET_WHEN_FOUND_TO_FOUND:\n+\t\treturn \"found\";\n+\tcase RESET_WHEN_FOUND_NONE:\n+\t\tBUG(\"no name for unset reset-when-found mode\");\n+\t}\n+\tBUG(\"unknown reset-when-found mode %d\", mode);\n+}\n+\n+static int bisect_reset_when_found(struct bisect_terms *terms)\n+{\n+\tstruct strbuf value = STRBUF_INIT;\n+\tenum reset_when_found_mode mode;\n+\tchar *commit = NULL;\n+\tint res;\n+\n+\tif (strbuf_read_file(&value, git_path_bisect_reset_when_found(), 0) < 0) {\n+\t\tres = error_errno(_(\"could not read '%s'\"),\n+\t\t\t\t  git_path_bisect_reset_when_found());\n+\t\tgoto cleanup;\n+\t}\n+\tstrbuf_trim(&value);\n+\tif (parse_reset_when_found(value.buf, &mode)) {\n+\t\tres = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tif (mode == RESET_WHEN_FOUND_TO_FOUND)\n+\t\tcommit = xstrfmt(\"refs/bisect/%s\", terms->term_bad);\n+\tres = bisect_reset(commit, 1);\n+\n+cleanup:\n+\tfree(commit);\n+\tstrbuf_release(&value);\n+\treturn res;\n+}\n+\n static void log_commit(FILE *fp,\n \t\t       const char *fmt, const char *state,\n \t\t       struct commit *commit)\n@@ -682,7 +744,8 @@ static int bisect_successful(struct bisect_terms *terms)\n \treturn res;\n }\n \n-static enum bisect_error bisect_next(struct bisect_terms *terms, const char *prefix)\n+static enum bisect_error bisect_next(struct bisect_terms *terms,\n+\t\t\t\t     const char *prefix, bool defer_reset)\n {\n \tenum bisect_error res;\n \n@@ -697,6 +760,9 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n \n \tif (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n \t\tres = bisect_successful(terms);\n+\t\tif (!res && !defer_reset &&\n+\t\t    !is_empty_or_missing_file(git_path_bisect_reset_when_found()))\n+\t\t\tres = bisect_reset_when_found(terms);\n \t\treturn res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;\n \t} else if (res == BISECT_ONLY_SKIPPED_LEFT) {\n \t\tres = bisect_skipped_commits(terms);\n@@ -705,14 +771,15 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n \treturn res;\n }\n \n-static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix)\n+static enum bisect_error bisect_auto_next(struct bisect_terms *terms,\n+\t\t\t\t\t  const char *prefix, bool defer_reset)\n {\n \tif (bisect_next_check(terms, NULL)) {\n \t\tbisect_print_status(terms);\n \t\treturn BISECT_OK;\n \t}\n \n-\treturn bisect_next(terms, prefix);\n+\treturn bisect_next(terms, prefix, defer_reset);\n }\n \n static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n@@ -720,6 +787,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n {\n \tint no_checkout = 0;\n \tint first_parent_only = 0;\n+\tenum reset_when_found_mode reset_when_found = RESET_WHEN_FOUND_NONE;\n \tint i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;\n \tint flags, pathspec_pos;\n \tenum bisect_error res = BISECT_OK;\n@@ -752,6 +820,13 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \t\t\tno_checkout = 1;\n \t\t} else if (!strcmp(arg, \"--first-parent\")) {\n \t\t\tfirst_parent_only = 1;\n+\t\t} else if (!strcmp(arg, \"--reset-when-found\")) {\n+\t\t\treset_when_found = RESET_WHEN_FOUND_TO_ORIGINAL;\n+\t\t} else if (skip_prefix(arg, \"--reset-when-found=\", &arg)) {\n+\t\t\tif (parse_reset_when_found(arg, &reset_when_found)) {\n+\t\t\t\tres = BISECT_FAILED;\n+\t\t\t\tgoto finish;\n+\t\t\t}\n \t\t} else if (!strcmp(arg, \"--term-good\") ||\n \t\t\t !strcmp(arg, \"--term-old\")) {\n \t\t\ti++;\n@@ -789,6 +864,11 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \t\t\tbreak;\n \t\t}\n \t}\n+\tif (reset_when_found != RESET_WHEN_FOUND_NONE && no_checkout) {\n+\t\tres = error(_(\"options '%s' and '%s' cannot be used together\"),\n+\t\t\t    \"--reset-when-found\", \"--no-checkout\");\n+\t\tgoto finish;\n+\t}\n \tpathspec_pos = i;\n \n \t/*\n@@ -868,6 +948,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \tif (first_parent_only)\n \t\twrite_file(git_path_bisect_first_parent(), \"\\n\");\n \n+\tif (reset_when_found != RESET_WHEN_FOUND_NONE)\n+\t\twrite_file(git_path_bisect_reset_when_found(), \"%s\\n\",\n+\t\t\t   reset_when_found_mode_name(reset_when_found));\n+\n \tif (no_checkout) {\n \t\tif (repo_get_oid(the_repository, start_head.buf, &oid) < 0) {\n \t\t\tres = error(_(\"invalid ref: '%s'\"), start_head.buf);\n@@ -909,7 +993,7 @@ finish:\n \tif (res)\n \t\treturn res;\n \n-\tres = bisect_auto_next(terms, NULL);\n+\tres = bisect_auto_next(terms, NULL, false);\n \tif (!is_bisect_success(res))\n \t\tbisect_clean_state();\n \treturn res;\n@@ -948,7 +1032,7 @@ static int bisect_autostart(struct bisect_terms *terms)\n }\n \n static enum bisect_error bisect_state(struct bisect_terms *terms, int argc,\n-\t\t\t\t      const char **argv)\n+\t\t\t\t      const char **argv, bool defer_reset)\n {\n \tconst char *state;\n \tint i, verify_expected = 1;\n@@ -1025,7 +1109,7 @@ static enum bisect_error bisect_state(struct bisect_terms *terms, int argc,\n \t}\n \n \toid_array_clear(&revs);\n-\treturn bisect_auto_next(terms, NULL);\n+\treturn bisect_auto_next(terms, NULL, defer_reset);\n }\n \n static enum bisect_error bisect_log(void)\n@@ -1114,7 +1198,7 @@ static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f\n \tif (res)\n \t\treturn BISECT_FAILED;\n \n-\treturn bisect_auto_next(terms, NULL);\n+\treturn bisect_auto_next(terms, NULL, false);\n }\n \n static enum bisect_error bisect_skip(struct bisect_terms *terms, int argc,\n@@ -1148,7 +1232,7 @@ static enum bisect_error bisect_skip(struct bisect_terms *terms, int argc,\n \t\t\tstrvec_push(&argv_state, argv[i]);\n \t\t}\n \t}\n-\tres = bisect_state(terms, argv_state.nr, argv_state.v);\n+\tres = bisect_state(terms, argv_state.nr, argv_state.v, false);\n \n \tstrvec_clear(&argv_state);\n \treturn res;\n@@ -1246,6 +1330,8 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n {\n \tint res = BISECT_OK;\n \tstruct strbuf command = STRBUF_INIT;\n+\tenum reset_when_found_mode reset_when_found = RESET_WHEN_FOUND_NONE;\n+\tconst char *reset_when_found_arg;\n \tconst char *new_state;\n \tint temporary_stdout_fd, saved_stdout;\n \tint is_first_run = 1;\n@@ -1253,6 +1339,24 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n \tif (bisect_next_check(terms, NULL))\n \t\treturn BISECT_FAILED;\n \n+\tif (argc && !strcmp(argv[0], \"--reset-when-found\"))\n+\t\treset_when_found = RESET_WHEN_FOUND_TO_ORIGINAL;\n+\telse if (argc && skip_prefix(argv[0], \"--reset-when-found=\",\n+\t\t\t\t    &reset_when_found_arg)) {\n+\t\tif (parse_reset_when_found(reset_when_found_arg, &reset_when_found))\n+\t\t\treturn BISECT_FAILED;\n+\t}\n+\n+\tif (reset_when_found != RESET_WHEN_FOUND_NONE) {\n+\t\tif (refs_ref_exists(get_main_ref_store(the_repository), \"BISECT_HEAD\"))\n+\t\t\treturn error(_(\"options '%s' and '%s' cannot be used together\"),\n+\t\t\t\t     \"--reset-when-found\", \"--no-checkout\");\n+\t\twrite_file(git_path_bisect_reset_when_found(), \"%s\\n\",\n+\t\t\t   reset_when_found_mode_name(reset_when_found));\n+\t\targc--;\n+\t\targv++;\n+\t}\n+\n \tif (!argc) {\n \t\terror(_(\"bisect run failed: no command provided.\"));\n \t\treturn BISECT_FAILED;\n@@ -1311,7 +1415,7 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n \t\tsaved_stdout = dup(1);\n \t\tdup2(temporary_stdout_fd, 1);\n \n-\t\tres = bisect_state(terms, 1, &new_state);\n+\t\tres = bisect_state(terms, 1, &new_state, true);\n \n \t\tfflush(stdout);\n \t\tdup2(saved_stdout, 1);\n@@ -1327,7 +1431,11 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n \t\t\tres = BISECT_OK;\n \t\t} else if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n \t\t\tprintf(_(\"bisect found first '%s' commit\\n\"), terms->term_bad);\n-\t\t\tres = BISECT_OK;\n+\t\t\tif (!is_empty_or_missing_file(git_path_bisect_reset_when_found()) &&\n+\t\t\t    bisect_reset_when_found(terms))\n+\t\t\t\tres = BISECT_FAILED;\n+\t\t\telse\n+\t\t\t\tres = BISECT_OK;\n \t\t} else if (res) {\n \t\t\terror(_(\"bisect run failed: 'git bisect %s'\"\n \t\t\t\t\" exited with error code %d\"), new_state, res);\n@@ -1386,7 +1494,7 @@ static int cmd_bisect__next(int argc, const char **argv UNUSED, const char *pref\n \t\treturn error(_(\"'%s' requires 0 arguments\"),\n \t\t\t     \"git bisect next\");\n \tget_terms(&terms);\n-\tres = bisect_next(&terms, prefix);\n+\tres = bisect_next(&terms, prefix, false);\n \tfree_terms(&terms);\n \treturn res;\n }\n@@ -1489,7 +1597,7 @@ int cmd_bisect(int argc,\n \t\t    !one_of(argv[0], terms.term_good, terms.term_bad, NULL))\n \t\t\tusage_msg_optf(_(\"unknown command: '%s'\"), git_bisect_usage,\n \t\t\t\t       options, argv[0]);\n-\t\tres = bisect_state(&terms, argc, argv);\n+\t\tres = bisect_state(&terms, argc, argv, false);\n \t\tfree_terms(&terms);\n \t} else {\n \t\targc--;\ndiff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh\nindex 338df4f886..a7588222a8 100755\n--- a/t/t6030-bisect-porcelain.sh\n+++ b/t/t6030-bisect-porcelain.sh\n@@ -43,6 +43,42 @@ test_bisect_usage () {\n \ttest_cmp expect actual\n }\n \n+test_bisect_state_file () {\n+\tlocal file &&\n+\tfile=$(git rev-parse --git-path \"$1\") &&\n+\ttest_path_is_file \"$file\"\n+}\n+\n+test_bisect_state_missing () {\n+\tlocal file &&\n+\tfile=$(git rev-parse --git-path \"$1\") &&\n+\ttest_path_is_missing \"$file\"\n+}\n+\n+bisect_start_and_finish () {\n+\tgit bisect start \"$1\" $HASH4 $HASH2 &&\n+\tgit bisect bad\n+}\n+\n+bisect_run_reset_when_found () {\n+\twrite_script test_script.sh <<-\\EOF &&\n+\t! grep Another hello >/dev/null\n+\tEOF\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect run \"$1\" ./test_script.sh >my_bisect_log.txt &&\n+\ttest_grep \"$HASH3 is the first .bad. commit\" my_bisect_log.txt &&\n+\ttest_bisect_state_missing BISECT_RUN\n+}\n+\n+test_reset_when_found_fails () {\n+\tlocal pattern=\"$1\" &&\n+\tlocal state_file=\"$2\" &&\n+\tshift 2 &&\n+\ttest_must_fail \"$@\" 2>err &&\n+\ttest_grep -- \"$pattern\" err &&\n+\ttest_bisect_state_missing \"$state_file\"\n+}\n+\n test_expect_success 'bisect usage' \"\n \ttest_bisect_usage 1 git bisect reset extra1 extra2 <<-\\EOF &&\n \terror: 'git bisect reset' requires either no argument or a commit\n@@ -453,6 +489,91 @@ test_expect_success '\"git bisect run\" simple case' '\n \tgit bisect reset\n '\n \n+test_expect_success '\"git bisect start --reset-when-found\" defaults to original' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\tgit checkout main &&\n+\tbisect_start_and_finish --reset-when-found &&\n+\tactual=$(git rev-parse HEAD) &&\n+\ttest \"$HASH4\" = \"$actual\" &&\n+\tactual=$(git branch --show-current) &&\n+\ttest main = \"$actual\" &&\n+\ttest_bisect_state_missing BISECT_START &&\n+\n+\tbisect_start_and_finish --reset-when-found=original &&\n+\tactual=$(git rev-parse HEAD) &&\n+\ttest \"$HASH4\" = \"$actual\" &&\n+\tactual=$(git branch --show-current) &&\n+\ttest main = \"$actual\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '\"git bisect start --reset-when-found=found\" leaves first bad checked out' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\tbisect_start_and_finish --reset-when-found=found &&\n+\tactual=$(git rev-parse HEAD) &&\n+\ttest \"$HASH3\" = \"$actual\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '\"git bisect run --reset-when-found\" defaults to original' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\tbisect_run_reset_when_found --reset-when-found &&\n+\tactual=$(git rev-parse HEAD) &&\n+\ttest \"$HASH4\" = \"$actual\" &&\n+\tactual=$(git branch --show-current) &&\n+\ttest main = \"$actual\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '\"git bisect run --reset-when-found=found\" leaves first bad checked out' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\tbisect_run_reset_when_found --reset-when-found=found &&\n+\tactual=$(git rev-parse HEAD) &&\n+\ttest \"$HASH3\" = \"$actual\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '--reset-when-found rejects an unknown reset target' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"invalid value for.*--reset-when-found.*unknown\" BISECT_START \\\n+\t\tgit bisect start --reset-when-found=unknown $HASH4 $HASH2 &&\n+\n+\tgit bisect start $HASH4 $HASH2 &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"invalid value for.*--reset-when-found.*unknown\" \\\n+\t\tBISECT_RESET_WHEN_FOUND \\\n+\t\tgit bisect run --reset-when-found=unknown true\n+'\n+\n+test_expect_success '--reset-when-found cannot be used with --no-checkout' '\n+\ttest_when_finished \"git bisect reset\" &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"options .*--reset-when-found.* and .*--no-checkout.* cannot be used together\" BISECT_START \\\n+\t\tgit bisect start --reset-when-found=original --no-checkout $HASH4 $HASH2 &&\n+\n+\tgit bisect start --no-checkout $HASH4 $HASH2 &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"options .*--reset-when-found.* and .*--no-checkout.* cannot be used together\" BISECT_RESET_WHEN_FOUND \\\n+\t\tgit bisect run --reset-when-found=found true\n+'\n+\n+test_expect_success 'without --reset-when-found the bisection state is kept' '\n+\ttest_when_finished \"git bisect reset\" &&\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect bad &&\n+\ttest_bisect_state_file BISECT_START\n+'\n+\n+test_expect_success '--reset-when-found does not leak into a later bisection' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\tbisect_start_and_finish --reset-when-found &&\n+\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect bad &&\n+\ttest_bisect_state_file BISECT_START\n+'\n+\n # We want to automatically find the commit that\n # added \"Ciao\" into hello.\n test_expect_success '\"git bisect run\" with more complex \"git bisect start\"' '\n-- \ngitgitgadget\n"},{"id":"549411","messageId":"xmqqa4r58xem.fsf@gitster.g","threadId":"66011","inReplyTo":"e39670edf4be8bc917a985666f200a88880212ce.1785577445.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 1/2] bisect: let bisect_reset() optionally check out quietly","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-01T19:15:45Z","receivedAt":"2026-08-01T19:15:48Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> Add a \"quiet\" parameter to bisect_reset() that passes \"--quiet\" to the\n> checkout restoring the original HEAD, suppressing its progress and\n> branch-status output.\n>\n> No caller sets the flag yet, so behavior is unchanged.\n>\n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> ---\n>  builtin/bisect.c | 12 +++++++-----\n>  1 file changed, 7 insertions(+), 5 deletions(-)\n>\n> diff --git a/builtin/bisect.c b/builtin/bisect.c\n> index 3264e2da54..1e0c043249 100644\n> --- a/builtin/bisect.c\n> +++ b/builtin/bisect.c\n> @@ -234,7 +234,7 @@ static int write_terms(const char *bad, const char *good)\n>  \treturn res;\n>  }\n>  \n> -static int bisect_reset(const char *commit)\n> +static int bisect_reset(const char *commit, int quiet)\n\nNot a huge deal but given that you are adding \"bool defer_reset\" in\nthe next step, it may be more consistent to add this also as a bool?\n\n> @@ -255,8 +255,10 @@ static int bisect_reset(const char *commit)\n>  \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n>  \n>  \t\tcmd.git_cmd = 1;\n> -\t\tstrvec_pushl(&cmd.args, \"checkout\", \"--ignore-other-worktrees\",\n> -\t\t\t\tbranch.buf, \"--\", NULL);\n> +\t\tstrvec_pushl(&cmd.args, \"checkout\", \"--ignore-other-worktrees\", NULL);\n> +\t\tif (quiet)\n> +\t\t\tstrvec_push(&cmd.args, \"--quiet\");\n> +\t\tstrvec_pushl(&cmd.args, branch.buf, \"--\", NULL);\n>  \t\tif (run_command(&cmd)) {\n>  \t\t\terror(_(\"could not check out original\"\n>  \t\t\t\t\" HEAD '%s'. Try 'git bisect\"\n> @@ -1096,7 +1098,7 @@ static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f\n>  \tif (is_empty_or_missing_file(filename))\n>  \t\treturn error(_(\"cannot read file '%s' for replaying\"), filename);\n>  \n> -\tif (bisect_reset(NULL))\n> +\tif (bisect_reset(NULL, 0))\n>  \t\treturn BISECT_FAILED;\n>  \n>  \tfp = fopen(filename, \"r\");\n> @@ -1345,7 +1347,7 @@ static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNU\n>  \tif (argc > 1)\n>  \t\treturn error(_(\"'%s' requires either no argument or a commit\"),\n>  \t\t\t     \"git bisect reset\");\n> -\treturn bisect_reset(argc ? argv[0] : NULL);\n> +\treturn bisect_reset(argc ? argv[0] : NULL, 0);\n>  }\n>  \n>  static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,\n"},{"id":"549412","messageId":"xmqqwlu97h1k.fsf@gitster.g","threadId":"66011","inReplyTo":"f5f370df1bab91872e32398386935d71d48a831b.1785577445.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 2/2] bisect: add --reset-when-found to leave when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-01T19:54:31Z","receivedAt":"2026-08-01T19:54:33Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> @@ -1211,6 +1212,7 @@ int bisect_clean_state(void)\n>  \tunlink_or_warn(git_path_bisect_run());\n>  \tunlink_or_warn(git_path_bisect_terms());\n>  \tunlink_or_warn(git_path_bisect_first_parent());\n> +\tunlink_or_warn(git_path_bisect_reset_when_found());\n>  \t/*\n>  \t * Cleanup BISECT_START last to support the --no-checkout option\n>  \t * introduced in the commit 4796e823a.\n\nOK.  If we\n\n> +static int bisect_reset_when_found(struct bisect_terms *terms)\n> +{\n> +\tstruct strbuf value = STRBUF_INIT;\n> +\tenum reset_when_found_mode mode;\n> +\tchar *commit = NULL;\n> +\tint res;\n> +\n> +\tif (strbuf_read_file(&value, git_path_bisect_reset_when_found(), 0) < 0) {\n> +\t\tres = error_errno(_(\"could not read '%s'\"),\n> +\t\t\t\t  git_path_bisect_reset_when_found());\n> +\t\tgoto cleanup;\n> +\t}\n\nWe expect that the caller calls this function only when we are doing\n\"--reset-when-found\"; otherwise we would give an error message from\nhere even though we do not cause any damage otherwise.\n\nWe also expect that the callers refrain from calling this function\nwhen bisect_next() that they eventually reach would not want to\nimmediately reset.\n\nThe defer_reset arrangement looks somewhat ugly even though what it\nachieves may be a worthy thing to do.  Is the only code path that\npasses defer_reset==true down the call chain the bisect_run()\ncodepath, to give that single caller a chance to close files that\nbisect_reset() would remove by calling bisect_clean_state()?\n\nI am wondering if the result of solving it slightly differently may\ngive us cleaner and easier to follow code, namely, we stop calling\nbisect_clean_state() from bisect_reset().  Of course you would need\nto find different place to call bisect_clean_state() to compensate,\nif we go that route, but how many code paths do we have that depends\non bisect_reset() calling biesct_clean_state()?\n\nAmong existing callers of bisect_reset():\n\n - Does replay have to call reset?  Just like start does, isn't it\n   sufficient to call clean_state?\n\n - cmd_bisect__reset() calls reset and returns, but it can call\n   clean fater reset returns, if we need to make reset not to call\n   clean.\n\n> +\tstrbuf_trim(&value);\n> +\tif (parse_reset_when_found(value.buf, &mode)) {\n> +\t\tres = -1;\n> +\t\tgoto cleanup;\n> +\t}\n> +\n> +\tif (mode == RESET_WHEN_FOUND_TO_FOUND)\n> +\t\tcommit = xstrfmt(\"refs/bisect/%s\", terms->term_bad);\n> +\tres = bisect_reset(commit, 1);\n> +\n> +cleanup:\n> +\tfree(commit);\n> +\tstrbuf_release(&value);\n> +\treturn res;\n> +}\n\nOK, \"commit\" is NULL unless mode specifies TO_FOUND in which case we\njump to the bad commit we found.  bisect_reset() knows that commit==NULL\nmeans we go back to where we started.  OK.\n\n> @@ -697,6 +760,9 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n>  \n>  \tif (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n>  \t\tres = bisect_successful(terms);\n> +\t\tif (!res && !defer_reset &&\n> +\t\t    !is_empty_or_missing_file(git_path_bisect_reset_when_found()))\n> +\t\t\tres = bisect_reset_when_found(terms);\n>  \t\treturn res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;\n>  \t} else if (res == BISECT_ONLY_SKIPPED_LEFT) {\n>  \t\tres = bisect_skipped_commits(terms);\n\nThis is the first ugliness I mentioned earlier.\n\n> @@ -1311,7 +1415,7 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n>  \t\tsaved_stdout = dup(1);\n>  \t\tdup2(temporary_stdout_fd, 1);\n>  \n> -\t\tres = bisect_state(terms, 1, &new_state);\n> +\t\tres = bisect_state(terms, 1, &new_state, true);\n>  \n>  \t\tfflush(stdout);\n>  \t\tdup2(saved_stdout, 1);\n> @@ -1327,7 +1431,11 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n>  \t\t\tres = BISECT_OK;\n>  \t\t} else if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n>  \t\t\tprintf(_(\"bisect found first '%s' commit\\n\"), terms->term_bad);\n> -\t\t\tres = BISECT_OK;\n> +\t\t\tif (!is_empty_or_missing_file(git_path_bisect_reset_when_found()) &&\n> +\t\t\t    bisect_reset_when_found(terms))\n> +\t\t\t\tres = BISECT_FAILED;\n> +\t\t\telse\n> +\t\t\t\tres = BISECT_OK;\n>  \t\t} else if (res) {\n>  \t\t\terror(_(\"bisect run failed: 'git bisect %s'\"\n>  \t\t\t\t\" exited with error code %d\"), new_state, res);\n\nAnd these are the second one, that made the first one needed.\n\nAnother thing that I find a bit iffy is that earlier we said:\n\n    We expect that the caller calls this function only when we are doing\n    \"--reset-when-found\"; otherwise we would give an error message from\n    here even though we do not cause any damage otherwise.\n\n    We also expect that the callers refrain from calling this function\n    when bisect_next() that they eventually reach would not want to\n    immediately reset.\n\nbut the way the callers see if \"--reset-when-found\" is in effect\nlooks quite ad-hoc.  Instead of sprinkling \"do we have that file in\nthe filesystem and what does it say?\" all over the place, I wonder\nif it is simpler to reason about if we do these checks upfront and\nstore the parsed result in a variable, so that places that say\n!is_empty_or_missing_file(...) etc. do not have to?  After all, we\ndo not keep calling get_terms() in the middle of operation, and\ninstead use values from \"struct bisect_terms\" that somebody else\nprepared much earlier before terms->term_good and terms->term_bad\nare used, right?  Shouldn't it be handled pretty much the same way?\n\n"},{"id":"549417","messageId":"xmqqo6fl7c4w.fsf@gitster.g","threadId":"66011","inReplyTo":"xmqqwlu97h1k.fsf@gitster.g","subject":"Re: [PATCH v4 2/2] bisect: add --reset-when-found to leave when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-01T21:40:31Z","receivedAt":"2026-08-01T21:40:33Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> The defer_reset arrangement looks somewhat ugly even though what it\n> achieves may be a worthy thing to do.  Is the only code path that\n> passes defer_reset==true down the call chain the bisect_run()\n> codepath, to give that single caller a chance to close files that\n> bisect_reset() would remove by calling bisect_clean_state()?\n>\n> I am wondering if the result of solving it slightly differently may\n> give us cleaner and easier to follow code, namely, we stop calling\n> bisect_clean_state() from bisect_reset().  Of course you would need\n> to find different place to call bisect_clean_state() to compensate,\n> if we go that route, but how many code paths do we have that depends\n> on bisect_reset() calling biesct_clean_state()?\n\nConceptually, what you want out of this new feature is:\n\n    Please run \"git bisect <anything>\" normally.  Make sure you\n    notice when the command completed and found the culprit, And\n    when you notice it, run \"git bisect reset\" (or \"git bisect reset\n    <oid-of-bad-commit>\").\n\nStepping back even further, shouldn't this be doable *a* *lot*\nsimpler, given that existing code does not locally exit(0)?\nWouldn't it be the matter of\n\n - Add --reset-when-finished option parsing to the main command and\n   the subcommands that want to parse it.\n\n - Make sure all indivudual cmd_bisect__<subcommand> returns the\n   result code, instead of calling exit(), and the result code\n   includes those INTERNAL ones.\n\n - Tweak bisect_next_all() to report the found commit when it\n   returns BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND.  Perhaps\n   bisect_next() can leave it in a file-scope static variable.\n\n - Instead of doing\n\n\treturn is_bisect_success(res) ? 0 : -res;\n\n   at the end of cmd_bisect(), if \"--reset-when-finished\" is in\n   effect, also react to BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND and\n   use the commit bisect_next() received from bisect_next_all() as\n   needed and do what bisect_reset() does.  Presumably at that\n   point, files that are problematic on Windows would all already\n   been closed, right?\n\nThat way, almost all the changes needed to cmd_bisect__<subcommand>\nand below will be pure clean-up changes.  \n\nHmm?\n"},{"id":"549431","messageId":"pull.2335.v5.git.git.1785663075.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.v4.git.git.1785577445.gitgitgadget@gmail.com","subject":"[PATCH v5 0/2] bisect: add --reset-when-found to leave when done","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-08-02T09:31:13Z","receivedAt":"2026-08-02T09:31:18Z","isPatch":true,"body":"Add a --reset-when-found option to git bisect that resets the bisect session\nwhen culprit is found.\n\nChanges in v5:\n\n * Move automatic reset handling to cmd_bisect() after subcommand resources\n   are closed.\n * Propagate the first-bad commit OID from bisect_next_all() and remove\n   defer_reset plumbing.\n * Separate checkout from state cleanup in bisect_reset(), and use bool for\n   its quiet flag.\n\nChanges in v4:\n\n * Simplify translation calls.\n * Avoid git subshell calls in tests, that can bury errors.\n\nChanges in v3:\n\n * Rename --auto-reset to --reset-when-found, including internal names.\n * Defer git bisect run cleanup until captured output is printed and\n   BISECT_RUN is closed. Drop the open-descriptor preparatory change,\n   retaining the existing filename-based output handling.\n\nChanges in v2:\n\n * Add option --auto-reset[=<where>] with option to go to final commit as\n   well as original.\n * Refactored tests.\n\nHarald Nordgren (2):\n  bisect: let bisect_reset() optionally check out quietly\n  bisect: add --reset-when-found to leave when done\n\n Documentation/git-bisect.adoc |  14 ++-\n bisect.c                      |   6 +-\n bisect.h                      |   3 +-\n builtin/bisect.c              | 165 +++++++++++++++++++++++++++++++---\n t/t6030-bisect-porcelain.sh   | 121 +++++++++++++++++++++++++\n 5 files changed, 292 insertions(+), 17 deletions(-)\n\n\nbase-commit: a97fcc37c2bc6340a8d7ce78dedf227aac4e9aa7\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2335%2FHaraldNordgren%2Fbisect-auto-reset-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2335/HaraldNordgren/bisect-auto-reset-v5\nPull-Request: https://github.com/git/git/pull/2335\n\nRange-diff vs v4:\n\n 1:  e39670edf4 ! 1:  b6ab87fd29 bisect: let bisect_reset() optionally check out quietly\n     @@ builtin/bisect.c: static int write_terms(const char *bad, const char *good)\n       }\n       \n      -static int bisect_reset(const char *commit)\n     -+static int bisect_reset(const char *commit, int quiet)\n     ++static int bisect_reset(const char *commit, bool quiet)\n       {\n       \tstruct strbuf branch = STRBUF_INIT;\n       \n     @@ builtin/bisect.c: static enum bisect_error bisect_replay(struct bisect_terms *te\n       \t\treturn error(_(\"cannot read file '%s' for replaying\"), filename);\n       \n      -\tif (bisect_reset(NULL))\n     -+\tif (bisect_reset(NULL, 0))\n     ++\tif (bisect_reset(NULL, false))\n       \t\treturn BISECT_FAILED;\n       \n       \tfp = fopen(filename, \"r\");\n     @@ builtin/bisect.c: static int cmd_bisect__reset(int argc, const char **argv, cons\n       \t\treturn error(_(\"'%s' requires either no argument or a commit\"),\n       \t\t\t     \"git bisect reset\");\n      -\treturn bisect_reset(argc ? argv[0] : NULL);\n     -+\treturn bisect_reset(argc ? argv[0] : NULL, 0);\n     ++\treturn bisect_reset(argc ? argv[0] : NULL, false);\n       }\n       \n       static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,\n 2:  f5f370df1b ! 2:  ec362f3b82 bisect: add --reset-when-found to leave when done\n     @@ Commit message\n          Persist the selected target in a BISECT_RESET_WHEN_FOUND state file\n          and perform the reset quietly.\n      \n     -    For \"git bisect run\", defer the reset until after the captured output\n     -    is printed and BISECT_RUN is closed. This lets cleanup remove the file\n     -    on systems that cannot unlink an open file.\n     +    Propagate the internal first-bad result and its commit to\n     +    cmd_bisect(), which performs the reset after the subcommand has\n     +    returned. For \"git bisect run\", this means BISECT_RUN has been printed\n     +    and closed before cleanup, which also works on systems that cannot\n     +    unlink an open file.\n      \n          Reject this option together with \"--no-checkout\", since that mode must\n          not check out either target.\n     @@ bisect.c: static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n       \n       static void read_bisect_paths(struct strvec *array)\n       {\n     +@@ bisect.c: void read_bisect_terms(char **read_bad, char **read_good)\n     +  * the end of bisect_helper::cmd_bisect__helper() helps bypassing\n     +  * all the code related to finding a commit to test.\n     +  */\n     +-enum bisect_error bisect_next_all(struct repository *r, const char *prefix)\n     ++enum bisect_error bisect_next_all(struct repository *r, const char *prefix,\n     ++\t\t\t\t  struct object_id *first_bad)\n     + {\n     + \tstruct strvec rev_argv = STRVEC_INIT;\n     + \tstruct rev_info revs = REV_INFO_INIT;\n     +@@ bisect.c: enum bisect_error bisect_next_all(struct repository *r, const char *prefix)\n     + \t\tres = error_if_skipped_commits(tried, current_bad_oid);\n     + \t\tif (res)\n     + \t\t\tgoto cleanup;\n     ++\t\toidcpy(first_bad, bisect_rev);\n     + \t\tprintf(\"%s is the first '%s' commit\\n\", oid_to_hex(bisect_rev),\n     + \t\t\tterm_bad);\n     + \n      @@ bisect.c: int bisect_clean_state(void)\n       \tunlink_or_warn(git_path_bisect_run());\n       \tunlink_or_warn(git_path_bisect_terms());\n     @@ bisect.c: int bisect_clean_state(void)\n       \t * Cleanup BISECT_START last to support the --no-checkout option\n       \t * introduced in the commit 4796e823a.\n      \n     + ## bisect.h ##\n     +@@ bisect.h: struct bisect_state {\n     + \tunsigned int nr_bad;\n     + };\n     + \n     +-enum bisect_error bisect_next_all(struct repository *r, const char *prefix);\n     ++enum bisect_error bisect_next_all(struct repository *r, const char *prefix,\n     ++\t\t\t\t  struct object_id *first_bad);\n     + \n     + int estimate_bisect_steps(int all);\n     + \n     +\n       ## builtin/bisect.c ##\n      @@ builtin/bisect.c: static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n       static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n     @@ builtin/bisect.c: static const char * const git_bisect_usage[] = {\n       struct add_bisect_ref_data {\n       \tstruct rev_info *revs;\n       \tunsigned int object_flags;\n     -@@ builtin/bisect.c: static int bisect_reset(const char *commit, int quiet)\n     - \treturn bisect_clean_state();\n     - }\n     +@@ builtin/bisect.c: struct bisect_terms {\n     + \tchar *term_bad;\n     + };\n       \n     ++static struct object_id first_bad_oid;\n     ++\n     + static void free_terms(struct bisect_terms *terms)\n     + {\n     + \tFREE_AND_NULL(terms->term_good);\n     +@@ builtin/bisect.c: static int bisect_reset(const char *commit, bool quiet)\n     + \t}\n     + \n     + \tstrbuf_release(&branch);\n     +-\treturn bisect_clean_state();\n     ++\treturn 0;\n     ++}\n     ++\n      +static int parse_reset_when_found(const char *value,\n      +\t\t\t\t  enum reset_when_found_mode *mode)\n      +{\n     @@ builtin/bisect.c: static int bisect_reset(const char *commit, int quiet)\n      +\tBUG(\"unknown reset-when-found mode %d\", mode);\n      +}\n      +\n     -+static int bisect_reset_when_found(struct bisect_terms *terms)\n     ++static int read_reset_when_found(enum reset_when_found_mode *mode)\n      +{\n      +\tstruct strbuf value = STRBUF_INIT;\n     -+\tenum reset_when_found_mode mode;\n     -+\tchar *commit = NULL;\n     -+\tint res;\n     ++\tint res = 0;\n     ++\n     ++\t*mode = RESET_WHEN_FOUND_NONE;\n     ++\tif (is_empty_or_missing_file(git_path_bisect_reset_when_found()))\n     ++\t\treturn 0;\n      +\n      +\tif (strbuf_read_file(&value, git_path_bisect_reset_when_found(), 0) < 0) {\n      +\t\tres = error_errno(_(\"could not read '%s'\"),\n      +\t\t\t\t  git_path_bisect_reset_when_found());\n     -+\t\tgoto cleanup;\n     ++\t\tgoto out;\n      +\t}\n      +\tstrbuf_trim(&value);\n     -+\tif (parse_reset_when_found(value.buf, &mode)) {\n     ++\tif (parse_reset_when_found(value.buf, mode))\n      +\t\tres = -1;\n     -+\t\tgoto cleanup;\n     -+\t}\n      +\n     -+\tif (mode == RESET_WHEN_FOUND_TO_FOUND)\n     -+\t\tcommit = xstrfmt(\"refs/bisect/%s\", terms->term_bad);\n     -+\tres = bisect_reset(commit, 1);\n     -+\n     -+cleanup:\n     -+\tfree(commit);\n     ++out:\n      +\tstrbuf_release(&value);\n      +\treturn res;\n      +}\n      +\n     ++static int bisect_reset_when_found(enum reset_when_found_mode mode)\n     ++{\n     ++\tchar first_bad_hex[GIT_MAX_HEXSZ + 1];\n     ++\tconst char *commit = NULL;\n     ++\tint res;\n     ++\n     ++\tif (mode == RESET_WHEN_FOUND_TO_FOUND)\n     ++\t\tcommit = oid_to_hex_r(first_bad_hex, &first_bad_oid);\n     ++\telse if (mode == RESET_WHEN_FOUND_NONE)\n     ++\t\tBUG(\"automatic reset requested without a reset mode\");\n     ++\n     ++\tres = bisect_reset(commit, true);\n     ++\tif (!res)\n     ++\t\tres = bisect_clean_state();\n     ++\n     ++\treturn res;\n     + }\n     + \n       static void log_commit(FILE *fp,\n     - \t\t       const char *fmt, const char *state,\n     - \t\t       struct commit *commit)\n      @@ builtin/bisect.c: static int bisect_successful(struct bisect_terms *terms)\n       \treturn res;\n       }\n       \n      -static enum bisect_error bisect_next(struct bisect_terms *terms, const char *prefix)\n      +static enum bisect_error bisect_next(struct bisect_terms *terms,\n     -+\t\t\t\t     const char *prefix, bool defer_reset)\n     ++\t\t\t\t     const char *prefix)\n       {\n       \tenum bisect_error res;\n       \n      @@ builtin/bisect.c: static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n     + \t\treturn BISECT_FAILED;\n     + \n     + \t/* Perform all bisection computation */\n     +-\tres = bisect_next_all(the_repository, prefix);\n     ++\tres = bisect_next_all(the_repository, prefix, &first_bad_oid);\n       \n       \tif (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n       \t\tres = bisect_successful(terms);\n     -+\t\tif (!res && !defer_reset &&\n     -+\t\t    !is_empty_or_missing_file(git_path_bisect_reset_when_found()))\n     -+\t\t\tres = bisect_reset_when_found(terms);\n     - \t\treturn res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;\n     - \t} else if (res == BISECT_ONLY_SKIPPED_LEFT) {\n     - \t\tres = bisect_skipped_commits(terms);\n      @@ builtin/bisect.c: static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n       \treturn res;\n       }\n       \n      -static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix)\n      +static enum bisect_error bisect_auto_next(struct bisect_terms *terms,\n     -+\t\t\t\t\t  const char *prefix, bool defer_reset)\n     ++\t\t\t\t\t  const char *prefix)\n       {\n       \tif (bisect_next_check(terms, NULL)) {\n       \t\tbisect_print_status(terms);\n     - \t\treturn BISECT_OK;\n     - \t}\n     - \n     --\treturn bisect_next(terms, prefix);\n     -+\treturn bisect_next(terms, prefix, defer_reset);\n     - }\n     - \n     - static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n      @@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n     - {\n     - \tint no_checkout = 0;\n     - \tint first_parent_only = 0;\n     + \tstruct strbuf bisect_names = STRBUF_INIT;\n     + \tstruct object_id head_oid;\n     + \tstruct object_id oid;\n      +\tenum reset_when_found_mode reset_when_found = RESET_WHEN_FOUND_NONE;\n     - \tint i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;\n     - \tint flags, pathspec_pos;\n     - \tenum bisect_error res = BISECT_OK;\n     + \tconst char *head;\n     + \n     + \tif (is_bare_repository(the_repository))\n      @@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n       \t\t\tno_checkout = 1;\n       \t\t} else if (!strcmp(arg, \"--first-parent\")) {\n     @@ builtin/bisect.c: static enum bisect_error bisect_start(struct bisect_terms *ter\n       \tif (no_checkout) {\n       \t\tif (repo_get_oid(the_repository, start_head.buf, &oid) < 0) {\n       \t\t\tres = error(_(\"invalid ref: '%s'\"), start_head.buf);\n     -@@ builtin/bisect.c: finish:\n     - \tif (res)\n     - \t\treturn res;\n     - \n     --\tres = bisect_auto_next(terms, NULL);\n     -+\tres = bisect_auto_next(terms, NULL, false);\n     - \tif (!is_bisect_success(res))\n     - \t\tbisect_clean_state();\n     - \treturn res;\n     -@@ builtin/bisect.c: static int bisect_autostart(struct bisect_terms *terms)\n     - }\n     - \n     - static enum bisect_error bisect_state(struct bisect_terms *terms, int argc,\n     --\t\t\t\t      const char **argv)\n     -+\t\t\t\t      const char **argv, bool defer_reset)\n     - {\n     - \tconst char *state;\n     - \tint i, verify_expected = 1;\n     -@@ builtin/bisect.c: static enum bisect_error bisect_state(struct bisect_terms *terms, int argc,\n     - \t}\n     - \n     - \toid_array_clear(&revs);\n     --\treturn bisect_auto_next(terms, NULL);\n     -+\treturn bisect_auto_next(terms, NULL, defer_reset);\n     - }\n     - \n     - static enum bisect_error bisect_log(void)\n      @@ builtin/bisect.c: static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f\n     - \tif (res)\n     - \t\treturn BISECT_FAILED;\n     + \tif (is_empty_or_missing_file(filename))\n     + \t\treturn error(_(\"cannot read file '%s' for replaying\"), filename);\n       \n     --\treturn bisect_auto_next(terms, NULL);\n     -+\treturn bisect_auto_next(terms, NULL, false);\n     - }\n     - \n     - static enum bisect_error bisect_skip(struct bisect_terms *terms, int argc,\n     -@@ builtin/bisect.c: static enum bisect_error bisect_skip(struct bisect_terms *terms, int argc,\n     - \t\t\tstrvec_push(&argv_state, argv[i]);\n     - \t\t}\n     - \t}\n     --\tres = bisect_state(terms, argv_state.nr, argv_state.v);\n     -+\tres = bisect_state(terms, argv_state.nr, argv_state.v, false);\n     +-\tif (bisect_reset(NULL, false))\n     ++\tif (bisect_clean_state())\n     + \t\treturn BISECT_FAILED;\n       \n     - \tstrvec_clear(&argv_state);\n     - \treturn res;\n     + \tfp = fopen(filename, \"r\");\n      @@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n       {\n       \tint res = BISECT_OK;\n       \tstruct strbuf command = STRBUF_INIT;\n     -+\tenum reset_when_found_mode reset_when_found = RESET_WHEN_FOUND_NONE;\n      +\tconst char *reset_when_found_arg;\n       \tconst char *new_state;\n       \tint temporary_stdout_fd, saved_stdout;\n       \tint is_first_run = 1;\n     -@@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n     ++\tenum reset_when_found_mode reset_when_found = RESET_WHEN_FOUND_NONE;\n     ++\tbool reset_when_found_arg_seen = false;\n     + \n       \tif (bisect_next_check(terms, NULL))\n       \t\treturn BISECT_FAILED;\n       \n     -+\tif (argc && !strcmp(argv[0], \"--reset-when-found\"))\n     ++\tif (argc && !strcmp(argv[0], \"--reset-when-found\")) {\n      +\t\treset_when_found = RESET_WHEN_FOUND_TO_ORIGINAL;\n     -+\telse if (argc && skip_prefix(argv[0], \"--reset-when-found=\",\n     ++\t\treset_when_found_arg_seen = true;\n     ++\t} else if (argc && skip_prefix(argv[0], \"--reset-when-found=\",\n      +\t\t\t\t    &reset_when_found_arg)) {\n     -+\t\tif (parse_reset_when_found(reset_when_found_arg, &reset_when_found))\n     ++\t\tif (parse_reset_when_found(reset_when_found_arg,\n     ++\t\t\t\t\t   &reset_when_found))\n      +\t\t\treturn BISECT_FAILED;\n     ++\t\treset_when_found_arg_seen = true;\n      +\t}\n      +\n     -+\tif (reset_when_found != RESET_WHEN_FOUND_NONE) {\n     -+\t\tif (refs_ref_exists(get_main_ref_store(the_repository), \"BISECT_HEAD\"))\n     -+\t\t\treturn error(_(\"options '%s' and '%s' cannot be used together\"),\n     -+\t\t\t\t     \"--reset-when-found\", \"--no-checkout\");\n     ++\tif (reset_when_found != RESET_WHEN_FOUND_NONE &&\n     ++\t    refs_ref_exists(get_main_ref_store(the_repository), \"BISECT_HEAD\"))\n     ++\t\treturn error(_(\"options '%s' and '%s' cannot be used together\"),\n     ++\t\t\t     \"--reset-when-found\", \"--no-checkout\");\n     ++\n     ++\tif (reset_when_found_arg_seen) {\n      +\t\twrite_file(git_path_bisect_reset_when_found(), \"%s\\n\",\n      +\t\t\t   reset_when_found_mode_name(reset_when_found));\n      +\t\targc--;\n     @@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, co\n       \tif (!argc) {\n       \t\terror(_(\"bisect run failed: no command provided.\"));\n       \t\treturn BISECT_FAILED;\n     -@@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n     - \t\tsaved_stdout = dup(1);\n     - \t\tdup2(temporary_stdout_fd, 1);\n     - \n     --\t\tres = bisect_state(terms, 1, &new_state);\n     -+\t\tres = bisect_state(terms, 1, &new_state, true);\n     - \n     - \t\tfflush(stdout);\n     - \t\tdup2(saved_stdout, 1);\n      @@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n       \t\t\tres = BISECT_OK;\n       \t\t} else if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n       \t\t\tprintf(_(\"bisect found first '%s' commit\\n\"), terms->term_bad);\n      -\t\t\tres = BISECT_OK;\n     -+\t\t\tif (!is_empty_or_missing_file(git_path_bisect_reset_when_found()) &&\n     -+\t\t\t    bisect_reset_when_found(terms))\n     -+\t\t\t\tres = BISECT_FAILED;\n     -+\t\t\telse\n     -+\t\t\t\tres = BISECT_OK;\n       \t\t} else if (res) {\n       \t\t\terror(_(\"bisect run failed: 'git bisect %s'\"\n       \t\t\t\t\" exited with error code %d\"), new_state, res);\n     -@@ builtin/bisect.c: static int cmd_bisect__next(int argc, const char **argv UNUSED, const char *pref\n     - \t\treturn error(_(\"'%s' requires 0 arguments\"),\n     - \t\t\t     \"git bisect next\");\n     - \tget_terms(&terms);\n     --\tres = bisect_next(&terms, prefix);\n     -+\tres = bisect_next(&terms, prefix, false);\n     - \tfree_terms(&terms);\n     - \treturn res;\n     +@@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n     + static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNUSED,\n     + \t\t\t     struct repository *repo UNUSED)\n     + {\n     ++\tint res;\n     ++\n     + \tif (argc > 1)\n     + \t\treturn error(_(\"'%s' requires either no argument or a commit\"),\n     + \t\t\t     \"git bisect reset\");\n     +-\treturn bisect_reset(argc ? argv[0] : NULL, false);\n     ++\tres = bisect_reset(argc ? argv[0] : NULL, false);\n     ++\tif (res)\n     ++\t\treturn res;\n     ++\treturn bisect_clean_state();\n       }\n     + \n     + static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,\n      @@ builtin/bisect.c: int cmd_bisect(int argc,\n       \t\t    !one_of(argv[0], terms.term_good, terms.term_bad, NULL))\n       \t\t\tusage_msg_optf(_(\"unknown command: '%s'\"), git_bisect_usage,\n       \t\t\t\t       options, argv[0]);\n      -\t\tres = bisect_state(&terms, argc, argv);\n     -+\t\tres = bisect_state(&terms, argc, argv, false);\n     ++\t\telse\n     ++\t\t\tres = bisect_state(&terms, argc, argv);\n       \t\tfree_terms(&terms);\n       \t} else {\n       \t\targc--;\n     +@@ builtin/bisect.c: int cmd_bisect(int argc,\n     + \t\tres = fn(argc, argv, prefix, repo);\n     + \t}\n     + \n     ++\tif (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n     ++\t\tenum reset_when_found_mode mode;\n     ++\n     ++\t\tif (read_reset_when_found(&mode))\n     ++\t\t\tres = BISECT_FAILED;\n     ++\t\telse if (mode != RESET_WHEN_FOUND_NONE &&\n     ++\t\t\t bisect_reset_when_found(mode))\n     ++\t\t\tres = BISECT_FAILED;\n     ++\t}\n     ++\n     + \treturn is_bisect_success(res) ? 0 : -res;\n     + }\n      \n       ## t/t6030-bisect-porcelain.sh ##\n      @@ t/t6030-bisect-porcelain.sh: test_bisect_usage () {\n\n-- \ngitgitgadget\n"},{"id":"549432","messageId":"b6ab87fd29ab744ce28bd86bc087bfc5956b8676.1785663075.git.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.v5.git.git.1785663075.gitgitgadget@gmail.com","subject":"[PATCH v5 1/2] bisect: let bisect_reset() optionally check out quietly","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-08-02T09:31:14Z","receivedAt":"2026-08-02T09:31:19Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a \"quiet\" parameter to bisect_reset() that passes \"--quiet\" to the\ncheckout restoring the original HEAD, suppressing its progress and\nbranch-status output.\n\nNo caller sets the flag yet, so behavior is unchanged.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/bisect.c | 12 +++++++-----\n 1 file changed, 7 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 3264e2da54..5393690f6b 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -234,7 +234,7 @@ static int write_terms(const char *bad, const char *good)\n \treturn res;\n }\n \n-static int bisect_reset(const char *commit)\n+static int bisect_reset(const char *commit, bool quiet)\n {\n \tstruct strbuf branch = STRBUF_INIT;\n \n@@ -255,8 +255,10 @@ static int bisect_reset(const char *commit)\n \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n \t\tcmd.git_cmd = 1;\n-\t\tstrvec_pushl(&cmd.args, \"checkout\", \"--ignore-other-worktrees\",\n-\t\t\t\tbranch.buf, \"--\", NULL);\n+\t\tstrvec_pushl(&cmd.args, \"checkout\", \"--ignore-other-worktrees\", NULL);\n+\t\tif (quiet)\n+\t\t\tstrvec_push(&cmd.args, \"--quiet\");\n+\t\tstrvec_pushl(&cmd.args, branch.buf, \"--\", NULL);\n \t\tif (run_command(&cmd)) {\n \t\t\terror(_(\"could not check out original\"\n \t\t\t\t\" HEAD '%s'. Try 'git bisect\"\n@@ -1096,7 +1098,7 @@ static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f\n \tif (is_empty_or_missing_file(filename))\n \t\treturn error(_(\"cannot read file '%s' for replaying\"), filename);\n \n-\tif (bisect_reset(NULL))\n+\tif (bisect_reset(NULL, false))\n \t\treturn BISECT_FAILED;\n \n \tfp = fopen(filename, \"r\");\n@@ -1345,7 +1347,7 @@ static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNU\n \tif (argc > 1)\n \t\treturn error(_(\"'%s' requires either no argument or a commit\"),\n \t\t\t     \"git bisect reset\");\n-\treturn bisect_reset(argc ? argv[0] : NULL);\n+\treturn bisect_reset(argc ? argv[0] : NULL, false);\n }\n \n static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,\n-- \ngitgitgadget\n\n"},{"id":"549433","messageId":"ec362f3b82227de55375db8610536138a6e5cdbd.1785663075.git.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.v5.git.git.1785663075.gitgitgadget@gmail.com","subject":"[PATCH v5 2/2] bisect: add --reset-when-found to leave when done","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-08-02T09:31:15Z","receivedAt":"2026-08-02T09:31:22Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen a bisection finishes, \"git bisect\" reports the first bad commit\nbut leaves the session active until \"git bisect reset\" is run by hand.\n\nAdd a \"--reset-when-found[=<where>]\" option, accepted by both \"git\nbisect start\" and \"git bisect run\", that resets as soon as the first\nbad commit is found. The \"original\" value returns to the commit checked\nout before \"git bisect start\", while \"found\" leaves the first bad commit\nchecked out; omitting the value defaults to \"original\".\n\nPersist the selected target in a BISECT_RESET_WHEN_FOUND state file\nand perform the reset quietly.\n\nPropagate the internal first-bad result and its commit to\ncmd_bisect(), which performs the reset after the subcommand has\nreturned. For \"git bisect run\", this means BISECT_RUN has been printed\nand closed before cleanup, which also works on systems that cannot\nunlink an open file.\n\nReject this option together with \"--no-checkout\", since that mode must\nnot check out either target.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-bisect.adoc |  14 ++-\n bisect.c                      |   6 +-\n bisect.h                      |   3 +-\n builtin/bisect.c              | 157 +++++++++++++++++++++++++++++++---\n t/t6030-bisect-porcelain.sh   | 121 ++++++++++++++++++++++++++\n 5 files changed, 287 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/git-bisect.adoc b/Documentation/git-bisect.adoc\nindex d2115b2990..aabddd42ca 100644\n--- a/Documentation/git-bisect.adoc\n+++ b/Documentation/git-bisect.adoc\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [synopsis]\n git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\n-\t\t [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\n+\t\t [--no-checkout] [--first-parent] [--reset-when-found[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\n git bisect (bad|new|<term-new>) [<rev>]\n git bisect (good|old|<term-old>) [<rev>...]\n git bisect terms [--term-(good|old) | --term-(bad|new)]\n@@ -20,7 +20,7 @@ git bisect reset [<commit>]\n git bisect (visualize|view)\n git bisect replay <logfile>\n git bisect log\n-git bisect run <cmd> [<arg>...]\n+git bisect run [--reset-when-found[=<where>]] <cmd> [<arg>...]\n git bisect help\n \n DESCRIPTION\n@@ -385,6 +385,16 @@ ignored.\n This option is particularly useful in avoiding false positives when a merged\n branch contained broken or non-buildable commits, but the merge itself was OK.\n \n+`--reset-when-found[=<where>]`::\n+\tOnce the first bad commit is found, report it and clean up the\n+\tbisection state. `<where>` may be `original` to return to the commit\n+\tchecked out before `git bisect start`, or `found` to leave the first\n+\tbad commit checked out. If `<where>` is omitted, it defaults to\n+\t`original`.\n++\n+This option may be given to `git bisect start` or to `git bisect run`. It\n+cannot be used for a bisection started with `--no-checkout`.\n+\n EXAMPLES\n --------\n \ndiff --git a/bisect.c b/bisect.c\nindex 94c7028d2a..a3cf1abc38 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -488,6 +488,7 @@ static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n static GIT_PATH_FUNC(git_path_bisect_terms, \"BISECT_TERMS\")\n static GIT_PATH_FUNC(git_path_bisect_first_parent, \"BISECT_FIRST_PARENT\")\n+static GIT_PATH_FUNC(git_path_bisect_reset_when_found, \"BISECT_RESET_WHEN_FOUND\")\n \n static void read_bisect_paths(struct strvec *array)\n {\n@@ -1041,7 +1042,8 @@ void read_bisect_terms(char **read_bad, char **read_good)\n  * the end of bisect_helper::cmd_bisect__helper() helps bypassing\n  * all the code related to finding a commit to test.\n  */\n-enum bisect_error bisect_next_all(struct repository *r, const char *prefix)\n+enum bisect_error bisect_next_all(struct repository *r, const char *prefix,\n+\t\t\t\t  struct object_id *first_bad)\n {\n \tstruct strvec rev_argv = STRVEC_INIT;\n \tstruct rev_info revs = REV_INFO_INIT;\n@@ -1113,6 +1115,7 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix)\n \t\tres = error_if_skipped_commits(tried, current_bad_oid);\n \t\tif (res)\n \t\t\tgoto cleanup;\n+\t\toidcpy(first_bad, bisect_rev);\n \t\tprintf(\"%s is the first '%s' commit\\n\", oid_to_hex(bisect_rev),\n \t\t\tterm_bad);\n \n@@ -1211,6 +1214,7 @@ int bisect_clean_state(void)\n \tunlink_or_warn(git_path_bisect_run());\n \tunlink_or_warn(git_path_bisect_terms());\n \tunlink_or_warn(git_path_bisect_first_parent());\n+\tunlink_or_warn(git_path_bisect_reset_when_found());\n \t/*\n \t * Cleanup BISECT_START last to support the --no-checkout option\n \t * introduced in the commit 4796e823a.\ndiff --git a/bisect.h b/bisect.h\nindex 8621460f93..d1e3c9c721 100644\n--- a/bisect.h\n+++ b/bisect.h\n@@ -63,7 +63,8 @@ struct bisect_state {\n \tunsigned int nr_bad;\n };\n \n-enum bisect_error bisect_next_all(struct repository *r, const char *prefix);\n+enum bisect_error bisect_next_all(struct repository *r, const char *prefix,\n+\t\t\t\t  struct object_id *first_bad);\n \n int estimate_bisect_steps(int all);\n \ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 5393690f6b..5217900976 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -24,11 +24,12 @@ static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n static GIT_PATH_FUNC(git_path_bisect_names, \"BISECT_NAMES\")\n static GIT_PATH_FUNC(git_path_bisect_first_parent, \"BISECT_FIRST_PARENT\")\n+static GIT_PATH_FUNC(git_path_bisect_reset_when_found, \"BISECT_RESET_WHEN_FOUND\")\n static GIT_PATH_FUNC(git_path_bisect_run, \"BISECT_RUN\")\n \n #define BUILTIN_GIT_BISECT_START_USAGE \\\n \tN_(\"git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\\n\" \\\n-\t   \"                 [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\")\n+\t   \"                 [--no-checkout] [--first-parent] [--reset-when-found[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\")\n #define BUILTIN_GIT_BISECT_BAD_USAGE \\\n \tN_(\"git bisect (bad|new|<term-new>) [<rev>]\")\n #define BUILTIN_GIT_BISECT_GOOD_USAGE \\\n@@ -48,7 +49,7 @@ static GIT_PATH_FUNC(git_path_bisect_run, \"BISECT_RUN\")\n #define BUILTIN_GIT_BISECT_LOG_USAGE \\\n \t\"git bisect log\"\n #define BUILTIN_GIT_BISECT_RUN_USAGE \\\n-\tN_(\"git bisect run <cmd> [<arg>...]\")\n+\tN_(\"git bisect run [--reset-when-found[=<where>]] <cmd> [<arg>...]\")\n #define BUILTIN_GIT_BISECT_HELP_USAGE \\\n \t\"git bisect help\"\n \n@@ -68,6 +69,12 @@ static const char * const git_bisect_usage[] = {\n \tNULL\n };\n \n+enum reset_when_found_mode {\n+\tRESET_WHEN_FOUND_NONE,\n+\tRESET_WHEN_FOUND_TO_ORIGINAL,\n+\tRESET_WHEN_FOUND_TO_FOUND,\n+};\n+\n struct add_bisect_ref_data {\n \tstruct rev_info *revs;\n \tunsigned int object_flags;\n@@ -78,6 +85,8 @@ struct bisect_terms {\n \tchar *term_bad;\n };\n \n+static struct object_id first_bad_oid;\n+\n static void free_terms(struct bisect_terms *terms)\n {\n \tFREE_AND_NULL(terms->term_good);\n@@ -269,7 +278,75 @@ static int bisect_reset(const char *commit, bool quiet)\n \t}\n \n \tstrbuf_release(&branch);\n-\treturn bisect_clean_state();\n+\treturn 0;\n+}\n+\n+static int parse_reset_when_found(const char *value,\n+\t\t\t\t  enum reset_when_found_mode *mode)\n+{\n+\tif (!strcmp(value, \"original\"))\n+\t\t*mode = RESET_WHEN_FOUND_TO_ORIGINAL;\n+\telse if (!strcmp(value, \"found\"))\n+\t\t*mode = RESET_WHEN_FOUND_TO_FOUND;\n+\telse\n+\t\treturn error(_(\"invalid value for '--reset-when-found': '%s'\"),\n+\t\t\t     value);\n+\n+\treturn 0;\n+}\n+\n+static const char *reset_when_found_mode_name(enum reset_when_found_mode mode)\n+{\n+\tswitch (mode) {\n+\tcase RESET_WHEN_FOUND_TO_ORIGINAL:\n+\t\treturn \"original\";\n+\tcase RESET_WHEN_FOUND_TO_FOUND:\n+\t\treturn \"found\";\n+\tcase RESET_WHEN_FOUND_NONE:\n+\t\tBUG(\"no name for unset reset-when-found mode\");\n+\t}\n+\tBUG(\"unknown reset-when-found mode %d\", mode);\n+}\n+\n+static int read_reset_when_found(enum reset_when_found_mode *mode)\n+{\n+\tstruct strbuf value = STRBUF_INIT;\n+\tint res = 0;\n+\n+\t*mode = RESET_WHEN_FOUND_NONE;\n+\tif (is_empty_or_missing_file(git_path_bisect_reset_when_found()))\n+\t\treturn 0;\n+\n+\tif (strbuf_read_file(&value, git_path_bisect_reset_when_found(), 0) < 0) {\n+\t\tres = error_errno(_(\"could not read '%s'\"),\n+\t\t\t\t  git_path_bisect_reset_when_found());\n+\t\tgoto out;\n+\t}\n+\tstrbuf_trim(&value);\n+\tif (parse_reset_when_found(value.buf, mode))\n+\t\tres = -1;\n+\n+out:\n+\tstrbuf_release(&value);\n+\treturn res;\n+}\n+\n+static int bisect_reset_when_found(enum reset_when_found_mode mode)\n+{\n+\tchar first_bad_hex[GIT_MAX_HEXSZ + 1];\n+\tconst char *commit = NULL;\n+\tint res;\n+\n+\tif (mode == RESET_WHEN_FOUND_TO_FOUND)\n+\t\tcommit = oid_to_hex_r(first_bad_hex, &first_bad_oid);\n+\telse if (mode == RESET_WHEN_FOUND_NONE)\n+\t\tBUG(\"automatic reset requested without a reset mode\");\n+\n+\tres = bisect_reset(commit, true);\n+\tif (!res)\n+\t\tres = bisect_clean_state();\n+\n+\treturn res;\n }\n \n static void log_commit(FILE *fp,\n@@ -682,7 +759,8 @@ static int bisect_successful(struct bisect_terms *terms)\n \treturn res;\n }\n \n-static enum bisect_error bisect_next(struct bisect_terms *terms, const char *prefix)\n+static enum bisect_error bisect_next(struct bisect_terms *terms,\n+\t\t\t\t     const char *prefix)\n {\n \tenum bisect_error res;\n \n@@ -693,7 +771,7 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n \t\treturn BISECT_FAILED;\n \n \t/* Perform all bisection computation */\n-\tres = bisect_next_all(the_repository, prefix);\n+\tres = bisect_next_all(the_repository, prefix, &first_bad_oid);\n \n \tif (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n \t\tres = bisect_successful(terms);\n@@ -705,7 +783,8 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n \treturn res;\n }\n \n-static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix)\n+static enum bisect_error bisect_auto_next(struct bisect_terms *terms,\n+\t\t\t\t\t  const char *prefix)\n {\n \tif (bisect_next_check(terms, NULL)) {\n \t\tbisect_print_status(terms);\n@@ -729,6 +808,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \tstruct strbuf bisect_names = STRBUF_INIT;\n \tstruct object_id head_oid;\n \tstruct object_id oid;\n+\tenum reset_when_found_mode reset_when_found = RESET_WHEN_FOUND_NONE;\n \tconst char *head;\n \n \tif (is_bare_repository(the_repository))\n@@ -752,6 +832,13 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \t\t\tno_checkout = 1;\n \t\t} else if (!strcmp(arg, \"--first-parent\")) {\n \t\t\tfirst_parent_only = 1;\n+\t\t} else if (!strcmp(arg, \"--reset-when-found\")) {\n+\t\t\treset_when_found = RESET_WHEN_FOUND_TO_ORIGINAL;\n+\t\t} else if (skip_prefix(arg, \"--reset-when-found=\", &arg)) {\n+\t\t\tif (parse_reset_when_found(arg, &reset_when_found)) {\n+\t\t\t\tres = BISECT_FAILED;\n+\t\t\t\tgoto finish;\n+\t\t\t}\n \t\t} else if (!strcmp(arg, \"--term-good\") ||\n \t\t\t !strcmp(arg, \"--term-old\")) {\n \t\t\ti++;\n@@ -789,6 +876,11 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \t\t\tbreak;\n \t\t}\n \t}\n+\tif (reset_when_found != RESET_WHEN_FOUND_NONE && no_checkout) {\n+\t\tres = error(_(\"options '%s' and '%s' cannot be used together\"),\n+\t\t\t    \"--reset-when-found\", \"--no-checkout\");\n+\t\tgoto finish;\n+\t}\n \tpathspec_pos = i;\n \n \t/*\n@@ -868,6 +960,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \tif (first_parent_only)\n \t\twrite_file(git_path_bisect_first_parent(), \"\\n\");\n \n+\tif (reset_when_found != RESET_WHEN_FOUND_NONE)\n+\t\twrite_file(git_path_bisect_reset_when_found(), \"%s\\n\",\n+\t\t\t   reset_when_found_mode_name(reset_when_found));\n+\n \tif (no_checkout) {\n \t\tif (repo_get_oid(the_repository, start_head.buf, &oid) < 0) {\n \t\t\tres = error(_(\"invalid ref: '%s'\"), start_head.buf);\n@@ -1098,7 +1194,7 @@ static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f\n \tif (is_empty_or_missing_file(filename))\n \t\treturn error(_(\"cannot read file '%s' for replaying\"), filename);\n \n-\tif (bisect_reset(NULL, false))\n+\tif (bisect_clean_state())\n \t\treturn BISECT_FAILED;\n \n \tfp = fopen(filename, \"r\");\n@@ -1246,13 +1342,39 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n {\n \tint res = BISECT_OK;\n \tstruct strbuf command = STRBUF_INIT;\n+\tconst char *reset_when_found_arg;\n \tconst char *new_state;\n \tint temporary_stdout_fd, saved_stdout;\n \tint is_first_run = 1;\n+\tenum reset_when_found_mode reset_when_found = RESET_WHEN_FOUND_NONE;\n+\tbool reset_when_found_arg_seen = false;\n \n \tif (bisect_next_check(terms, NULL))\n \t\treturn BISECT_FAILED;\n \n+\tif (argc && !strcmp(argv[0], \"--reset-when-found\")) {\n+\t\treset_when_found = RESET_WHEN_FOUND_TO_ORIGINAL;\n+\t\treset_when_found_arg_seen = true;\n+\t} else if (argc && skip_prefix(argv[0], \"--reset-when-found=\",\n+\t\t\t\t    &reset_when_found_arg)) {\n+\t\tif (parse_reset_when_found(reset_when_found_arg,\n+\t\t\t\t\t   &reset_when_found))\n+\t\t\treturn BISECT_FAILED;\n+\t\treset_when_found_arg_seen = true;\n+\t}\n+\n+\tif (reset_when_found != RESET_WHEN_FOUND_NONE &&\n+\t    refs_ref_exists(get_main_ref_store(the_repository), \"BISECT_HEAD\"))\n+\t\treturn error(_(\"options '%s' and '%s' cannot be used together\"),\n+\t\t\t     \"--reset-when-found\", \"--no-checkout\");\n+\n+\tif (reset_when_found_arg_seen) {\n+\t\twrite_file(git_path_bisect_reset_when_found(), \"%s\\n\",\n+\t\t\t   reset_when_found_mode_name(reset_when_found));\n+\t\targc--;\n+\t\targv++;\n+\t}\n+\n \tif (!argc) {\n \t\terror(_(\"bisect run failed: no command provided.\"));\n \t\treturn BISECT_FAILED;\n@@ -1327,7 +1449,6 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n \t\t\tres = BISECT_OK;\n \t\t} else if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n \t\t\tprintf(_(\"bisect found first '%s' commit\\n\"), terms->term_bad);\n-\t\t\tres = BISECT_OK;\n \t\t} else if (res) {\n \t\t\terror(_(\"bisect run failed: 'git bisect %s'\"\n \t\t\t\t\" exited with error code %d\"), new_state, res);\n@@ -1344,10 +1465,15 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNUSED,\n \t\t\t     struct repository *repo UNUSED)\n {\n+\tint res;\n+\n \tif (argc > 1)\n \t\treturn error(_(\"'%s' requires either no argument or a commit\"),\n \t\t\t     \"git bisect reset\");\n-\treturn bisect_reset(argc ? argv[0] : NULL, false);\n+\tres = bisect_reset(argc ? argv[0] : NULL, false);\n+\tif (res)\n+\t\treturn res;\n+\treturn bisect_clean_state();\n }\n \n static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,\n@@ -1489,7 +1615,8 @@ int cmd_bisect(int argc,\n \t\t    !one_of(argv[0], terms.term_good, terms.term_bad, NULL))\n \t\t\tusage_msg_optf(_(\"unknown command: '%s'\"), git_bisect_usage,\n \t\t\t\t       options, argv[0]);\n-\t\tres = bisect_state(&terms, argc, argv);\n+\t\telse\n+\t\t\tres = bisect_state(&terms, argc, argv);\n \t\tfree_terms(&terms);\n \t} else {\n \t\targc--;\n@@ -1497,5 +1624,15 @@ int cmd_bisect(int argc,\n \t\tres = fn(argc, argv, prefix, repo);\n \t}\n \n+\tif (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n+\t\tenum reset_when_found_mode mode;\n+\n+\t\tif (read_reset_when_found(&mode))\n+\t\t\tres = BISECT_FAILED;\n+\t\telse if (mode != RESET_WHEN_FOUND_NONE &&\n+\t\t\t bisect_reset_when_found(mode))\n+\t\t\tres = BISECT_FAILED;\n+\t}\n+\n \treturn is_bisect_success(res) ? 0 : -res;\n }\ndiff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh\nindex 338df4f886..a7588222a8 100755\n--- a/t/t6030-bisect-porcelain.sh\n+++ b/t/t6030-bisect-porcelain.sh\n@@ -43,6 +43,42 @@ test_bisect_usage () {\n \ttest_cmp expect actual\n }\n \n+test_bisect_state_file () {\n+\tlocal file &&\n+\tfile=$(git rev-parse --git-path \"$1\") &&\n+\ttest_path_is_file \"$file\"\n+}\n+\n+test_bisect_state_missing () {\n+\tlocal file &&\n+\tfile=$(git rev-parse --git-path \"$1\") &&\n+\ttest_path_is_missing \"$file\"\n+}\n+\n+bisect_start_and_finish () {\n+\tgit bisect start \"$1\" $HASH4 $HASH2 &&\n+\tgit bisect bad\n+}\n+\n+bisect_run_reset_when_found () {\n+\twrite_script test_script.sh <<-\\EOF &&\n+\t! grep Another hello >/dev/null\n+\tEOF\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect run \"$1\" ./test_script.sh >my_bisect_log.txt &&\n+\ttest_grep \"$HASH3 is the first .bad. commit\" my_bisect_log.txt &&\n+\ttest_bisect_state_missing BISECT_RUN\n+}\n+\n+test_reset_when_found_fails () {\n+\tlocal pattern=\"$1\" &&\n+\tlocal state_file=\"$2\" &&\n+\tshift 2 &&\n+\ttest_must_fail \"$@\" 2>err &&\n+\ttest_grep -- \"$pattern\" err &&\n+\ttest_bisect_state_missing \"$state_file\"\n+}\n+\n test_expect_success 'bisect usage' \"\n \ttest_bisect_usage 1 git bisect reset extra1 extra2 <<-\\EOF &&\n \terror: 'git bisect reset' requires either no argument or a commit\n@@ -453,6 +489,91 @@ test_expect_success '\"git bisect run\" simple case' '\n \tgit bisect reset\n '\n \n+test_expect_success '\"git bisect start --reset-when-found\" defaults to original' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\tgit checkout main &&\n+\tbisect_start_and_finish --reset-when-found &&\n+\tactual=$(git rev-parse HEAD) &&\n+\ttest \"$HASH4\" = \"$actual\" &&\n+\tactual=$(git branch --show-current) &&\n+\ttest main = \"$actual\" &&\n+\ttest_bisect_state_missing BISECT_START &&\n+\n+\tbisect_start_and_finish --reset-when-found=original &&\n+\tactual=$(git rev-parse HEAD) &&\n+\ttest \"$HASH4\" = \"$actual\" &&\n+\tactual=$(git branch --show-current) &&\n+\ttest main = \"$actual\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '\"git bisect start --reset-when-found=found\" leaves first bad checked out' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\tbisect_start_and_finish --reset-when-found=found &&\n+\tactual=$(git rev-parse HEAD) &&\n+\ttest \"$HASH3\" = \"$actual\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '\"git bisect run --reset-when-found\" defaults to original' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\tbisect_run_reset_when_found --reset-when-found &&\n+\tactual=$(git rev-parse HEAD) &&\n+\ttest \"$HASH4\" = \"$actual\" &&\n+\tactual=$(git branch --show-current) &&\n+\ttest main = \"$actual\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '\"git bisect run --reset-when-found=found\" leaves first bad checked out' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\tbisect_run_reset_when_found --reset-when-found=found &&\n+\tactual=$(git rev-parse HEAD) &&\n+\ttest \"$HASH3\" = \"$actual\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '--reset-when-found rejects an unknown reset target' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"invalid value for.*--reset-when-found.*unknown\" BISECT_START \\\n+\t\tgit bisect start --reset-when-found=unknown $HASH4 $HASH2 &&\n+\n+\tgit bisect start $HASH4 $HASH2 &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"invalid value for.*--reset-when-found.*unknown\" \\\n+\t\tBISECT_RESET_WHEN_FOUND \\\n+\t\tgit bisect run --reset-when-found=unknown true\n+'\n+\n+test_expect_success '--reset-when-found cannot be used with --no-checkout' '\n+\ttest_when_finished \"git bisect reset\" &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"options .*--reset-when-found.* and .*--no-checkout.* cannot be used together\" BISECT_START \\\n+\t\tgit bisect start --reset-when-found=original --no-checkout $HASH4 $HASH2 &&\n+\n+\tgit bisect start --no-checkout $HASH4 $HASH2 &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"options .*--reset-when-found.* and .*--no-checkout.* cannot be used together\" BISECT_RESET_WHEN_FOUND \\\n+\t\tgit bisect run --reset-when-found=found true\n+'\n+\n+test_expect_success 'without --reset-when-found the bisection state is kept' '\n+\ttest_when_finished \"git bisect reset\" &&\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect bad &&\n+\ttest_bisect_state_file BISECT_START\n+'\n+\n+test_expect_success '--reset-when-found does not leak into a later bisection' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\tbisect_start_and_finish --reset-when-found &&\n+\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect bad &&\n+\ttest_bisect_state_file BISECT_START\n+'\n+\n # We want to automatically find the commit that\n # added \"Ciao\" into hello.\n test_expect_success '\"git bisect run\" with more complex \"git bisect start\"' '\n-- \ngitgitgadget\n"},{"id":"549440","messageId":"xmqqh5lc4ilx.fsf@gitster.g","threadId":"66011","inReplyTo":"pull.2335.v5.git.git.1785663075.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 0/2] bisect: add --reset-when-found to leave when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-02T16:01:14Z","receivedAt":"2026-08-02T16:01:16Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Add a --reset-when-found option to git bisect that resets the bisect session\n> when culprit is found.\n>\n> Changes in v5:\n>\n>  * Move automatic reset handling to cmd_bisect() after subcommand resources\n>    are closed.\n>  * Propagate the first-bad commit OID from bisect_next_all() and remove\n>    defer_reset plumbing.\n>  * Separate checkout from state cleanup in bisect_reset(), and use bool for\n>    its quiet flag.\n\nI know I suggested the second one, but is it really needed?  The\nonly reason the \"first bad commit\" is needed is because we may need\nto reset to it when --reset-when-done is in effect, but before that\nreset happens, we can look at refs/bisect/bad to find it out, and if\nthat is the case, there is already a mechanism for the next-all to\ntell later code what that first bad commit is already.\n\nIf it make the patch series a lot smaller to take advantage of that\nfact, that would be superb.\n\nI also do not understand exactly why we need both reset_when_found\nand reset_when_found_arg_seen.  If we initialize the former to a\n\"not set\" value that command line argument or the state file can\nnever set to, then wouldn't it make the latter unnecessary?\n"},{"id":"549445","messageId":"pull.2335.v6.git.git.1785705860.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.v5.git.git.1785663075.gitgitgadget@gmail.com","subject":"[PATCH v6 0/2] bisect: add --reset-when-found to leave when done","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-08-02T21:24:18Z","receivedAt":"2026-08-02T21:24:24Z","isPatch":true,"body":"Add a --reset-when-found option to git bisect that resets the bisect session\nwhen culprit is found.\n\nChanges in v6:\n\n * Reuse the existing bad bisect ref instead of propagating the culprit OID\n   through bisect_next_all().\n * Remove the redundant reset_when_found_arg_seen flag and use\n   RESET_WHEN_FOUND_NONE to detect whether the option was given.\n\nChanges in v5:\n\n * Move automatic reset handling to cmd_bisect() after subcommand resources\n   are closed.\n * Propagate the first-bad commit OID from bisect_next_all() and remove\n   defer_reset plumbing.\n * Separate checkout from state cleanup in bisect_reset(), and use bool for\n   its quiet flag.\n\nChanges in v4:\n\n * Simplify translation calls.\n * Avoid git subshell calls in tests, that can bury errors.\n\nChanges in v3:\n\n * Rename --auto-reset to --reset-when-found, including internal names.\n * Defer git bisect run cleanup until captured output is printed and\n   BISECT_RUN is closed. Drop the open-descriptor preparatory change,\n   retaining the existing filename-based output handling.\n\nChanges in v2:\n\n * Add option --auto-reset[=<where>] with option to go to final commit as\n   well as original.\n * Refactored tests.\n\nHarald Nordgren (2):\n  bisect: let bisect_reset() optionally check out quietly\n  bisect: add --reset-when-found to leave when done\n\n Documentation/git-bisect.adoc |  14 ++-\n bisect.c                      |   2 +\n builtin/bisect.c              | 162 +++++++++++++++++++++++++++++++---\n t/t6030-bisect-porcelain.sh   | 121 +++++++++++++++++++++++++\n 4 files changed, 285 insertions(+), 14 deletions(-)\n\n\nbase-commit: a97fcc37c2bc6340a8d7ce78dedf227aac4e9aa7\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2335%2FHaraldNordgren%2Fbisect-auto-reset-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2335/HaraldNordgren/bisect-auto-reset-v6\nPull-Request: https://github.com/git/git/pull/2335\n\nRange-diff vs v5:\n\n 1:  b6ab87fd29 = 1:  b6ab87fd29 bisect: let bisect_reset() optionally check out quietly\n 2:  ec362f3b82 ! 2:  97a4da5537 bisect: add --reset-when-found to leave when done\n     @@ Commit message\n          Persist the selected target in a BISECT_RESET_WHEN_FOUND state file\n          and perform the reset quietly.\n      \n     -    Propagate the internal first-bad result and its commit to\n     -    cmd_bisect(), which performs the reset after the subcommand has\n     -    returned. For \"git bisect run\", this means BISECT_RUN has been printed\n     -    and closed before cleanup, which also works on systems that cannot\n     -    unlink an open file.\n     +    Let the internal first-bad result propagate to cmd_bisect(), which\n     +    performs the reset using the existing bad bisect ref after the\n     +    subcommand has returned. For \"git bisect run\", this means BISECT_RUN\n     +    has been printed and closed before cleanup, which also works on systems\n     +    that cannot unlink an open file.\n      \n          Reject this option together with \"--no-checkout\", since that mode must\n          not check out either target.\n     @@ bisect.c: static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n       \n       static void read_bisect_paths(struct strvec *array)\n       {\n     -@@ bisect.c: void read_bisect_terms(char **read_bad, char **read_good)\n     -  * the end of bisect_helper::cmd_bisect__helper() helps bypassing\n     -  * all the code related to finding a commit to test.\n     -  */\n     --enum bisect_error bisect_next_all(struct repository *r, const char *prefix)\n     -+enum bisect_error bisect_next_all(struct repository *r, const char *prefix,\n     -+\t\t\t\t  struct object_id *first_bad)\n     - {\n     - \tstruct strvec rev_argv = STRVEC_INIT;\n     - \tstruct rev_info revs = REV_INFO_INIT;\n     -@@ bisect.c: enum bisect_error bisect_next_all(struct repository *r, const char *prefix)\n     - \t\tres = error_if_skipped_commits(tried, current_bad_oid);\n     - \t\tif (res)\n     - \t\t\tgoto cleanup;\n     -+\t\toidcpy(first_bad, bisect_rev);\n     - \t\tprintf(\"%s is the first '%s' commit\\n\", oid_to_hex(bisect_rev),\n     - \t\t\tterm_bad);\n     - \n      @@ bisect.c: int bisect_clean_state(void)\n       \tunlink_or_warn(git_path_bisect_run());\n       \tunlink_or_warn(git_path_bisect_terms());\n     @@ bisect.c: int bisect_clean_state(void)\n       \t * Cleanup BISECT_START last to support the --no-checkout option\n       \t * introduced in the commit 4796e823a.\n      \n     - ## bisect.h ##\n     -@@ bisect.h: struct bisect_state {\n     - \tunsigned int nr_bad;\n     - };\n     - \n     --enum bisect_error bisect_next_all(struct repository *r, const char *prefix);\n     -+enum bisect_error bisect_next_all(struct repository *r, const char *prefix,\n     -+\t\t\t\t  struct object_id *first_bad);\n     - \n     - int estimate_bisect_steps(int all);\n     - \n     -\n       ## builtin/bisect.c ##\n      @@ builtin/bisect.c: static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n       static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n     @@ builtin/bisect.c: static const char * const git_bisect_usage[] = {\n       struct add_bisect_ref_data {\n       \tstruct rev_info *revs;\n       \tunsigned int object_flags;\n     -@@ builtin/bisect.c: struct bisect_terms {\n     - \tchar *term_bad;\n     - };\n     - \n     -+static struct object_id first_bad_oid;\n     -+\n     - static void free_terms(struct bisect_terms *terms)\n     - {\n     - \tFREE_AND_NULL(terms->term_good);\n      @@ builtin/bisect.c: static int bisect_reset(const char *commit, bool quiet)\n       \t}\n       \n     @@ builtin/bisect.c: static int bisect_reset(const char *commit, bool quiet)\n      +\n      +static int bisect_reset_when_found(enum reset_when_found_mode mode)\n      +{\n     -+\tchar first_bad_hex[GIT_MAX_HEXSZ + 1];\n     -+\tconst char *commit = NULL;\n     ++\tstruct bisect_terms terms = { 0 };\n     ++\tchar *commit = NULL;\n      +\tint res;\n      +\n     -+\tif (mode == RESET_WHEN_FOUND_TO_FOUND)\n     -+\t\tcommit = oid_to_hex_r(first_bad_hex, &first_bad_oid);\n     -+\telse if (mode == RESET_WHEN_FOUND_NONE)\n     ++\tif (mode == RESET_WHEN_FOUND_TO_FOUND) {\n     ++\t\tread_bisect_terms(&terms.term_bad, &terms.term_good);\n     ++\t\tcommit = xstrfmt(\"refs/bisect/%s\", terms.term_bad);\n     ++\t} else if (mode == RESET_WHEN_FOUND_NONE) {\n      +\t\tBUG(\"automatic reset requested without a reset mode\");\n     ++\t}\n      +\n      +\tres = bisect_reset(commit, true);\n      +\tif (!res)\n      +\t\tres = bisect_clean_state();\n      +\n     ++\tfree(commit);\n     ++\tfree_terms(&terms);\n      +\treturn res;\n       }\n       \n     @@ builtin/bisect.c: static int bisect_successful(struct bisect_terms *terms)\n       {\n       \tenum bisect_error res;\n       \n     -@@ builtin/bisect.c: static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n     - \t\treturn BISECT_FAILED;\n     - \n     - \t/* Perform all bisection computation */\n     --\tres = bisect_next_all(the_repository, prefix);\n     -+\tres = bisect_next_all(the_repository, prefix, &first_bad_oid);\n     - \n     - \tif (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n     - \t\tres = bisect_successful(terms);\n      @@ builtin/bisect.c: static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n       \treturn res;\n       }\n     @@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, co\n       \tint temporary_stdout_fd, saved_stdout;\n       \tint is_first_run = 1;\n      +\tenum reset_when_found_mode reset_when_found = RESET_WHEN_FOUND_NONE;\n     -+\tbool reset_when_found_arg_seen = false;\n       \n       \tif (bisect_next_check(terms, NULL))\n       \t\treturn BISECT_FAILED;\n       \n      +\tif (argc && !strcmp(argv[0], \"--reset-when-found\")) {\n      +\t\treset_when_found = RESET_WHEN_FOUND_TO_ORIGINAL;\n     -+\t\treset_when_found_arg_seen = true;\n      +\t} else if (argc && skip_prefix(argv[0], \"--reset-when-found=\",\n      +\t\t\t\t    &reset_when_found_arg)) {\n      +\t\tif (parse_reset_when_found(reset_when_found_arg,\n      +\t\t\t\t\t   &reset_when_found))\n      +\t\t\treturn BISECT_FAILED;\n     -+\t\treset_when_found_arg_seen = true;\n      +\t}\n      +\n      +\tif (reset_when_found != RESET_WHEN_FOUND_NONE &&\n     @@ builtin/bisect.c: static int bisect_run(struct bisect_terms *terms, int argc, co\n      +\t\treturn error(_(\"options '%s' and '%s' cannot be used together\"),\n      +\t\t\t     \"--reset-when-found\", \"--no-checkout\");\n      +\n     -+\tif (reset_when_found_arg_seen) {\n     ++\tif (reset_when_found != RESET_WHEN_FOUND_NONE) {\n      +\t\twrite_file(git_path_bisect_reset_when_found(), \"%s\\n\",\n      +\t\t\t   reset_when_found_mode_name(reset_when_found));\n      +\t\targc--;\n\n-- \ngitgitgadget\n"},{"id":"549446","messageId":"b6ab87fd29ab744ce28bd86bc087bfc5956b8676.1785705860.git.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.v6.git.git.1785705860.gitgitgadget@gmail.com","subject":"[PATCH v6 1/2] bisect: let bisect_reset() optionally check out quietly","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-08-02T21:24:19Z","receivedAt":"2026-08-02T21:24:26Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a \"quiet\" parameter to bisect_reset() that passes \"--quiet\" to the\ncheckout restoring the original HEAD, suppressing its progress and\nbranch-status output.\n\nNo caller sets the flag yet, so behavior is unchanged.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/bisect.c | 12 +++++++-----\n 1 file changed, 7 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 3264e2da54..5393690f6b 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -234,7 +234,7 @@ static int write_terms(const char *bad, const char *good)\n \treturn res;\n }\n \n-static int bisect_reset(const char *commit)\n+static int bisect_reset(const char *commit, bool quiet)\n {\n \tstruct strbuf branch = STRBUF_INIT;\n \n@@ -255,8 +255,10 @@ static int bisect_reset(const char *commit)\n \t\tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n \t\tcmd.git_cmd = 1;\n-\t\tstrvec_pushl(&cmd.args, \"checkout\", \"--ignore-other-worktrees\",\n-\t\t\t\tbranch.buf, \"--\", NULL);\n+\t\tstrvec_pushl(&cmd.args, \"checkout\", \"--ignore-other-worktrees\", NULL);\n+\t\tif (quiet)\n+\t\t\tstrvec_push(&cmd.args, \"--quiet\");\n+\t\tstrvec_pushl(&cmd.args, branch.buf, \"--\", NULL);\n \t\tif (run_command(&cmd)) {\n \t\t\terror(_(\"could not check out original\"\n \t\t\t\t\" HEAD '%s'. Try 'git bisect\"\n@@ -1096,7 +1098,7 @@ static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f\n \tif (is_empty_or_missing_file(filename))\n \t\treturn error(_(\"cannot read file '%s' for replaying\"), filename);\n \n-\tif (bisect_reset(NULL))\n+\tif (bisect_reset(NULL, false))\n \t\treturn BISECT_FAILED;\n \n \tfp = fopen(filename, \"r\");\n@@ -1345,7 +1347,7 @@ static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNU\n \tif (argc > 1)\n \t\treturn error(_(\"'%s' requires either no argument or a commit\"),\n \t\t\t     \"git bisect reset\");\n-\treturn bisect_reset(argc ? argv[0] : NULL);\n+\treturn bisect_reset(argc ? argv[0] : NULL, false);\n }\n \n static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,\n-- \ngitgitgadget\n\n"},{"id":"549447","messageId":"97a4da55374c6dc52e6e990b3cadfaaa86a57640.1785705860.git.gitgitgadget@gmail.com","threadId":"66011","inReplyTo":"pull.2335.v6.git.git.1785705860.gitgitgadget@gmail.com","subject":"[PATCH v6 2/2] bisect: add --reset-when-found to leave when done","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-08-02T21:24:20Z","receivedAt":"2026-08-02T21:24:30Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen a bisection finishes, \"git bisect\" reports the first bad commit\nbut leaves the session active until \"git bisect reset\" is run by hand.\n\nAdd a \"--reset-when-found[=<where>]\" option, accepted by both \"git\nbisect start\" and \"git bisect run\", that resets as soon as the first\nbad commit is found. The \"original\" value returns to the commit checked\nout before \"git bisect start\", while \"found\" leaves the first bad commit\nchecked out; omitting the value defaults to \"original\".\n\nPersist the selected target in a BISECT_RESET_WHEN_FOUND state file\nand perform the reset quietly.\n\nLet the internal first-bad result propagate to cmd_bisect(), which\nperforms the reset using the existing bad bisect ref after the\nsubcommand has returned. For \"git bisect run\", this means BISECT_RUN\nhas been printed and closed before cleanup, which also works on systems\nthat cannot unlink an open file.\n\nReject this option together with \"--no-checkout\", since that mode must\nnot check out either target.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-bisect.adoc |  14 +++-\n bisect.c                      |   2 +\n builtin/bisect.c              | 154 ++++++++++++++++++++++++++++++++--\n t/t6030-bisect-porcelain.sh   | 121 ++++++++++++++++++++++++++\n 4 files changed, 280 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-bisect.adoc b/Documentation/git-bisect.adoc\nindex d2115b2990..aabddd42ca 100644\n--- a/Documentation/git-bisect.adoc\n+++ b/Documentation/git-bisect.adoc\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [synopsis]\n git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\n-\t\t [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\n+\t\t [--no-checkout] [--first-parent] [--reset-when-found[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\n git bisect (bad|new|<term-new>) [<rev>]\n git bisect (good|old|<term-old>) [<rev>...]\n git bisect terms [--term-(good|old) | --term-(bad|new)]\n@@ -20,7 +20,7 @@ git bisect reset [<commit>]\n git bisect (visualize|view)\n git bisect replay <logfile>\n git bisect log\n-git bisect run <cmd> [<arg>...]\n+git bisect run [--reset-when-found[=<where>]] <cmd> [<arg>...]\n git bisect help\n \n DESCRIPTION\n@@ -385,6 +385,16 @@ ignored.\n This option is particularly useful in avoiding false positives when a merged\n branch contained broken or non-buildable commits, but the merge itself was OK.\n \n+`--reset-when-found[=<where>]`::\n+\tOnce the first bad commit is found, report it and clean up the\n+\tbisection state. `<where>` may be `original` to return to the commit\n+\tchecked out before `git bisect start`, or `found` to leave the first\n+\tbad commit checked out. If `<where>` is omitted, it defaults to\n+\t`original`.\n++\n+This option may be given to `git bisect start` or to `git bisect run`. It\n+cannot be used for a bisection started with `--no-checkout`.\n+\n EXAMPLES\n --------\n \ndiff --git a/bisect.c b/bisect.c\nindex 94c7028d2a..d426fcd5a9 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -488,6 +488,7 @@ static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n static GIT_PATH_FUNC(git_path_bisect_terms, \"BISECT_TERMS\")\n static GIT_PATH_FUNC(git_path_bisect_first_parent, \"BISECT_FIRST_PARENT\")\n+static GIT_PATH_FUNC(git_path_bisect_reset_when_found, \"BISECT_RESET_WHEN_FOUND\")\n \n static void read_bisect_paths(struct strvec *array)\n {\n@@ -1211,6 +1212,7 @@ int bisect_clean_state(void)\n \tunlink_or_warn(git_path_bisect_run());\n \tunlink_or_warn(git_path_bisect_terms());\n \tunlink_or_warn(git_path_bisect_first_parent());\n+\tunlink_or_warn(git_path_bisect_reset_when_found());\n \t/*\n \t * Cleanup BISECT_START last to support the --no-checkout option\n \t * introduced in the commit 4796e823a.\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 5393690f6b..93420ac0ea 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -24,11 +24,12 @@ static GIT_PATH_FUNC(git_path_bisect_start, \"BISECT_START\")\n static GIT_PATH_FUNC(git_path_bisect_log, \"BISECT_LOG\")\n static GIT_PATH_FUNC(git_path_bisect_names, \"BISECT_NAMES\")\n static GIT_PATH_FUNC(git_path_bisect_first_parent, \"BISECT_FIRST_PARENT\")\n+static GIT_PATH_FUNC(git_path_bisect_reset_when_found, \"BISECT_RESET_WHEN_FOUND\")\n static GIT_PATH_FUNC(git_path_bisect_run, \"BISECT_RUN\")\n \n #define BUILTIN_GIT_BISECT_START_USAGE \\\n \tN_(\"git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]\\n\" \\\n-\t   \"                 [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]\")\n+\t   \"                 [--no-checkout] [--first-parent] [--reset-when-found[=<where>]] [<bad> [<good>...]] [--] [<pathspec>...]\")\n #define BUILTIN_GIT_BISECT_BAD_USAGE \\\n \tN_(\"git bisect (bad|new|<term-new>) [<rev>]\")\n #define BUILTIN_GIT_BISECT_GOOD_USAGE \\\n@@ -48,7 +49,7 @@ static GIT_PATH_FUNC(git_path_bisect_run, \"BISECT_RUN\")\n #define BUILTIN_GIT_BISECT_LOG_USAGE \\\n \t\"git bisect log\"\n #define BUILTIN_GIT_BISECT_RUN_USAGE \\\n-\tN_(\"git bisect run <cmd> [<arg>...]\")\n+\tN_(\"git bisect run [--reset-when-found[=<where>]] <cmd> [<arg>...]\")\n #define BUILTIN_GIT_BISECT_HELP_USAGE \\\n \t\"git bisect help\"\n \n@@ -68,6 +69,12 @@ static const char * const git_bisect_usage[] = {\n \tNULL\n };\n \n+enum reset_when_found_mode {\n+\tRESET_WHEN_FOUND_NONE,\n+\tRESET_WHEN_FOUND_TO_ORIGINAL,\n+\tRESET_WHEN_FOUND_TO_FOUND,\n+};\n+\n struct add_bisect_ref_data {\n \tstruct rev_info *revs;\n \tunsigned int object_flags;\n@@ -269,7 +276,79 @@ static int bisect_reset(const char *commit, bool quiet)\n \t}\n \n \tstrbuf_release(&branch);\n-\treturn bisect_clean_state();\n+\treturn 0;\n+}\n+\n+static int parse_reset_when_found(const char *value,\n+\t\t\t\t  enum reset_when_found_mode *mode)\n+{\n+\tif (!strcmp(value, \"original\"))\n+\t\t*mode = RESET_WHEN_FOUND_TO_ORIGINAL;\n+\telse if (!strcmp(value, \"found\"))\n+\t\t*mode = RESET_WHEN_FOUND_TO_FOUND;\n+\telse\n+\t\treturn error(_(\"invalid value for '--reset-when-found': '%s'\"),\n+\t\t\t     value);\n+\n+\treturn 0;\n+}\n+\n+static const char *reset_when_found_mode_name(enum reset_when_found_mode mode)\n+{\n+\tswitch (mode) {\n+\tcase RESET_WHEN_FOUND_TO_ORIGINAL:\n+\t\treturn \"original\";\n+\tcase RESET_WHEN_FOUND_TO_FOUND:\n+\t\treturn \"found\";\n+\tcase RESET_WHEN_FOUND_NONE:\n+\t\tBUG(\"no name for unset reset-when-found mode\");\n+\t}\n+\tBUG(\"unknown reset-when-found mode %d\", mode);\n+}\n+\n+static int read_reset_when_found(enum reset_when_found_mode *mode)\n+{\n+\tstruct strbuf value = STRBUF_INIT;\n+\tint res = 0;\n+\n+\t*mode = RESET_WHEN_FOUND_NONE;\n+\tif (is_empty_or_missing_file(git_path_bisect_reset_when_found()))\n+\t\treturn 0;\n+\n+\tif (strbuf_read_file(&value, git_path_bisect_reset_when_found(), 0) < 0) {\n+\t\tres = error_errno(_(\"could not read '%s'\"),\n+\t\t\t\t  git_path_bisect_reset_when_found());\n+\t\tgoto out;\n+\t}\n+\tstrbuf_trim(&value);\n+\tif (parse_reset_when_found(value.buf, mode))\n+\t\tres = -1;\n+\n+out:\n+\tstrbuf_release(&value);\n+\treturn res;\n+}\n+\n+static int bisect_reset_when_found(enum reset_when_found_mode mode)\n+{\n+\tstruct bisect_terms terms = { 0 };\n+\tchar *commit = NULL;\n+\tint res;\n+\n+\tif (mode == RESET_WHEN_FOUND_TO_FOUND) {\n+\t\tread_bisect_terms(&terms.term_bad, &terms.term_good);\n+\t\tcommit = xstrfmt(\"refs/bisect/%s\", terms.term_bad);\n+\t} else if (mode == RESET_WHEN_FOUND_NONE) {\n+\t\tBUG(\"automatic reset requested without a reset mode\");\n+\t}\n+\n+\tres = bisect_reset(commit, true);\n+\tif (!res)\n+\t\tres = bisect_clean_state();\n+\n+\tfree(commit);\n+\tfree_terms(&terms);\n+\treturn res;\n }\n \n static void log_commit(FILE *fp,\n@@ -682,7 +761,8 @@ static int bisect_successful(struct bisect_terms *terms)\n \treturn res;\n }\n \n-static enum bisect_error bisect_next(struct bisect_terms *terms, const char *prefix)\n+static enum bisect_error bisect_next(struct bisect_terms *terms,\n+\t\t\t\t     const char *prefix)\n {\n \tenum bisect_error res;\n \n@@ -705,7 +785,8 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n \treturn res;\n }\n \n-static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix)\n+static enum bisect_error bisect_auto_next(struct bisect_terms *terms,\n+\t\t\t\t\t  const char *prefix)\n {\n \tif (bisect_next_check(terms, NULL)) {\n \t\tbisect_print_status(terms);\n@@ -729,6 +810,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \tstruct strbuf bisect_names = STRBUF_INIT;\n \tstruct object_id head_oid;\n \tstruct object_id oid;\n+\tenum reset_when_found_mode reset_when_found = RESET_WHEN_FOUND_NONE;\n \tconst char *head;\n \n \tif (is_bare_repository(the_repository))\n@@ -752,6 +834,13 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \t\t\tno_checkout = 1;\n \t\t} else if (!strcmp(arg, \"--first-parent\")) {\n \t\t\tfirst_parent_only = 1;\n+\t\t} else if (!strcmp(arg, \"--reset-when-found\")) {\n+\t\t\treset_when_found = RESET_WHEN_FOUND_TO_ORIGINAL;\n+\t\t} else if (skip_prefix(arg, \"--reset-when-found=\", &arg)) {\n+\t\t\tif (parse_reset_when_found(arg, &reset_when_found)) {\n+\t\t\t\tres = BISECT_FAILED;\n+\t\t\t\tgoto finish;\n+\t\t\t}\n \t\t} else if (!strcmp(arg, \"--term-good\") ||\n \t\t\t !strcmp(arg, \"--term-old\")) {\n \t\t\ti++;\n@@ -789,6 +878,11 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \t\t\tbreak;\n \t\t}\n \t}\n+\tif (reset_when_found != RESET_WHEN_FOUND_NONE && no_checkout) {\n+\t\tres = error(_(\"options '%s' and '%s' cannot be used together\"),\n+\t\t\t    \"--reset-when-found\", \"--no-checkout\");\n+\t\tgoto finish;\n+\t}\n \tpathspec_pos = i;\n \n \t/*\n@@ -868,6 +962,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \tif (first_parent_only)\n \t\twrite_file(git_path_bisect_first_parent(), \"\\n\");\n \n+\tif (reset_when_found != RESET_WHEN_FOUND_NONE)\n+\t\twrite_file(git_path_bisect_reset_when_found(), \"%s\\n\",\n+\t\t\t   reset_when_found_mode_name(reset_when_found));\n+\n \tif (no_checkout) {\n \t\tif (repo_get_oid(the_repository, start_head.buf, &oid) < 0) {\n \t\t\tres = error(_(\"invalid ref: '%s'\"), start_head.buf);\n@@ -1098,7 +1196,7 @@ static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *f\n \tif (is_empty_or_missing_file(filename))\n \t\treturn error(_(\"cannot read file '%s' for replaying\"), filename);\n \n-\tif (bisect_reset(NULL, false))\n+\tif (bisect_clean_state())\n \t\treturn BISECT_FAILED;\n \n \tfp = fopen(filename, \"r\");\n@@ -1246,13 +1344,36 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n {\n \tint res = BISECT_OK;\n \tstruct strbuf command = STRBUF_INIT;\n+\tconst char *reset_when_found_arg;\n \tconst char *new_state;\n \tint temporary_stdout_fd, saved_stdout;\n \tint is_first_run = 1;\n+\tenum reset_when_found_mode reset_when_found = RESET_WHEN_FOUND_NONE;\n \n \tif (bisect_next_check(terms, NULL))\n \t\treturn BISECT_FAILED;\n \n+\tif (argc && !strcmp(argv[0], \"--reset-when-found\")) {\n+\t\treset_when_found = RESET_WHEN_FOUND_TO_ORIGINAL;\n+\t} else if (argc && skip_prefix(argv[0], \"--reset-when-found=\",\n+\t\t\t\t    &reset_when_found_arg)) {\n+\t\tif (parse_reset_when_found(reset_when_found_arg,\n+\t\t\t\t\t   &reset_when_found))\n+\t\t\treturn BISECT_FAILED;\n+\t}\n+\n+\tif (reset_when_found != RESET_WHEN_FOUND_NONE &&\n+\t    refs_ref_exists(get_main_ref_store(the_repository), \"BISECT_HEAD\"))\n+\t\treturn error(_(\"options '%s' and '%s' cannot be used together\"),\n+\t\t\t     \"--reset-when-found\", \"--no-checkout\");\n+\n+\tif (reset_when_found != RESET_WHEN_FOUND_NONE) {\n+\t\twrite_file(git_path_bisect_reset_when_found(), \"%s\\n\",\n+\t\t\t   reset_when_found_mode_name(reset_when_found));\n+\t\targc--;\n+\t\targv++;\n+\t}\n+\n \tif (!argc) {\n \t\terror(_(\"bisect run failed: no command provided.\"));\n \t\treturn BISECT_FAILED;\n@@ -1327,7 +1448,6 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n \t\t\tres = BISECT_OK;\n \t\t} else if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n \t\t\tprintf(_(\"bisect found first '%s' commit\\n\"), terms->term_bad);\n-\t\t\tres = BISECT_OK;\n \t\t} else if (res) {\n \t\t\terror(_(\"bisect run failed: 'git bisect %s'\"\n \t\t\t\t\" exited with error code %d\"), new_state, res);\n@@ -1344,10 +1464,15 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNUSED,\n \t\t\t     struct repository *repo UNUSED)\n {\n+\tint res;\n+\n \tif (argc > 1)\n \t\treturn error(_(\"'%s' requires either no argument or a commit\"),\n \t\t\t     \"git bisect reset\");\n-\treturn bisect_reset(argc ? argv[0] : NULL, false);\n+\tres = bisect_reset(argc ? argv[0] : NULL, false);\n+\tif (res)\n+\t\treturn res;\n+\treturn bisect_clean_state();\n }\n \n static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,\n@@ -1489,7 +1614,8 @@ int cmd_bisect(int argc,\n \t\t    !one_of(argv[0], terms.term_good, terms.term_bad, NULL))\n \t\t\tusage_msg_optf(_(\"unknown command: '%s'\"), git_bisect_usage,\n \t\t\t\t       options, argv[0]);\n-\t\tres = bisect_state(&terms, argc, argv);\n+\t\telse\n+\t\t\tres = bisect_state(&terms, argc, argv);\n \t\tfree_terms(&terms);\n \t} else {\n \t\targc--;\n@@ -1497,5 +1623,15 @@ int cmd_bisect(int argc,\n \t\tres = fn(argc, argv, prefix, repo);\n \t}\n \n+\tif (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n+\t\tenum reset_when_found_mode mode;\n+\n+\t\tif (read_reset_when_found(&mode))\n+\t\t\tres = BISECT_FAILED;\n+\t\telse if (mode != RESET_WHEN_FOUND_NONE &&\n+\t\t\t bisect_reset_when_found(mode))\n+\t\t\tres = BISECT_FAILED;\n+\t}\n+\n \treturn is_bisect_success(res) ? 0 : -res;\n }\ndiff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh\nindex 338df4f886..a7588222a8 100755\n--- a/t/t6030-bisect-porcelain.sh\n+++ b/t/t6030-bisect-porcelain.sh\n@@ -43,6 +43,42 @@ test_bisect_usage () {\n \ttest_cmp expect actual\n }\n \n+test_bisect_state_file () {\n+\tlocal file &&\n+\tfile=$(git rev-parse --git-path \"$1\") &&\n+\ttest_path_is_file \"$file\"\n+}\n+\n+test_bisect_state_missing () {\n+\tlocal file &&\n+\tfile=$(git rev-parse --git-path \"$1\") &&\n+\ttest_path_is_missing \"$file\"\n+}\n+\n+bisect_start_and_finish () {\n+\tgit bisect start \"$1\" $HASH4 $HASH2 &&\n+\tgit bisect bad\n+}\n+\n+bisect_run_reset_when_found () {\n+\twrite_script test_script.sh <<-\\EOF &&\n+\t! grep Another hello >/dev/null\n+\tEOF\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect run \"$1\" ./test_script.sh >my_bisect_log.txt &&\n+\ttest_grep \"$HASH3 is the first .bad. commit\" my_bisect_log.txt &&\n+\ttest_bisect_state_missing BISECT_RUN\n+}\n+\n+test_reset_when_found_fails () {\n+\tlocal pattern=\"$1\" &&\n+\tlocal state_file=\"$2\" &&\n+\tshift 2 &&\n+\ttest_must_fail \"$@\" 2>err &&\n+\ttest_grep -- \"$pattern\" err &&\n+\ttest_bisect_state_missing \"$state_file\"\n+}\n+\n test_expect_success 'bisect usage' \"\n \ttest_bisect_usage 1 git bisect reset extra1 extra2 <<-\\EOF &&\n \terror: 'git bisect reset' requires either no argument or a commit\n@@ -453,6 +489,91 @@ test_expect_success '\"git bisect run\" simple case' '\n \tgit bisect reset\n '\n \n+test_expect_success '\"git bisect start --reset-when-found\" defaults to original' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\tgit checkout main &&\n+\tbisect_start_and_finish --reset-when-found &&\n+\tactual=$(git rev-parse HEAD) &&\n+\ttest \"$HASH4\" = \"$actual\" &&\n+\tactual=$(git branch --show-current) &&\n+\ttest main = \"$actual\" &&\n+\ttest_bisect_state_missing BISECT_START &&\n+\n+\tbisect_start_and_finish --reset-when-found=original &&\n+\tactual=$(git rev-parse HEAD) &&\n+\ttest \"$HASH4\" = \"$actual\" &&\n+\tactual=$(git branch --show-current) &&\n+\ttest main = \"$actual\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '\"git bisect start --reset-when-found=found\" leaves first bad checked out' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\tbisect_start_and_finish --reset-when-found=found &&\n+\tactual=$(git rev-parse HEAD) &&\n+\ttest \"$HASH3\" = \"$actual\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '\"git bisect run --reset-when-found\" defaults to original' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\tbisect_run_reset_when_found --reset-when-found &&\n+\tactual=$(git rev-parse HEAD) &&\n+\ttest \"$HASH4\" = \"$actual\" &&\n+\tactual=$(git branch --show-current) &&\n+\ttest main = \"$actual\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '\"git bisect run --reset-when-found=found\" leaves first bad checked out' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\tbisect_run_reset_when_found --reset-when-found=found &&\n+\tactual=$(git rev-parse HEAD) &&\n+\ttest \"$HASH3\" = \"$actual\" &&\n+\ttest_bisect_state_missing BISECT_START\n+'\n+\n+test_expect_success '--reset-when-found rejects an unknown reset target' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"invalid value for.*--reset-when-found.*unknown\" BISECT_START \\\n+\t\tgit bisect start --reset-when-found=unknown $HASH4 $HASH2 &&\n+\n+\tgit bisect start $HASH4 $HASH2 &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"invalid value for.*--reset-when-found.*unknown\" \\\n+\t\tBISECT_RESET_WHEN_FOUND \\\n+\t\tgit bisect run --reset-when-found=unknown true\n+'\n+\n+test_expect_success '--reset-when-found cannot be used with --no-checkout' '\n+\ttest_when_finished \"git bisect reset\" &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"options .*--reset-when-found.* and .*--no-checkout.* cannot be used together\" BISECT_START \\\n+\t\tgit bisect start --reset-when-found=original --no-checkout $HASH4 $HASH2 &&\n+\n+\tgit bisect start --no-checkout $HASH4 $HASH2 &&\n+\ttest_reset_when_found_fails \\\n+\t\t\"options .*--reset-when-found.* and .*--no-checkout.* cannot be used together\" BISECT_RESET_WHEN_FOUND \\\n+\t\tgit bisect run --reset-when-found=found true\n+'\n+\n+test_expect_success 'without --reset-when-found the bisection state is kept' '\n+\ttest_when_finished \"git bisect reset\" &&\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect bad &&\n+\ttest_bisect_state_file BISECT_START\n+'\n+\n+test_expect_success '--reset-when-found does not leak into a later bisection' '\n+\ttest_when_finished \"git bisect reset && git checkout main\" &&\n+\tbisect_start_and_finish --reset-when-found &&\n+\n+\tgit bisect start $HASH4 $HASH2 &&\n+\tgit bisect bad &&\n+\ttest_bisect_state_file BISECT_START\n+'\n+\n # We want to automatically find the commit that\n # added \"Ciao\" into hello.\n test_expect_success '\"git bisect run\" with more complex \"git bisect start\"' '\n-- \ngitgitgadget\n"},{"id":"549462","messageId":"xmqqjyq812wr.fsf@gitster.g","threadId":"66011","inReplyTo":"97a4da55374c6dc52e6e990b3cadfaaa86a57640.1785705860.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 2/2] bisect: add --reset-when-found to leave when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-03T00:08:36Z","receivedAt":"2026-08-03T00:08:39Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> @@ -269,7 +276,79 @@ static int bisect_reset(const char *commit, bool quiet)\n>  \t}\n>  \n>  \tstrbuf_release(&branch);\n> -\treturn bisect_clean_state();\n> +\treturn 0;\n> +}\n\nLet's make a mental note that the intenral \"bisect_reset()\" no\nlonger calls bisect_clean_state(), so those that call this function\nwould eventually need to call it to compensate.\n\n> @@ -682,7 +761,8 @@ static int bisect_successful(struct bisect_terms *terms)\n>  \treturn res;\n>  }\n>  \n> -static enum bisect_error bisect_next(struct bisect_terms *terms, const char *prefix)\n> +static enum bisect_error bisect_next(struct bisect_terms *terms,\n> +\t\t\t\t     const char *prefix)\n>  {\n>  \tenum bisect_error res;\n>  \n> @@ -705,7 +785,8 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n>  \treturn res;\n>  }\n>  \n> -static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix)\n> +static enum bisect_error bisect_auto_next(struct bisect_terms *terms,\n> +\t\t\t\t\t  const char *prefix)\n>  {\n>  \tif (bisect_next_check(terms, NULL)) {\n>  \t\tbisect_print_status(terms);\n\nThe above two hunks are pure style clean-ups.  When having others to\nreview a 500+ line patch, you would want to omit them or move them\nto a separate preliminary clean-up step, to avoid distracting them.\n\n> @@ -1246,13 +1344,36 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n>  {\n>  \tint res = BISECT_OK;\n>  \tstruct strbuf command = STRBUF_INIT;\n> +\tconst char *reset_when_found_arg;\n>  \tconst char *new_state;\n>  \tint temporary_stdout_fd, saved_stdout;\n>  \tint is_first_run = 1;\n> +\tenum reset_when_found_mode reset_when_found = RESET_WHEN_FOUND_NONE;\n>  \n>  \tif (bisect_next_check(terms, NULL))\n>  \t\treturn BISECT_FAILED;\n>  \n> +\tif (argc && !strcmp(argv[0], \"--reset-when-found\")) {\n> +\t\treset_when_found = RESET_WHEN_FOUND_TO_ORIGINAL;\n> +\t} else if (argc && skip_prefix(argv[0], \"--reset-when-found=\",\n> +\t\t\t\t    &reset_when_found_arg)) {\n> +\t\tif (parse_reset_when_found(reset_when_found_arg,\n> +\t\t\t\t\t   &reset_when_found))\n> +\t\t\treturn BISECT_FAILED;\n> +\t}\n> +\n> +\tif (reset_when_found != RESET_WHEN_FOUND_NONE &&\n> +\t    refs_ref_exists(get_main_ref_store(the_repository), \"BISECT_HEAD\"))\n> +\t\treturn error(_(\"options '%s' and '%s' cannot be used together\"),\n> +\t\t\t     \"--reset-when-found\", \"--no-checkout\");\n> +\n> +\tif (reset_when_found != RESET_WHEN_FOUND_NONE) {\n> +\t\twrite_file(git_path_bisect_reset_when_found(), \"%s\\n\",\n> +\t\t\t   reset_when_found_mode_name(reset_when_found));\n> +\t\targc--;\n> +\t\targv++;\n> +\t}\n> +\n>  \tif (!argc) {\n>  \t\terror(_(\"bisect run failed: no command provided.\"));\n>  \t\treturn BISECT_FAILED;\n> @@ -1327,7 +1448,6 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n>  \t\t\tres = BISECT_OK;\n>  \t\t} else if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n>  \t\t\tprintf(_(\"bisect found first '%s' commit\\n\"), terms->term_bad);\n> -\t\t\tres = BISECT_OK;\n\nNow whoever called bisect_run() can react to 1st-bad-found but it is\ntheir responsibility to report that overall bisect was OK to their\ncallers.\n\n> @@ -1344,10 +1464,15 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)\n>  static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNUSED,\n>  \t\t\t     struct repository *repo UNUSED)\n>  {\n> +\tint res;\n> +\n>  \tif (argc > 1)\n>  \t\treturn error(_(\"'%s' requires either no argument or a commit\"),\n>  \t\t\t     \"git bisect reset\");\n> -\treturn bisect_reset(argc ? argv[0] : NULL, false);\n> +\tres = bisect_reset(argc ? argv[0] : NULL, false);\n> +\tif (res)\n> +\t\treturn res;\n> +\treturn bisect_clean_state();\n>  }\n\nEverything contained in this patch to enable --reset-when-finished\nare exactly as expected and very understandable to me, but this bit\nwas a bit hard to grok.  Let me think aloud to see if I can explain\nit.\n\n * Lower level bisect_reset() used to almost always called\n   clean_state(), but except when it returned error().\n\n * Now bisect_reset() never calls clean_state().\n\nHence, somebody has to call it in the new code.  The above change is\nan example of doing exactly that.  If bisect_reset() returns an\nerror, we refrain from cleaning the state.  If it succeeds, we clean\nthe state.\n\nEarlier we saw that bisect_reset_when_found() does the same thing.\nIf bisect_reset() did not fail, it called clean_state().\n\nBoth make sense.\n\n>  static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,\n> @@ -1489,7 +1614,8 @@ int cmd_bisect(int argc,\n>  \t\t    !one_of(argv[0], terms.term_good, terms.term_bad, NULL))\n>  \t\t\tusage_msg_optf(_(\"unknown command: '%s'\"), git_bisect_usage,\n>  \t\t\t\t       options, argv[0]);\n> -\t\tres = bisect_state(&terms, argc, argv);\n> +\t\telse\n> +\t\t\tres = bisect_state(&terms, argc, argv);\n>  \t\tfree_terms(&terms);\n>  \t} else {\n>  \t\targc--;\n\nWhat is this change about?  We used to see if the given terms\n(bad/good) are sensible and otherwise barfed with usage_msg_optf()\nthat never returns, so we did without \"else\".  With \"else\" you are\nmaking it more explicit.  The value of such a change is debatable.\nSome would say that, just like 'if ... die()', it is already\nexplicit enough that 'if ... usage()' never returns and does not\nrequire an \"else\".  Some would say new readers may not know die()\nand usage() do not return, so \"else\" makes it more explicit.  My\nstance is that we should not optimize our code for total newbies\n[*], so I may have a mild preference for the original over the\nupdated version, but it is minor.  In other words, I would not mind\nif an author wrote this either way in new code.\n\nHowever.\n\nIf an author is adding a new feature, I would recommend against\nmaking such a change that would only force reviewers to read more\nand think more about the change.  Do not waste reviewers' attention,\nwhich is a precious resource, to something much less relevant for\nthe goal of your topic.\n\n> @@ -1497,5 +1623,15 @@ int cmd_bisect(int argc,\n>  \t\tres = fn(argc, argv, prefix, repo);\n>  \t}\n>  \n> +\tif (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n> +\t\tenum reset_when_found_mode mode;\n> +\n> +\t\tif (read_reset_when_found(&mode))\n> +\t\t\tres = BISECT_FAILED;\n> +\t\telse if (mode != RESET_WHEN_FOUND_NONE &&\n> +\t\t\t bisect_reset_when_found(mode))\n> +\t\t\tres = BISECT_FAILED;\n> +\t}\n\nAre there \"dead end\" states, other than '1st-bad-found', in which we\ncan no longer make any progress?  One thing that comes to mind is\n\"you said this one is good, but that contradicts what you said about\nits ancestor that you said is bad\".  I wonder if we want to do\nanything special here, just as this part of the code handles the\n'1st-bad-found' state, for such \"dead end\" states.\n\nThis is just for my own education, as I am pondering possible future\nextensions.\n\n>  \treturn is_bisect_success(res) ? 0 : -res;\n>  }\n\nOverall, the patch looks very nicely done, except for a few minor\nnits that made my reading hiccup while I was reviewing this round.\n\nThanks.\n"},{"id":"549801","messageId":"CAHwyqnXOno2De_eOvH=LizyfWSpyVWpzGBwV4ZGW4bxOEtETOg@mail.gmail.com","threadId":"66011","inReplyTo":"xmqqjyq812wr.fsf@gitster.g","subject":"Re: [PATCH v6 2/2] bisect: add --reset-when-found to leave when done","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-08-06T07:30:57Z","receivedAt":"2026-08-06T07:31:38Z","isPatch":true,"body":"> > @@ -682,7 +761,8 @@ static int bisect_successful(struct bisect_terms *terms)\n> >       return res;\n> >  }\n> >\n> > -static enum bisect_error bisect_next(struct bisect_terms *terms, const char *prefix)\n> > +static enum bisect_error bisect_next(struct bisect_terms *terms,\n> > +                                  const char *prefix)\n> >  {\n> >       enum bisect_error res;\n> >\n> > @@ -705,7 +785,8 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre\n> >       return res;\n> >  }\n> >\n> > -static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix)\n> > +static enum bisect_error bisect_auto_next(struct bisect_terms *terms,\n> > +                                       const char *prefix)\n> >  {\n> >       if (bisect_next_check(terms, NULL)) {\n> >               bisect_print_status(terms);\n>\n> The above two hunks are pure style clean-ups.  When having others to\n> review a 500+ line patch, you would want to omit them or move them\n> to a separate preliminary clean-up step, to avoid distracting them.\n\nI'll remove.\n\n> >  static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED,\n> > @@ -1489,7 +1614,8 @@ int cmd_bisect(int argc,\n> >                   !one_of(argv[0], terms.term_good, terms.term_bad, NULL))\n> >                       usage_msg_optf(_(\"unknown command: '%s'\"), git_bisect_usage,\n> >                                      options, argv[0]);\n> > -             res = bisect_state(&terms, argc, argv);\n> > +             else\n> > +                     res = bisect_state(&terms, argc, argv);\n> >               free_terms(&terms);\n> >       } else {\n> >               argc--;\n>\n> What is this change about?  We used to see if the given terms\n> (bad/good) are sensible and otherwise barfed with usage_msg_optf()\n> that never returns, so we did without \"else\".  With \"else\" you are\n> making it more explicit.  The value of such a change is debatable.\n> Some would say that, just like 'if ... die()', it is already\n> explicit enough that 'if ... usage()' never returns and does not\n> require an \"else\".  Some would say new readers may not know die()\n> and usage() do not return, so \"else\" makes it more explicit.  My\n> stance is that we should not optimize our code for total newbies\n> [*], so I may have a mild preference for the original over the\n> updated version, but it is minor.  In other words, I would not mind\n> if an author wrote this either way in new code.\n>\n> However.\n>\n> If an author is adding a new feature, I would recommend against\n> making such a change that would only force reviewers to read more\n> and think more about the change.  Do not waste reviewers' attention,\n> which is a precious resource, to something much less relevant for\n> the goal of your topic.\n\nGood points.\n\n> > @@ -1497,5 +1623,15 @@ int cmd_bisect(int argc,\n> >               res = fn(argc, argv, prefix, repo);\n> >       }\n> >\n> > +     if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {\n> > +             enum reset_when_found_mode mode;\n> > +\n> > +             if (read_reset_when_found(&mode))\n> > +                     res = BISECT_FAILED;\n> > +             else if (mode != RESET_WHEN_FOUND_NONE &&\n> > +                      bisect_reset_when_found(mode))\n> > +                     res = BISECT_FAILED;\n> > +     }\n>\n> Are there \"dead end\" states, other than '1st-bad-found', in which we\n> can no longer make any progress?  One thing that comes to mind is\n> \"you said this one is good, but that contradicts what you said about\n> its ancestor that you said is bad\".  I wonder if we want to do\n> anything special here, just as this part of the code handles the\n> '1st-bad-found' state, for such \"dead end\" states.\n\nMaybe, but I'm not sure this topic is the right place to dig into this?\n\n\nHarald\n"},{"id":"549853","messageId":"xmqqik5nmk7s.fsf@gitster.g","threadId":"66011","inReplyTo":"CAHwyqnXOno2De_eOvH=LizyfWSpyVWpzGBwV4ZGW4bxOEtETOg@mail.gmail.com","subject":"Re: [PATCH v6 2/2] bisect: add --reset-when-found to leave when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-06T13:50:31Z","receivedAt":"2026-08-06T13:50:34Z","isPatch":true,"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n>> Are there \"dead end\" states, other than '1st-bad-found', in which we\n>> can no longer make any progress?  One thing that comes to mind is\n>> \"you said this one is good, but that contradicts what you said about\n>> its ancestor that you said is bad\".  I wonder if we want to do\n>> anything special here, just as this part of the code handles the\n>> '1st-bad-found' state, for such \"dead end\" states.\n>\n> Maybe, but I'm not sure this topic is the right place to dig into this?\n\nNo.  But thinking if it supports such new use cases in the future\nbefore committing to the new code structure is soemthing you would\nwant to make a habit of doing.\n\nI personally think the new \"do your thing just as usual, but then\nthe new code cleans up afterwards\" structure better supports\nanything new we will throw at in the future.\n\nThanks.\n\n"},{"id":"549861","messageId":"CAHwyqnX2wUsccqwmp-3WTsZQOcq0VV2TGHMPEpgKsGxOcH3sTQ@mail.gmail.com","threadId":"66011","inReplyTo":"xmqqik5nmk7s.fsf@gitster.g","subject":"Re: [PATCH v6 2/2] bisect: add --reset-when-found to leave when done","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-08-06T15:59:17Z","receivedAt":"2026-08-06T15:59:57Z","isPatch":true,"body":"On Thu, Aug 6, 2026 at 3:50 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Harald Nordgren <haraldnordgren@gmail.com> writes:\n>\n> >> Are there \"dead end\" states, other than '1st-bad-found', in which we\n> >> can no longer make any progress?  One thing that comes to mind is\n> >> \"you said this one is good, but that contradicts what you said about\n> >> its ancestor that you said is bad\".  I wonder if we want to do\n> >> anything special here, just as this part of the code handles the\n> >> '1st-bad-found' state, for such \"dead end\" states.\n> >\n> > Maybe, but I'm not sure this topic is the right place to dig into this?\n>\n> No.  But thinking if it supports such new use cases in the future\n> before committing to the new code structure is something you would\n> want to make a habit of doing.\n\nIt makes sense, but it is still hard for me to reason about the system\nas a whole or how it will develop. I still have a myopic view of the\ncodebase. But I agree in principle.\n\n> I personally think the new \"do your thing just as usual, but then\n> the new code cleans up afterwards\" structure better supports\n> anything new we will throw at in the future.\n\nI'll take this as not needing any change now. Overall, I am a big\nbeliever in the power of iterative development.\n\n\nHarald\n"},{"id":"549874","messageId":"xmqqldajjgn3.fsf@gitster.g","threadId":"66011","inReplyTo":"CAHwyqnX2wUsccqwmp-3WTsZQOcq0VV2TGHMPEpgKsGxOcH3sTQ@mail.gmail.com","subject":"Re: [PATCH v6 2/2] bisect: add --reset-when-found to leave when done","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-06T17:36:00Z","receivedAt":"2026-08-06T17:36:03Z","isPatch":true,"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n>> I personally think the new \"do your thing just as usual, but then\n>> the new code cleans up afterwards\" structure better supports\n>> anything new we will throw at in the future.\n>\n> I'll take this as not needing any change now. Overall, I am a big\n> believer in the power of iterative development.\n\nYeah, I am pretty happy with where we are with this series.\n\nThanks.\n\n\n\n"}]}