Merge branch 'js/enhanced-version-info'
[git] / builtin / bisect--helper.c
1 #include "builtin.h"
2 #include "cache.h"
3 #include "parse-options.h"
4 #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")
10
11 static const char * const git_bisect_helper_usage[] = {
12         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"),
15         NULL
16 };
17
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", "view", "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
109 int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
110 {
111         enum {
112                 NEXT_ALL = 1,
113                 WRITE_TERMS,
114                 BISECT_CLEAN_STATE,
115                 CHECK_EXPECTED_REVS
116         } cmdmode = 0;
117         int no_checkout = 0;
118         struct option options[] = {
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),
127                 OPT_BOOL(0, "no-checkout", &no_checkout,
128                          N_("update BISECT_HEAD instead of checking out the current commit")),
129                 OPT_END()
130         };
131
132         argc = parse_options(argc, argv, prefix, options,
133                              git_bisect_helper_usage, 0);
134
135         if (!cmdmode)
136                 usage_with_options(git_bisect_helper_usage, options);
137
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;
156 }