From 5e82c3dd22aa804ba0ae571bf903b387b0f766a6 Mon Sep 17 00:00:00 2001 From: Pranit Bauva Date: Wed, 2 Jan 2019 07:38:31 -0800 Subject: [PATCH 1/7] bisect--helper: `bisect_reset` shell function in C Reimplement `bisect_reset` shell function in C and add a `--bisect-reset` subcommand to `git bisect--helper` to call it from git-bisect.sh . Using `bisect_reset` subcommand is a temporary measure to port shell functions to C so as to use the existing test suite. As more functions are ported, this subcommand would be retired but its implementation will be called by some other method. Note: --bisect-clean-state subcommand has not been retired as there are still a function namely `bisect_start()` which still uses this subcommand. Mentored-by: Lars Schneider Mentored-by: Christian Couder Mentored by: Johannes Schindelin Signed-off-by: Pranit Bauva Signed-off-by: Tanushree Tumane Signed-off-by: Junio C Hamano --- builtin/bisect--helper.c | 51 +++++++++++++++++++++++++++++++++++++++- git-bisect.sh | 28 ++-------------------- 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 4b5fadcbe1..aa6495dc84 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -3,15 +3,21 @@ #include "parse-options.h" #include "bisect.h" #include "refs.h" +#include "dir.h" +#include "argv-array.h" +#include "run-command.h" static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS") static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV") static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK") +static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START") +static GIT_PATH_FUNC(git_path_bisect_head, "BISECT_HEAD") static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --next-all [--no-checkout]"), N_("git bisect--helper --write-terms "), N_("git bisect--helper --bisect-clean-state"), + N_("git bisect--helper --bisect-reset []"), NULL }; @@ -106,13 +112,50 @@ static void check_expected_revs(const char **revs, int rev_nr) } } +static int bisect_reset(const char *commit) +{ + struct strbuf branch = STRBUF_INIT; + + if (!commit) { + if (strbuf_read_file(&branch, git_path_bisect_start(), 0) < 1) { + printf(_("We are not bisecting.\n")); + return 0; + } + strbuf_rtrim(&branch); + } else { + struct object_id oid; + + if (get_oid_commit(commit, &oid)) + return error(_("'%s' is not a valid commit"), commit); + strbuf_addstr(&branch, commit); + } + + if (!file_exists(git_path_bisect_head())) { + struct argv_array argv = ARGV_ARRAY_INIT; + + argv_array_pushl(&argv, "checkout", branch.buf, "--", NULL); + if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { + strbuf_release(&branch); + argv_array_clear(&argv); + return error(_("could not check out original" + " HEAD '%s'. Try 'git bisect" + "reset '."), branch.buf); + } + argv_array_clear(&argv); + } + + strbuf_release(&branch); + return bisect_clean_state(); +} + int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { enum { NEXT_ALL = 1, WRITE_TERMS, BISECT_CLEAN_STATE, - CHECK_EXPECTED_REVS + CHECK_EXPECTED_REVS, + BISECT_RESET } cmdmode = 0; int no_checkout = 0; struct option options[] = { @@ -124,6 +167,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) N_("cleanup the bisection state"), BISECT_CLEAN_STATE), OPT_CMDMODE(0, "check-expected-revs", &cmdmode, N_("check for expected revs"), CHECK_EXPECTED_REVS), + OPT_CMDMODE(0, "bisect-reset", &cmdmode, + N_("reset the bisection state"), BISECT_RESET), OPT_BOOL(0, "no-checkout", &no_checkout, N_("update BISECT_HEAD instead of checking out the current commit")), OPT_END() @@ -149,6 +194,10 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) case CHECK_EXPECTED_REVS: check_expected_revs(argv, argc); return 0; + case BISECT_RESET: + if (argc > 1) + return error(_("--bisect-reset requires either no argument or a commit")); + return !!bisect_reset(argc ? argv[0] : NULL); default: return error("BUG: unknown subcommand '%d'", cmdmode); } diff --git a/git-bisect.sh b/git-bisect.sh index 54cbfecc5a..afbfbc1f8e 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -393,35 +393,11 @@ bisect_visualize() { eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES") } -bisect_reset() { - test -s "$GIT_DIR/BISECT_START" || { - gettextln "We are not bisecting." - return - } - case "$#" in - 0) branch=$(cat "$GIT_DIR/BISECT_START") ;; - 1) git rev-parse --quiet --verify "$1^{commit}" >/dev/null || { - invalid="$1" - die "$(eval_gettext "'\$invalid' is not a valid commit")" - } - branch="$1" ;; - *) - usage ;; - esac - - if ! test -f "$GIT_DIR/BISECT_HEAD" && ! git checkout "$branch" -- - then - die "$(eval_gettext "Could not check out original HEAD '\$branch'. -Try 'git bisect reset '.")" - fi - git bisect--helper --bisect-clean-state || exit -} - bisect_replay () { file="$1" test "$#" -eq 1 || die "$(gettext "No logfile given")" test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")" - bisect_reset + git bisect--helper --bisect-reset || exit while read git bisect command rev do test "$git $bisect" = "git bisect" || test "$git" = "git-bisect" || continue @@ -613,7 +589,7 @@ case "$#" in visualize|view) bisect_visualize "$@" ;; reset) - bisect_reset "$@" ;; + git bisect--helper --bisect-reset "$@" ;; replay) bisect_replay "$@" ;; log) From 0f30233a11f6d89fb387ddf96db64877adb90713 Mon Sep 17 00:00:00 2001 From: Pranit Bauva Date: Wed, 2 Jan 2019 07:38:32 -0800 Subject: [PATCH 2/7] bisect--helper: `bisect_write` shell function in C Reimplement the `bisect_write` shell function in C and add a `bisect-write` subcommand to `git bisect--helper` to call it from git-bisect.sh Using `--bisect-write` 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 but its implementation will be called by some other methods. Note: bisect_write() uses two variables namely TERM_GOOD and TERM_BAD from the global shell script thus we need to pass it to the subcommand using the arguments. We then store them in a struct bisect_terms and pass the memory address around functions. Add a log_commit() helper function to write the contents of the commit message header to a file which will be re-used in future parts of the code as well. Also introduce a function free_terms() to free the memory of `struct bisect_terms` and set_terms() to set the values of members in `struct bisect_terms`. Helped-by: Ramsay Jones Mentored-by: Lars Schneider Mentored-by: Christian Couder Mentored-by: Johannes Schindelin Signed-off-by: Pranit Bauva Signed-off-by: Tanushree Tumane Signed-off-by: Junio C Hamano --- builtin/bisect--helper.c | 105 +++++++++++++++++++++++++++++++++++++-- git-bisect.sh | 25 ++-------- 2 files changed, 106 insertions(+), 24 deletions(-) diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index aa6495dc84..df821be4b2 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -12,15 +12,37 @@ static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV") static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK") static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START") static GIT_PATH_FUNC(git_path_bisect_head, "BISECT_HEAD") +static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG") static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --next-all [--no-checkout]"), N_("git bisect--helper --write-terms "), N_("git bisect--helper --bisect-clean-state"), N_("git bisect--helper --bisect-reset []"), + N_("git bisect--helper --bisect-write [--no-log] "), NULL }; +struct bisect_terms { + char *term_good; + char *term_bad; +}; + +static void free_terms(struct bisect_terms *terms) +{ + FREE_AND_NULL(terms->term_good); + FREE_AND_NULL(terms->term_bad); +} + +static void set_terms(struct bisect_terms *terms, const char *bad, + const char *good) +{ + free((void *)terms->term_good); + terms->term_good = xstrdup(good); + free((void *)terms->term_bad); + terms->term_bad = xstrdup(bad); +} + /* * Check whether the string `term` belongs to the set of strings * included in the variable arguments. @@ -148,6 +170,70 @@ static int bisect_reset(const char *commit) return bisect_clean_state(); } +static void log_commit(FILE *fp, char *fmt, const char *state, + struct commit *commit) +{ + struct pretty_print_context pp = {0}; + struct strbuf commit_msg = STRBUF_INIT; + char *label = xstrfmt(fmt, state); + + format_commit_message(commit, "%s", &commit_msg, &pp); + + fprintf(fp, "# %s: [%s] %s\n", label, oid_to_hex(&commit->object.oid), + commit_msg.buf); + + strbuf_release(&commit_msg); + free(label); +} + +static int bisect_write(const char *state, const char *rev, + const struct bisect_terms *terms, int nolog) +{ + struct strbuf tag = STRBUF_INIT; + struct object_id oid; + struct commit *commit; + FILE *fp = NULL; + int retval = 0; + + if (!strcmp(state, terms->term_bad)) { + strbuf_addf(&tag, "refs/bisect/%s", state); + } else if (one_of(state, terms->term_good, "skip", NULL)) { + strbuf_addf(&tag, "refs/bisect/%s-%s", state, rev); + } else { + retval = error(_("Bad bisect_write argument: %s"), state); + goto finish; + } + + if (get_oid(rev, &oid)) { + retval = error(_("couldn't get the oid of the rev '%s'"), rev); + goto finish; + } + + if (update_ref(NULL, tag.buf, &oid, NULL, 0, + UPDATE_REFS_MSG_ON_ERR)) { + retval = -1; + goto finish; + } + + fp = fopen(git_path_bisect_log(), "a"); + if (!fp) { + retval = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log()); + goto finish; + } + + commit = lookup_commit_reference(the_repository, &oid); + log_commit(fp, "%s", state, commit); + + if (!nolog) + fprintf(fp, "git bisect %s %s\n", state, rev); + +finish: + if (fp) + fclose(fp); + strbuf_release(&tag); + return retval; +} + int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { enum { @@ -155,9 +241,10 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) WRITE_TERMS, BISECT_CLEAN_STATE, CHECK_EXPECTED_REVS, - BISECT_RESET + BISECT_RESET, + BISECT_WRITE } cmdmode = 0; - int no_checkout = 0; + int no_checkout = 0, res = 0, nolog = 0; struct option options[] = { OPT_CMDMODE(0, "next-all", &cmdmode, N_("perform 'git bisect next'"), NEXT_ALL), @@ -169,10 +256,15 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) N_("check for expected revs"), CHECK_EXPECTED_REVS), OPT_CMDMODE(0, "bisect-reset", &cmdmode, N_("reset the bisection state"), BISECT_RESET), + OPT_CMDMODE(0, "bisect-write", &cmdmode, + N_("write out the bisection state in BISECT_LOG"), BISECT_WRITE), OPT_BOOL(0, "no-checkout", &no_checkout, N_("update BISECT_HEAD instead of checking out the current commit")), + OPT_BOOL(0, "no-log", &nolog, + N_("no log for BISECT_WRITE ")), OPT_END() }; + struct bisect_terms terms = { .term_good = NULL, .term_bad = NULL }; argc = parse_options(argc, argv, prefix, options, git_bisect_helper_usage, 0); @@ -198,8 +290,15 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) if (argc > 1) return error(_("--bisect-reset requires either no argument or a commit")); return !!bisect_reset(argc ? argv[0] : NULL); + case BISECT_WRITE: + if (argc != 4 && argc != 5) + return error(_("--bisect-write requires either 4 or 5 arguments")); + set_terms(&terms, argv[3], argv[2]); + res = bisect_write(argv[0], argv[1], &terms, nolog); + break; default: return error("BUG: unknown subcommand '%d'", cmdmode); } - return 0; + free_terms(&terms); + return !!res; } diff --git a/git-bisect.sh b/git-bisect.sh index afbfbc1f8e..ebf445223c 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -144,7 +144,7 @@ bisect_start() { 0) state=$TERM_BAD ; bad_seen=1 ;; *) state=$TERM_GOOD ;; esac - eval="$eval bisect_write '$state' '$rev' 'nolog' &&" + eval="$eval git bisect--helper --bisect-write '$state' '$rev' '$TERM_GOOD' '$TERM_BAD' 'nolog' &&" done # # Verify HEAD. @@ -220,23 +220,6 @@ bisect_start() { trap '-' 0 } -bisect_write() { - state="$1" - rev="$2" - nolog="$3" - case "$state" in - "$TERM_BAD") - tag="$state" ;; - "$TERM_GOOD"|skip) - tag="$state"-"$rev" ;; - *) - die "$(eval_gettext "Bad bisect_write argument: \$state")" ;; - esac - git update-ref "refs/bisect/$tag" "$rev" || exit - echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG" - test -n "$nolog" || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG" -} - bisect_skip() { all='' for arg in "$@" @@ -263,7 +246,7 @@ bisect_state() { bisected_head=$(bisect_head) rev=$(git rev-parse --verify "$bisected_head") || die "$(eval_gettext "Bad rev input: \$bisected_head")" - bisect_write "$state" "$rev" + git bisect--helper --bisect-write "$state" "$rev" "$TERM_GOOD" "$TERM_BAD" || exit git bisect--helper --check-expected-revs "$rev" ;; 2,"$TERM_BAD"|*,"$TERM_GOOD"|*,skip) shift @@ -276,7 +259,7 @@ bisect_state() { done for rev in $hash_list do - bisect_write "$state" "$rev" + git bisect--helper --bisect-write "$state" "$rev" "$TERM_GOOD" "$TERM_BAD" || exit done git bisect--helper --check-expected-revs $hash_list ;; *,"$TERM_BAD") @@ -413,7 +396,7 @@ bisect_replay () { cmd="bisect_start $rev" eval "$cmd" ;; "$TERM_GOOD"|"$TERM_BAD"|skip) - bisect_write "$command" "$rev" ;; + git bisect--helper --bisect-write "$command" "$rev" "$TERM_GOOD" "$TERM_BAD" || exit;; terms) bisect_terms $rev ;; *) From e3b1e3bdc0aa5fa6a474874a2395ae0584b2aea7 Mon Sep 17 00:00:00 2001 From: Pranit Bauva Date: Wed, 2 Jan 2019 07:38:32 -0800 Subject: [PATCH 3/7] wrapper: move is_empty_file() and rename it as is_empty_or_missing_file() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit is_empty_file() can help to refactor a lot of code. This will be very helpful in porting "git bisect" to C. Suggested-by: Torsten Bögershausen Mentored-by: Lars Schneider Mentored-by: Christian Couder Signed-off-by: Pranit Bauva Signed-off-by: Junio C Hamano --- builtin/am.c | 20 ++------------------ cache.h | 3 +++ wrapper.c | 13 +++++++++++++ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index 8f27f3375b..310eefe9e8 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -34,22 +34,6 @@ #include "packfile.h" #include "repository.h" -/** - * Returns 1 if the file is empty or does not exist, 0 otherwise. - */ -static int is_empty_file(const char *filename) -{ - struct stat st; - - if (stat(filename, &st) < 0) { - if (errno == ENOENT) - return 1; - die_errno(_("could not stat %s"), filename); - } - - return !st.st_size; -} - /** * Returns the length of the first line of msg. */ @@ -1220,7 +1204,7 @@ static int parse_mail(struct am_state *state, const char *mail) goto finish; } - if (is_empty_file(am_path(state, "patch"))) { + if (is_empty_or_missing_file(am_path(state, "patch"))) { printf_ln(_("Patch is empty.")); die_user_resolve(state); } @@ -1803,7 +1787,7 @@ next: resume = 0; } - if (!is_empty_file(am_path(state, "rewritten"))) { + if (!is_empty_or_missing_file(am_path(state, "rewritten"))) { assert(state->rebasing); copy_notes_for_rebase(state); run_post_rewrite_hook(state); diff --git a/cache.h b/cache.h index ca36b44ee0..9ff7e56b74 100644 --- a/cache.h +++ b/cache.h @@ -1788,4 +1788,7 @@ void safe_create_dir(const char *dir, int share); */ extern int print_sha1_ellipsis(void); +/* Return 1 if the file is empty or does not exists, 0 otherwise. */ +extern int is_empty_or_missing_file(const char *filename); + #endif /* CACHE_H */ diff --git a/wrapper.c b/wrapper.c index e4fa9d84cd..ea3cf64d4c 100644 --- a/wrapper.c +++ b/wrapper.c @@ -690,3 +690,16 @@ int xgethostname(char *buf, size_t len) buf[len - 1] = 0; return ret; } + +int is_empty_or_missing_file(const char *filename) +{ + struct stat st; + + if (stat(filename, &st) < 0) { + if (errno == ENOENT) + return 1; + die_errno(_("could not stat %s"), filename); + } + + return !st.st_size; +} From 4fbdbd5bfff874330115f8d7556cf7c9f7f138c9 Mon Sep 17 00:00:00 2001 From: Pranit Bauva Date: Wed, 2 Jan 2019 07:38:33 -0800 Subject: [PATCH 4/7] bisect--helper: `check_and_set_terms` shell function in C Reimplement the `check_and_set_terms` shell function in C and add `check-and-set-terms` subcommand to `git bisect--helper` to call it from git-bisect.sh Using `--check-and-set-terms` 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 but its implementation will be called by some other methods. check_and_set_terms() sets and receives two global variables namely TERM_GOOD and TERM_BAD in the shell script. Luckily the file BISECT_TERMS also contains the value of those variables so its appropriate to evoke the method get_terms() after calling the subcommand so that it retrieves the value of TERM_GOOD and TERM_BAD from the file BISECT_TERMS. The two global variables are passed as arguments to the subcommand. Mentored-by: Lars Schneider Mentored-by: Christian Couder Mentored-by: Johannes Schindelin Signed-off-by: Pranit Bauva Signed-off-by: Tanushree Tumane Signed-off-by: Junio C Hamano --- builtin/bisect--helper.c | 39 ++++++++++++++++++++++++++++++++++++++- git-bisect.sh | 36 ++++-------------------------------- 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index df821be4b2..02ea5a9611 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -20,6 +20,7 @@ static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --bisect-clean-state"), N_("git bisect--helper --bisect-reset []"), N_("git bisect--helper --bisect-write [--no-log] "), + N_("git bisect--helper --bisect-check-and-set-terms "), NULL }; @@ -234,6 +235,33 @@ finish: return retval; } +static int check_and_set_terms(struct bisect_terms *terms, const char *cmd) +{ + int has_term_file = !is_empty_or_missing_file(git_path_bisect_terms()); + + if (one_of(cmd, "skip", "start", "terms", NULL)) + return 0; + + if (has_term_file && strcmp(cmd, terms->term_bad) && + strcmp(cmd, terms->term_good)) + return error(_("Invalid command: you're currently in a " + "%s/%s bisect"), terms->term_bad, + terms->term_good); + + if (!has_term_file) { + if (one_of(cmd, "bad", "good", NULL)) { + set_terms(terms, "bad", "good"); + return write_terms(terms->term_bad, terms->term_good); + } + if (one_of(cmd, "new", "old", NULL)) { + set_terms(terms, "new", "old"); + return write_terms(terms->term_bad, terms->term_good); + } + } + + return 0; +} + int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { enum { @@ -242,7 +270,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) BISECT_CLEAN_STATE, CHECK_EXPECTED_REVS, BISECT_RESET, - BISECT_WRITE + BISECT_WRITE, + CHECK_AND_SET_TERMS } cmdmode = 0; int no_checkout = 0, res = 0, nolog = 0; struct option options[] = { @@ -258,6 +287,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) N_("reset the bisection state"), BISECT_RESET), OPT_CMDMODE(0, "bisect-write", &cmdmode, N_("write out the bisection state in BISECT_LOG"), BISECT_WRITE), + OPT_CMDMODE(0, "check-and-set-terms", &cmdmode, + N_("check and set terms in a bisection state"), CHECK_AND_SET_TERMS), OPT_BOOL(0, "no-checkout", &no_checkout, N_("update BISECT_HEAD instead of checking out the current commit")), OPT_BOOL(0, "no-log", &nolog, @@ -296,6 +327,12 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) set_terms(&terms, argv[3], argv[2]); res = bisect_write(argv[0], argv[1], &terms, nolog); break; + case CHECK_AND_SET_TERMS: + if (argc != 3) + return error(_("--check-and-set-terms requires 3 arguments")); + set_terms(&terms, argv[2], argv[1]); + res = check_and_set_terms(&terms, argv[0]); + break; default: return error("BUG: unknown subcommand '%d'", cmdmode); } diff --git a/git-bisect.sh b/git-bisect.sh index ebf445223c..9e993a8187 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -238,7 +238,8 @@ bisect_skip() { bisect_state() { bisect_autostart state=$1 - check_and_set_terms $state + git bisect--helper --check-and-set-terms $state $TERM_GOOD $TERM_BAD || exit + get_terms case "$#,$state" in 0,*) die "Please call 'bisect_state' with at least one argument." ;; @@ -390,7 +391,8 @@ bisect_replay () { command="$bisect" fi get_terms - check_and_set_terms "$command" + git bisect--helper --check-and-set-terms "$command" "$TERM_GOOD" "$TERM_BAD" || exit + get_terms case "$command" in start) cmd="bisect_start $rev" @@ -482,36 +484,6 @@ get_terms () { fi } -check_and_set_terms () { - cmd="$1" - case "$cmd" in - skip|start|terms) ;; - *) - if test -s "$GIT_DIR/BISECT_TERMS" && test "$cmd" != "$TERM_BAD" && test "$cmd" != "$TERM_GOOD" - then - die "$(eval_gettext "Invalid command: you're currently in a \$TERM_BAD/\$TERM_GOOD bisect.")" - fi - case "$cmd" in - bad|good) - if ! test -s "$GIT_DIR/BISECT_TERMS" - then - TERM_BAD=bad - TERM_GOOD=good - git bisect--helper --write-terms "$TERM_BAD" "$TERM_GOOD" || exit - fi - ;; - new|old) - if ! test -s "$GIT_DIR/BISECT_TERMS" - then - TERM_BAD=new - TERM_GOOD=old - git bisect--helper --write-terms "$TERM_BAD" "$TERM_GOOD" || exit - fi - ;; - esac ;; - esac -} - bisect_voc () { case "$1" in bad) echo "bad|new" ;; From 129a6cf344c4b352433f5a24de02a62783f6c6df Mon Sep 17 00:00:00 2001 From: Pranit Bauva Date: Wed, 2 Jan 2019 07:38:34 -0800 Subject: [PATCH 5/7] bisect--helper: `bisect_next_check` shell function in C Reimplement `bisect_next_check` shell function in C and add `bisect-next-check` subcommand to `git bisect--helper` to call it from git-bisect.sh . `bisect_voc` shell function is no longer useful now and is replaced by using a char *[] of "new|bad" and "good|old" values. Using `--bisect-next-check` is a temporary measure to port shell function to C so as to use the existing test suite. As more functions are ported, this subcommand will be retired but its implementation will be called by some other methods. Helped-by: Stephan Beyer Mentored-by: Lars Schneider Mentored-by: Christian Couder Mentored-by: Johannes Schindelin Signed-off-by: Pranit Bauva Signed-off-by: Tanushree Tumane Signed-off-by: Junio C Hamano --- builtin/bisect--helper.c | 88 +++++++++++++++++++++++++++++++++++++++- git-bisect.sh | 60 ++------------------------- 2 files changed, 91 insertions(+), 57 deletions(-) diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 02ea5a9611..38ae825b9a 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -6,6 +6,7 @@ #include "dir.h" #include "argv-array.h" #include "run-command.h" +#include "prompt.h" static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS") static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV") @@ -21,6 +22,7 @@ static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --bisect-reset []"), N_("git bisect--helper --bisect-write [--no-log] "), N_("git bisect--helper --bisect-check-and-set-terms "), + N_("git bisect--helper --bisect-next-check []"), NULL }; @@ -44,6 +46,9 @@ static void set_terms(struct bisect_terms *terms, const char *bad, terms->term_bad = xstrdup(bad); } +static const char *vocab_bad = "bad|new"; +static const char *vocab_good = "good|old"; + /* * Check whether the string `term` belongs to the set of strings * included in the variable arguments. @@ -262,6 +267,78 @@ static int check_and_set_terms(struct bisect_terms *terms, const char *cmd) return 0; } +static int mark_good(const char *refname, const struct object_id *oid, + int flag, void *cb_data) +{ + int *m_good = (int *)cb_data; + *m_good = 0; + return 1; +} + +static const char *need_bad_and_good_revision_warning = + N_("You need to give me at least one %s and %s revision.\n" + "You can use \"git bisect %s\" and \"git bisect %s\" for that."); + +static const char *need_bisect_start_warning = + N_("You need to start by \"git bisect start\".\n" + "You then need to give me at least one %s and %s revision.\n" + "You can use \"git bisect %s\" and \"git bisect %s\" for that."); + +static int bisect_next_check(const struct bisect_terms *terms, + const char *current_term) +{ + int missing_good = 1, missing_bad = 1, retval = 0; + const char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad); + const char *good_glob = xstrfmt("%s-*", terms->term_good); + + if (ref_exists(bad_ref)) + missing_bad = 0; + + for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/", + (void *) &missing_good); + + if (!missing_good && !missing_bad) + goto finish; + + if (!current_term) { + retval = -1; + goto finish; + } + + if (missing_good && !missing_bad && + !strcmp(current_term, terms->term_good)) { + char *yesno; + /* + * have bad (or new) but not good (or old). We could bisect + * although this is less optimum. + */ + warning(_("bisecting only with a %s commit"), terms->term_bad); + if (!isatty(0)) + goto finish; + /* + * TRANSLATORS: Make sure to include [Y] and [n] in your + * translation. The program will only accept English input + * at this point. + */ + yesno = git_prompt(_("Are you sure [Y/n]? "), PROMPT_ECHO); + if (starts_with(yesno, "N") || starts_with(yesno, "n")) + retval = -1; + goto finish; + } + if (!is_empty_or_missing_file(git_path_bisect_start())) { + retval = error(_(need_bad_and_good_revision_warning), + vocab_bad, vocab_good, vocab_bad, vocab_good); + } else { + retval = error(_(need_bisect_start_warning), + vocab_good, vocab_bad, vocab_good, vocab_bad); + } + +finish: + free((void *) good_glob); + free((void *) bad_ref); + return retval; +} + int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { enum { @@ -271,7 +348,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) CHECK_EXPECTED_REVS, BISECT_RESET, BISECT_WRITE, - CHECK_AND_SET_TERMS + CHECK_AND_SET_TERMS, + BISECT_NEXT_CHECK } cmdmode = 0; int no_checkout = 0, res = 0, nolog = 0; struct option options[] = { @@ -289,6 +367,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) N_("write out the bisection state in BISECT_LOG"), BISECT_WRITE), OPT_CMDMODE(0, "check-and-set-terms", &cmdmode, N_("check and set terms in a bisection state"), CHECK_AND_SET_TERMS), + OPT_CMDMODE(0, "bisect-next-check", &cmdmode, + N_("check whether bad or good terms exist"), BISECT_NEXT_CHECK), OPT_BOOL(0, "no-checkout", &no_checkout, N_("update BISECT_HEAD instead of checking out the current commit")), OPT_BOOL(0, "no-log", &nolog, @@ -333,6 +413,12 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) set_terms(&terms, argv[2], argv[1]); res = check_and_set_terms(&terms, argv[0]); break; + case BISECT_NEXT_CHECK: + if (argc != 2 && argc != 3) + return error(_("--bisect-next-check requires 2 or 3 arguments")); + set_terms(&terms, argv[1], argv[0]); + res = bisect_next_check(&terms, argc == 3 ? argv[2] : NULL); + break; default: return error("BUG: unknown subcommand '%d'", cmdmode); } diff --git a/git-bisect.sh b/git-bisect.sh index 9e993a8187..5ef3e25621 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -271,59 +271,14 @@ bisect_state() { bisect_auto_next } -bisect_next_check() { - missing_good= missing_bad= - git show-ref -q --verify refs/bisect/$TERM_BAD || missing_bad=t - test -n "$(git for-each-ref "refs/bisect/$TERM_GOOD-*")" || missing_good=t - - case "$missing_good,$missing_bad,$1" in - ,,*) - : have both $TERM_GOOD and $TERM_BAD - ok - ;; - *,) - # do not have both but not asked to fail - just report. - false - ;; - t,,"$TERM_GOOD") - # have bad (or new) but not good (or old). we could bisect although - # this is less optimum. - eval_gettextln "Warning: bisecting only with a \$TERM_BAD commit." >&2 - if test -t 0 - then - # TRANSLATORS: Make sure to include [Y] and [n] in your - # translation. The program will only accept English input - # at this point. - gettext "Are you sure [Y/n]? " >&2 - read yesno - case "$yesno" in [Nn]*) exit 1 ;; esac - fi - : bisect without $TERM_GOOD... - ;; - *) - bad_syn=$(bisect_voc bad) - good_syn=$(bisect_voc good) - if test -s "$GIT_DIR/BISECT_START" - then - - eval_gettextln "You need to give me at least one \$bad_syn and one \$good_syn revision. -(You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2 - else - eval_gettextln "You need to start by \"git bisect start\". -You then need to give me at least one \$good_syn and one \$bad_syn revision. -(You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2 - fi - exit 1 ;; - esac -} - bisect_auto_next() { - bisect_next_check && bisect_next || : + git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD && bisect_next || : } bisect_next() { case "$#" in 0) ;; *) usage ;; esac bisect_autostart - bisect_next_check $TERM_GOOD + git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD $TERM_GOOD|| exit # Perform all bisection computation, display and checkout git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout) @@ -355,7 +310,7 @@ bisect_next() { } bisect_visualize() { - bisect_next_check fail + git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD fail || exit if test $# = 0 then @@ -409,7 +364,7 @@ bisect_replay () { } bisect_run () { - bisect_next_check fail + git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD fail || exit test -n "$*" || die "$(gettext "bisect run failed: no command provided.")" @@ -484,13 +439,6 @@ get_terms () { fi } -bisect_voc () { - case "$1" in - bad) echo "bad|new" ;; - good) echo "good|old" ;; - esac -} - bisect_terms () { get_terms if ! test -s "$GIT_DIR/BISECT_TERMS" From 450ebb7359ec379a282670e85536540734c45eed Mon Sep 17 00:00:00 2001 From: Pranit Bauva Date: Wed, 2 Jan 2019 07:38:35 -0800 Subject: [PATCH 6/7] bisect--helper: `get_terms` & `bisect_terms` shell function in C Reimplement the `get_terms` and `bisect_terms` shell function in C and add `bisect-terms` subcommand to `git bisect--helper` to call it from git-bisect.sh . Using `--bisect-terms` 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 but its implementation will be called by some other methods. Also use error() to report "no terms defined" and accordingly change the test in t6030. We need to use PARSE_OPT_KEEP_UNKNOWN here to allow for parameters that look like options (e.g --term-good) but should not be parsed by cmd_bisect__helper(). This change is safe because all other cmdmodes have strict argc checks already. Mentored-by: Lars Schneider Mentored-by: Christian Couder Mentored-by: Johannes Schindelin Signed-off-by: Pranit Bauva Signed-off-by: Tanushree Tumane Signed-off-by: Junio C Hamano --- builtin/bisect--helper.c | 62 +++++++++++++++++++++++++++++++++++-- git-bisect.sh | 35 ++------------------- t/t6030-bisect-porcelain.sh | 2 +- 3 files changed, 63 insertions(+), 36 deletions(-) diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 38ae825b9a..d61edd3c91 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -23,6 +23,7 @@ static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --bisect-write [--no-log] "), N_("git bisect--helper --bisect-check-and-set-terms "), N_("git bisect--helper --bisect-next-check []"), + N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"), NULL }; @@ -339,6 +340,55 @@ finish: return retval; } +static int get_terms(struct bisect_terms *terms) +{ + struct strbuf str = STRBUF_INIT; + FILE *fp = NULL; + int res = 0; + + fp = fopen(git_path_bisect_terms(), "r"); + if (!fp) { + res = -1; + goto finish; + } + + free_terms(terms); + strbuf_getline_lf(&str, fp); + terms->term_bad = strbuf_detach(&str, NULL); + strbuf_getline_lf(&str, fp); + terms->term_good = strbuf_detach(&str, NULL); + +finish: + if (fp) + fclose(fp); + strbuf_release(&str); + return res; +} + +static int bisect_terms(struct bisect_terms *terms, const char *option) +{ + if (get_terms(terms)) + return error(_("no terms defined")); + + if (option == NULL) { + printf(_("Your current terms are %s for the old state\n" + "and %s for the new state.\n"), + terms->term_good, terms->term_bad); + return 0; + } + if (one_of(option, "--term-good", "--term-old", NULL)) + printf("%s\n", terms->term_good); + else if (one_of(option, "--term-bad", "--term-new", NULL)) + printf("%s\n", terms->term_bad); + else + return error(_("invalid argument %s for 'git bisect terms'.\n" + "Supported options are: " + "--term-good|--term-old and " + "--term-bad|--term-new."), option); + + return 0; +} + int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { enum { @@ -349,7 +399,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) BISECT_RESET, BISECT_WRITE, CHECK_AND_SET_TERMS, - BISECT_NEXT_CHECK + BISECT_NEXT_CHECK, + BISECT_TERMS } cmdmode = 0; int no_checkout = 0, res = 0, nolog = 0; struct option options[] = { @@ -369,6 +420,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) N_("check and set terms in a bisection state"), CHECK_AND_SET_TERMS), OPT_CMDMODE(0, "bisect-next-check", &cmdmode, N_("check whether bad or good terms exist"), BISECT_NEXT_CHECK), + OPT_CMDMODE(0, "bisect-terms", &cmdmode, + N_("print out the bisect terms"), BISECT_TERMS), OPT_BOOL(0, "no-checkout", &no_checkout, N_("update BISECT_HEAD instead of checking out the current commit")), OPT_BOOL(0, "no-log", &nolog, @@ -378,7 +431,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) struct bisect_terms terms = { .term_good = NULL, .term_bad = NULL }; argc = parse_options(argc, argv, prefix, options, - git_bisect_helper_usage, 0); + git_bisect_helper_usage, PARSE_OPT_KEEP_UNKNOWN); if (!cmdmode) usage_with_options(git_bisect_helper_usage, options); @@ -419,6 +472,11 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) set_terms(&terms, argv[1], argv[0]); res = bisect_next_check(&terms, argc == 3 ? argv[2] : NULL); break; + case BISECT_TERMS: + if (argc > 1) + return error(_("--bisect-terms requires 0 or 1 argument")); + res = bisect_terms(&terms, argc == 1 ? argv[0] : NULL); + break; default: return error("BUG: unknown subcommand '%d'", cmdmode); } diff --git a/git-bisect.sh b/git-bisect.sh index 5ef3e25621..bdb614e3c2 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -355,7 +355,7 @@ bisect_replay () { "$TERM_GOOD"|"$TERM_BAD"|skip) git bisect--helper --bisect-write "$command" "$rev" "$TERM_GOOD" "$TERM_BAD" || exit;; terms) - bisect_terms $rev ;; + git bisect--helper --bisect-terms $rev || exit;; *) die "$(gettext "?? what are you talking about?")" ;; esac @@ -439,37 +439,6 @@ get_terms () { fi } -bisect_terms () { - get_terms - if ! test -s "$GIT_DIR/BISECT_TERMS" - then - die "$(gettext "no terms defined")" - fi - case "$#" in - 0) - gettextln "Your current terms are $TERM_GOOD for the old state -and $TERM_BAD for the new state." - ;; - 1) - arg=$1 - case "$arg" in - --term-good|--term-old) - printf '%s\n' "$TERM_GOOD" - ;; - --term-bad|--term-new) - printf '%s\n' "$TERM_BAD" - ;; - *) - die "$(eval_gettext "invalid argument \$arg for 'git bisect terms'. -Supported options are: --term-good|--term-old and --term-bad|--term-new.")" - ;; - esac - ;; - *) - usage ;; - esac -} - case "$#" in 0) usage ;; @@ -500,7 +469,7 @@ case "$#" in run) bisect_run "$@" ;; terms) - bisect_terms "$@" ;; + git bisect--helper --bisect-terms "$@" || exit;; *) usage ;; esac diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index f84ff941c3..55835ee4a4 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -802,7 +802,7 @@ test_expect_success 'bisect terms needs 0 or 1 argument' ' test_must_fail git bisect terms only-one && test_must_fail git bisect terms 1 2 && test_must_fail git bisect terms 2>actual && - echo "no terms defined" >expected && + echo "error: no terms defined" >expected && test_i18ncmp expected actual ' From 06f5608c14e6972748b84649d5b8ffd335bbd209 Mon Sep 17 00:00:00 2001 From: Pranit Bauva Date: Wed, 2 Jan 2019 07:38:36 -0800 Subject: [PATCH 7/7] 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 Helped-by: Stephan Beyer Mentored-by: Lars Schneider Mentored-by: Christian Couder Mentored-by: Johannes Schindelin Signed-off-by: Pranit Bauva Signed-off-by: Tanushree Tumane Signed-off-by: Junio C Hamano --- builtin/bisect--helper.c | 230 ++++++++++++++++++++++++++++++++++++++- git-bisect.sh | 132 +--------------------- 2 files changed, 230 insertions(+), 132 deletions(-) diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index d61edd3c91..22e669e3b1 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -7,6 +7,7 @@ #include "argv-array.h" #include "run-command.h" #include "prompt.h" +#include "quote.h" static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS") 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") static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START") static GIT_PATH_FUNC(git_path_bisect_head, "BISECT_HEAD") static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG") +static GIT_PATH_FUNC(git_path_head_name, "head-name") +static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES") static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --next-all [--no-checkout]"), @@ -24,6 +27,8 @@ static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --bisect-check-and-set-terms "), N_("git bisect--helper --bisect-next-check []"), N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"), + N_("git bisect--helper --bisect-start [--term-{old,good}= --term-{new,bad}=]" + "[--no-checkout] [ [...]] [--] [...]"), NULL }; @@ -389,6 +394,219 @@ static int bisect_terms(struct bisect_terms *terms, const char *option) return 0; } +static int bisect_append_log_quoted(const char **argv) +{ + int retval = 0; + FILE *fp = fopen(git_path_bisect_log(), "a"); + struct strbuf orig_args = STRBUF_INIT; + + if (!fp) + return -1; + + if (fprintf(fp, "git bisect start") < 1) { + retval = -1; + goto finish; + } + + sq_quote_argv(&orig_args, argv); + if (fprintf(fp, "%s\n", orig_args.buf) < 1) + retval = -1; + +finish: + fclose(fp); + strbuf_release(&orig_args); + return retval; +} + +static int bisect_start(struct bisect_terms *terms, int no_checkout, + const char **argv, int argc) +{ + int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0; + int flags, pathspec_pos, retval = 0; + struct string_list revs = STRING_LIST_INIT_DUP; + struct string_list states = STRING_LIST_INIT_DUP; + struct strbuf start_head = STRBUF_INIT; + struct strbuf bisect_names = STRBUF_INIT; + struct object_id head_oid; + struct object_id oid; + const char *head; + + if (is_bare_repository()) + no_checkout = 1; + + /* + * Check for one bad and then some good revisions + */ + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--")) { + has_double_dash = 1; + break; + } + } + + for (i = 0; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp(argv[i], "--")) { + break; + } else if (!strcmp(arg, "--no-checkout")) { + no_checkout = 1; + } else if (!strcmp(arg, "--term-good") || + !strcmp(arg, "--term-old")) { + must_write_terms = 1; + free((void *) terms->term_good); + terms->term_good = xstrdup(argv[++i]); + } else if (skip_prefix(arg, "--term-good=", &arg) || + skip_prefix(arg, "--term-old=", &arg)) { + must_write_terms = 1; + free((void *) terms->term_good); + terms->term_good = xstrdup(arg); + } else if (!strcmp(arg, "--term-bad") || + !strcmp(arg, "--term-new")) { + must_write_terms = 1; + free((void *) terms->term_bad); + terms->term_bad = xstrdup(argv[++i]); + } else if (skip_prefix(arg, "--term-bad=", &arg) || + skip_prefix(arg, "--term-new=", &arg)) { + must_write_terms = 1; + free((void *) terms->term_bad); + terms->term_bad = xstrdup(arg); + } else if (starts_with(arg, "--") && + !one_of(arg, "--term-good", "--term-bad", NULL)) { + return error(_("unrecognized option: '%s'"), arg); + } else { + char *commit_id = xstrfmt("%s^{commit}", arg); + if (get_oid(commit_id, &oid) && has_double_dash) + die(_("'%s' does not appear to be a valid " + "revision"), arg); + + string_list_append(&revs, oid_to_hex(&oid)); + free(commit_id); + } + } + pathspec_pos = i; + + /* + * The user ran "git bisect start ", hence did not + * explicitly specify the terms, but we are already starting to + * set references named with the default terms, and won't be able + * to change afterwards. + */ + if (revs.nr) + must_write_terms = 1; + for (i = 0; i < revs.nr; i++) { + if (bad_seen) { + string_list_append(&states, terms->term_good); + } else { + bad_seen = 1; + string_list_append(&states, terms->term_bad); + } + } + + /* + * Verify HEAD + */ + head = resolve_ref_unsafe("HEAD", 0, &head_oid, &flags); + if (!head) + if (get_oid("HEAD", &head_oid)) + return error(_("bad HEAD - I need a HEAD")); + + /* + * Check if we are bisecting + */ + if (!is_empty_or_missing_file(git_path_bisect_start())) { + /* Reset to the rev from where we started */ + strbuf_read_file(&start_head, git_path_bisect_start(), 0); + strbuf_trim(&start_head); + if (!no_checkout) { + struct argv_array argv = ARGV_ARRAY_INIT; + + argv_array_pushl(&argv, "checkout", start_head.buf, + "--", NULL); + if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { + retval = error(_("checking out '%s' failed." + " Try 'git bisect start " + "'."), + start_head.buf); + goto finish; + } + } + } else { + /* Get the rev from where we start. */ + if (!get_oid(head, &head_oid) && + !starts_with(head, "refs/heads/")) { + strbuf_reset(&start_head); + strbuf_addstr(&start_head, oid_to_hex(&head_oid)); + } else if (!get_oid(head, &head_oid) && + skip_prefix(head, "refs/heads/", &head)) { + /* + * This error message should only be triggered by + * cogito usage, and cogito users should understand + * it relates to cg-seek. + */ + if (!is_empty_or_missing_file(git_path_head_name())) + return error(_("won't bisect on cg-seek'ed tree")); + strbuf_addstr(&start_head, head); + } else { + return error(_("bad HEAD - strange symbolic ref")); + } + } + + /* + * Get rid of any old bisect state. + */ + if (bisect_clean_state()) + return -1; + + /* + * In case of mistaken revs or checkout error, or signals received, + * "bisect_auto_next" below may exit or misbehave. + * We have to trap this to be able to clean up using + * "bisect_clean_state". + */ + + /* + * Write new start state + */ + write_file(git_path_bisect_start(), "%s\n", start_head.buf); + + if (no_checkout) { + get_oid(start_head.buf, &oid); + if (update_ref(NULL, "BISECT_HEAD", &oid, NULL, 0, + UPDATE_REFS_MSG_ON_ERR)) { + retval = -1; + goto finish; + } + } + + if (pathspec_pos < argc - 1) + sq_quote_argv(&bisect_names, argv + pathspec_pos); + write_file(git_path_bisect_names(), "%s\n", bisect_names.buf); + + for (i = 0; i < states.nr; i++) + if (bisect_write(states.items[i].string, + revs.items[i].string, terms, 1)) { + retval = -1; + goto finish; + } + + if (must_write_terms && write_terms(terms->term_bad, + terms->term_good)) { + retval = -1; + goto finish; + } + + retval = bisect_append_log_quoted(argv); + if (retval) + retval = -1; + +finish: + string_list_clear(&revs, 0); + string_list_clear(&states, 0); + strbuf_release(&start_head); + strbuf_release(&bisect_names); + return retval; +} + int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { enum { @@ -400,7 +618,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) BISECT_WRITE, CHECK_AND_SET_TERMS, BISECT_NEXT_CHECK, - BISECT_TERMS + BISECT_TERMS, + BISECT_START } cmdmode = 0; int no_checkout = 0, res = 0, nolog = 0; struct option options[] = { @@ -422,6 +641,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) N_("check whether bad or good terms exist"), BISECT_NEXT_CHECK), OPT_CMDMODE(0, "bisect-terms", &cmdmode, N_("print out the bisect terms"), BISECT_TERMS), + OPT_CMDMODE(0, "bisect-start", &cmdmode, + N_("start the bisect session"), BISECT_START), OPT_BOOL(0, "no-checkout", &no_checkout, N_("update BISECT_HEAD instead of checking out the current commit")), OPT_BOOL(0, "no-log", &nolog, @@ -431,7 +652,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) struct bisect_terms terms = { .term_good = NULL, .term_bad = NULL }; argc = parse_options(argc, argv, prefix, options, - git_bisect_helper_usage, PARSE_OPT_KEEP_UNKNOWN); + git_bisect_helper_usage, + PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN); if (!cmdmode) usage_with_options(git_bisect_helper_usage, options); @@ -477,6 +699,10 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) return error(_("--bisect-terms requires 0 or 1 argument")); res = bisect_terms(&terms, argc == 1 ? argv[0] : NULL); break; + case BISECT_START: + set_terms(&terms, "bad", "good"); + res = bisect_start(&terms, no_checkout, argv, argc); + break; default: return error("BUG: unknown subcommand '%d'", cmdmode); } diff --git a/git-bisect.sh b/git-bisect.sh index bdb614e3c2..efee12b8b1 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -71,122 +71,7 @@ bisect_autostart() { } bisect_start() { - # - # Check for one bad and then some good revisions. - # - has_double_dash=0 - for arg; do - case "$arg" in --) has_double_dash=1; break ;; esac - done - orig_args=$(git rev-parse --sq-quote "$@") - bad_seen=0 - eval='' - must_write_terms=0 - revs='' - if test "z$(git rev-parse --is-bare-repository)" != zfalse - then - mode=--no-checkout - else - mode='' - fi - while [ $# -gt 0 ]; do - arg="$1" - case "$arg" in - --) - shift - break - ;; - --no-checkout) - mode=--no-checkout - shift ;; - --term-good|--term-old) - shift - must_write_terms=1 - TERM_GOOD=$1 - shift ;; - --term-good=*|--term-old=*) - must_write_terms=1 - TERM_GOOD=${1#*=} - shift ;; - --term-bad|--term-new) - shift - must_write_terms=1 - TERM_BAD=$1 - shift ;; - --term-bad=*|--term-new=*) - must_write_terms=1 - TERM_BAD=${1#*=} - shift ;; - --*) - die "$(eval_gettext "unrecognised option: '\$arg'")" ;; - *) - rev=$(git rev-parse -q --verify "$arg^{commit}") || { - test $has_double_dash -eq 1 && - die "$(eval_gettext "'\$arg' does not appear to be a valid revision")" - break - } - revs="$revs $rev" - shift - ;; - esac - done - - for rev in $revs - do - # The user ran "git bisect start - # ", hence did not explicitly specify - # the terms, but we are already starting to - # set references named with the default terms, - # and won't be able to change afterwards. - must_write_terms=1 - - case $bad_seen in - 0) state=$TERM_BAD ; bad_seen=1 ;; - *) state=$TERM_GOOD ;; - esac - eval="$eval git bisect--helper --bisect-write '$state' '$rev' '$TERM_GOOD' '$TERM_BAD' 'nolog' &&" - done - # - # Verify HEAD. - # - head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) || - head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) || - die "$(gettext "Bad HEAD - I need a HEAD")" - - # - # Check if we are bisecting. - # - start_head='' - if test -s "$GIT_DIR/BISECT_START" - then - # Reset to the rev from where we started. - start_head=$(cat "$GIT_DIR/BISECT_START") - if test "z$mode" != "z--no-checkout" - then - git checkout "$start_head" -- || - die "$(eval_gettext "Checking out '\$start_head' failed. Try 'git bisect reset '.")" - fi - else - # Get rev from where we start. - case "$head" in - refs/heads/*|$_x40) - # This error message should only be triggered by - # cogito usage, and cogito users should understand - # it relates to cg-seek. - [ -s "$GIT_DIR/head-name" ] && - die "$(gettext "won't bisect on cg-seek'ed tree")" - start_head="${head#refs/heads/}" - ;; - *) - die "$(gettext "Bad HEAD - strange symbolic ref")" - ;; - esac - fi - - # - # Get rid of any old bisect state. - # - git bisect--helper --bisect-clean-state || exit + git bisect--helper --bisect-start $@ || exit # # Change state. @@ -198,23 +83,10 @@ bisect_start() { trap 'git bisect--helper --bisect-clean-state' 0 trap 'exit 255' 1 2 3 15 - # - # Write new start state. - # - echo "$start_head" >"$GIT_DIR/BISECT_START" && { - test "z$mode" != "z--no-checkout" || - git update-ref --no-deref BISECT_HEAD "$start_head" - } && - git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" && - eval "$eval true" && - if test $must_write_terms -eq 1 - then - git bisect--helper --write-terms "$TERM_BAD" "$TERM_GOOD" || exit - fi && - echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit # # Check if we can proceed to the next bisect state. # + get_terms bisect_auto_next trap '-' 0