Skip to content

Commit e828747

Browse files
committed
Merge branch 'rs/bisect-executable-not-found'
A not-so-common mistake is to write a script to feed "git bisect run" without making it executable, in which case all tests will exit with 126 or 127 error codes, even on revisions that are marked as good. Try to recognize this situation and stop iteration early. * rs/bisect-executable-not-found: bisect--helper: double-check run command on exit code 126 and 127 bisect: document run behavior with exit codes 126 and 127 bisect--helper: release strbuf and strvec on run error bisect--helper: report actual bisect_state() argument on error
2 parents 9671764 + 48af1fd commit e828747

File tree

4 files changed

+125
-14
lines changed

4 files changed

+125
-14
lines changed

bisect.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -724,7 +724,8 @@ static int is_expected_rev(const struct object_id *oid)
724724
return res;
725725
}
726726

727-
static enum bisect_error bisect_checkout(const struct object_id *bisect_rev, int no_checkout)
727+
enum bisect_error bisect_checkout(const struct object_id *bisect_rev,
728+
int no_checkout)
728729
{
729730
char bisect_rev_hex[GIT_MAX_HEXSZ + 1];
730731
struct commit *commit;

bisect.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
struct commit_list;
55
struct repository;
6+
struct object_id;
67

78
/*
89
* Find bisection. If something is found, `reaches` will be the number of
@@ -69,4 +70,7 @@ void read_bisect_terms(const char **bad, const char **good);
6970

7071
int bisect_clean_state(void);
7172

73+
enum bisect_error bisect_checkout(const struct object_id *bisect_rev,
74+
int no_checkout);
75+
7276
#endif

builtin/bisect--helper.c

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,14 +1089,52 @@ static int bisect_visualize(struct bisect_terms *terms, const char **argv, int a
10891089
return res;
10901090
}
10911091

1092+
static int get_first_good(const char *refname, const struct object_id *oid,
1093+
int flag, void *cb_data)
1094+
{
1095+
oidcpy(cb_data, oid);
1096+
return 1;
1097+
}
1098+
1099+
static int verify_good(const struct bisect_terms *terms,
1100+
const char **quoted_argv)
1101+
{
1102+
int rc;
1103+
enum bisect_error res;
1104+
struct object_id good_rev;
1105+
struct object_id current_rev;
1106+
char *good_glob = xstrfmt("%s-*", terms->term_good);
1107+
int no_checkout = ref_exists("BISECT_HEAD");
1108+
1109+
for_each_glob_ref_in(get_first_good, good_glob, "refs/bisect/",
1110+
&good_rev);
1111+
free(good_glob);
1112+
1113+
if (read_ref(no_checkout ? "BISECT_HEAD" : "HEAD", &current_rev))
1114+
return -1;
1115+
1116+
res = bisect_checkout(&good_rev, no_checkout);
1117+
if (res != BISECT_OK)
1118+
return -1;
1119+
1120+
printf(_("running %s\n"), quoted_argv[0]);
1121+
rc = run_command_v_opt(quoted_argv, RUN_USING_SHELL);
1122+
1123+
res = bisect_checkout(&current_rev, no_checkout);
1124+
if (res != BISECT_OK)
1125+
return -1;
1126+
1127+
return rc;
1128+
}
1129+
10921130
static int bisect_run(struct bisect_terms *terms, const char **argv, int argc)
10931131
{
10941132
int res = BISECT_OK;
10951133
struct strbuf command = STRBUF_INIT;
1096-
struct strvec args = STRVEC_INIT;
10971134
struct strvec run_args = STRVEC_INIT;
10981135
const char *new_state;
10991136
int temporary_stdout_fd, saved_stdout;
1137+
int is_first_run = 1;
11001138

11011139
if (bisect_next_check(terms, NULL))
11021140
return BISECT_FAILED;
@@ -1111,16 +1149,37 @@ static int bisect_run(struct bisect_terms *terms, const char **argv, int argc)
11111149
strvec_push(&run_args, command.buf);
11121150

11131151
while (1) {
1114-
strvec_clear(&args);
1115-
11161152
printf(_("running %s\n"), command.buf);
11171153
res = run_command_v_opt(run_args.v, RUN_USING_SHELL);
11181154

1155+
/*
1156+
* Exit code 126 and 127 can either come from the shell
1157+
* if it was unable to execute or even find the script,
1158+
* or from the script itself. Check with a known-good
1159+
* revision to avoid trashing the bisect run due to a
1160+
* missing or non-executable script.
1161+
*/
1162+
if (is_first_run && (res == 126 || res == 127)) {
1163+
int rc = verify_good(terms, run_args.v);
1164+
is_first_run = 0;
1165+
if (rc < 0) {
1166+
error(_("unable to verify '%s' on good"
1167+
" revision"), command.buf);
1168+
res = BISECT_FAILED;
1169+
break;
1170+
}
1171+
if (rc == res) {
1172+
error(_("bogus exit code %d for good revision"),
1173+
rc);
1174+
res = BISECT_FAILED;
1175+
break;
1176+
}
1177+
}
1178+
11191179
if (res < 0 || 128 <= res) {
11201180
error(_("bisect run failed: exit code %d from"
11211181
" '%s' is < 0 or >= 128"), res, command.buf);
1122-
strbuf_release(&command);
1123-
return res;
1182+
break;
11241183
}
11251184

11261185
if (res == 125)
@@ -1132,8 +1191,10 @@ static int bisect_run(struct bisect_terms *terms, const char **argv, int argc)
11321191

11331192
temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
11341193

1135-
if (temporary_stdout_fd < 0)
1136-
return error_errno(_("cannot open file '%s' for writing"), git_path_bisect_run());
1194+
if (temporary_stdout_fd < 0) {
1195+
res = error_errno(_("cannot open file '%s' for writing"), git_path_bisect_run());
1196+
break;
1197+
}
11371198

11381199
fflush(stdout);
11391200
saved_stdout = dup(1);
@@ -1158,16 +1219,16 @@ static int bisect_run(struct bisect_terms *terms, const char **argv, int argc)
11581219
res = BISECT_OK;
11591220
} else if (res) {
11601221
error(_("bisect run failed: 'git bisect--helper --bisect-state"
1161-
" %s' exited with error code %d"), args.v[0], res);
1222+
" %s' exited with error code %d"), new_state, res);
11621223
} else {
11631224
continue;
11641225
}
1165-
1166-
strbuf_release(&command);
1167-
strvec_clear(&args);
1168-
strvec_clear(&run_args);
1169-
return res;
1226+
break;
11701227
}
1228+
1229+
strbuf_release(&command);
1230+
strvec_clear(&run_args);
1231+
return res;
11711232
}
11721233

11731234
int cmd_bisect__helper(int argc, const char **argv, const char *prefix)

t/t6030-bisect-porcelain.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,51 @@ test_expect_success '"git bisect run" with more complex "git bisect start"' '
278278
git bisect reset
279279
'
280280

281+
test_expect_success 'bisect run accepts exit code 126 as bad' '
282+
test_when_finished "git bisect reset" &&
283+
write_script test_script.sh <<-\EOF &&
284+
! grep Another hello || exit 126 >/dev/null
285+
EOF
286+
git bisect start &&
287+
git bisect good $HASH1 &&
288+
git bisect bad $HASH4 &&
289+
git bisect run ./test_script.sh >my_bisect_log.txt &&
290+
grep "$HASH3 is the first bad commit" my_bisect_log.txt
291+
'
292+
293+
test_expect_success POSIXPERM 'bisect run fails with non-executable test script' '
294+
test_when_finished "git bisect reset" &&
295+
>not-executable.sh &&
296+
chmod -x not-executable.sh &&
297+
git bisect start &&
298+
git bisect good $HASH1 &&
299+
git bisect bad $HASH4 &&
300+
test_must_fail git bisect run ./not-executable.sh >my_bisect_log.txt &&
301+
! grep "is the first bad commit" my_bisect_log.txt
302+
'
303+
304+
test_expect_success 'bisect run accepts exit code 127 as bad' '
305+
test_when_finished "git bisect reset" &&
306+
write_script test_script.sh <<-\EOF &&
307+
! grep Another hello || exit 127 >/dev/null
308+
EOF
309+
git bisect start &&
310+
git bisect good $HASH1 &&
311+
git bisect bad $HASH4 &&
312+
git bisect run ./test_script.sh >my_bisect_log.txt &&
313+
grep "$HASH3 is the first bad commit" my_bisect_log.txt
314+
'
315+
316+
test_expect_success 'bisect run fails with missing test script' '
317+
test_when_finished "git bisect reset" &&
318+
rm -f does-not-exist.sh &&
319+
git bisect start &&
320+
git bisect good $HASH1 &&
321+
git bisect bad $HASH4 &&
322+
test_must_fail git bisect run ./does-not-exist.sh >my_bisect_log.txt &&
323+
! grep "is the first bad commit" my_bisect_log.txt
324+
'
325+
281326
# $HASH1 is good, $HASH5 is bad, we skip $HASH3
282327
# but $HASH4 is good,
283328
# so we should find $HASH5 as the first bad commit

0 commit comments

Comments
 (0)