Skip to content

Commit 5faa27a

Browse files
committed
Merge branch 'pb/bisect-helper'
An early part of piece-by-piece rewrite of "git bisect". * pb/bisect-helper: bisect--helper: `is_expected_rev` & `check_expected_revs` shell function in C t6030: explicitly test for bisection cleanup bisect--helper: `bisect_clean_state` shell function in C bisect--helper: `write_terms` shell function in C bisect--helper: rewrite `check_term_format` shell function in C bisect--helper: use OPT_CMDMODE instead of OPT_BOOL
2 parents 130b512 + b903674 commit 5faa27a

File tree

5 files changed

+204
-89
lines changed

5 files changed

+204
-89
lines changed

bisect.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,12 @@ static int read_bisect_refs(void)
433433

434434
static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
435435
static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
436+
static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK")
437+
static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
438+
static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
439+
static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
436440
static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
441+
static GIT_PATH_FUNC(git_path_head_name, "head-name")
437442

438443
static void read_bisect_paths(struct argv_array *array)
439444
{
@@ -1044,3 +1049,40 @@ int estimate_bisect_steps(int all)
10441049

10451050
return (e < 3 * x) ? n : n - 1;
10461051
}
1052+
1053+
static int mark_for_removal(const char *refname, const struct object_id *oid,
1054+
int flag, void *cb_data)
1055+
{
1056+
struct string_list *refs = cb_data;
1057+
char *ref = xstrfmt("refs/bisect%s", refname);
1058+
string_list_append(refs, ref);
1059+
return 0;
1060+
}
1061+
1062+
int bisect_clean_state(void)
1063+
{
1064+
int result = 0;
1065+
1066+
/* There may be some refs packed during bisection */
1067+
struct string_list refs_for_removal = STRING_LIST_INIT_NODUP;
1068+
for_each_ref_in("refs/bisect", mark_for_removal, (void *) &refs_for_removal);
1069+
string_list_append(&refs_for_removal, xstrdup("BISECT_HEAD"));
1070+
result = delete_refs("bisect: remove", &refs_for_removal, REF_NODEREF);
1071+
refs_for_removal.strdup_strings = 1;
1072+
string_list_clear(&refs_for_removal, 0);
1073+
unlink_or_warn(git_path_bisect_expected_rev());
1074+
unlink_or_warn(git_path_bisect_ancestors_ok());
1075+
unlink_or_warn(git_path_bisect_log());
1076+
unlink_or_warn(git_path_bisect_names());
1077+
unlink_or_warn(git_path_bisect_run());
1078+
unlink_or_warn(git_path_bisect_terms());
1079+
/* Cleanup head-name if it got left by an old version of git-bisect */
1080+
unlink_or_warn(git_path_head_name());
1081+
/*
1082+
* Cleanup BISECT_START last to support the --no-checkout option
1083+
* introduced in the commit 4796e823a.
1084+
*/
1085+
unlink_or_warn(git_path_bisect_start());
1086+
1087+
return result;
1088+
}

bisect.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,6 @@ extern int estimate_bisect_steps(int all);
2828

2929
extern void read_bisect_terms(const char **bad, const char **good);
3030

31+
extern int bisect_clean_state(void);
32+
3133
#endif

builtin/bisect--helper.c

Lines changed: 131 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,128 @@
22
#include "cache.h"
33
#include "parse-options.h"
44
#include "bisect.h"
5+
#include "refs.h"
6+
7+
static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
8+
static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
9+
static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK")
510

611
static const char * const git_bisect_helper_usage[] = {
712
N_("git bisect--helper --next-all [--no-checkout]"),
13+
N_("git bisect--helper --write-terms <bad_term> <good_term>"),
14+
N_("git bisect--helper --bisect-clean-state"),
815
NULL
916
};
1017

18+
/*
19+
* Check whether the string `term` belongs to the set of strings
20+
* included in the variable arguments.
21+
*/
22+
LAST_ARG_MUST_BE_NULL
23+
static int one_of(const char *term, ...)
24+
{
25+
int res = 0;
26+
va_list matches;
27+
const char *match;
28+
29+
va_start(matches, term);
30+
while (!res && (match = va_arg(matches, const char *)))
31+
res = !strcmp(term, match);
32+
va_end(matches);
33+
34+
return res;
35+
}
36+
37+
static int check_term_format(const char *term, const char *orig_term)
38+
{
39+
int res;
40+
char *new_term = xstrfmt("refs/bisect/%s", term);
41+
42+
res = check_refname_format(new_term, 0);
43+
free(new_term);
44+
45+
if (res)
46+
return error(_("'%s' is not a valid term"), term);
47+
48+
if (one_of(term, "help", "start", "skip", "next", "reset",
49+
"visualize", "replay", "log", "run", "terms", NULL))
50+
return error(_("can't use the builtin command '%s' as a term"), term);
51+
52+
/*
53+
* In theory, nothing prevents swapping completely good and bad,
54+
* but this situation could be confusing and hasn't been tested
55+
* enough. Forbid it for now.
56+
*/
57+
58+
if ((strcmp(orig_term, "bad") && one_of(term, "bad", "new", NULL)) ||
59+
(strcmp(orig_term, "good") && one_of(term, "good", "old", NULL)))
60+
return error(_("can't change the meaning of the term '%s'"), term);
61+
62+
return 0;
63+
}
64+
65+
static int write_terms(const char *bad, const char *good)
66+
{
67+
FILE *fp = NULL;
68+
int res;
69+
70+
if (!strcmp(bad, good))
71+
return error(_("please use two different terms"));
72+
73+
if (check_term_format(bad, "bad") || check_term_format(good, "good"))
74+
return -1;
75+
76+
fp = fopen(git_path_bisect_terms(), "w");
77+
if (!fp)
78+
return error_errno(_("could not open the file BISECT_TERMS"));
79+
80+
res = fprintf(fp, "%s\n%s\n", bad, good);
81+
res |= fclose(fp);
82+
return (res < 0) ? -1 : 0;
83+
}
84+
85+
static int is_expected_rev(const char *expected_hex)
86+
{
87+
struct strbuf actual_hex = STRBUF_INIT;
88+
int res = 0;
89+
if (strbuf_read_file(&actual_hex, git_path_bisect_expected_rev(), 0) >= 40) {
90+
strbuf_trim(&actual_hex);
91+
res = !strcmp(actual_hex.buf, expected_hex);
92+
}
93+
strbuf_release(&actual_hex);
94+
return res;
95+
}
96+
97+
static void check_expected_revs(const char **revs, int rev_nr)
98+
{
99+
int i;
100+
101+
for (i = 0; i < rev_nr; i++) {
102+
if (!is_expected_rev(revs[i])) {
103+
unlink_or_warn(git_path_bisect_ancestors_ok());
104+
unlink_or_warn(git_path_bisect_expected_rev());
105+
}
106+
}
107+
}
108+
11109
int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
12110
{
13-
int next_all = 0;
111+
enum {
112+
NEXT_ALL = 1,
113+
WRITE_TERMS,
114+
BISECT_CLEAN_STATE,
115+
CHECK_EXPECTED_REVS
116+
} cmdmode = 0;
14117
int no_checkout = 0;
15118
struct option options[] = {
16-
OPT_BOOL(0, "next-all", &next_all,
17-
N_("perform 'git bisect next'")),
119+
OPT_CMDMODE(0, "next-all", &cmdmode,
120+
N_("perform 'git bisect next'"), NEXT_ALL),
121+
OPT_CMDMODE(0, "write-terms", &cmdmode,
122+
N_("write the terms to .git/BISECT_TERMS"), WRITE_TERMS),
123+
OPT_CMDMODE(0, "bisect-clean-state", &cmdmode,
124+
N_("cleanup the bisection state"), BISECT_CLEAN_STATE),
125+
OPT_CMDMODE(0, "check-expected-revs", &cmdmode,
126+
N_("check for expected revs"), CHECK_EXPECTED_REVS),
18127
OPT_BOOL(0, "no-checkout", &no_checkout,
19128
N_("update BISECT_HEAD instead of checking out the current commit")),
20129
OPT_END()
@@ -23,9 +132,25 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
23132
argc = parse_options(argc, argv, prefix, options,
24133
git_bisect_helper_usage, 0);
25134

26-
if (!next_all)
135+
if (!cmdmode)
27136
usage_with_options(git_bisect_helper_usage, options);
28137

29-
/* next-all */
30-
return bisect_next_all(prefix, no_checkout);
138+
switch (cmdmode) {
139+
case NEXT_ALL:
140+
return bisect_next_all(prefix, no_checkout);
141+
case WRITE_TERMS:
142+
if (argc != 2)
143+
return error(_("--write-terms requires two arguments"));
144+
return write_terms(argv[0], argv[1]);
145+
case BISECT_CLEAN_STATE:
146+
if (argc != 0)
147+
return error(_("--bisect-clean-state requires no arguments"));
148+
return bisect_clean_state();
149+
case CHECK_EXPECTED_REVS:
150+
check_expected_revs(argv, argc);
151+
return 0;
152+
default:
153+
return error("BUG: unknown subcommand '%d'", cmdmode);
154+
}
155+
return 0;
31156
}

git-bisect.sh

Lines changed: 12 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ bisect_start() {
186186
#
187187
# Get rid of any old bisect state.
188188
#
189-
bisect_clean_state || exit
189+
git bisect--helper --bisect-clean-state || exit
190190

191191
#
192192
# Change state.
@@ -195,7 +195,7 @@ bisect_start() {
195195
# We have to trap this to be able to clean up using
196196
# "bisect_clean_state".
197197
#
198-
trap 'bisect_clean_state' 0
198+
trap 'git bisect--helper --bisect-clean-state' 0
199199
trap 'exit 255' 1 2 3 15
200200

201201
#
@@ -209,7 +209,7 @@ bisect_start() {
209209
eval "$eval true" &&
210210
if test $must_write_terms -eq 1
211211
then
212-
write_terms "$TERM_BAD" "$TERM_GOOD"
212+
git bisect--helper --write-terms "$TERM_BAD" "$TERM_GOOD" || exit
213213
fi &&
214214
echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
215215
#
@@ -237,22 +237,6 @@ bisect_write() {
237237
test -n "$nolog" || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
238238
}
239239

240-
is_expected_rev() {
241-
test -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
242-
test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV")
243-
}
244-
245-
check_expected_revs() {
246-
for _rev in "$@"; do
247-
if ! is_expected_rev "$_rev"
248-
then
249-
rm -f "$GIT_DIR/BISECT_ANCESTORS_OK"
250-
rm -f "$GIT_DIR/BISECT_EXPECTED_REV"
251-
return
252-
fi
253-
done
254-
}
255-
256240
bisect_skip() {
257241
all=''
258242
for arg in "$@"
@@ -280,7 +264,7 @@ bisect_state() {
280264
rev=$(git rev-parse --verify "$bisected_head") ||
281265
die "$(eval_gettext "Bad rev input: \$bisected_head")"
282266
bisect_write "$state" "$rev"
283-
check_expected_revs "$rev" ;;
267+
git bisect--helper --check-expected-revs "$rev" ;;
284268
2,"$TERM_BAD"|*,"$TERM_GOOD"|*,skip)
285269
shift
286270
hash_list=''
@@ -294,7 +278,7 @@ bisect_state() {
294278
do
295279
bisect_write "$state" "$rev"
296280
done
297-
check_expected_revs $hash_list ;;
281+
git bisect--helper --check-expected-revs $hash_list ;;
298282
*,"$TERM_BAD")
299283
die "$(eval_gettext "'git bisect \$TERM_BAD' can take only one argument.")" ;;
300284
*)
@@ -430,27 +414,7 @@ bisect_reset() {
430414
die "$(eval_gettext "Could not check out original HEAD '\$branch'.
431415
Try 'git bisect reset <commit>'.")"
432416
fi
433-
bisect_clean_state
434-
}
435-
436-
bisect_clean_state() {
437-
# There may be some refs packed during bisection.
438-
git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
439-
while read ref hash
440-
do
441-
git update-ref -d $ref $hash || exit
442-
done
443-
rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
444-
rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
445-
rm -f "$GIT_DIR/BISECT_LOG" &&
446-
rm -f "$GIT_DIR/BISECT_NAMES" &&
447-
rm -f "$GIT_DIR/BISECT_RUN" &&
448-
rm -f "$GIT_DIR/BISECT_TERMS" &&
449-
# Cleanup head-name if it got left by an old version of git-bisect
450-
rm -f "$GIT_DIR/head-name" &&
451-
git update-ref -d --no-deref BISECT_HEAD &&
452-
# clean up BISECT_START last
453-
rm -f "$GIT_DIR/BISECT_START"
417+
git bisect--helper --bisect-clean-state || exit
454418
}
455419

456420
bisect_replay () {
@@ -557,45 +521,6 @@ get_terms () {
557521
fi
558522
}
559523

560-
write_terms () {
561-
TERM_BAD=$1
562-
TERM_GOOD=$2
563-
if test "$TERM_BAD" = "$TERM_GOOD"
564-
then
565-
die "$(gettext "please use two different terms")"
566-
fi
567-
check_term_format "$TERM_BAD" bad
568-
check_term_format "$TERM_GOOD" good
569-
printf '%s\n%s\n' "$TERM_BAD" "$TERM_GOOD" >"$GIT_DIR/BISECT_TERMS"
570-
}
571-
572-
check_term_format () {
573-
term=$1
574-
git check-ref-format refs/bisect/"$term" ||
575-
die "$(eval_gettext "'\$term' is not a valid term")"
576-
case "$term" in
577-
help|start|terms|skip|next|reset|visualize|replay|log|run)
578-
die "$(eval_gettext "can't use the builtin command '\$term' as a term")"
579-
;;
580-
bad|new)
581-
if test "$2" != bad
582-
then
583-
# In theory, nothing prevents swapping
584-
# completely good and bad, but this situation
585-
# could be confusing and hasn't been tested
586-
# enough. Forbid it for now.
587-
die "$(eval_gettext "can't change the meaning of term '\$term'")"
588-
fi
589-
;;
590-
good|old)
591-
if test "$2" != good
592-
then
593-
die "$(eval_gettext "can't change the meaning of term '\$term'")"
594-
fi
595-
;;
596-
esac
597-
}
598-
599524
check_and_set_terms () {
600525
cmd="$1"
601526
case "$cmd" in
@@ -609,13 +534,17 @@ check_and_set_terms () {
609534
bad|good)
610535
if ! test -s "$GIT_DIR/BISECT_TERMS"
611536
then
612-
write_terms bad good
537+
TERM_BAD=bad
538+
TERM_GOOD=good
539+
git bisect--helper --write-terms "$TERM_BAD" "$TERM_GOOD" || exit
613540
fi
614541
;;
615542
new|old)
616543
if ! test -s "$GIT_DIR/BISECT_TERMS"
617544
then
618-
write_terms new old
545+
TERM_BAD=new
546+
TERM_GOOD=old
547+
git bisect--helper --write-terms "$TERM_BAD" "$TERM_GOOD" || exit
619548
fi
620549
;;
621550
esac ;;

0 commit comments

Comments
 (0)