02ea5a96112c02637f0d26f9609e11522f6d5fab
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
10static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
11static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
12static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK")
13static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
14static GIT_PATH_FUNC(git_path_bisect_head, "BISECT_HEAD")
15static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
16
17static const char * const git_bisect_helper_usage[] = {
18 N_("git bisect--helper --next-all [--no-checkout]"),
19 N_("git bisect--helper --write-terms <bad_term> <good_term>"),
20 N_("git bisect--helper --bisect-clean-state"),
21 N_("git bisect--helper --bisect-reset [<commit>]"),
22 N_("git bisect--helper --bisect-write [--no-log] <state> <revision> <good_term> <bad_term>"),
23 N_("git bisect--helper --bisect-check-and-set-terms <command> <good_term> <bad_term>"),
24 NULL
25};
26
27struct bisect_terms {
28 char *term_good;
29 char *term_bad;
30};
31
32static void free_terms(struct bisect_terms *terms)
33{
34 FREE_AND_NULL(terms->term_good);
35 FREE_AND_NULL(terms->term_bad);
36}
37
38static void set_terms(struct bisect_terms *terms, const char *bad,
39 const char *good)
40{
41 free((void *)terms->term_good);
42 terms->term_good = xstrdup(good);
43 free((void *)terms->term_bad);
44 terms->term_bad = xstrdup(bad);
45}
46
47/*
48 * Check whether the string `term` belongs to the set of strings
49 * included in the variable arguments.
50 */
51LAST_ARG_MUST_BE_NULL
52static int one_of(const char *term, ...)
53{
54 int res = 0;
55 va_list matches;
56 const char *match;
57
58 va_start(matches, term);
59 while (!res && (match = va_arg(matches, const char *)))
60 res = !strcmp(term, match);
61 va_end(matches);
62
63 return res;
64}
65
66static int check_term_format(const char *term, const char *orig_term)
67{
68 int res;
69 char *new_term = xstrfmt("refs/bisect/%s", term);
70
71 res = check_refname_format(new_term, 0);
72 free(new_term);
73
74 if (res)
75 return error(_("'%s' is not a valid term"), term);
76
77 if (one_of(term, "help", "start", "skip", "next", "reset",
78 "visualize", "view", "replay", "log", "run", "terms", NULL))
79 return error(_("can't use the builtin command '%s' as a term"), term);
80
81 /*
82 * In theory, nothing prevents swapping completely good and bad,
83 * but this situation could be confusing and hasn't been tested
84 * enough. Forbid it for now.
85 */
86
87 if ((strcmp(orig_term, "bad") && one_of(term, "bad", "new", NULL)) ||
88 (strcmp(orig_term, "good") && one_of(term, "good", "old", NULL)))
89 return error(_("can't change the meaning of the term '%s'"), term);
90
91 return 0;
92}
93
94static int write_terms(const char *bad, const char *good)
95{
96 FILE *fp = NULL;
97 int res;
98
99 if (!strcmp(bad, good))
100 return error(_("please use two different terms"));
101
102 if (check_term_format(bad, "bad") || check_term_format(good, "good"))
103 return -1;
104
105 fp = fopen(git_path_bisect_terms(), "w");
106 if (!fp)
107 return error_errno(_("could not open the file BISECT_TERMS"));
108
109 res = fprintf(fp, "%s\n%s\n", bad, good);
110 res |= fclose(fp);
111 return (res < 0) ? -1 : 0;
112}
113
114static int is_expected_rev(const char *expected_hex)
115{
116 struct strbuf actual_hex = STRBUF_INIT;
117 int res = 0;
118 if (strbuf_read_file(&actual_hex, git_path_bisect_expected_rev(), 0) >= 40) {
119 strbuf_trim(&actual_hex);
120 res = !strcmp(actual_hex.buf, expected_hex);
121 }
122 strbuf_release(&actual_hex);
123 return res;
124}
125
126static void check_expected_revs(const char **revs, int rev_nr)
127{
128 int i;
129
130 for (i = 0; i < rev_nr; i++) {
131 if (!is_expected_rev(revs[i])) {
132 unlink_or_warn(git_path_bisect_ancestors_ok());
133 unlink_or_warn(git_path_bisect_expected_rev());
134 }
135 }
136}
137
138static int bisect_reset(const char *commit)
139{
140 struct strbuf branch = STRBUF_INIT;
141
142 if (!commit) {
143 if (strbuf_read_file(&branch, git_path_bisect_start(), 0) < 1) {
144 printf(_("We are not bisecting.\n"));
145 return 0;
146 }
147 strbuf_rtrim(&branch);
148 } else {
149 struct object_id oid;
150
151 if (get_oid_commit(commit, &oid))
152 return error(_("'%s' is not a valid commit"), commit);
153 strbuf_addstr(&branch, commit);
154 }
155
156 if (!file_exists(git_path_bisect_head())) {
157 struct argv_array argv = ARGV_ARRAY_INIT;
158
159 argv_array_pushl(&argv, "checkout", branch.buf, "--", NULL);
160 if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
161 strbuf_release(&branch);
162 argv_array_clear(&argv);
163 return error(_("could not check out original"
164 " HEAD '%s'. Try 'git bisect"
165 "reset <commit>'."), branch.buf);
166 }
167 argv_array_clear(&argv);
168 }
169
170 strbuf_release(&branch);
171 return bisect_clean_state();
172}
173
174static void log_commit(FILE *fp, char *fmt, const char *state,
175 struct commit *commit)
176{
177 struct pretty_print_context pp = {0};
178 struct strbuf commit_msg = STRBUF_INIT;
179 char *label = xstrfmt(fmt, state);
180
181 format_commit_message(commit, "%s", &commit_msg, &pp);
182
183 fprintf(fp, "# %s: [%s] %s\n", label, oid_to_hex(&commit->object.oid),
184 commit_msg.buf);
185
186 strbuf_release(&commit_msg);
187 free(label);
188}
189
190static int bisect_write(const char *state, const char *rev,
191 const struct bisect_terms *terms, int nolog)
192{
193 struct strbuf tag = STRBUF_INIT;
194 struct object_id oid;
195 struct commit *commit;
196 FILE *fp = NULL;
197 int retval = 0;
198
199 if (!strcmp(state, terms->term_bad)) {
200 strbuf_addf(&tag, "refs/bisect/%s", state);
201 } else if (one_of(state, terms->term_good, "skip", NULL)) {
202 strbuf_addf(&tag, "refs/bisect/%s-%s", state, rev);
203 } else {
204 retval = error(_("Bad bisect_write argument: %s"), state);
205 goto finish;
206 }
207
208 if (get_oid(rev, &oid)) {
209 retval = error(_("couldn't get the oid of the rev '%s'"), rev);
210 goto finish;
211 }
212
213 if (update_ref(NULL, tag.buf, &oid, NULL, 0,
214 UPDATE_REFS_MSG_ON_ERR)) {
215 retval = -1;
216 goto finish;
217 }
218
219 fp = fopen(git_path_bisect_log(), "a");
220 if (!fp) {
221 retval = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log());
222 goto finish;
223 }
224
225 commit = lookup_commit_reference(the_repository, &oid);
226 log_commit(fp, "%s", state, commit);
227
228 if (!nolog)
229 fprintf(fp, "git bisect %s %s\n", state, rev);
230
231finish:
232 if (fp)
233 fclose(fp);
234 strbuf_release(&tag);
235 return retval;
236}
237
238static int check_and_set_terms(struct bisect_terms *terms, const char *cmd)
239{
240 int has_term_file = !is_empty_or_missing_file(git_path_bisect_terms());
241
242 if (one_of(cmd, "skip", "start", "terms", NULL))
243 return 0;
244
245 if (has_term_file && strcmp(cmd, terms->term_bad) &&
246 strcmp(cmd, terms->term_good))
247 return error(_("Invalid command: you're currently in a "
248 "%s/%s bisect"), terms->term_bad,
249 terms->term_good);
250
251 if (!has_term_file) {
252 if (one_of(cmd, "bad", "good", NULL)) {
253 set_terms(terms, "bad", "good");
254 return write_terms(terms->term_bad, terms->term_good);
255 }
256 if (one_of(cmd, "new", "old", NULL)) {
257 set_terms(terms, "new", "old");
258 return write_terms(terms->term_bad, terms->term_good);
259 }
260 }
261
262 return 0;
263}
264
265int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
266{
267 enum {
268 NEXT_ALL = 1,
269 WRITE_TERMS,
270 BISECT_CLEAN_STATE,
271 CHECK_EXPECTED_REVS,
272 BISECT_RESET,
273 BISECT_WRITE,
274 CHECK_AND_SET_TERMS
275 } cmdmode = 0;
276 int no_checkout = 0, res = 0, nolog = 0;
277 struct option options[] = {
278 OPT_CMDMODE(0, "next-all", &cmdmode,
279 N_("perform 'git bisect next'"), NEXT_ALL),
280 OPT_CMDMODE(0, "write-terms", &cmdmode,
281 N_("write the terms to .git/BISECT_TERMS"), WRITE_TERMS),
282 OPT_CMDMODE(0, "bisect-clean-state", &cmdmode,
283 N_("cleanup the bisection state"), BISECT_CLEAN_STATE),
284 OPT_CMDMODE(0, "check-expected-revs", &cmdmode,
285 N_("check for expected revs"), CHECK_EXPECTED_REVS),
286 OPT_CMDMODE(0, "bisect-reset", &cmdmode,
287 N_("reset the bisection state"), BISECT_RESET),
288 OPT_CMDMODE(0, "bisect-write", &cmdmode,
289 N_("write out the bisection state in BISECT_LOG"), BISECT_WRITE),
290 OPT_CMDMODE(0, "check-and-set-terms", &cmdmode,
291 N_("check and set terms in a bisection state"), CHECK_AND_SET_TERMS),
292 OPT_BOOL(0, "no-checkout", &no_checkout,
293 N_("update BISECT_HEAD instead of checking out the current commit")),
294 OPT_BOOL(0, "no-log", &nolog,
295 N_("no log for BISECT_WRITE ")),
296 OPT_END()
297 };
298 struct bisect_terms terms = { .term_good = NULL, .term_bad = NULL };
299
300 argc = parse_options(argc, argv, prefix, options,
301 git_bisect_helper_usage, 0);
302
303 if (!cmdmode)
304 usage_with_options(git_bisect_helper_usage, options);
305
306 switch (cmdmode) {
307 case NEXT_ALL:
308 return bisect_next_all(prefix, no_checkout);
309 case WRITE_TERMS:
310 if (argc != 2)
311 return error(_("--write-terms requires two arguments"));
312 return write_terms(argv[0], argv[1]);
313 case BISECT_CLEAN_STATE:
314 if (argc != 0)
315 return error(_("--bisect-clean-state requires no arguments"));
316 return bisect_clean_state();
317 case CHECK_EXPECTED_REVS:
318 check_expected_revs(argv, argc);
319 return 0;
320 case BISECT_RESET:
321 if (argc > 1)
322 return error(_("--bisect-reset requires either no argument or a commit"));
323 return !!bisect_reset(argc ? argv[0] : NULL);
324 case BISECT_WRITE:
325 if (argc != 4 && argc != 5)
326 return error(_("--bisect-write requires either 4 or 5 arguments"));
327 set_terms(&terms, argv[3], argv[2]);
328 res = bisect_write(argv[0], argv[1], &terms, nolog);
329 break;
330 case CHECK_AND_SET_TERMS:
331 if (argc != 3)
332 return error(_("--check-and-set-terms requires 3 arguments"));
333 set_terms(&terms, argv[2], argv[1]);
334 res = check_and_set_terms(&terms, argv[0]);
335 break;
336 default:
337 return error("BUG: unknown subcommand '%d'", cmdmode);
338 }
339 free_terms(&terms);
340 return !!res;
341}