]>
Commit | Line | Data |
---|---|---|
f6aa7ecc JS |
1 | #include "cache.h" |
2 | #include "add-interactive.h" | |
3 | #include "strbuf.h" | |
4 | #include "run-command.h" | |
5 | #include "argv-array.h" | |
6 | #include "pathspec.h" | |
e3bd11b4 | 7 | #include "color.h" |
25ea47af JS |
8 | #include "diff.h" |
9 | ||
10 | struct hunk_header { | |
11 | unsigned long old_offset, old_count, new_offset, new_count; | |
12 | /* | |
13 | * Start/end offsets to the extra text after the second `@@` in the | |
14 | * hunk header, e.g. the function signature. This is expected to | |
15 | * include the newline. | |
16 | */ | |
17 | size_t extra_start, extra_end, colored_extra_start, colored_extra_end; | |
18 | }; | |
f6aa7ecc JS |
19 | |
20 | struct hunk { | |
e3bd11b4 | 21 | size_t start, end, colored_start, colored_end; |
f6aa7ecc | 22 | enum { UNDECIDED_HUNK = 0, SKIP_HUNK, USE_HUNK } use; |
25ea47af | 23 | struct hunk_header header; |
f6aa7ecc JS |
24 | }; |
25 | ||
26 | struct add_p_state { | |
25ea47af | 27 | struct add_i_state s; |
f6aa7ecc JS |
28 | struct strbuf answer, buf; |
29 | ||
30 | /* parsed diff */ | |
e3bd11b4 | 31 | struct strbuf plain, colored; |
f6aa7ecc JS |
32 | struct hunk head; |
33 | struct hunk *hunk; | |
34 | size_t hunk_nr, hunk_alloc; | |
35 | }; | |
36 | ||
7584dd3c JS |
37 | static void err(struct add_p_state *s, const char *fmt, ...) |
38 | { | |
39 | va_list args; | |
40 | ||
41 | va_start(args, fmt); | |
42 | fputs(s->s.error_color, stderr); | |
43 | vfprintf(stderr, fmt, args); | |
44 | fputs(s->s.reset_color, stderr); | |
45 | fputc('\n', stderr); | |
46 | va_end(args); | |
47 | } | |
48 | ||
f6aa7ecc JS |
49 | static void setup_child_process(struct add_p_state *s, |
50 | struct child_process *cp, ...) | |
51 | { | |
52 | va_list ap; | |
53 | const char *arg; | |
54 | ||
55 | va_start(ap, cp); | |
56 | while ((arg = va_arg(ap, const char *))) | |
57 | argv_array_push(&cp->args, arg); | |
58 | va_end(ap); | |
59 | ||
60 | cp->git_cmd = 1; | |
61 | argv_array_pushf(&cp->env_array, | |
25ea47af JS |
62 | INDEX_ENVIRONMENT "=%s", s->s.r->index_file); |
63 | } | |
64 | ||
65 | static int parse_range(const char **p, | |
66 | unsigned long *offset, unsigned long *count) | |
67 | { | |
68 | char *pend; | |
69 | ||
70 | *offset = strtoul(*p, &pend, 10); | |
71 | if (pend == *p) | |
72 | return -1; | |
73 | if (*pend != ',') { | |
74 | *count = 1; | |
75 | *p = pend; | |
76 | return 0; | |
77 | } | |
78 | *count = strtoul(pend + 1, (char **)p, 10); | |
79 | return *p == pend + 1 ? -1 : 0; | |
80 | } | |
81 | ||
82 | static int parse_hunk_header(struct add_p_state *s, struct hunk *hunk) | |
83 | { | |
84 | struct hunk_header *header = &hunk->header; | |
85 | const char *line = s->plain.buf + hunk->start, *p = line; | |
86 | char *eol = memchr(p, '\n', s->plain.len - hunk->start); | |
87 | ||
88 | if (!eol) | |
89 | eol = s->plain.buf + s->plain.len; | |
90 | ||
91 | if (!skip_prefix(p, "@@ -", &p) || | |
92 | parse_range(&p, &header->old_offset, &header->old_count) < 0 || | |
93 | !skip_prefix(p, " +", &p) || | |
94 | parse_range(&p, &header->new_offset, &header->new_count) < 0 || | |
95 | !skip_prefix(p, " @@", &p)) | |
96 | return error(_("could not parse hunk header '%.*s'"), | |
97 | (int)(eol - line), line); | |
98 | ||
99 | hunk->start = eol - s->plain.buf + (*eol == '\n'); | |
100 | header->extra_start = p - s->plain.buf; | |
101 | header->extra_end = hunk->start; | |
102 | ||
103 | if (!s->colored.len) { | |
104 | header->colored_extra_start = header->colored_extra_end = 0; | |
105 | return 0; | |
106 | } | |
107 | ||
108 | /* Now find the extra text in the colored diff */ | |
109 | line = s->colored.buf + hunk->colored_start; | |
110 | eol = memchr(line, '\n', s->colored.len - hunk->colored_start); | |
111 | if (!eol) | |
112 | eol = s->colored.buf + s->colored.len; | |
113 | p = memmem(line, eol - line, "@@ -", 4); | |
114 | if (!p) | |
115 | return error(_("could not parse colored hunk header '%.*s'"), | |
116 | (int)(eol - line), line); | |
117 | p = memmem(p + 4, eol - p - 4, " @@", 3); | |
118 | if (!p) | |
119 | return error(_("could not parse colored hunk header '%.*s'"), | |
120 | (int)(eol - line), line); | |
121 | hunk->colored_start = eol - s->colored.buf + (*eol == '\n'); | |
122 | header->colored_extra_start = p + 3 - s->colored.buf; | |
123 | header->colored_extra_end = hunk->colored_start; | |
124 | ||
125 | return 0; | |
f6aa7ecc JS |
126 | } |
127 | ||
128 | static int parse_diff(struct add_p_state *s, const struct pathspec *ps) | |
129 | { | |
e3bd11b4 JS |
130 | struct argv_array args = ARGV_ARRAY_INIT; |
131 | struct strbuf *plain = &s->plain, *colored = NULL; | |
f6aa7ecc | 132 | struct child_process cp = CHILD_PROCESS_INIT; |
e3bd11b4 JS |
133 | char *p, *pend, *colored_p = NULL, *colored_pend = NULL; |
134 | size_t i, color_arg_index; | |
f6aa7ecc JS |
135 | struct hunk *hunk = NULL; |
136 | int res; | |
137 | ||
138 | /* Use `--no-color` explicitly, just in case `diff.color = always`. */ | |
e3bd11b4 JS |
139 | argv_array_pushl(&args, "diff-files", "-p", "--no-color", "--", NULL); |
140 | color_arg_index = args.argc - 2; | |
f6aa7ecc | 141 | for (i = 0; i < ps->nr; i++) |
e3bd11b4 | 142 | argv_array_push(&args, ps->items[i].original); |
f6aa7ecc | 143 | |
e3bd11b4 JS |
144 | setup_child_process(s, &cp, NULL); |
145 | cp.argv = args.argv; | |
f6aa7ecc | 146 | res = capture_command(&cp, plain, 0); |
e3bd11b4 JS |
147 | if (res) { |
148 | argv_array_clear(&args); | |
f6aa7ecc | 149 | return error(_("could not parse diff")); |
e3bd11b4 JS |
150 | } |
151 | if (!plain->len) { | |
152 | argv_array_clear(&args); | |
f6aa7ecc | 153 | return 0; |
e3bd11b4 | 154 | } |
f6aa7ecc JS |
155 | strbuf_complete_line(plain); |
156 | ||
e3bd11b4 JS |
157 | if (want_color_fd(1, -1)) { |
158 | struct child_process colored_cp = CHILD_PROCESS_INIT; | |
159 | ||
160 | setup_child_process(s, &colored_cp, NULL); | |
161 | xsnprintf((char *)args.argv[color_arg_index], 8, "--color"); | |
162 | colored_cp.argv = args.argv; | |
163 | colored = &s->colored; | |
164 | res = capture_command(&colored_cp, colored, 0); | |
165 | argv_array_clear(&args); | |
166 | if (res) | |
167 | return error(_("could not parse colored diff")); | |
168 | strbuf_complete_line(colored); | |
169 | colored_p = colored->buf; | |
170 | colored_pend = colored_p + colored->len; | |
171 | } | |
172 | argv_array_clear(&args); | |
173 | ||
f6aa7ecc JS |
174 | /* parse hunks */ |
175 | p = plain->buf; | |
176 | pend = p + plain->len; | |
177 | while (p != pend) { | |
178 | char *eol = memchr(p, '\n', pend - p); | |
179 | if (!eol) | |
180 | eol = pend; | |
181 | ||
182 | if (starts_with(p, "diff ")) { | |
183 | if (p != plain->buf) | |
184 | BUG("multi-file diff not yet handled"); | |
185 | hunk = &s->head; | |
186 | } else if (p == plain->buf) | |
187 | BUG("diff starts with unexpected line:\n" | |
188 | "%.*s\n", (int)(eol - p), p); | |
189 | else if (starts_with(p, "@@ ")) { | |
190 | s->hunk_nr++; | |
191 | ALLOC_GROW(s->hunk, s->hunk_nr, | |
192 | s->hunk_alloc); | |
193 | hunk = s->hunk + s->hunk_nr - 1; | |
194 | memset(hunk, 0, sizeof(*hunk)); | |
195 | ||
196 | hunk->start = p - plain->buf; | |
e3bd11b4 JS |
197 | if (colored) |
198 | hunk->colored_start = colored_p - colored->buf; | |
25ea47af JS |
199 | |
200 | if (parse_hunk_header(s, hunk) < 0) | |
201 | return -1; | |
f6aa7ecc JS |
202 | } |
203 | ||
204 | p = eol == pend ? pend : eol + 1; | |
205 | hunk->end = p - plain->buf; | |
e3bd11b4 JS |
206 | |
207 | if (colored) { | |
208 | char *colored_eol = memchr(colored_p, '\n', | |
209 | colored_pend - colored_p); | |
210 | if (colored_eol) | |
211 | colored_p = colored_eol + 1; | |
212 | else | |
213 | colored_p = colored_pend; | |
214 | ||
215 | hunk->colored_end = colored_p - colored->buf; | |
216 | } | |
f6aa7ecc JS |
217 | } |
218 | ||
219 | return 0; | |
220 | } | |
221 | ||
222 | static void render_hunk(struct add_p_state *s, struct hunk *hunk, | |
25ea47af | 223 | ssize_t delta, int colored, struct strbuf *out) |
f6aa7ecc | 224 | { |
25ea47af JS |
225 | struct hunk_header *header = &hunk->header; |
226 | ||
227 | if (hunk->header.old_offset != 0 || hunk->header.new_offset != 0) { | |
228 | /* | |
229 | * Generate the hunk header dynamically, except for special | |
230 | * hunks (such as the diff header). | |
231 | */ | |
232 | const char *p; | |
233 | size_t len; | |
234 | unsigned long old_offset = header->old_offset; | |
235 | unsigned long new_offset = header->new_offset; | |
236 | ||
237 | if (!colored) { | |
238 | p = s->plain.buf + header->extra_start; | |
239 | len = header->extra_end - header->extra_start; | |
240 | } else { | |
241 | strbuf_addstr(out, s->s.fraginfo_color); | |
242 | p = s->colored.buf + header->colored_extra_start; | |
243 | len = header->colored_extra_end | |
244 | - header->colored_extra_start; | |
245 | } | |
246 | ||
247 | new_offset += delta; | |
248 | ||
249 | strbuf_addf(out, "@@ -%lu,%lu +%lu,%lu @@", | |
250 | old_offset, header->old_count, | |
251 | new_offset, header->new_count); | |
252 | if (len) | |
253 | strbuf_add(out, p, len); | |
254 | else if (colored) | |
255 | strbuf_addf(out, "%s\n", GIT_COLOR_RESET); | |
256 | else | |
257 | strbuf_addch(out, '\n'); | |
258 | } | |
259 | ||
e3bd11b4 JS |
260 | if (colored) |
261 | strbuf_add(out, s->colored.buf + hunk->colored_start, | |
262 | hunk->colored_end - hunk->colored_start); | |
263 | else | |
264 | strbuf_add(out, s->plain.buf + hunk->start, | |
265 | hunk->end - hunk->start); | |
f6aa7ecc JS |
266 | } |
267 | ||
268 | static void reassemble_patch(struct add_p_state *s, struct strbuf *out) | |
269 | { | |
270 | struct hunk *hunk; | |
271 | size_t i; | |
25ea47af | 272 | ssize_t delta = 0; |
f6aa7ecc | 273 | |
25ea47af | 274 | render_hunk(s, &s->head, 0, 0, out); |
f6aa7ecc JS |
275 | |
276 | for (i = 0; i < s->hunk_nr; i++) { | |
277 | hunk = s->hunk + i; | |
25ea47af JS |
278 | if (hunk->use != USE_HUNK) |
279 | delta += hunk->header.old_count | |
280 | - hunk->header.new_count; | |
281 | else | |
282 | render_hunk(s, hunk, delta, 0, out); | |
f6aa7ecc JS |
283 | } |
284 | } | |
285 | ||
286 | static const char help_patch_text[] = | |
287 | N_("y - stage this hunk\n" | |
288 | "n - do not stage this hunk\n" | |
289 | "a - stage this and all the remaining hunks\n" | |
290 | "d - do not stage this hunk nor any of the remaining hunks\n" | |
291 | "j - leave this hunk undecided, see next undecided hunk\n" | |
292 | "J - leave this hunk undecided, see next hunk\n" | |
293 | "k - leave this hunk undecided, see previous undecided hunk\n" | |
294 | "K - leave this hunk undecided, see previous hunk\n" | |
295 | "? - print help\n"); | |
296 | ||
297 | static int patch_update_file(struct add_p_state *s) | |
298 | { | |
299 | size_t hunk_index = 0; | |
300 | ssize_t i, undecided_previous, undecided_next; | |
301 | struct hunk *hunk; | |
302 | char ch; | |
303 | struct child_process cp = CHILD_PROCESS_INIT; | |
e3bd11b4 | 304 | int colored = !!s->colored.len; |
f6aa7ecc JS |
305 | |
306 | if (!s->hunk_nr) | |
307 | return 0; | |
308 | ||
309 | strbuf_reset(&s->buf); | |
25ea47af | 310 | render_hunk(s, &s->head, 0, colored, &s->buf); |
f6aa7ecc JS |
311 | fputs(s->buf.buf, stdout); |
312 | for (;;) { | |
313 | if (hunk_index >= s->hunk_nr) | |
314 | hunk_index = 0; | |
315 | hunk = s->hunk + hunk_index; | |
316 | ||
317 | undecided_previous = -1; | |
318 | for (i = hunk_index - 1; i >= 0; i--) | |
319 | if (s->hunk[i].use == UNDECIDED_HUNK) { | |
320 | undecided_previous = i; | |
321 | break; | |
322 | } | |
323 | ||
324 | undecided_next = -1; | |
325 | for (i = hunk_index + 1; i < s->hunk_nr; i++) | |
326 | if (s->hunk[i].use == UNDECIDED_HUNK) { | |
327 | undecided_next = i; | |
328 | break; | |
329 | } | |
330 | ||
331 | /* Everything decided? */ | |
332 | if (undecided_previous < 0 && undecided_next < 0 && | |
333 | hunk->use != UNDECIDED_HUNK) | |
334 | break; | |
335 | ||
336 | strbuf_reset(&s->buf); | |
25ea47af | 337 | render_hunk(s, hunk, 0, colored, &s->buf); |
f6aa7ecc JS |
338 | fputs(s->buf.buf, stdout); |
339 | ||
340 | strbuf_reset(&s->buf); | |
341 | if (undecided_previous >= 0) | |
342 | strbuf_addstr(&s->buf, ",k"); | |
343 | if (hunk_index) | |
344 | strbuf_addstr(&s->buf, ",K"); | |
345 | if (undecided_next >= 0) | |
346 | strbuf_addstr(&s->buf, ",j"); | |
347 | if (hunk_index + 1 < s->hunk_nr) | |
348 | strbuf_addstr(&s->buf, ",J"); | |
12c24cf8 JS |
349 | color_fprintf(stdout, s->s.prompt_color, |
350 | "(%"PRIuMAX"/%"PRIuMAX") ", | |
351 | (uintmax_t)hunk_index + 1, (uintmax_t)s->hunk_nr); | |
352 | color_fprintf(stdout, s->s.prompt_color, | |
353 | _("Stage this hunk [y,n,a,d%s,?]? "), | |
354 | s->buf.buf); | |
f6aa7ecc JS |
355 | fflush(stdout); |
356 | if (strbuf_getline(&s->answer, stdin) == EOF) | |
357 | break; | |
358 | strbuf_trim_trailing_newline(&s->answer); | |
359 | ||
360 | if (!s->answer.len) | |
361 | continue; | |
362 | ch = tolower(s->answer.buf[0]); | |
363 | if (ch == 'y') { | |
364 | hunk->use = USE_HUNK; | |
365 | soft_increment: | |
366 | hunk_index = undecided_next < 0 ? | |
367 | s->hunk_nr : undecided_next; | |
368 | } else if (ch == 'n') { | |
369 | hunk->use = SKIP_HUNK; | |
370 | goto soft_increment; | |
371 | } else if (ch == 'a') { | |
372 | for (; hunk_index < s->hunk_nr; hunk_index++) { | |
373 | hunk = s->hunk + hunk_index; | |
374 | if (hunk->use == UNDECIDED_HUNK) | |
375 | hunk->use = USE_HUNK; | |
376 | } | |
377 | } else if (ch == 'd') { | |
378 | for (; hunk_index < s->hunk_nr; hunk_index++) { | |
379 | hunk = s->hunk + hunk_index; | |
380 | if (hunk->use == UNDECIDED_HUNK) | |
381 | hunk->use = SKIP_HUNK; | |
382 | } | |
7584dd3c JS |
383 | } else if (s->answer.buf[0] == 'K') { |
384 | if (hunk_index) | |
385 | hunk_index--; | |
386 | else | |
387 | err(s, _("No previous hunk")); | |
388 | } else if (s->answer.buf[0] == 'J') { | |
389 | if (hunk_index + 1 < s->hunk_nr) | |
390 | hunk_index++; | |
391 | else | |
392 | err(s, _("No next hunk")); | |
393 | } else if (s->answer.buf[0] == 'k') { | |
394 | if (undecided_previous >= 0) | |
395 | hunk_index = undecided_previous; | |
396 | else | |
397 | err(s, _("No previous hunk")); | |
398 | } else if (s->answer.buf[0] == 'j') { | |
399 | if (undecided_next >= 0) | |
400 | hunk_index = undecided_next; | |
401 | else | |
402 | err(s, _("No next hunk")); | |
403 | } else | |
12c24cf8 JS |
404 | color_fprintf(stdout, s->s.help_color, |
405 | _(help_patch_text)); | |
f6aa7ecc JS |
406 | } |
407 | ||
408 | /* Any hunk to be used? */ | |
409 | for (i = 0; i < s->hunk_nr; i++) | |
410 | if (s->hunk[i].use == USE_HUNK) | |
411 | break; | |
412 | ||
413 | if (i < s->hunk_nr) { | |
414 | /* At least one hunk selected: apply */ | |
415 | strbuf_reset(&s->buf); | |
416 | reassemble_patch(s, &s->buf); | |
417 | ||
25ea47af | 418 | discard_index(s->s.r->index); |
f6aa7ecc JS |
419 | setup_child_process(s, &cp, "apply", "--cached", NULL); |
420 | if (pipe_command(&cp, s->buf.buf, s->buf.len, | |
421 | NULL, 0, NULL, 0)) | |
422 | error(_("'git apply --cached' failed")); | |
25ea47af JS |
423 | if (!repo_read_index(s->s.r)) |
424 | repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0, | |
f6aa7ecc JS |
425 | 1, NULL, NULL, NULL); |
426 | } | |
427 | ||
428 | putchar('\n'); | |
429 | return 0; | |
430 | } | |
431 | ||
432 | int run_add_p(struct repository *r, const struct pathspec *ps) | |
433 | { | |
25ea47af JS |
434 | struct add_p_state s = { |
435 | { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT | |
436 | }; | |
437 | ||
438 | init_add_i_state(&s.s, r); | |
f6aa7ecc JS |
439 | |
440 | if (discard_index(r->index) < 0 || repo_read_index(r) < 0 || | |
441 | repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1, | |
442 | NULL, NULL, NULL) < 0 || | |
443 | parse_diff(&s, ps) < 0) { | |
444 | strbuf_release(&s.plain); | |
e3bd11b4 | 445 | strbuf_release(&s.colored); |
f6aa7ecc JS |
446 | return -1; |
447 | } | |
448 | ||
449 | if (s.hunk_nr) | |
450 | patch_update_file(&s); | |
451 | ||
452 | strbuf_release(&s.answer); | |
453 | strbuf_release(&s.buf); | |
454 | strbuf_release(&s.plain); | |
e3bd11b4 | 455 | strbuf_release(&s.colored); |
f6aa7ecc JS |
456 | return 0; |
457 | } |