bisect--helper: `bisect_start` shell function partially in C

Reimplement the `bisect_start` shell function partially in C and add `bisect-start` subcommand to `git bisect--helper` to call it from git-bisect.sh . The last part is not converted because it calls another shell function. `bisect_start` shell function will be completed after the `bisect_next` shell function is ported in C. Using `--bisect-start` subcommand is a temporary measure to port shell function in C so as to use the existing test suite. As more functions are ported, this subcommand will be retired and will be called by some other methods. Also introduce a method `bisect_append_log_quoted` to keep things short and crisp. Note that we are a bit lax about command-line parsing because the helper is not supposed to be called by the user directly (but only from the git bisect script). Helped-by: Ramsay Jones <ramsay@ramsayjones.plus.com> Helped-by: Stephan Beyer <s-beyer@gmx.net> Mentored-by: Lars Schneider <larsxschneider@gmail.com> Mentored-by: Christian Couder <chriscool@tuxfamily.org> Mentored-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Pranit Bauva <pranit.bauva@gmail.com> Signed-off-by: Tanushree Tumane <tanushreetumane@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Pranit Bauva committed Jan 2, 2019 at 07:38 UTC 06f5608c14e6972748b84649d5b8ffd335bbd209
2 files changed +230 -132
builtin/bisect--helper.c
+228 -2
@@ -7,6 +7,7 @@
7 #include "argv-array.h"
8 #include "run-command.h"
9 #include "prompt.h"
10 +#include "quote.h"
11
12 static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
13 static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
@@ -14,6 +15,8 @@ static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK")
15 static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
16 static GIT_PATH_FUNC(git_path_bisect_head, "BISECT_HEAD")
17 static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
18 +static GIT_PATH_FUNC(git_path_head_name, "head-name")
19 +static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
20
21 static const char * const git_bisect_helper_usage[] = {
22 N_("git bisect--helper --next-all [--no-checkout]"),
@@ -24,6 +27,8 @@ static const char * const git_bisect_helper_usage[] = {
27 N_("git bisect--helper --bisect-check-and-set-terms <command> <good_term> <bad_term>"),
28 N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"),
29 N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"),
30 + N_("git bisect--helper --bisect-start [--term-{old,good}=<term> --term-{new,bad}=<term>]"
31 + "[--no-checkout] [<bad> [<good>...]] [--] [<paths>...]"),
32 NULL
33 };
34
@@ -389,6 +394,219 @@ static int bisect_terms(struct bisect_terms *terms, const char *option)
394 return 0;
395 }
396
397 +static int bisect_append_log_quoted(const char **argv)
398 +{
399 + int retval = 0;
400 + FILE *fp = fopen(git_path_bisect_log(), "a");
401 + struct strbuf orig_args = STRBUF_INIT;
402 +
403 + if (!fp)
404 + return -1;
405 +
406 + if (fprintf(fp, "git bisect start") < 1) {
407 + retval = -1;
408 + goto finish;
409 + }
410 +
411 + sq_quote_argv(&orig_args, argv);
412 + if (fprintf(fp, "%s\n", orig_args.buf) < 1)
413 + retval = -1;
414 +
415 +finish:
416 + fclose(fp);
417 + strbuf_release(&orig_args);
418 + return retval;
419 +}
420 +
421 +static int bisect_start(struct bisect_terms *terms, int no_checkout,
422 + const char **argv, int argc)
423 +{
424 + int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;
425 + int flags, pathspec_pos, retval = 0;
426 + struct string_list revs = STRING_LIST_INIT_DUP;
427 + struct string_list states = STRING_LIST_INIT_DUP;
428 + struct strbuf start_head = STRBUF_INIT;
429 + struct strbuf bisect_names = STRBUF_INIT;
430 + struct object_id head_oid;
431 + struct object_id oid;
432 + const char *head;
433 +
434 + if (is_bare_repository())
435 + no_checkout = 1;
436 +
437 + /*
438 + * Check for one bad and then some good revisions
439 + */
440 + for (i = 0; i < argc; i++) {
441 + if (!strcmp(argv[i], "--")) {
442 + has_double_dash = 1;
443 + break;
444 + }
445 + }
446 +
447 + for (i = 0; i < argc; i++) {
448 + const char *arg = argv[i];
449 + if (!strcmp(argv[i], "--")) {
450 + break;
451 + } else if (!strcmp(arg, "--no-checkout")) {
452 + no_checkout = 1;
453 + } else if (!strcmp(arg, "--term-good") ||
454 + !strcmp(arg, "--term-old")) {
455 + must_write_terms = 1;
456 + free((void *) terms->term_good);
457 + terms->term_good = xstrdup(argv[++i]);
458 + } else if (skip_prefix(arg, "--term-good=", &arg) ||
459 + skip_prefix(arg, "--term-old=", &arg)) {
460 + must_write_terms = 1;
461 + free((void *) terms->term_good);
462 + terms->term_good = xstrdup(arg);
463 + } else if (!strcmp(arg, "--term-bad") ||
464 + !strcmp(arg, "--term-new")) {
465 + must_write_terms = 1;
466 + free((void *) terms->term_bad);
467 + terms->term_bad = xstrdup(argv[++i]);
468 + } else if (skip_prefix(arg, "--term-bad=", &arg) ||
469 + skip_prefix(arg, "--term-new=", &arg)) {
470 + must_write_terms = 1;
471 + free((void *) terms->term_bad);
472 + terms->term_bad = xstrdup(arg);
473 + } else if (starts_with(arg, "--") &&
474 + !one_of(arg, "--term-good", "--term-bad", NULL)) {
475 + return error(_("unrecognized option: '%s'"), arg);
476 + } else {
477 + char *commit_id = xstrfmt("%s^{commit}", arg);
478 + if (get_oid(commit_id, &oid) && has_double_dash)
479 + die(_("'%s' does not appear to be a valid "
480 + "revision"), arg);
481 +
482 + string_list_append(&revs, oid_to_hex(&oid));
483 + free(commit_id);
484 + }
485 + }
486 + pathspec_pos = i;
487 +
488 + /*
489 + * The user ran "git bisect start <sha1> <sha1>", hence did not
490 + * explicitly specify the terms, but we are already starting to
491 + * set references named with the default terms, and won't be able
492 + * to change afterwards.
493 + */
494 + if (revs.nr)
495 + must_write_terms = 1;
496 + for (i = 0; i < revs.nr; i++) {
497 + if (bad_seen) {
498 + string_list_append(&states, terms->term_good);
499 + } else {
500 + bad_seen = 1;
501 + string_list_append(&states, terms->term_bad);
502 + }
503 + }
504 +
505 + /*
506 + * Verify HEAD
507 + */
508 + head = resolve_ref_unsafe("HEAD", 0, &head_oid, &flags);
509 + if (!head)
510 + if (get_oid("HEAD", &head_oid))
511 + return error(_("bad HEAD - I need a HEAD"));
512 +
513 + /*
514 + * Check if we are bisecting
515 + */
516 + if (!is_empty_or_missing_file(git_path_bisect_start())) {
517 + /* Reset to the rev from where we started */
518 + strbuf_read_file(&start_head, git_path_bisect_start(), 0);
519 + strbuf_trim(&start_head);
520 + if (!no_checkout) {
521 + struct argv_array argv = ARGV_ARRAY_INIT;
522 +
523 + argv_array_pushl(&argv, "checkout", start_head.buf,
524 + "--", NULL);
525 + if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
526 + retval = error(_("checking out '%s' failed."
527 + " Try 'git bisect start "
528 + "<valid-branch>'."),
529 + start_head.buf);
530 + goto finish;
531 + }
532 + }
533 + } else {
534 + /* Get the rev from where we start. */
535 + if (!get_oid(head, &head_oid) &&
536 + !starts_with(head, "refs/heads/")) {
537 + strbuf_reset(&start_head);
538 + strbuf_addstr(&start_head, oid_to_hex(&head_oid));
539 + } else if (!get_oid(head, &head_oid) &&
540 + skip_prefix(head, "refs/heads/", &head)) {
541 + /*
542 + * This error message should only be triggered by
543 + * cogito usage, and cogito users should understand
544 + * it relates to cg-seek.
545 + */
546 + if (!is_empty_or_missing_file(git_path_head_name()))
547 + return error(_("won't bisect on cg-seek'ed tree"));
548 + strbuf_addstr(&start_head, head);
549 + } else {
550 + return error(_("bad HEAD - strange symbolic ref"));
551 + }
552 + }
553 +
554 + /*
555 + * Get rid of any old bisect state.
556 + */
557 + if (bisect_clean_state())
558 + return -1;
559 +
560 + /*
561 + * In case of mistaken revs or checkout error, or signals received,
562 + * "bisect_auto_next" below may exit or misbehave.
563 + * We have to trap this to be able to clean up using
564 + * "bisect_clean_state".
565 + */
566 +
567 + /*
568 + * Write new start state
569 + */
570 + write_file(git_path_bisect_start(), "%s\n", start_head.buf);
571 +
572 + if (no_checkout) {
573 + get_oid(start_head.buf, &oid);
574 + if (update_ref(NULL, "BISECT_HEAD", &oid, NULL, 0,
575 + UPDATE_REFS_MSG_ON_ERR)) {
576 + retval = -1;
577 + goto finish;
578 + }
579 + }
580 +
581 + if (pathspec_pos < argc - 1)
582 + sq_quote_argv(&bisect_names, argv + pathspec_pos);
583 + write_file(git_path_bisect_names(), "%s\n", bisect_names.buf);
584 +
585 + for (i = 0; i < states.nr; i++)
586 + if (bisect_write(states.items[i].string,
587 + revs.items[i].string, terms, 1)) {
588 + retval = -1;
589 + goto finish;
590 + }
591 +
592 + if (must_write_terms && write_terms(terms->term_bad,
593 + terms->term_good)) {
594 + retval = -1;
595 + goto finish;
596 + }
597 +
598 + retval = bisect_append_log_quoted(argv);
599 + if (retval)
600 + retval = -1;
601 +
602 +finish:
603 + string_list_clear(&revs, 0);
604 + string_list_clear(&states, 0);
605 + strbuf_release(&start_head);
606 + strbuf_release(&bisect_names);
607 + return retval;
608 +}
609 +
610 int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
611 {
612 enum {
@@ -400,7 +618,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
618 BISECT_WRITE,
619 CHECK_AND_SET_TERMS,
620 BISECT_NEXT_CHECK,
403 - BISECT_TERMS
621 + BISECT_TERMS,
622 + BISECT_START
623 } cmdmode = 0;
624 int no_checkout = 0, res = 0, nolog = 0;
625 struct option options[] = {
@@ -422,6 +641,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
641 N_("check whether bad or good terms exist"), BISECT_NEXT_CHECK),
642 OPT_CMDMODE(0, "bisect-terms", &cmdmode,
643 N_("print out the bisect terms"), BISECT_TERMS),
644 + OPT_CMDMODE(0, "bisect-start", &cmdmode,
645 + N_("start the bisect session"), BISECT_START),
646 OPT_BOOL(0, "no-checkout", &no_checkout,
647 N_("update BISECT_HEAD instead of checking out the current commit")),
648 OPT_BOOL(0, "no-log", &nolog,
@@ -431,7 +652,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
652 struct bisect_terms terms = { .term_good = NULL, .term_bad = NULL };
653
654 argc = parse_options(argc, argv, prefix, options,
434 - git_bisect_helper_usage, PARSE_OPT_KEEP_UNKNOWN);
655 + git_bisect_helper_usage,
656 + PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN);
657
658 if (!cmdmode)
659 usage_with_options(git_bisect_helper_usage, options);
@@ -477,6 +699,10 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
699 return error(_("--bisect-terms requires 0 or 1 argument"));
700 res = bisect_terms(&terms, argc == 1 ? argv[0] : NULL);
701 break;
702 + case BISECT_START:
703 + set_terms(&terms, "bad", "good");
704 + res = bisect_start(&terms, no_checkout, argv, argc);
705 + break;
706 default:
707 return error("BUG: unknown subcommand '%d'", cmdmode);
708 }
git-bisect.sh
+2 -130
@@ -71,122 +71,7 @@ bisect_autostart() {
71 }
72
73 bisect_start() {
74 - #
75 - # Check for one bad and then some good revisions.
76 - #
77 - has_double_dash=0
78 - for arg; do
79 - case "$arg" in --) has_double_dash=1; break ;; esac
80 - done
81 - orig_args=$(git rev-parse --sq-quote "$@")
82 - bad_seen=0
83 - eval=''
84 - must_write_terms=0
85 - revs=''
86 - if test "z$(git rev-parse --is-bare-repository)" != zfalse
87 - then
88 - mode=--no-checkout
89 - else
90 - mode=''
91 - fi
92 - while [ $# -gt 0 ]; do
93 - arg="$1"
94 - case "$arg" in
95 - --)
96 - shift
97 - break
98 - ;;
99 - --no-checkout)
100 - mode=--no-checkout
101 - shift ;;
102 - --term-good|--term-old)
103 - shift
104 - must_write_terms=1
105 - TERM_GOOD=$1
106 - shift ;;
107 - --term-good=*|--term-old=*)
108 - must_write_terms=1
109 - TERM_GOOD=${1#*=}
110 - shift ;;
111 - --term-bad|--term-new)
112 - shift
113 - must_write_terms=1
114 - TERM_BAD=$1
115 - shift ;;
116 - --term-bad=*|--term-new=*)
117 - must_write_terms=1
118 - TERM_BAD=${1#*=}
119 - shift ;;
120 - --*)
121 - die "$(eval_gettext "unrecognised option: '\$arg'")" ;;
122 - *)
123 - rev=$(git rev-parse -q --verify "$arg^{commit}") || {
124 - test $has_double_dash -eq 1 &&
125 - die "$(eval_gettext "'\$arg' does not appear to be a valid revision")"
126 - break
127 - }
128 - revs="$revs $rev"
129 - shift
130 - ;;
131 - esac
132 - done
133 -
134 - for rev in $revs
135 - do
136 - # The user ran "git bisect start <sha1>
137 - # <sha1>", hence did not explicitly specify
138 - # the terms, but we are already starting to
139 - # set references named with the default terms,
140 - # and won't be able to change afterwards.
141 - must_write_terms=1
142 -
143 - case $bad_seen in
144 - 0) state=$TERM_BAD ; bad_seen=1 ;;
145 - *) state=$TERM_GOOD ;;
146 - esac
147 - eval="$eval git bisect--helper --bisect-write '$state' '$rev' '$TERM_GOOD' '$TERM_BAD' 'nolog' &&"
148 - done
149 - #
150 - # Verify HEAD.
151 - #
152 - head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) ||
153 - head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
154 - die "$(gettext "Bad HEAD - I need a HEAD")"
155 -
156 - #
157 - # Check if we are bisecting.
158 - #
159 - start_head=''
160 - if test -s "$GIT_DIR/BISECT_START"
161 - then
162 - # Reset to the rev from where we started.
163 - start_head=$(cat "$GIT_DIR/BISECT_START")
164 - if test "z$mode" != "z--no-checkout"
165 - then
166 - git checkout "$start_head" -- ||
167 - die "$(eval_gettext "Checking out '\$start_head' failed. Try 'git bisect reset <valid-branch>'.")"
168 - fi
169 - else
170 - # Get rev from where we start.
171 - case "$head" in
172 - refs/heads/*|$_x40)
173 - # This error message should only be triggered by
174 - # cogito usage, and cogito users should understand
175 - # it relates to cg-seek.
176 - [ -s "$GIT_DIR/head-name" ] &&
177 - die "$(gettext "won't bisect on cg-seek'ed tree")"
178 - start_head="${head#refs/heads/}"
179 - ;;
180 - *)
181 - die "$(gettext "Bad HEAD - strange symbolic ref")"
182 - ;;
183 - esac
184 - fi
185 -
186 - #
187 - # Get rid of any old bisect state.
188 - #
189 - git bisect--helper --bisect-clean-state || exit
74 + git bisect--helper --bisect-start $@ || exit
75
76 #
77 # Change state.
@@ -198,23 +83,10 @@ bisect_start() {
83 trap 'git bisect--helper --bisect-clean-state' 0
84 trap 'exit 255' 1 2 3 15
85
201 - #
202 - # Write new start state.
203 - #
204 - echo "$start_head" >"$GIT_DIR/BISECT_START" && {
205 - test "z$mode" != "z--no-checkout" ||
206 - git update-ref --no-deref BISECT_HEAD "$start_head"
207 - } &&
208 - git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" &&
209 - eval "$eval true" &&
210 - if test $must_write_terms -eq 1
211 - then
212 - git bisect--helper --write-terms "$TERM_BAD" "$TERM_GOOD" || exit
213 - fi &&
214 - echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
86 #
87 # Check if we can proceed to the next bisect state.
88 #
89 + get_terms
90 bisect_auto_next
91
92 trap '-' 0