/src/git/builtin/bisect.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "builtin.h" |
2 | | #include "copy.h" |
3 | | #include "environment.h" |
4 | | #include "gettext.h" |
5 | | #include "hex.h" |
6 | | #include "object-name.h" |
7 | | #include "parse-options.h" |
8 | | #include "bisect.h" |
9 | | #include "refs.h" |
10 | | #include "strvec.h" |
11 | | #include "run-command.h" |
12 | | #include "oid-array.h" |
13 | | #include "path.h" |
14 | | #include "prompt.h" |
15 | | #include "quote.h" |
16 | | #include "revision.h" |
17 | | |
18 | | static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS") |
19 | | static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK") |
20 | | static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START") |
21 | | static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG") |
22 | | static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES") |
23 | | static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT") |
24 | | static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN") |
25 | | |
26 | | #define BUILTIN_GIT_BISECT_START_USAGE \ |
27 | | N_("git bisect start [--term-(new|bad)=<term> --term-(old|good)=<term>]" \ |
28 | | " [--no-checkout] [--first-parent] [<bad> [<good>...]] [--]" \ |
29 | | " [<pathspec>...]") |
30 | | #define BUILTIN_GIT_BISECT_STATE_USAGE \ |
31 | | N_("git bisect (good|bad) [<rev>...]") |
32 | | #define BUILTIN_GIT_BISECT_TERMS_USAGE \ |
33 | | "git bisect terms [--term-good | --term-bad]" |
34 | | #define BUILTIN_GIT_BISECT_SKIP_USAGE \ |
35 | | N_("git bisect skip [(<rev>|<range>)...]") |
36 | | #define BUILTIN_GIT_BISECT_NEXT_USAGE \ |
37 | | "git bisect next" |
38 | | #define BUILTIN_GIT_BISECT_RESET_USAGE \ |
39 | | N_("git bisect reset [<commit>]") |
40 | | #define BUILTIN_GIT_BISECT_VISUALIZE_USAGE \ |
41 | | "git bisect visualize" |
42 | | #define BUILTIN_GIT_BISECT_REPLAY_USAGE \ |
43 | | N_("git bisect replay <logfile>") |
44 | | #define BUILTIN_GIT_BISECT_LOG_USAGE \ |
45 | | "git bisect log" |
46 | | #define BUILTIN_GIT_BISECT_RUN_USAGE \ |
47 | | N_("git bisect run <cmd> [<arg>...]") |
48 | | |
49 | | static const char * const git_bisect_usage[] = { |
50 | | BUILTIN_GIT_BISECT_START_USAGE, |
51 | | BUILTIN_GIT_BISECT_STATE_USAGE, |
52 | | BUILTIN_GIT_BISECT_TERMS_USAGE, |
53 | | BUILTIN_GIT_BISECT_SKIP_USAGE, |
54 | | BUILTIN_GIT_BISECT_NEXT_USAGE, |
55 | | BUILTIN_GIT_BISECT_RESET_USAGE, |
56 | | BUILTIN_GIT_BISECT_VISUALIZE_USAGE, |
57 | | BUILTIN_GIT_BISECT_REPLAY_USAGE, |
58 | | BUILTIN_GIT_BISECT_LOG_USAGE, |
59 | | BUILTIN_GIT_BISECT_RUN_USAGE, |
60 | | NULL |
61 | | }; |
62 | | |
63 | | struct add_bisect_ref_data { |
64 | | struct rev_info *revs; |
65 | | unsigned int object_flags; |
66 | | }; |
67 | | |
68 | | struct bisect_terms { |
69 | | char *term_good; |
70 | | char *term_bad; |
71 | | }; |
72 | | |
73 | | static void free_terms(struct bisect_terms *terms) |
74 | 0 | { |
75 | 0 | FREE_AND_NULL(terms->term_good); |
76 | 0 | FREE_AND_NULL(terms->term_bad); |
77 | 0 | } |
78 | | |
79 | | static void set_terms(struct bisect_terms *terms, const char *bad, |
80 | | const char *good) |
81 | 0 | { |
82 | 0 | free((void *)terms->term_good); |
83 | 0 | terms->term_good = xstrdup(good); |
84 | 0 | free((void *)terms->term_bad); |
85 | 0 | terms->term_bad = xstrdup(bad); |
86 | 0 | } |
87 | | |
88 | | static const char vocab_bad[] = "bad|new"; |
89 | | static const char vocab_good[] = "good|old"; |
90 | | |
91 | | static int bisect_autostart(struct bisect_terms *terms); |
92 | | |
93 | | /* |
94 | | * Check whether the string `term` belongs to the set of strings |
95 | | * included in the variable arguments. |
96 | | */ |
97 | | LAST_ARG_MUST_BE_NULL |
98 | | static int one_of(const char *term, ...) |
99 | 0 | { |
100 | 0 | int res = 0; |
101 | 0 | va_list matches; |
102 | 0 | const char *match; |
103 | |
|
104 | 0 | va_start(matches, term); |
105 | 0 | while (!res && (match = va_arg(matches, const char *))) |
106 | 0 | res = !strcmp(term, match); |
107 | 0 | va_end(matches); |
108 | |
|
109 | 0 | return res; |
110 | 0 | } |
111 | | |
112 | | /* |
113 | | * return code BISECT_INTERNAL_SUCCESS_MERGE_BASE |
114 | | * and BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND are codes |
115 | | * that indicate special success. |
116 | | */ |
117 | | |
118 | | static int is_bisect_success(enum bisect_error res) |
119 | 0 | { |
120 | 0 | return !res || |
121 | 0 | res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND || |
122 | 0 | res == BISECT_INTERNAL_SUCCESS_MERGE_BASE; |
123 | 0 | } |
124 | | |
125 | | static int write_in_file(const char *path, const char *mode, const char *format, va_list args) |
126 | 0 | { |
127 | 0 | FILE *fp = NULL; |
128 | 0 | int res = 0; |
129 | |
|
130 | 0 | if (strcmp(mode, "w") && strcmp(mode, "a")) |
131 | 0 | BUG("write-in-file does not support '%s' mode", mode); |
132 | 0 | fp = fopen(path, mode); |
133 | 0 | if (!fp) |
134 | 0 | return error_errno(_("cannot open file '%s' in mode '%s'"), path, mode); |
135 | 0 | res = vfprintf(fp, format, args); |
136 | |
|
137 | 0 | if (res < 0) { |
138 | 0 | int saved_errno = errno; |
139 | 0 | fclose(fp); |
140 | 0 | errno = saved_errno; |
141 | 0 | return error_errno(_("could not write to file '%s'"), path); |
142 | 0 | } |
143 | | |
144 | 0 | return fclose(fp); |
145 | 0 | } |
146 | | |
147 | | __attribute__((format (printf, 2, 3))) |
148 | | static int write_to_file(const char *path, const char *format, ...) |
149 | 0 | { |
150 | 0 | int res; |
151 | 0 | va_list args; |
152 | |
|
153 | 0 | va_start(args, format); |
154 | 0 | res = write_in_file(path, "w", format, args); |
155 | 0 | va_end(args); |
156 | |
|
157 | 0 | return res; |
158 | 0 | } |
159 | | |
160 | | __attribute__((format (printf, 2, 3))) |
161 | | static int append_to_file(const char *path, const char *format, ...) |
162 | 0 | { |
163 | 0 | int res; |
164 | 0 | va_list args; |
165 | |
|
166 | 0 | va_start(args, format); |
167 | 0 | res = write_in_file(path, "a", format, args); |
168 | 0 | va_end(args); |
169 | |
|
170 | 0 | return res; |
171 | 0 | } |
172 | | |
173 | | static int print_file_to_stdout(const char *path) |
174 | 0 | { |
175 | 0 | int fd = open(path, O_RDONLY); |
176 | 0 | int ret = 0; |
177 | |
|
178 | 0 | if (fd < 0) |
179 | 0 | return error_errno(_("cannot open file '%s' for reading"), path); |
180 | 0 | if (copy_fd(fd, 1) < 0) |
181 | 0 | ret = error_errno(_("failed to read '%s'"), path); |
182 | 0 | close(fd); |
183 | 0 | return ret; |
184 | 0 | } |
185 | | |
186 | | static int check_term_format(const char *term, const char *orig_term) |
187 | 0 | { |
188 | 0 | int res; |
189 | 0 | char *new_term = xstrfmt("refs/bisect/%s", term); |
190 | |
|
191 | 0 | res = check_refname_format(new_term, 0); |
192 | 0 | free(new_term); |
193 | |
|
194 | 0 | if (res) |
195 | 0 | return error(_("'%s' is not a valid term"), term); |
196 | | |
197 | 0 | if (one_of(term, "help", "start", "skip", "next", "reset", |
198 | 0 | "visualize", "view", "replay", "log", "run", "terms", NULL)) |
199 | 0 | return error(_("can't use the builtin command '%s' as a term"), term); |
200 | | |
201 | | /* |
202 | | * In theory, nothing prevents swapping completely good and bad, |
203 | | * but this situation could be confusing and hasn't been tested |
204 | | * enough. Forbid it for now. |
205 | | */ |
206 | | |
207 | 0 | if ((strcmp(orig_term, "bad") && one_of(term, "bad", "new", NULL)) || |
208 | 0 | (strcmp(orig_term, "good") && one_of(term, "good", "old", NULL))) |
209 | 0 | return error(_("can't change the meaning of the term '%s'"), term); |
210 | | |
211 | 0 | return 0; |
212 | 0 | } |
213 | | |
214 | | static int write_terms(const char *bad, const char *good) |
215 | 0 | { |
216 | 0 | int res; |
217 | |
|
218 | 0 | if (!strcmp(bad, good)) |
219 | 0 | return error(_("please use two different terms")); |
220 | | |
221 | 0 | if (check_term_format(bad, "bad") || check_term_format(good, "good")) |
222 | 0 | return -1; |
223 | | |
224 | 0 | res = write_to_file(git_path_bisect_terms(), "%s\n%s\n", bad, good); |
225 | |
|
226 | 0 | return res; |
227 | 0 | } |
228 | | |
229 | | static int bisect_reset(const char *commit) |
230 | 0 | { |
231 | 0 | struct strbuf branch = STRBUF_INIT; |
232 | |
|
233 | 0 | if (!commit) { |
234 | 0 | if (!strbuf_read_file(&branch, git_path_bisect_start(), 0)) |
235 | 0 | printf(_("We are not bisecting.\n")); |
236 | 0 | else |
237 | 0 | strbuf_rtrim(&branch); |
238 | 0 | } else { |
239 | 0 | struct object_id oid; |
240 | |
|
241 | 0 | if (repo_get_oid_commit(the_repository, commit, &oid)) |
242 | 0 | return error(_("'%s' is not a valid commit"), commit); |
243 | 0 | strbuf_addstr(&branch, commit); |
244 | 0 | } |
245 | | |
246 | 0 | if (branch.len && !refs_ref_exists(get_main_ref_store(the_repository), "BISECT_HEAD")) { |
247 | 0 | struct child_process cmd = CHILD_PROCESS_INIT; |
248 | |
|
249 | 0 | cmd.git_cmd = 1; |
250 | 0 | strvec_pushl(&cmd.args, "checkout", "--ignore-other-worktrees", |
251 | 0 | branch.buf, "--", NULL); |
252 | 0 | if (run_command(&cmd)) { |
253 | 0 | error(_("could not check out original" |
254 | 0 | " HEAD '%s'. Try 'git bisect" |
255 | 0 | " reset <commit>'."), branch.buf); |
256 | 0 | strbuf_release(&branch); |
257 | 0 | return -1; |
258 | 0 | } |
259 | 0 | } |
260 | | |
261 | 0 | strbuf_release(&branch); |
262 | 0 | return bisect_clean_state(); |
263 | 0 | } |
264 | | |
265 | | static void log_commit(FILE *fp, |
266 | | const char *fmt, const char *state, |
267 | | struct commit *commit) |
268 | 0 | { |
269 | 0 | struct pretty_print_context pp = {0}; |
270 | 0 | struct strbuf commit_msg = STRBUF_INIT; |
271 | 0 | char *label = xstrfmt(fmt, state); |
272 | |
|
273 | 0 | repo_format_commit_message(the_repository, commit, "%s", &commit_msg, |
274 | 0 | &pp); |
275 | |
|
276 | 0 | fprintf(fp, "# %s: [%s] %s\n", label, oid_to_hex(&commit->object.oid), |
277 | 0 | commit_msg.buf); |
278 | |
|
279 | 0 | strbuf_release(&commit_msg); |
280 | 0 | free(label); |
281 | 0 | } |
282 | | |
283 | | static int bisect_write(const char *state, const char *rev, |
284 | | const struct bisect_terms *terms, int nolog) |
285 | 0 | { |
286 | 0 | struct strbuf tag = STRBUF_INIT; |
287 | 0 | struct object_id oid; |
288 | 0 | struct commit *commit; |
289 | 0 | FILE *fp = NULL; |
290 | 0 | int res = 0; |
291 | |
|
292 | 0 | if (!strcmp(state, terms->term_bad)) { |
293 | 0 | strbuf_addf(&tag, "refs/bisect/%s", state); |
294 | 0 | } else if (one_of(state, terms->term_good, "skip", NULL)) { |
295 | 0 | strbuf_addf(&tag, "refs/bisect/%s-%s", state, rev); |
296 | 0 | } else { |
297 | 0 | res = error(_("Bad bisect_write argument: %s"), state); |
298 | 0 | goto finish; |
299 | 0 | } |
300 | | |
301 | 0 | if (repo_get_oid(the_repository, rev, &oid)) { |
302 | 0 | res = error(_("couldn't get the oid of the rev '%s'"), rev); |
303 | 0 | goto finish; |
304 | 0 | } |
305 | | |
306 | 0 | if (refs_update_ref(get_main_ref_store(the_repository), NULL, tag.buf, &oid, NULL, 0, |
307 | 0 | UPDATE_REFS_MSG_ON_ERR)) { |
308 | 0 | res = -1; |
309 | 0 | goto finish; |
310 | 0 | } |
311 | | |
312 | 0 | fp = fopen(git_path_bisect_log(), "a"); |
313 | 0 | if (!fp) { |
314 | 0 | res = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log()); |
315 | 0 | goto finish; |
316 | 0 | } |
317 | | |
318 | 0 | commit = lookup_commit_reference(the_repository, &oid); |
319 | 0 | log_commit(fp, "%s", state, commit); |
320 | |
|
321 | 0 | if (!nolog) |
322 | 0 | fprintf(fp, "git bisect %s %s\n", state, rev); |
323 | |
|
324 | 0 | finish: |
325 | 0 | if (fp) |
326 | 0 | fclose(fp); |
327 | 0 | strbuf_release(&tag); |
328 | 0 | return res; |
329 | 0 | } |
330 | | |
331 | | static int check_and_set_terms(struct bisect_terms *terms, const char *cmd) |
332 | 0 | { |
333 | 0 | int has_term_file = !is_empty_or_missing_file(git_path_bisect_terms()); |
334 | |
|
335 | 0 | if (one_of(cmd, "skip", "start", "terms", NULL)) |
336 | 0 | return 0; |
337 | | |
338 | 0 | if (has_term_file && strcmp(cmd, terms->term_bad) && |
339 | 0 | strcmp(cmd, terms->term_good)) |
340 | 0 | return error(_("Invalid command: you're currently in a " |
341 | 0 | "%s/%s bisect"), terms->term_bad, |
342 | 0 | terms->term_good); |
343 | | |
344 | 0 | if (!has_term_file) { |
345 | 0 | if (one_of(cmd, "bad", "good", NULL)) { |
346 | 0 | set_terms(terms, "bad", "good"); |
347 | 0 | return write_terms(terms->term_bad, terms->term_good); |
348 | 0 | } |
349 | 0 | if (one_of(cmd, "new", "old", NULL)) { |
350 | 0 | set_terms(terms, "new", "old"); |
351 | 0 | return write_terms(terms->term_bad, terms->term_good); |
352 | 0 | } |
353 | 0 | } |
354 | | |
355 | 0 | return 0; |
356 | 0 | } |
357 | | |
358 | | static int inc_nr(const char *refname UNUSED, |
359 | | const char *referent UNUSED, |
360 | | const struct object_id *oid UNUSED, |
361 | | int flag UNUSED, void *cb_data) |
362 | 0 | { |
363 | 0 | unsigned int *nr = (unsigned int *)cb_data; |
364 | 0 | (*nr)++; |
365 | 0 | return 0; |
366 | 0 | } |
367 | | |
368 | | static const char need_bad_and_good_revision_warning[] = |
369 | | N_("You need to give me at least one %s and %s revision.\n" |
370 | | "You can use \"git bisect %s\" and \"git bisect %s\" for that."); |
371 | | |
372 | | static const char need_bisect_start_warning[] = |
373 | | N_("You need to start by \"git bisect start\".\n" |
374 | | "You then need to give me at least one %s and %s revision.\n" |
375 | | "You can use \"git bisect %s\" and \"git bisect %s\" for that."); |
376 | | |
377 | | static int decide_next(const struct bisect_terms *terms, |
378 | | const char *current_term, int missing_good, |
379 | | int missing_bad) |
380 | 0 | { |
381 | 0 | if (!missing_good && !missing_bad) |
382 | 0 | return 0; |
383 | 0 | if (!current_term) |
384 | 0 | return -1; |
385 | | |
386 | 0 | if (missing_good && !missing_bad && |
387 | 0 | !strcmp(current_term, terms->term_good)) { |
388 | 0 | char *yesno; |
389 | | /* |
390 | | * have bad (or new) but not good (or old). We could bisect |
391 | | * although this is less optimum. |
392 | | */ |
393 | 0 | warning(_("bisecting only with a %s commit"), terms->term_bad); |
394 | 0 | if (!isatty(0)) |
395 | 0 | return 0; |
396 | | /* |
397 | | * TRANSLATORS: Make sure to include [Y] and [n] in your |
398 | | * translation. The program will only accept English input |
399 | | * at this point. |
400 | | */ |
401 | 0 | yesno = git_prompt(_("Are you sure [Y/n]? "), PROMPT_ECHO); |
402 | 0 | if (starts_with(yesno, "N") || starts_with(yesno, "n")) |
403 | 0 | return -1; |
404 | 0 | return 0; |
405 | 0 | } |
406 | | |
407 | 0 | if (!is_empty_or_missing_file(git_path_bisect_start())) |
408 | 0 | return error(_(need_bad_and_good_revision_warning), |
409 | 0 | vocab_bad, vocab_good, vocab_bad, vocab_good); |
410 | 0 | else |
411 | 0 | return error(_(need_bisect_start_warning), |
412 | 0 | vocab_good, vocab_bad, vocab_good, vocab_bad); |
413 | 0 | } |
414 | | |
415 | | static void bisect_status(struct bisect_state *state, |
416 | | const struct bisect_terms *terms) |
417 | 0 | { |
418 | 0 | char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad); |
419 | 0 | char *good_glob = xstrfmt("%s-*", terms->term_good); |
420 | |
|
421 | 0 | if (refs_ref_exists(get_main_ref_store(the_repository), bad_ref)) |
422 | 0 | state->nr_bad = 1; |
423 | |
|
424 | 0 | refs_for_each_glob_ref_in(get_main_ref_store(the_repository), inc_nr, |
425 | 0 | good_glob, "refs/bisect/", |
426 | 0 | (void *) &state->nr_good); |
427 | |
|
428 | 0 | free(good_glob); |
429 | 0 | free(bad_ref); |
430 | 0 | } |
431 | | |
432 | | __attribute__((format (printf, 1, 2))) |
433 | | static void bisect_log_printf(const char *fmt, ...) |
434 | 0 | { |
435 | 0 | struct strbuf buf = STRBUF_INIT; |
436 | 0 | va_list ap; |
437 | |
|
438 | 0 | va_start(ap, fmt); |
439 | 0 | strbuf_vaddf(&buf, fmt, ap); |
440 | 0 | va_end(ap); |
441 | |
|
442 | 0 | printf("%s", buf.buf); |
443 | 0 | append_to_file(git_path_bisect_log(), "# %s", buf.buf); |
444 | |
|
445 | 0 | strbuf_release(&buf); |
446 | 0 | } |
447 | | |
448 | | static void bisect_print_status(const struct bisect_terms *terms) |
449 | 0 | { |
450 | 0 | struct bisect_state state = { 0 }; |
451 | |
|
452 | 0 | bisect_status(&state, terms); |
453 | | |
454 | | /* If we had both, we'd already be started, and shouldn't get here. */ |
455 | 0 | if (state.nr_good && state.nr_bad) |
456 | 0 | return; |
457 | | |
458 | 0 | if (!state.nr_good && !state.nr_bad) |
459 | 0 | bisect_log_printf(_("status: waiting for both good and bad commits\n")); |
460 | 0 | else if (state.nr_good) |
461 | 0 | bisect_log_printf(Q_("status: waiting for bad commit, %d good commit known\n", |
462 | 0 | "status: waiting for bad commit, %d good commits known\n", |
463 | 0 | state.nr_good), state.nr_good); |
464 | 0 | else |
465 | 0 | bisect_log_printf(_("status: waiting for good commit(s), bad commit known\n")); |
466 | 0 | } |
467 | | |
468 | | static int bisect_next_check(const struct bisect_terms *terms, |
469 | | const char *current_term) |
470 | 0 | { |
471 | 0 | struct bisect_state state = { 0 }; |
472 | 0 | bisect_status(&state, terms); |
473 | 0 | return decide_next(terms, current_term, !state.nr_good, !state.nr_bad); |
474 | 0 | } |
475 | | |
476 | | static int get_terms(struct bisect_terms *terms) |
477 | 0 | { |
478 | 0 | struct strbuf str = STRBUF_INIT; |
479 | 0 | FILE *fp = NULL; |
480 | 0 | int res = 0; |
481 | |
|
482 | 0 | fp = fopen(git_path_bisect_terms(), "r"); |
483 | 0 | if (!fp) { |
484 | 0 | res = -1; |
485 | 0 | goto finish; |
486 | 0 | } |
487 | | |
488 | 0 | free_terms(terms); |
489 | 0 | strbuf_getline_lf(&str, fp); |
490 | 0 | terms->term_bad = strbuf_detach(&str, NULL); |
491 | 0 | strbuf_getline_lf(&str, fp); |
492 | 0 | terms->term_good = strbuf_detach(&str, NULL); |
493 | |
|
494 | 0 | finish: |
495 | 0 | if (fp) |
496 | 0 | fclose(fp); |
497 | 0 | strbuf_release(&str); |
498 | 0 | return res; |
499 | 0 | } |
500 | | |
501 | | static int bisect_terms(struct bisect_terms *terms, const char *option) |
502 | 0 | { |
503 | 0 | if (get_terms(terms)) |
504 | 0 | return error(_("no terms defined")); |
505 | | |
506 | 0 | if (!option) { |
507 | 0 | printf(_("Your current terms are %s for the old state\n" |
508 | 0 | "and %s for the new state.\n"), |
509 | 0 | terms->term_good, terms->term_bad); |
510 | 0 | return 0; |
511 | 0 | } |
512 | 0 | if (one_of(option, "--term-good", "--term-old", NULL)) |
513 | 0 | printf("%s\n", terms->term_good); |
514 | 0 | else if (one_of(option, "--term-bad", "--term-new", NULL)) |
515 | 0 | printf("%s\n", terms->term_bad); |
516 | 0 | else |
517 | 0 | return error(_("invalid argument %s for 'git bisect terms'.\n" |
518 | 0 | "Supported options are: " |
519 | 0 | "--term-good|--term-old and " |
520 | 0 | "--term-bad|--term-new."), option); |
521 | | |
522 | 0 | return 0; |
523 | 0 | } |
524 | | |
525 | | static int bisect_append_log_quoted(const char **argv) |
526 | 0 | { |
527 | 0 | int res = 0; |
528 | 0 | FILE *fp = fopen(git_path_bisect_log(), "a"); |
529 | 0 | struct strbuf orig_args = STRBUF_INIT; |
530 | |
|
531 | 0 | if (!fp) |
532 | 0 | return -1; |
533 | | |
534 | 0 | if (fprintf(fp, "git bisect start") < 1) { |
535 | 0 | res = -1; |
536 | 0 | goto finish; |
537 | 0 | } |
538 | | |
539 | 0 | sq_quote_argv(&orig_args, argv); |
540 | 0 | if (fprintf(fp, "%s\n", orig_args.buf) < 1) |
541 | 0 | res = -1; |
542 | |
|
543 | 0 | finish: |
544 | 0 | fclose(fp); |
545 | 0 | strbuf_release(&orig_args); |
546 | 0 | return res; |
547 | 0 | } |
548 | | |
549 | | static int add_bisect_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, |
550 | | int flags UNUSED, void *cb) |
551 | 0 | { |
552 | 0 | struct add_bisect_ref_data *data = cb; |
553 | |
|
554 | 0 | add_pending_oid(data->revs, refname, oid, data->object_flags); |
555 | |
|
556 | 0 | return 0; |
557 | 0 | } |
558 | | |
559 | | static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs) |
560 | 0 | { |
561 | 0 | int res = 0; |
562 | 0 | struct add_bisect_ref_data cb = { revs }; |
563 | 0 | char *good = xstrfmt("%s-*", terms->term_good); |
564 | | |
565 | | /* |
566 | | * We cannot use terms->term_bad directly in |
567 | | * for_each_glob_ref_in() and we have to append a '*' to it, |
568 | | * otherwise for_each_glob_ref_in() will append '/' and '*'. |
569 | | */ |
570 | 0 | char *bad = xstrfmt("%s*", terms->term_bad); |
571 | | |
572 | | /* |
573 | | * It is important to reset the flags used by revision walks |
574 | | * as the previous call to bisect_next_all() in turn |
575 | | * sets up a revision walk. |
576 | | */ |
577 | 0 | reset_revision_walk(); |
578 | 0 | repo_init_revisions(the_repository, revs, NULL); |
579 | 0 | setup_revisions(0, NULL, revs, NULL); |
580 | 0 | refs_for_each_glob_ref_in(get_main_ref_store(the_repository), |
581 | 0 | add_bisect_ref, bad, "refs/bisect/", &cb); |
582 | 0 | cb.object_flags = UNINTERESTING; |
583 | 0 | refs_for_each_glob_ref_in(get_main_ref_store(the_repository), |
584 | 0 | add_bisect_ref, good, "refs/bisect/", &cb); |
585 | 0 | if (prepare_revision_walk(revs)) |
586 | 0 | res = error(_("revision walk setup failed")); |
587 | |
|
588 | 0 | free(good); |
589 | 0 | free(bad); |
590 | 0 | return res; |
591 | 0 | } |
592 | | |
593 | | static int bisect_skipped_commits(struct bisect_terms *terms) |
594 | 0 | { |
595 | 0 | int res; |
596 | 0 | FILE *fp = NULL; |
597 | 0 | struct rev_info revs; |
598 | 0 | struct commit *commit; |
599 | 0 | struct pretty_print_context pp = {0}; |
600 | 0 | struct strbuf commit_name = STRBUF_INIT; |
601 | |
|
602 | 0 | res = prepare_revs(terms, &revs); |
603 | 0 | if (res) |
604 | 0 | return res; |
605 | | |
606 | 0 | fp = fopen(git_path_bisect_log(), "a"); |
607 | 0 | if (!fp) |
608 | 0 | return error_errno(_("could not open '%s' for appending"), |
609 | 0 | git_path_bisect_log()); |
610 | | |
611 | 0 | if (fprintf(fp, "# only skipped commits left to test\n") < 0) |
612 | 0 | return error_errno(_("failed to write to '%s'"), git_path_bisect_log()); |
613 | | |
614 | 0 | while ((commit = get_revision(&revs)) != NULL) { |
615 | 0 | strbuf_reset(&commit_name); |
616 | 0 | repo_format_commit_message(the_repository, commit, "%s", |
617 | 0 | &commit_name, &pp); |
618 | 0 | fprintf(fp, "# possible first %s commit: [%s] %s\n", |
619 | 0 | terms->term_bad, oid_to_hex(&commit->object.oid), |
620 | 0 | commit_name.buf); |
621 | 0 | } |
622 | | |
623 | | /* |
624 | | * Reset the flags used by revision walks in case |
625 | | * there is another revision walk after this one. |
626 | | */ |
627 | 0 | reset_revision_walk(); |
628 | |
|
629 | 0 | strbuf_release(&commit_name); |
630 | 0 | release_revisions(&revs); |
631 | 0 | fclose(fp); |
632 | 0 | return 0; |
633 | 0 | } |
634 | | |
635 | | static int bisect_successful(struct bisect_terms *terms) |
636 | 0 | { |
637 | 0 | struct object_id oid; |
638 | 0 | struct commit *commit; |
639 | 0 | struct pretty_print_context pp = {0}; |
640 | 0 | struct strbuf commit_name = STRBUF_INIT; |
641 | 0 | char *bad_ref = xstrfmt("refs/bisect/%s",terms->term_bad); |
642 | 0 | int res; |
643 | |
|
644 | 0 | refs_read_ref(get_main_ref_store(the_repository), bad_ref, &oid); |
645 | 0 | commit = lookup_commit_reference_by_name(bad_ref); |
646 | 0 | repo_format_commit_message(the_repository, commit, "%s", &commit_name, |
647 | 0 | &pp); |
648 | |
|
649 | 0 | res = append_to_file(git_path_bisect_log(), "# first %s commit: [%s] %s\n", |
650 | 0 | terms->term_bad, oid_to_hex(&commit->object.oid), |
651 | 0 | commit_name.buf); |
652 | |
|
653 | 0 | strbuf_release(&commit_name); |
654 | 0 | free(bad_ref); |
655 | 0 | return res; |
656 | 0 | } |
657 | | |
658 | | static enum bisect_error bisect_next(struct bisect_terms *terms, const char *prefix) |
659 | 0 | { |
660 | 0 | enum bisect_error res; |
661 | |
|
662 | 0 | if (bisect_autostart(terms)) |
663 | 0 | return BISECT_FAILED; |
664 | | |
665 | 0 | if (bisect_next_check(terms, terms->term_good)) |
666 | 0 | return BISECT_FAILED; |
667 | | |
668 | | /* Perform all bisection computation */ |
669 | 0 | res = bisect_next_all(the_repository, prefix); |
670 | |
|
671 | 0 | if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) { |
672 | 0 | res = bisect_successful(terms); |
673 | 0 | return res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND; |
674 | 0 | } else if (res == BISECT_ONLY_SKIPPED_LEFT) { |
675 | 0 | res = bisect_skipped_commits(terms); |
676 | 0 | return res ? res : BISECT_ONLY_SKIPPED_LEFT; |
677 | 0 | } |
678 | 0 | return res; |
679 | 0 | } |
680 | | |
681 | | static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix) |
682 | 0 | { |
683 | 0 | if (bisect_next_check(terms, NULL)) { |
684 | 0 | bisect_print_status(terms); |
685 | 0 | return BISECT_OK; |
686 | 0 | } |
687 | | |
688 | 0 | return bisect_next(terms, prefix); |
689 | 0 | } |
690 | | |
691 | | static enum bisect_error bisect_start(struct bisect_terms *terms, int argc, |
692 | | const char **argv) |
693 | 0 | { |
694 | 0 | int no_checkout = 0; |
695 | 0 | int first_parent_only = 0; |
696 | 0 | int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0; |
697 | 0 | int flags, pathspec_pos; |
698 | 0 | enum bisect_error res = BISECT_OK; |
699 | 0 | struct string_list revs = STRING_LIST_INIT_DUP; |
700 | 0 | struct string_list states = STRING_LIST_INIT_DUP; |
701 | 0 | struct strbuf start_head = STRBUF_INIT; |
702 | 0 | struct strbuf bisect_names = STRBUF_INIT; |
703 | 0 | struct object_id head_oid; |
704 | 0 | struct object_id oid; |
705 | 0 | const char *head; |
706 | |
|
707 | 0 | if (is_bare_repository()) |
708 | 0 | no_checkout = 1; |
709 | | |
710 | | /* |
711 | | * Check for one bad and then some good revisions |
712 | | */ |
713 | 0 | for (i = 0; i < argc; i++) { |
714 | 0 | if (!strcmp(argv[i], "--")) { |
715 | 0 | has_double_dash = 1; |
716 | 0 | break; |
717 | 0 | } |
718 | 0 | } |
719 | |
|
720 | 0 | for (i = 0; i < argc; i++) { |
721 | 0 | const char *arg = argv[i]; |
722 | 0 | if (!strcmp(argv[i], "--")) { |
723 | 0 | break; |
724 | 0 | } else if (!strcmp(arg, "--no-checkout")) { |
725 | 0 | no_checkout = 1; |
726 | 0 | } else if (!strcmp(arg, "--first-parent")) { |
727 | 0 | first_parent_only = 1; |
728 | 0 | } else if (!strcmp(arg, "--term-good") || |
729 | 0 | !strcmp(arg, "--term-old")) { |
730 | 0 | i++; |
731 | 0 | if (argc <= i) |
732 | 0 | return error(_("'' is not a valid term")); |
733 | 0 | must_write_terms = 1; |
734 | 0 | free((void *) terms->term_good); |
735 | 0 | terms->term_good = xstrdup(argv[i]); |
736 | 0 | } else if (skip_prefix(arg, "--term-good=", &arg) || |
737 | 0 | skip_prefix(arg, "--term-old=", &arg)) { |
738 | 0 | must_write_terms = 1; |
739 | 0 | free((void *) terms->term_good); |
740 | 0 | terms->term_good = xstrdup(arg); |
741 | 0 | } else if (!strcmp(arg, "--term-bad") || |
742 | 0 | !strcmp(arg, "--term-new")) { |
743 | 0 | i++; |
744 | 0 | if (argc <= i) |
745 | 0 | return error(_("'' is not a valid term")); |
746 | 0 | must_write_terms = 1; |
747 | 0 | free((void *) terms->term_bad); |
748 | 0 | terms->term_bad = xstrdup(argv[i]); |
749 | 0 | } else if (skip_prefix(arg, "--term-bad=", &arg) || |
750 | 0 | skip_prefix(arg, "--term-new=", &arg)) { |
751 | 0 | must_write_terms = 1; |
752 | 0 | free((void *) terms->term_bad); |
753 | 0 | terms->term_bad = xstrdup(arg); |
754 | 0 | } else if (starts_with(arg, "--")) { |
755 | 0 | return error(_("unrecognized option: '%s'"), arg); |
756 | 0 | } else if (!get_oidf(&oid, "%s^{commit}", arg)) { |
757 | 0 | string_list_append(&revs, oid_to_hex(&oid)); |
758 | 0 | } else if (has_double_dash) { |
759 | 0 | die(_("'%s' does not appear to be a valid " |
760 | 0 | "revision"), arg); |
761 | 0 | } else { |
762 | 0 | break; |
763 | 0 | } |
764 | 0 | } |
765 | 0 | pathspec_pos = i; |
766 | | |
767 | | /* |
768 | | * The user ran "git bisect start <sha1> <sha1>", hence did not |
769 | | * explicitly specify the terms, but we are already starting to |
770 | | * set references named with the default terms, and won't be able |
771 | | * to change afterwards. |
772 | | */ |
773 | 0 | if (revs.nr) |
774 | 0 | must_write_terms = 1; |
775 | 0 | for (i = 0; i < revs.nr; i++) { |
776 | 0 | if (bad_seen) { |
777 | 0 | string_list_append(&states, terms->term_good); |
778 | 0 | } else { |
779 | 0 | bad_seen = 1; |
780 | 0 | string_list_append(&states, terms->term_bad); |
781 | 0 | } |
782 | 0 | } |
783 | | |
784 | | /* |
785 | | * Verify HEAD |
786 | | */ |
787 | 0 | head = refs_resolve_ref_unsafe(get_main_ref_store(the_repository), |
788 | 0 | "HEAD", 0, &head_oid, &flags); |
789 | 0 | if (!head) |
790 | 0 | if (repo_get_oid(the_repository, "HEAD", &head_oid)) |
791 | 0 | return error(_("bad HEAD - I need a HEAD")); |
792 | | |
793 | | /* |
794 | | * Check if we are bisecting |
795 | | */ |
796 | 0 | if (!is_empty_or_missing_file(git_path_bisect_start())) { |
797 | | /* Reset to the rev from where we started */ |
798 | 0 | strbuf_read_file(&start_head, git_path_bisect_start(), 0); |
799 | 0 | strbuf_trim(&start_head); |
800 | 0 | if (!no_checkout) { |
801 | 0 | struct child_process cmd = CHILD_PROCESS_INIT; |
802 | |
|
803 | 0 | cmd.git_cmd = 1; |
804 | 0 | strvec_pushl(&cmd.args, "checkout", start_head.buf, |
805 | 0 | "--", NULL); |
806 | 0 | if (run_command(&cmd)) { |
807 | 0 | res = error(_("checking out '%s' failed." |
808 | 0 | " Try 'git bisect start " |
809 | 0 | "<valid-branch>'."), |
810 | 0 | start_head.buf); |
811 | 0 | goto finish; |
812 | 0 | } |
813 | 0 | } |
814 | 0 | } else { |
815 | | /* Get the rev from where we start. */ |
816 | 0 | if (!repo_get_oid(the_repository, head, &head_oid) && |
817 | 0 | !starts_with(head, "refs/heads/")) { |
818 | 0 | strbuf_reset(&start_head); |
819 | 0 | strbuf_addstr(&start_head, oid_to_hex(&head_oid)); |
820 | 0 | } else if (!repo_get_oid(the_repository, head, &head_oid) && |
821 | 0 | skip_prefix(head, "refs/heads/", &head)) { |
822 | 0 | strbuf_addstr(&start_head, head); |
823 | 0 | } else { |
824 | 0 | return error(_("bad HEAD - strange symbolic ref")); |
825 | 0 | } |
826 | 0 | } |
827 | | |
828 | | /* |
829 | | * Get rid of any old bisect state. |
830 | | */ |
831 | 0 | if (bisect_clean_state()) |
832 | 0 | return BISECT_FAILED; |
833 | | |
834 | | /* |
835 | | * Write new start state |
836 | | */ |
837 | 0 | write_file(git_path_bisect_start(), "%s\n", start_head.buf); |
838 | |
|
839 | 0 | if (first_parent_only) |
840 | 0 | write_file(git_path_bisect_first_parent(), "\n"); |
841 | |
|
842 | 0 | if (no_checkout) { |
843 | 0 | if (repo_get_oid(the_repository, start_head.buf, &oid) < 0) { |
844 | 0 | res = error(_("invalid ref: '%s'"), start_head.buf); |
845 | 0 | goto finish; |
846 | 0 | } |
847 | 0 | if (refs_update_ref(get_main_ref_store(the_repository), NULL, "BISECT_HEAD", &oid, NULL, 0, |
848 | 0 | UPDATE_REFS_MSG_ON_ERR)) { |
849 | 0 | res = BISECT_FAILED; |
850 | 0 | goto finish; |
851 | 0 | } |
852 | 0 | } |
853 | | |
854 | 0 | if (pathspec_pos < argc - 1) |
855 | 0 | sq_quote_argv(&bisect_names, argv + pathspec_pos); |
856 | 0 | write_file(git_path_bisect_names(), "%s\n", bisect_names.buf); |
857 | |
|
858 | 0 | for (i = 0; i < states.nr; i++) |
859 | 0 | if (bisect_write(states.items[i].string, |
860 | 0 | revs.items[i].string, terms, 1)) { |
861 | 0 | res = BISECT_FAILED; |
862 | 0 | goto finish; |
863 | 0 | } |
864 | | |
865 | 0 | if (must_write_terms && write_terms(terms->term_bad, |
866 | 0 | terms->term_good)) { |
867 | 0 | res = BISECT_FAILED; |
868 | 0 | goto finish; |
869 | 0 | } |
870 | | |
871 | 0 | res = bisect_append_log_quoted(argv); |
872 | 0 | if (res) |
873 | 0 | res = BISECT_FAILED; |
874 | |
|
875 | 0 | finish: |
876 | 0 | string_list_clear(&revs, 0); |
877 | 0 | string_list_clear(&states, 0); |
878 | 0 | strbuf_release(&start_head); |
879 | 0 | strbuf_release(&bisect_names); |
880 | 0 | if (res) |
881 | 0 | return res; |
882 | | |
883 | 0 | res = bisect_auto_next(terms, NULL); |
884 | 0 | if (!is_bisect_success(res)) |
885 | 0 | bisect_clean_state(); |
886 | 0 | return res; |
887 | 0 | } |
888 | | |
889 | | static inline int file_is_not_empty(const char *path) |
890 | 0 | { |
891 | 0 | return !is_empty_or_missing_file(path); |
892 | 0 | } |
893 | | |
894 | | static int bisect_autostart(struct bisect_terms *terms) |
895 | 0 | { |
896 | 0 | int res; |
897 | 0 | const char *yesno; |
898 | |
|
899 | 0 | if (file_is_not_empty(git_path_bisect_start())) |
900 | 0 | return 0; |
901 | | |
902 | 0 | fprintf_ln(stderr, _("You need to start by \"git bisect " |
903 | 0 | "start\"\n")); |
904 | |
|
905 | 0 | if (!isatty(STDIN_FILENO)) |
906 | 0 | return -1; |
907 | | |
908 | | /* |
909 | | * TRANSLATORS: Make sure to include [Y] and [n] in your |
910 | | * translation. The program will only accept English input |
911 | | * at this point. |
912 | | */ |
913 | 0 | yesno = git_prompt(_("Do you want me to do it for you " |
914 | 0 | "[Y/n]? "), PROMPT_ECHO); |
915 | 0 | res = tolower(*yesno) == 'n' ? |
916 | 0 | -1 : bisect_start(terms, 0, empty_strvec); |
917 | |
|
918 | 0 | return res; |
919 | 0 | } |
920 | | |
921 | | static enum bisect_error bisect_state(struct bisect_terms *terms, int argc, |
922 | | const char **argv) |
923 | 0 | { |
924 | 0 | const char *state; |
925 | 0 | int i, verify_expected = 1; |
926 | 0 | struct object_id oid, expected; |
927 | 0 | struct oid_array revs = OID_ARRAY_INIT; |
928 | |
|
929 | 0 | if (!argc) |
930 | 0 | return error(_("Please call `--bisect-state` with at least one argument")); |
931 | | |
932 | 0 | if (bisect_autostart(terms)) |
933 | 0 | return BISECT_FAILED; |
934 | | |
935 | 0 | state = argv[0]; |
936 | 0 | if (check_and_set_terms(terms, state) || |
937 | 0 | !one_of(state, terms->term_good, terms->term_bad, "skip", NULL)) |
938 | 0 | return BISECT_FAILED; |
939 | | |
940 | 0 | argv++; |
941 | 0 | argc--; |
942 | 0 | if (argc > 1 && !strcmp(state, terms->term_bad)) |
943 | 0 | return error(_("'git bisect %s' can take only one argument."), terms->term_bad); |
944 | | |
945 | 0 | if (argc == 0) { |
946 | 0 | const char *head = "BISECT_HEAD"; |
947 | 0 | enum get_oid_result res_head = repo_get_oid(the_repository, |
948 | 0 | head, &oid); |
949 | |
|
950 | 0 | if (res_head == MISSING_OBJECT) { |
951 | 0 | head = "HEAD"; |
952 | 0 | res_head = repo_get_oid(the_repository, head, &oid); |
953 | 0 | } |
954 | |
|
955 | 0 | if (res_head) |
956 | 0 | error(_("Bad rev input: %s"), head); |
957 | 0 | oid_array_append(&revs, &oid); |
958 | 0 | } |
959 | | |
960 | | /* |
961 | | * All input revs must be checked before executing bisect_write() |
962 | | * to discard junk revs. |
963 | | */ |
964 | |
|
965 | 0 | for (; argc; argc--, argv++) { |
966 | 0 | struct commit *commit; |
967 | |
|
968 | 0 | if (repo_get_oid(the_repository, *argv, &oid)){ |
969 | 0 | error(_("Bad rev input: %s"), *argv); |
970 | 0 | oid_array_clear(&revs); |
971 | 0 | return BISECT_FAILED; |
972 | 0 | } |
973 | | |
974 | 0 | commit = lookup_commit_reference(the_repository, &oid); |
975 | 0 | if (!commit) |
976 | 0 | die(_("Bad rev input (not a commit): %s"), *argv); |
977 | | |
978 | 0 | oid_array_append(&revs, &commit->object.oid); |
979 | 0 | } |
980 | | |
981 | 0 | if (refs_read_ref(get_main_ref_store(the_repository), "BISECT_EXPECTED_REV", &expected)) |
982 | 0 | verify_expected = 0; /* Ignore invalid file contents */ |
983 | |
|
984 | 0 | for (i = 0; i < revs.nr; i++) { |
985 | 0 | if (bisect_write(state, oid_to_hex(&revs.oid[i]), terms, 0)) { |
986 | 0 | oid_array_clear(&revs); |
987 | 0 | return BISECT_FAILED; |
988 | 0 | } |
989 | 0 | if (verify_expected && !oideq(&revs.oid[i], &expected)) { |
990 | 0 | unlink_or_warn(git_path_bisect_ancestors_ok()); |
991 | 0 | refs_delete_ref(get_main_ref_store(the_repository), |
992 | 0 | NULL, "BISECT_EXPECTED_REV", NULL, |
993 | 0 | REF_NO_DEREF); |
994 | 0 | verify_expected = 0; |
995 | 0 | } |
996 | 0 | } |
997 | | |
998 | 0 | oid_array_clear(&revs); |
999 | 0 | return bisect_auto_next(terms, NULL); |
1000 | 0 | } |
1001 | | |
1002 | | static enum bisect_error bisect_log(void) |
1003 | 0 | { |
1004 | 0 | int fd, status; |
1005 | 0 | const char* filename = git_path_bisect_log(); |
1006 | |
|
1007 | 0 | if (is_empty_or_missing_file(filename)) |
1008 | 0 | return error(_("We are not bisecting.")); |
1009 | | |
1010 | 0 | fd = open(filename, O_RDONLY); |
1011 | 0 | if (fd < 0) |
1012 | 0 | return BISECT_FAILED; |
1013 | | |
1014 | 0 | status = copy_fd(fd, STDOUT_FILENO); |
1015 | 0 | close(fd); |
1016 | 0 | return status ? BISECT_FAILED : BISECT_OK; |
1017 | 0 | } |
1018 | | |
1019 | | static int process_replay_line(struct bisect_terms *terms, struct strbuf *line) |
1020 | 0 | { |
1021 | 0 | const char *p = line->buf + strspn(line->buf, " \t"); |
1022 | 0 | char *word_end, *rev; |
1023 | |
|
1024 | 0 | if ((!skip_prefix(p, "git bisect", &p) && |
1025 | 0 | !skip_prefix(p, "git-bisect", &p)) || !isspace(*p)) |
1026 | 0 | return 0; |
1027 | 0 | p += strspn(p, " \t"); |
1028 | |
|
1029 | 0 | word_end = (char *)p + strcspn(p, " \t"); |
1030 | 0 | rev = word_end + strspn(word_end, " \t"); |
1031 | 0 | *word_end = '\0'; /* NUL-terminate the word */ |
1032 | |
|
1033 | 0 | get_terms(terms); |
1034 | 0 | if (check_and_set_terms(terms, p)) |
1035 | 0 | return -1; |
1036 | | |
1037 | 0 | if (!strcmp(p, "start")) { |
1038 | 0 | struct strvec argv = STRVEC_INIT; |
1039 | 0 | int res; |
1040 | 0 | sq_dequote_to_strvec(rev, &argv); |
1041 | 0 | res = bisect_start(terms, argv.nr, argv.v); |
1042 | 0 | strvec_clear(&argv); |
1043 | 0 | return res; |
1044 | 0 | } |
1045 | | |
1046 | 0 | if (one_of(p, terms->term_good, |
1047 | 0 | terms->term_bad, "skip", NULL)) |
1048 | 0 | return bisect_write(p, rev, terms, 0); |
1049 | | |
1050 | 0 | if (!strcmp(p, "terms")) { |
1051 | 0 | struct strvec argv = STRVEC_INIT; |
1052 | 0 | int res; |
1053 | 0 | sq_dequote_to_strvec(rev, &argv); |
1054 | 0 | res = bisect_terms(terms, argv.nr == 1 ? argv.v[0] : NULL); |
1055 | 0 | strvec_clear(&argv); |
1056 | 0 | return res; |
1057 | 0 | } |
1058 | 0 | error(_("'%s'?? what are you talking about?"), p); |
1059 | |
|
1060 | 0 | return -1; |
1061 | 0 | } |
1062 | | |
1063 | | static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *filename) |
1064 | 0 | { |
1065 | 0 | FILE *fp = NULL; |
1066 | 0 | enum bisect_error res = BISECT_OK; |
1067 | 0 | struct strbuf line = STRBUF_INIT; |
1068 | |
|
1069 | 0 | if (is_empty_or_missing_file(filename)) |
1070 | 0 | return error(_("cannot read file '%s' for replaying"), filename); |
1071 | | |
1072 | 0 | if (bisect_reset(NULL)) |
1073 | 0 | return BISECT_FAILED; |
1074 | | |
1075 | 0 | fp = fopen(filename, "r"); |
1076 | 0 | if (!fp) |
1077 | 0 | return BISECT_FAILED; |
1078 | | |
1079 | 0 | while ((strbuf_getline(&line, fp) != EOF) && !res) |
1080 | 0 | res = process_replay_line(terms, &line); |
1081 | |
|
1082 | 0 | strbuf_release(&line); |
1083 | 0 | fclose(fp); |
1084 | |
|
1085 | 0 | if (res) |
1086 | 0 | return BISECT_FAILED; |
1087 | | |
1088 | 0 | return bisect_auto_next(terms, NULL); |
1089 | 0 | } |
1090 | | |
1091 | | static enum bisect_error bisect_skip(struct bisect_terms *terms, int argc, |
1092 | | const char **argv) |
1093 | 0 | { |
1094 | 0 | int i; |
1095 | 0 | enum bisect_error res; |
1096 | 0 | struct strvec argv_state = STRVEC_INIT; |
1097 | |
|
1098 | 0 | strvec_push(&argv_state, "skip"); |
1099 | |
|
1100 | 0 | for (i = 0; i < argc; i++) { |
1101 | 0 | const char *dotdot = strstr(argv[i], ".."); |
1102 | |
|
1103 | 0 | if (dotdot) { |
1104 | 0 | struct rev_info revs; |
1105 | 0 | struct commit *commit; |
1106 | |
|
1107 | 0 | repo_init_revisions(the_repository, &revs, NULL); |
1108 | 0 | setup_revisions(2, argv + i - 1, &revs, NULL); |
1109 | |
|
1110 | 0 | if (prepare_revision_walk(&revs)) |
1111 | 0 | die(_("revision walk setup failed")); |
1112 | 0 | while ((commit = get_revision(&revs)) != NULL) |
1113 | 0 | strvec_push(&argv_state, |
1114 | 0 | oid_to_hex(&commit->object.oid)); |
1115 | |
|
1116 | 0 | reset_revision_walk(); |
1117 | 0 | release_revisions(&revs); |
1118 | 0 | } else { |
1119 | 0 | strvec_push(&argv_state, argv[i]); |
1120 | 0 | } |
1121 | 0 | } |
1122 | 0 | res = bisect_state(terms, argv_state.nr, argv_state.v); |
1123 | |
|
1124 | 0 | strvec_clear(&argv_state); |
1125 | 0 | return res; |
1126 | 0 | } |
1127 | | |
1128 | | static int bisect_visualize(struct bisect_terms *terms, int argc, |
1129 | | const char **argv) |
1130 | 0 | { |
1131 | 0 | struct child_process cmd = CHILD_PROCESS_INIT; |
1132 | 0 | struct strbuf sb = STRBUF_INIT; |
1133 | |
|
1134 | 0 | if (bisect_next_check(terms, NULL) != 0) |
1135 | 0 | return BISECT_FAILED; |
1136 | | |
1137 | 0 | cmd.no_stdin = 1; |
1138 | 0 | if (!argc) { |
1139 | 0 | if ((getenv("DISPLAY") || getenv("SESSIONNAME") || getenv("MSYSTEM") || |
1140 | 0 | getenv("SECURITYSESSIONID")) && exists_in_PATH("gitk")) { |
1141 | 0 | strvec_push(&cmd.args, "gitk"); |
1142 | 0 | } else { |
1143 | 0 | strvec_push(&cmd.args, "log"); |
1144 | 0 | cmd.git_cmd = 1; |
1145 | 0 | } |
1146 | 0 | } else { |
1147 | 0 | if (argv[0][0] == '-') { |
1148 | 0 | strvec_push(&cmd.args, "log"); |
1149 | 0 | cmd.git_cmd = 1; |
1150 | 0 | } else if (strcmp(argv[0], "tig") && !starts_with(argv[0], "git")) |
1151 | 0 | cmd.git_cmd = 1; |
1152 | |
|
1153 | 0 | strvec_pushv(&cmd.args, argv); |
1154 | 0 | } |
1155 | |
|
1156 | 0 | strvec_pushl(&cmd.args, "--bisect", "--", NULL); |
1157 | |
|
1158 | 0 | strbuf_read_file(&sb, git_path_bisect_names(), 0); |
1159 | 0 | sq_dequote_to_strvec(sb.buf, &cmd.args); |
1160 | 0 | strbuf_release(&sb); |
1161 | |
|
1162 | 0 | return run_command(&cmd); |
1163 | 0 | } |
1164 | | |
1165 | | static int get_first_good(const char *refname UNUSED, |
1166 | | const char *referent UNUSED, |
1167 | | const struct object_id *oid, |
1168 | | int flag UNUSED, void *cb_data) |
1169 | 0 | { |
1170 | 0 | oidcpy(cb_data, oid); |
1171 | 0 | return 1; |
1172 | 0 | } |
1173 | | |
1174 | | static int do_bisect_run(const char *command) |
1175 | 0 | { |
1176 | 0 | struct child_process cmd = CHILD_PROCESS_INIT; |
1177 | |
|
1178 | 0 | printf(_("running %s\n"), command); |
1179 | 0 | cmd.use_shell = 1; |
1180 | 0 | strvec_push(&cmd.args, command); |
1181 | 0 | return run_command(&cmd); |
1182 | 0 | } |
1183 | | |
1184 | | static int verify_good(const struct bisect_terms *terms, const char *command) |
1185 | 0 | { |
1186 | 0 | int rc; |
1187 | 0 | enum bisect_error res; |
1188 | 0 | struct object_id good_rev; |
1189 | 0 | struct object_id current_rev; |
1190 | 0 | char *good_glob = xstrfmt("%s-*", terms->term_good); |
1191 | 0 | int no_checkout = refs_ref_exists(get_main_ref_store(the_repository), |
1192 | 0 | "BISECT_HEAD"); |
1193 | |
|
1194 | 0 | refs_for_each_glob_ref_in(get_main_ref_store(the_repository), |
1195 | 0 | get_first_good, good_glob, "refs/bisect/", |
1196 | 0 | &good_rev); |
1197 | 0 | free(good_glob); |
1198 | |
|
1199 | 0 | if (refs_read_ref(get_main_ref_store(the_repository), no_checkout ? "BISECT_HEAD" : "HEAD", ¤t_rev)) |
1200 | 0 | return -1; |
1201 | | |
1202 | 0 | res = bisect_checkout(&good_rev, no_checkout); |
1203 | 0 | if (res != BISECT_OK) |
1204 | 0 | return -1; |
1205 | | |
1206 | 0 | rc = do_bisect_run(command); |
1207 | |
|
1208 | 0 | res = bisect_checkout(¤t_rev, no_checkout); |
1209 | 0 | if (res != BISECT_OK) |
1210 | 0 | return -1; |
1211 | | |
1212 | 0 | return rc; |
1213 | 0 | } |
1214 | | |
1215 | | static int bisect_run(struct bisect_terms *terms, int argc, const char **argv) |
1216 | 0 | { |
1217 | 0 | int res = BISECT_OK; |
1218 | 0 | struct strbuf command = STRBUF_INIT; |
1219 | 0 | const char *new_state; |
1220 | 0 | int temporary_stdout_fd, saved_stdout; |
1221 | 0 | int is_first_run = 1; |
1222 | |
|
1223 | 0 | if (bisect_next_check(terms, NULL)) |
1224 | 0 | return BISECT_FAILED; |
1225 | | |
1226 | 0 | if (!argc) { |
1227 | 0 | error(_("bisect run failed: no command provided.")); |
1228 | 0 | return BISECT_FAILED; |
1229 | 0 | } |
1230 | | |
1231 | 0 | sq_quote_argv(&command, argv); |
1232 | 0 | strbuf_ltrim(&command); |
1233 | 0 | while (1) { |
1234 | 0 | res = do_bisect_run(command.buf); |
1235 | | |
1236 | | /* |
1237 | | * Exit code 126 and 127 can either come from the shell |
1238 | | * if it was unable to execute or even find the script, |
1239 | | * or from the script itself. Check with a known-good |
1240 | | * revision to avoid trashing the bisect run due to a |
1241 | | * missing or non-executable script. |
1242 | | */ |
1243 | 0 | if (is_first_run && (res == 126 || res == 127)) { |
1244 | 0 | int rc = verify_good(terms, command.buf); |
1245 | 0 | is_first_run = 0; |
1246 | 0 | if (rc < 0 || 128 <= rc) { |
1247 | 0 | error(_("unable to verify %s on good" |
1248 | 0 | " revision"), command.buf); |
1249 | 0 | res = BISECT_FAILED; |
1250 | 0 | break; |
1251 | 0 | } |
1252 | 0 | if (rc == res) { |
1253 | 0 | error(_("bogus exit code %d for good revision"), |
1254 | 0 | rc); |
1255 | 0 | res = BISECT_FAILED; |
1256 | 0 | break; |
1257 | 0 | } |
1258 | 0 | } |
1259 | | |
1260 | 0 | if (res < 0 || 128 <= res) { |
1261 | 0 | error(_("bisect run failed: exit code %d from" |
1262 | 0 | " %s is < 0 or >= 128"), res, command.buf); |
1263 | 0 | break; |
1264 | 0 | } |
1265 | | |
1266 | 0 | if (res == 125) |
1267 | 0 | new_state = "skip"; |
1268 | 0 | else if (!res) |
1269 | 0 | new_state = terms->term_good; |
1270 | 0 | else |
1271 | 0 | new_state = terms->term_bad; |
1272 | |
|
1273 | 0 | temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_WRONLY | O_TRUNC, 0666); |
1274 | |
|
1275 | 0 | if (temporary_stdout_fd < 0) { |
1276 | 0 | res = error_errno(_("cannot open file '%s' for writing"), git_path_bisect_run()); |
1277 | 0 | break; |
1278 | 0 | } |
1279 | | |
1280 | 0 | fflush(stdout); |
1281 | 0 | saved_stdout = dup(1); |
1282 | 0 | dup2(temporary_stdout_fd, 1); |
1283 | |
|
1284 | 0 | res = bisect_state(terms, 1, &new_state); |
1285 | |
|
1286 | 0 | fflush(stdout); |
1287 | 0 | dup2(saved_stdout, 1); |
1288 | 0 | close(saved_stdout); |
1289 | 0 | close(temporary_stdout_fd); |
1290 | |
|
1291 | 0 | print_file_to_stdout(git_path_bisect_run()); |
1292 | |
|
1293 | 0 | if (res == BISECT_ONLY_SKIPPED_LEFT) |
1294 | 0 | error(_("bisect run cannot continue any more")); |
1295 | 0 | else if (res == BISECT_INTERNAL_SUCCESS_MERGE_BASE) { |
1296 | 0 | puts(_("bisect run success")); |
1297 | 0 | res = BISECT_OK; |
1298 | 0 | } else if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) { |
1299 | 0 | puts(_("bisect found first bad commit")); |
1300 | 0 | res = BISECT_OK; |
1301 | 0 | } else if (res) { |
1302 | 0 | error(_("bisect run failed: 'git bisect %s'" |
1303 | 0 | " exited with error code %d"), new_state, res); |
1304 | 0 | } else { |
1305 | 0 | continue; |
1306 | 0 | } |
1307 | 0 | break; |
1308 | 0 | } |
1309 | |
|
1310 | 0 | strbuf_release(&command); |
1311 | 0 | return res; |
1312 | 0 | } |
1313 | | |
1314 | | static int cmd_bisect__reset(int argc, const char **argv, const char *prefix UNUSED) |
1315 | 0 | { |
1316 | 0 | if (argc > 1) |
1317 | 0 | return error(_("'%s' requires either no argument or a commit"), |
1318 | 0 | "git bisect reset"); |
1319 | 0 | return bisect_reset(argc ? argv[0] : NULL); |
1320 | 0 | } |
1321 | | |
1322 | | static int cmd_bisect__terms(int argc, const char **argv, const char *prefix UNUSED) |
1323 | 0 | { |
1324 | 0 | int res; |
1325 | 0 | struct bisect_terms terms = { 0 }; |
1326 | |
|
1327 | 0 | if (argc > 1) |
1328 | 0 | return error(_("'%s' requires 0 or 1 argument"), |
1329 | 0 | "git bisect terms"); |
1330 | 0 | res = bisect_terms(&terms, argc == 1 ? argv[0] : NULL); |
1331 | 0 | free_terms(&terms); |
1332 | 0 | return res; |
1333 | 0 | } |
1334 | | |
1335 | | static int cmd_bisect__start(int argc, const char **argv, const char *prefix UNUSED) |
1336 | 0 | { |
1337 | 0 | int res; |
1338 | 0 | struct bisect_terms terms = { 0 }; |
1339 | |
|
1340 | 0 | set_terms(&terms, "bad", "good"); |
1341 | 0 | res = bisect_start(&terms, argc, argv); |
1342 | 0 | free_terms(&terms); |
1343 | 0 | return res; |
1344 | 0 | } |
1345 | | |
1346 | | static int cmd_bisect__next(int argc, const char **argv UNUSED, const char *prefix) |
1347 | 0 | { |
1348 | 0 | int res; |
1349 | 0 | struct bisect_terms terms = { 0 }; |
1350 | |
|
1351 | 0 | if (argc) |
1352 | 0 | return error(_("'%s' requires 0 arguments"), |
1353 | 0 | "git bisect next"); |
1354 | 0 | get_terms(&terms); |
1355 | 0 | res = bisect_next(&terms, prefix); |
1356 | 0 | free_terms(&terms); |
1357 | 0 | return res; |
1358 | 0 | } |
1359 | | |
1360 | | static int cmd_bisect__log(int argc UNUSED, const char **argv UNUSED, const char *prefix UNUSED) |
1361 | 0 | { |
1362 | 0 | return bisect_log(); |
1363 | 0 | } |
1364 | | |
1365 | | static int cmd_bisect__replay(int argc, const char **argv, const char *prefix UNUSED) |
1366 | 0 | { |
1367 | 0 | int res; |
1368 | 0 | struct bisect_terms terms = { 0 }; |
1369 | |
|
1370 | 0 | if (argc != 1) |
1371 | 0 | return error(_("no logfile given")); |
1372 | 0 | set_terms(&terms, "bad", "good"); |
1373 | 0 | res = bisect_replay(&terms, argv[0]); |
1374 | 0 | free_terms(&terms); |
1375 | 0 | return res; |
1376 | 0 | } |
1377 | | |
1378 | | static int cmd_bisect__skip(int argc, const char **argv, const char *prefix UNUSED) |
1379 | 0 | { |
1380 | 0 | int res; |
1381 | 0 | struct bisect_terms terms = { 0 }; |
1382 | |
|
1383 | 0 | set_terms(&terms, "bad", "good"); |
1384 | 0 | get_terms(&terms); |
1385 | 0 | res = bisect_skip(&terms, argc, argv); |
1386 | 0 | free_terms(&terms); |
1387 | 0 | return res; |
1388 | 0 | } |
1389 | | |
1390 | | static int cmd_bisect__visualize(int argc, const char **argv, const char *prefix UNUSED) |
1391 | 0 | { |
1392 | 0 | int res; |
1393 | 0 | struct bisect_terms terms = { 0 }; |
1394 | |
|
1395 | 0 | get_terms(&terms); |
1396 | 0 | res = bisect_visualize(&terms, argc, argv); |
1397 | 0 | free_terms(&terms); |
1398 | 0 | return res; |
1399 | 0 | } |
1400 | | |
1401 | | static int cmd_bisect__run(int argc, const char **argv, const char *prefix UNUSED) |
1402 | 0 | { |
1403 | 0 | int res; |
1404 | 0 | struct bisect_terms terms = { 0 }; |
1405 | |
|
1406 | 0 | if (!argc) |
1407 | 0 | return error(_("'%s' failed: no command provided."), "git bisect run"); |
1408 | 0 | get_terms(&terms); |
1409 | 0 | res = bisect_run(&terms, argc, argv); |
1410 | 0 | free_terms(&terms); |
1411 | 0 | return res; |
1412 | 0 | } |
1413 | | |
1414 | | int cmd_bisect(int argc, const char **argv, const char *prefix) |
1415 | 0 | { |
1416 | 0 | int res = 0; |
1417 | 0 | parse_opt_subcommand_fn *fn = NULL; |
1418 | 0 | struct option options[] = { |
1419 | 0 | OPT_SUBCOMMAND("reset", &fn, cmd_bisect__reset), |
1420 | 0 | OPT_SUBCOMMAND("terms", &fn, cmd_bisect__terms), |
1421 | 0 | OPT_SUBCOMMAND("start", &fn, cmd_bisect__start), |
1422 | 0 | OPT_SUBCOMMAND("next", &fn, cmd_bisect__next), |
1423 | 0 | OPT_SUBCOMMAND("log", &fn, cmd_bisect__log), |
1424 | 0 | OPT_SUBCOMMAND("replay", &fn, cmd_bisect__replay), |
1425 | 0 | OPT_SUBCOMMAND("skip", &fn, cmd_bisect__skip), |
1426 | 0 | OPT_SUBCOMMAND("visualize", &fn, cmd_bisect__visualize), |
1427 | 0 | OPT_SUBCOMMAND("view", &fn, cmd_bisect__visualize), |
1428 | 0 | OPT_SUBCOMMAND("run", &fn, cmd_bisect__run), |
1429 | 0 | OPT_END() |
1430 | 0 | }; |
1431 | 0 | argc = parse_options(argc, argv, prefix, options, git_bisect_usage, |
1432 | 0 | PARSE_OPT_SUBCOMMAND_OPTIONAL); |
1433 | |
|
1434 | 0 | if (!fn) { |
1435 | 0 | struct bisect_terms terms = { 0 }; |
1436 | |
|
1437 | 0 | if (!argc) |
1438 | 0 | usage_msg_opt(_("need a command"), git_bisect_usage, options); |
1439 | | |
1440 | 0 | set_terms(&terms, "bad", "good"); |
1441 | 0 | get_terms(&terms); |
1442 | 0 | if (check_and_set_terms(&terms, argv[0])) |
1443 | 0 | usage_msg_optf(_("unknown command: '%s'"), git_bisect_usage, |
1444 | 0 | options, argv[0]); |
1445 | 0 | res = bisect_state(&terms, argc, argv); |
1446 | 0 | free_terms(&terms); |
1447 | 0 | } else { |
1448 | 0 | argc--; |
1449 | 0 | argv++; |
1450 | 0 | res = fn(argc, argv, prefix); |
1451 | 0 | } |
1452 | | |
1453 | 0 | return is_bisect_success(res) ? 0 : -res; |
1454 | 0 | } |