d61edd3c91167cdcb400e9c28bb3c14161bccefe
   1#include "builtin.h"
   2#include "cache.h"
   3#include "parse-options.h"
   4#include "bisect.h"
   5#include "refs.h"
   6#include "dir.h"
   7#include "argv-array.h"
   8#include "run-command.h"
   9#include "prompt.h"
  10
  11static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
  12static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
  13static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK")
  14static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
  15static GIT_PATH_FUNC(git_path_bisect_head, "BISECT_HEAD")
  16static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
  17
  18static const char * const git_bisect_helper_usage[] = {
  19        N_("git bisect--helper --next-all [--no-checkout]"),
  20        N_("git bisect--helper --write-terms <bad_term> <good_term>"),
  21        N_("git bisect--helper --bisect-clean-state"),
  22        N_("git bisect--helper --bisect-reset [<commit>]"),
  23        N_("git bisect--helper --bisect-write [--no-log] <state> <revision> <good_term> <bad_term>"),
  24        N_("git bisect--helper --bisect-check-and-set-terms <command> <good_term> <bad_term>"),
  25        N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"),
  26        N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"),
  27        NULL
  28};
  29
  30struct bisect_terms {
  31        char *term_good;
  32        char *term_bad;
  33};
  34
  35static void free_terms(struct bisect_terms *terms)
  36{
  37        FREE_AND_NULL(terms->term_good);
  38        FREE_AND_NULL(terms->term_bad);
  39}
  40
  41static void set_terms(struct bisect_terms *terms, const char *bad,
  42                      const char *good)
  43{
  44        free((void *)terms->term_good);
  45        terms->term_good = xstrdup(good);
  46        free((void *)terms->term_bad);
  47        terms->term_bad = xstrdup(bad);
  48}
  49
  50static const char *vocab_bad = "bad|new";
  51static const char *vocab_good = "good|old";
  52
  53/*
  54 * Check whether the string `term` belongs to the set of strings
  55 * included in the variable arguments.
  56 */
  57LAST_ARG_MUST_BE_NULL
  58static int one_of(const char *term, ...)
  59{
  60        int res = 0;
  61        va_list matches;
  62        const char *match;
  63
  64        va_start(matches, term);
  65        while (!res && (match = va_arg(matches, const char *)))
  66                res = !strcmp(term, match);
  67        va_end(matches);
  68
  69        return res;
  70}
  71
  72static int check_term_format(const char *term, const char *orig_term)
  73{
  74        int res;
  75        char *new_term = xstrfmt("refs/bisect/%s", term);
  76
  77        res = check_refname_format(new_term, 0);
  78        free(new_term);
  79
  80        if (res)
  81                return error(_("'%s' is not a valid term"), term);
  82
  83        if (one_of(term, "help", "start", "skip", "next", "reset",
  84                        "visualize", "view", "replay", "log", "run", "terms", NULL))
  85                return error(_("can't use the builtin command '%s' as a term"), term);
  86
  87        /*
  88         * In theory, nothing prevents swapping completely good and bad,
  89         * but this situation could be confusing and hasn't been tested
  90         * enough. Forbid it for now.
  91         */
  92
  93        if ((strcmp(orig_term, "bad") && one_of(term, "bad", "new", NULL)) ||
  94                 (strcmp(orig_term, "good") && one_of(term, "good", "old", NULL)))
  95                return error(_("can't change the meaning of the term '%s'"), term);
  96
  97        return 0;
  98}
  99
 100static int write_terms(const char *bad, const char *good)
 101{
 102        FILE *fp = NULL;
 103        int res;
 104
 105        if (!strcmp(bad, good))
 106                return error(_("please use two different terms"));
 107
 108        if (check_term_format(bad, "bad") || check_term_format(good, "good"))
 109                return -1;
 110
 111        fp = fopen(git_path_bisect_terms(), "w");
 112        if (!fp)
 113                return error_errno(_("could not open the file BISECT_TERMS"));
 114
 115        res = fprintf(fp, "%s\n%s\n", bad, good);
 116        res |= fclose(fp);
 117        return (res < 0) ? -1 : 0;
 118}
 119
 120static int is_expected_rev(const char *expected_hex)
 121{
 122        struct strbuf actual_hex = STRBUF_INIT;
 123        int res = 0;
 124        if (strbuf_read_file(&actual_hex, git_path_bisect_expected_rev(), 0) >= 40) {
 125                strbuf_trim(&actual_hex);
 126                res = !strcmp(actual_hex.buf, expected_hex);
 127        }
 128        strbuf_release(&actual_hex);
 129        return res;
 130}
 131
 132static void check_expected_revs(const char **revs, int rev_nr)
 133{
 134        int i;
 135
 136        for (i = 0; i < rev_nr; i++) {
 137                if (!is_expected_rev(revs[i])) {
 138                        unlink_or_warn(git_path_bisect_ancestors_ok());
 139                        unlink_or_warn(git_path_bisect_expected_rev());
 140                }
 141        }
 142}
 143
 144static int bisect_reset(const char *commit)
 145{
 146        struct strbuf branch = STRBUF_INIT;
 147
 148        if (!commit) {
 149                if (strbuf_read_file(&branch, git_path_bisect_start(), 0) < 1) {
 150                        printf(_("We are not bisecting.\n"));
 151                        return 0;
 152                }
 153                strbuf_rtrim(&branch);
 154        } else {
 155                struct object_id oid;
 156
 157                if (get_oid_commit(commit, &oid))
 158                        return error(_("'%s' is not a valid commit"), commit);
 159                strbuf_addstr(&branch, commit);
 160        }
 161
 162        if (!file_exists(git_path_bisect_head())) {
 163                struct argv_array argv = ARGV_ARRAY_INIT;
 164
 165                argv_array_pushl(&argv, "checkout", branch.buf, "--", NULL);
 166                if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
 167                        strbuf_release(&branch);
 168                        argv_array_clear(&argv);
 169                        return error(_("could not check out original"
 170                                       " HEAD '%s'. Try 'git bisect"
 171                                       "reset <commit>'."), branch.buf);
 172                }
 173                argv_array_clear(&argv);
 174        }
 175
 176        strbuf_release(&branch);
 177        return bisect_clean_state();
 178}
 179
 180static void log_commit(FILE *fp, char *fmt, const char *state,
 181                       struct commit *commit)
 182{
 183        struct pretty_print_context pp = {0};
 184        struct strbuf commit_msg = STRBUF_INIT;
 185        char *label = xstrfmt(fmt, state);
 186
 187        format_commit_message(commit, "%s", &commit_msg, &pp);
 188
 189        fprintf(fp, "# %s: [%s] %s\n", label, oid_to_hex(&commit->object.oid),
 190                commit_msg.buf);
 191
 192        strbuf_release(&commit_msg);
 193        free(label);
 194}
 195
 196static int bisect_write(const char *state, const char *rev,
 197                        const struct bisect_terms *terms, int nolog)
 198{
 199        struct strbuf tag = STRBUF_INIT;
 200        struct object_id oid;
 201        struct commit *commit;
 202        FILE *fp = NULL;
 203        int retval = 0;
 204
 205        if (!strcmp(state, terms->term_bad)) {
 206                strbuf_addf(&tag, "refs/bisect/%s", state);
 207        } else if (one_of(state, terms->term_good, "skip", NULL)) {
 208                strbuf_addf(&tag, "refs/bisect/%s-%s", state, rev);
 209        } else {
 210                retval = error(_("Bad bisect_write argument: %s"), state);
 211                goto finish;
 212        }
 213
 214        if (get_oid(rev, &oid)) {
 215                retval = error(_("couldn't get the oid of the rev '%s'"), rev);
 216                goto finish;
 217        }
 218
 219        if (update_ref(NULL, tag.buf, &oid, NULL, 0,
 220                       UPDATE_REFS_MSG_ON_ERR)) {
 221                retval = -1;
 222                goto finish;
 223        }
 224
 225        fp = fopen(git_path_bisect_log(), "a");
 226        if (!fp) {
 227                retval = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log());
 228                goto finish;
 229        }
 230
 231        commit = lookup_commit_reference(the_repository, &oid);
 232        log_commit(fp, "%s", state, commit);
 233
 234        if (!nolog)
 235                fprintf(fp, "git bisect %s %s\n", state, rev);
 236
 237finish:
 238        if (fp)
 239                fclose(fp);
 240        strbuf_release(&tag);
 241        return retval;
 242}
 243
 244static int check_and_set_terms(struct bisect_terms *terms, const char *cmd)
 245{
 246        int has_term_file = !is_empty_or_missing_file(git_path_bisect_terms());
 247
 248        if (one_of(cmd, "skip", "start", "terms", NULL))
 249                return 0;
 250
 251        if (has_term_file && strcmp(cmd, terms->term_bad) &&
 252            strcmp(cmd, terms->term_good))
 253                return error(_("Invalid command: you're currently in a "
 254                                "%s/%s bisect"), terms->term_bad,
 255                                terms->term_good);
 256
 257        if (!has_term_file) {
 258                if (one_of(cmd, "bad", "good", NULL)) {
 259                        set_terms(terms, "bad", "good");
 260                        return write_terms(terms->term_bad, terms->term_good);
 261                }
 262                if (one_of(cmd, "new", "old", NULL)) {
 263                        set_terms(terms, "new", "old");
 264                        return write_terms(terms->term_bad, terms->term_good);
 265                }
 266        }
 267
 268        return 0;
 269}
 270
 271static int mark_good(const char *refname, const struct object_id *oid,
 272                     int flag, void *cb_data)
 273{
 274        int *m_good = (int *)cb_data;
 275        *m_good = 0;
 276        return 1;
 277}
 278
 279static const char *need_bad_and_good_revision_warning =
 280        N_("You need to give me at least one %s and %s revision.\n"
 281           "You can use \"git bisect %s\" and \"git bisect %s\" for that.");
 282
 283static const char *need_bisect_start_warning =
 284        N_("You need to start by \"git bisect start\".\n"
 285           "You then need to give me at least one %s and %s revision.\n"
 286           "You can use \"git bisect %s\" and \"git bisect %s\" for that.");
 287
 288static int bisect_next_check(const struct bisect_terms *terms,
 289                             const char *current_term)
 290{
 291        int missing_good = 1, missing_bad = 1, retval = 0;
 292        const char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad);
 293        const char *good_glob = xstrfmt("%s-*", terms->term_good);
 294
 295        if (ref_exists(bad_ref))
 296                missing_bad = 0;
 297
 298        for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/",
 299                             (void *) &missing_good);
 300
 301        if (!missing_good && !missing_bad)
 302                goto finish;
 303
 304        if (!current_term) {
 305                retval = -1;
 306                goto finish;
 307        }
 308
 309        if (missing_good && !missing_bad &&
 310            !strcmp(current_term, terms->term_good)) {
 311                char *yesno;
 312                /*
 313                 * have bad (or new) but not good (or old). We could bisect
 314                 * although this is less optimum.
 315                 */
 316                warning(_("bisecting only with a %s commit"), terms->term_bad);
 317                if (!isatty(0))
 318                        goto finish;
 319                /*
 320                 * TRANSLATORS: Make sure to include [Y] and [n] in your
 321                 * translation. The program will only accept English input
 322                 * at this point.
 323                 */
 324                yesno = git_prompt(_("Are you sure [Y/n]? "), PROMPT_ECHO);
 325                if (starts_with(yesno, "N") || starts_with(yesno, "n"))
 326                        retval = -1;
 327                goto finish;
 328        }
 329        if (!is_empty_or_missing_file(git_path_bisect_start())) {
 330                retval = error(_(need_bad_and_good_revision_warning),
 331                               vocab_bad, vocab_good, vocab_bad, vocab_good);
 332        } else {
 333                retval = error(_(need_bisect_start_warning),
 334                               vocab_good, vocab_bad, vocab_good, vocab_bad);
 335        }
 336
 337finish:
 338        free((void *) good_glob);
 339        free((void *) bad_ref);
 340        return retval;
 341}
 342
 343static int get_terms(struct bisect_terms *terms)
 344{
 345        struct strbuf str = STRBUF_INIT;
 346        FILE *fp = NULL;
 347        int res = 0;
 348
 349        fp = fopen(git_path_bisect_terms(), "r");
 350        if (!fp) {
 351                res = -1;
 352                goto finish;
 353        }
 354
 355        free_terms(terms);
 356        strbuf_getline_lf(&str, fp);
 357        terms->term_bad = strbuf_detach(&str, NULL);
 358        strbuf_getline_lf(&str, fp);
 359        terms->term_good = strbuf_detach(&str, NULL);
 360
 361finish:
 362        if (fp)
 363                fclose(fp);
 364        strbuf_release(&str);
 365        return res;
 366}
 367
 368static int bisect_terms(struct bisect_terms *terms, const char *option)
 369{
 370        if (get_terms(terms))
 371                return error(_("no terms defined"));
 372
 373        if (option == NULL) {
 374                printf(_("Your current terms are %s for the old state\n"
 375                         "and %s for the new state.\n"),
 376                       terms->term_good, terms->term_bad);
 377                return 0;
 378        }
 379        if (one_of(option, "--term-good", "--term-old", NULL))
 380                printf("%s\n", terms->term_good);
 381        else if (one_of(option, "--term-bad", "--term-new", NULL))
 382                printf("%s\n", terms->term_bad);
 383        else
 384                return error(_("invalid argument %s for 'git bisect terms'.\n"
 385                               "Supported options are: "
 386                               "--term-good|--term-old and "
 387                               "--term-bad|--term-new."), option);
 388
 389        return 0;
 390}
 391
 392int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
 393{
 394        enum {
 395                NEXT_ALL = 1,
 396                WRITE_TERMS,
 397                BISECT_CLEAN_STATE,
 398                CHECK_EXPECTED_REVS,
 399                BISECT_RESET,
 400                BISECT_WRITE,
 401                CHECK_AND_SET_TERMS,
 402                BISECT_NEXT_CHECK,
 403                BISECT_TERMS
 404        } cmdmode = 0;
 405        int no_checkout = 0, res = 0, nolog = 0;
 406        struct option options[] = {
 407                OPT_CMDMODE(0, "next-all", &cmdmode,
 408                         N_("perform 'git bisect next'"), NEXT_ALL),
 409                OPT_CMDMODE(0, "write-terms", &cmdmode,
 410                         N_("write the terms to .git/BISECT_TERMS"), WRITE_TERMS),
 411                OPT_CMDMODE(0, "bisect-clean-state", &cmdmode,
 412                         N_("cleanup the bisection state"), BISECT_CLEAN_STATE),
 413                OPT_CMDMODE(0, "check-expected-revs", &cmdmode,
 414                         N_("check for expected revs"), CHECK_EXPECTED_REVS),
 415                OPT_CMDMODE(0, "bisect-reset", &cmdmode,
 416                         N_("reset the bisection state"), BISECT_RESET),
 417                OPT_CMDMODE(0, "bisect-write", &cmdmode,
 418                         N_("write out the bisection state in BISECT_LOG"), BISECT_WRITE),
 419                OPT_CMDMODE(0, "check-and-set-terms", &cmdmode,
 420                         N_("check and set terms in a bisection state"), CHECK_AND_SET_TERMS),
 421                OPT_CMDMODE(0, "bisect-next-check", &cmdmode,
 422                         N_("check whether bad or good terms exist"), BISECT_NEXT_CHECK),
 423                OPT_CMDMODE(0, "bisect-terms", &cmdmode,
 424                         N_("print out the bisect terms"), BISECT_TERMS),
 425                OPT_BOOL(0, "no-checkout", &no_checkout,
 426                         N_("update BISECT_HEAD instead of checking out the current commit")),
 427                OPT_BOOL(0, "no-log", &nolog,
 428                         N_("no log for BISECT_WRITE ")),
 429                OPT_END()
 430        };
 431        struct bisect_terms terms = { .term_good = NULL, .term_bad = NULL };
 432
 433        argc = parse_options(argc, argv, prefix, options,
 434                             git_bisect_helper_usage, PARSE_OPT_KEEP_UNKNOWN);
 435
 436        if (!cmdmode)
 437                usage_with_options(git_bisect_helper_usage, options);
 438
 439        switch (cmdmode) {
 440        case NEXT_ALL:
 441                return bisect_next_all(prefix, no_checkout);
 442        case WRITE_TERMS:
 443                if (argc != 2)
 444                        return error(_("--write-terms requires two arguments"));
 445                return write_terms(argv[0], argv[1]);
 446        case BISECT_CLEAN_STATE:
 447                if (argc != 0)
 448                        return error(_("--bisect-clean-state requires no arguments"));
 449                return bisect_clean_state();
 450        case CHECK_EXPECTED_REVS:
 451                check_expected_revs(argv, argc);
 452                return 0;
 453        case BISECT_RESET:
 454                if (argc > 1)
 455                        return error(_("--bisect-reset requires either no argument or a commit"));
 456                return !!bisect_reset(argc ? argv[0] : NULL);
 457        case BISECT_WRITE:
 458                if (argc != 4 && argc != 5)
 459                        return error(_("--bisect-write requires either 4 or 5 arguments"));
 460                set_terms(&terms, argv[3], argv[2]);
 461                res = bisect_write(argv[0], argv[1], &terms, nolog);
 462                break;
 463        case CHECK_AND_SET_TERMS:
 464                if (argc != 3)
 465                        return error(_("--check-and-set-terms requires 3 arguments"));
 466                set_terms(&terms, argv[2], argv[1]);
 467                res = check_and_set_terms(&terms, argv[0]);
 468                break;
 469        case BISECT_NEXT_CHECK:
 470                if (argc != 2 && argc != 3)
 471                        return error(_("--bisect-next-check requires 2 or 3 arguments"));
 472                set_terms(&terms, argv[1], argv[0]);
 473                res = bisect_next_check(&terms, argc == 3 ? argv[2] : NULL);
 474                break;
 475        case BISECT_TERMS:
 476                if (argc > 1)
 477                        return error(_("--bisect-terms requires 0 or 1 argument"));
 478                res = bisect_terms(&terms, argc == 1 ? argv[0] : NULL);
 479                break;
 480        default:
 481                return error("BUG: unknown subcommand '%d'", cmdmode);
 482        }
 483        free_terms(&terms);
 484        return !!res;
 485}