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