]> git.ipfire.org Git - thirdparty/git.git/blame - add-patch.c
built-in add -p: implement the 'g' ("goto") command
[thirdparty/git.git] / add-patch.c
CommitLineData
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
0ecd9d27
JS
10enum prompt_mode_type {
11 PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK
12};
13
14static const char *prompt_mode[] = {
15 N_("Stage mode change [y,n,a,d%s,?]? "),
16 N_("Stage deletion [y,n,a,d%s,?]? "),
17 N_("Stage this hunk [y,n,a,d%s,?]? ")
18};
19
25ea47af
JS
20struct hunk_header {
21 unsigned long old_offset, old_count, new_offset, new_count;
22 /*
23 * Start/end offsets to the extra text after the second `@@` in the
24 * hunk header, e.g. the function signature. This is expected to
25 * include the newline.
26 */
27 size_t extra_start, extra_end, colored_extra_start, colored_extra_end;
28};
f6aa7ecc
JS
29
30struct hunk {
510aeca1 31 size_t start, end, colored_start, colored_end, splittable_into;
bcdd297b 32 ssize_t delta;
f6aa7ecc 33 enum { UNDECIDED_HUNK = 0, SKIP_HUNK, USE_HUNK } use;
25ea47af 34 struct hunk_header header;
f6aa7ecc
JS
35};
36
37struct add_p_state {
25ea47af 38 struct add_i_state s;
f6aa7ecc
JS
39 struct strbuf answer, buf;
40
41 /* parsed diff */
e3bd11b4 42 struct strbuf plain, colored;
80399aec
JS
43 struct file_diff {
44 struct hunk head;
45 struct hunk *hunk;
46 size_t hunk_nr, hunk_alloc;
5906d5de 47 unsigned deleted:1, mode_change:1;
80399aec
JS
48 } *file_diff;
49 size_t file_diff_nr;
f6aa7ecc
JS
50};
51
7584dd3c
JS
52static void err(struct add_p_state *s, const char *fmt, ...)
53{
54 va_list args;
55
56 va_start(args, fmt);
57 fputs(s->s.error_color, stderr);
58 vfprintf(stderr, fmt, args);
59 fputs(s->s.reset_color, stderr);
60 fputc('\n', stderr);
61 va_end(args);
62}
63
f6aa7ecc
JS
64static void setup_child_process(struct add_p_state *s,
65 struct child_process *cp, ...)
66{
67 va_list ap;
68 const char *arg;
69
70 va_start(ap, cp);
71 while ((arg = va_arg(ap, const char *)))
72 argv_array_push(&cp->args, arg);
73 va_end(ap);
74
75 cp->git_cmd = 1;
76 argv_array_pushf(&cp->env_array,
25ea47af
JS
77 INDEX_ENVIRONMENT "=%s", s->s.r->index_file);
78}
79
80static int parse_range(const char **p,
81 unsigned long *offset, unsigned long *count)
82{
83 char *pend;
84
85 *offset = strtoul(*p, &pend, 10);
86 if (pend == *p)
87 return -1;
88 if (*pend != ',') {
89 *count = 1;
90 *p = pend;
91 return 0;
92 }
93 *count = strtoul(pend + 1, (char **)p, 10);
94 return *p == pend + 1 ? -1 : 0;
95}
96
97static int parse_hunk_header(struct add_p_state *s, struct hunk *hunk)
98{
99 struct hunk_header *header = &hunk->header;
100 const char *line = s->plain.buf + hunk->start, *p = line;
101 char *eol = memchr(p, '\n', s->plain.len - hunk->start);
102
103 if (!eol)
104 eol = s->plain.buf + s->plain.len;
105
106 if (!skip_prefix(p, "@@ -", &p) ||
107 parse_range(&p, &header->old_offset, &header->old_count) < 0 ||
108 !skip_prefix(p, " +", &p) ||
109 parse_range(&p, &header->new_offset, &header->new_count) < 0 ||
110 !skip_prefix(p, " @@", &p))
111 return error(_("could not parse hunk header '%.*s'"),
112 (int)(eol - line), line);
113
114 hunk->start = eol - s->plain.buf + (*eol == '\n');
115 header->extra_start = p - s->plain.buf;
116 header->extra_end = hunk->start;
117
118 if (!s->colored.len) {
119 header->colored_extra_start = header->colored_extra_end = 0;
120 return 0;
121 }
122
123 /* Now find the extra text in the colored diff */
124 line = s->colored.buf + hunk->colored_start;
125 eol = memchr(line, '\n', s->colored.len - hunk->colored_start);
126 if (!eol)
127 eol = s->colored.buf + s->colored.len;
128 p = memmem(line, eol - line, "@@ -", 4);
129 if (!p)
130 return error(_("could not parse colored hunk header '%.*s'"),
131 (int)(eol - line), line);
132 p = memmem(p + 4, eol - p - 4, " @@", 3);
133 if (!p)
134 return error(_("could not parse colored hunk header '%.*s'"),
135 (int)(eol - line), line);
136 hunk->colored_start = eol - s->colored.buf + (*eol == '\n');
137 header->colored_extra_start = p + 3 - s->colored.buf;
138 header->colored_extra_end = hunk->colored_start;
139
140 return 0;
f6aa7ecc
JS
141}
142
5906d5de
JS
143static int is_octal(const char *p, size_t len)
144{
145 if (!len)
146 return 0;
147
148 while (len--)
149 if (*p < '0' || *(p++) > '7')
150 return 0;
151 return 1;
152}
153
f6aa7ecc
JS
154static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
155{
e3bd11b4
JS
156 struct argv_array args = ARGV_ARRAY_INIT;
157 struct strbuf *plain = &s->plain, *colored = NULL;
f6aa7ecc 158 struct child_process cp = CHILD_PROCESS_INIT;
510aeca1 159 char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
80399aec
JS
160 size_t file_diff_alloc = 0, i, color_arg_index;
161 struct file_diff *file_diff = NULL;
f6aa7ecc
JS
162 struct hunk *hunk = NULL;
163 int res;
164
165 /* Use `--no-color` explicitly, just in case `diff.color = always`. */
e3bd11b4
JS
166 argv_array_pushl(&args, "diff-files", "-p", "--no-color", "--", NULL);
167 color_arg_index = args.argc - 2;
f6aa7ecc 168 for (i = 0; i < ps->nr; i++)
e3bd11b4 169 argv_array_push(&args, ps->items[i].original);
f6aa7ecc 170
e3bd11b4
JS
171 setup_child_process(s, &cp, NULL);
172 cp.argv = args.argv;
f6aa7ecc 173 res = capture_command(&cp, plain, 0);
e3bd11b4
JS
174 if (res) {
175 argv_array_clear(&args);
f6aa7ecc 176 return error(_("could not parse diff"));
e3bd11b4
JS
177 }
178 if (!plain->len) {
179 argv_array_clear(&args);
f6aa7ecc 180 return 0;
e3bd11b4 181 }
f6aa7ecc
JS
182 strbuf_complete_line(plain);
183
e3bd11b4
JS
184 if (want_color_fd(1, -1)) {
185 struct child_process colored_cp = CHILD_PROCESS_INIT;
186
187 setup_child_process(s, &colored_cp, NULL);
188 xsnprintf((char *)args.argv[color_arg_index], 8, "--color");
189 colored_cp.argv = args.argv;
190 colored = &s->colored;
191 res = capture_command(&colored_cp, colored, 0);
192 argv_array_clear(&args);
193 if (res)
194 return error(_("could not parse colored diff"));
195 strbuf_complete_line(colored);
196 colored_p = colored->buf;
197 colored_pend = colored_p + colored->len;
198 }
199 argv_array_clear(&args);
200
80399aec 201 /* parse files and hunks */
f6aa7ecc
JS
202 p = plain->buf;
203 pend = p + plain->len;
204 while (p != pend) {
205 char *eol = memchr(p, '\n', pend - p);
5906d5de 206 const char *deleted = NULL, *mode_change = NULL;
47dc4fd5 207
f6aa7ecc
JS
208 if (!eol)
209 eol = pend;
210
211 if (starts_with(p, "diff ")) {
80399aec
JS
212 s->file_diff_nr++;
213 ALLOC_GROW(s->file_diff, s->file_diff_nr,
214 file_diff_alloc);
215 file_diff = s->file_diff + s->file_diff_nr - 1;
216 memset(file_diff, 0, sizeof(*file_diff));
217 hunk = &file_diff->head;
218 hunk->start = p - plain->buf;
219 if (colored_p)
220 hunk->colored_start = colored_p - colored->buf;
510aeca1 221 marker = '\0';
f6aa7ecc
JS
222 } else if (p == plain->buf)
223 BUG("diff starts with unexpected line:\n"
224 "%.*s\n", (int)(eol - p), p);
47dc4fd5
JS
225 else if (file_diff->deleted)
226 ; /* keep the rest of the file in a single "hunk" */
227 else if (starts_with(p, "@@ ") ||
228 (hunk == &file_diff->head &&
229 skip_prefix(p, "deleted file", &deleted))) {
510aeca1
JS
230 if (marker == '-' || marker == '+')
231 /*
232 * Should not happen; previous hunk did not end
233 * in a context line? Handle it anyway.
234 */
235 hunk->splittable_into++;
236
80399aec
JS
237 file_diff->hunk_nr++;
238 ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr,
239 file_diff->hunk_alloc);
240 hunk = file_diff->hunk + file_diff->hunk_nr - 1;
f6aa7ecc
JS
241 memset(hunk, 0, sizeof(*hunk));
242
243 hunk->start = p - plain->buf;
e3bd11b4
JS
244 if (colored)
245 hunk->colored_start = colored_p - colored->buf;
25ea47af 246
47dc4fd5
JS
247 if (deleted)
248 file_diff->deleted = 1;
249 else if (parse_hunk_header(s, hunk) < 0)
25ea47af 250 return -1;
510aeca1
JS
251
252 /*
253 * Start counting into how many hunks this one can be
254 * split
255 */
256 marker = *p;
5906d5de
JS
257 } else if (hunk == &file_diff->head &&
258 skip_prefix(p, "old mode ", &mode_change) &&
259 is_octal(mode_change, eol - mode_change)) {
260 if (file_diff->mode_change)
261 BUG("double mode change?\n\n%.*s",
262 (int)(eol - plain->buf), plain->buf);
263 if (file_diff->hunk_nr++)
264 BUG("mode change in the middle?\n\n%.*s",
265 (int)(eol - plain->buf), plain->buf);
266
267 /*
268 * Do *not* change `hunk`: the mode change pseudo-hunk
269 * is _part of_ the header "hunk".
270 */
271 file_diff->mode_change = 1;
272 ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr,
273 file_diff->hunk_alloc);
274 memset(file_diff->hunk, 0, sizeof(struct hunk));
275 file_diff->hunk->start = p - plain->buf;
276 if (colored_p)
277 file_diff->hunk->colored_start =
278 colored_p - colored->buf;
279 } else if (hunk == &file_diff->head &&
280 skip_prefix(p, "new mode ", &mode_change) &&
281 is_octal(mode_change, eol - mode_change)) {
282
283 /*
284 * Extend the "mode change" pseudo-hunk to include also
285 * the "new mode" line.
286 */
287 if (!file_diff->mode_change)
288 BUG("'new mode' without 'old mode'?\n\n%.*s",
289 (int)(eol - plain->buf), plain->buf);
290 if (file_diff->hunk_nr != 1)
291 BUG("mode change in the middle?\n\n%.*s",
292 (int)(eol - plain->buf), plain->buf);
293 if (p - plain->buf != file_diff->hunk->end)
294 BUG("'new mode' does not immediately follow "
295 "'old mode'?\n\n%.*s",
296 (int)(eol - plain->buf), plain->buf);
f6aa7ecc
JS
297 }
298
5906d5de
JS
299 if (file_diff->deleted && file_diff->mode_change)
300 BUG("diff contains delete *and* a mode change?!?\n%.*s",
301 (int)(eol - (plain->buf + file_diff->head.start)),
302 plain->buf + file_diff->head.start);
303
510aeca1
JS
304 if ((marker == '-' || marker == '+') && *p == ' ')
305 hunk->splittable_into++;
306 if (marker && *p != '\\')
307 marker = *p;
308
f6aa7ecc
JS
309 p = eol == pend ? pend : eol + 1;
310 hunk->end = p - plain->buf;
e3bd11b4
JS
311
312 if (colored) {
313 char *colored_eol = memchr(colored_p, '\n',
314 colored_pend - colored_p);
315 if (colored_eol)
316 colored_p = colored_eol + 1;
317 else
318 colored_p = colored_pend;
319
320 hunk->colored_end = colored_p - colored->buf;
321 }
5906d5de
JS
322
323 if (mode_change) {
324 if (file_diff->hunk_nr != 1)
325 BUG("mode change in hunk #%d???",
326 (int)file_diff->hunk_nr);
327 /* Adjust the end of the "mode change" pseudo-hunk */
328 file_diff->hunk->end = hunk->end;
329 if (colored)
330 file_diff->hunk->colored_end = hunk->colored_end;
331 }
f6aa7ecc
JS
332 }
333
510aeca1
JS
334 if (marker == '-' || marker == '+')
335 /*
336 * Last hunk ended in non-context line (i.e. it appended lines
337 * to the file, so there are no trailing context lines).
338 */
339 hunk->splittable_into++;
340
f6aa7ecc
JS
341 return 0;
342}
343
510aeca1
JS
344static size_t find_next_line(struct strbuf *sb, size_t offset)
345{
346 char *eol;
347
348 if (offset >= sb->len)
349 BUG("looking for next line beyond buffer (%d >= %d)\n%s",
350 (int)offset, (int)sb->len, sb->buf);
351
352 eol = memchr(sb->buf + offset, '\n', sb->len - offset);
353 if (!eol)
354 return sb->len;
355 return eol - sb->buf + 1;
356}
357
f6aa7ecc 358static void render_hunk(struct add_p_state *s, struct hunk *hunk,
25ea47af 359 ssize_t delta, int colored, struct strbuf *out)
f6aa7ecc 360{
25ea47af
JS
361 struct hunk_header *header = &hunk->header;
362
363 if (hunk->header.old_offset != 0 || hunk->header.new_offset != 0) {
364 /*
365 * Generate the hunk header dynamically, except for special
366 * hunks (such as the diff header).
367 */
368 const char *p;
369 size_t len;
370 unsigned long old_offset = header->old_offset;
371 unsigned long new_offset = header->new_offset;
372
373 if (!colored) {
374 p = s->plain.buf + header->extra_start;
375 len = header->extra_end - header->extra_start;
376 } else {
377 strbuf_addstr(out, s->s.fraginfo_color);
378 p = s->colored.buf + header->colored_extra_start;
379 len = header->colored_extra_end
380 - header->colored_extra_start;
381 }
382
383 new_offset += delta;
384
385 strbuf_addf(out, "@@ -%lu,%lu +%lu,%lu @@",
386 old_offset, header->old_count,
387 new_offset, header->new_count);
388 if (len)
389 strbuf_add(out, p, len);
390 else if (colored)
391 strbuf_addf(out, "%s\n", GIT_COLOR_RESET);
392 else
393 strbuf_addch(out, '\n');
394 }
395
e3bd11b4
JS
396 if (colored)
397 strbuf_add(out, s->colored.buf + hunk->colored_start,
398 hunk->colored_end - hunk->colored_start);
399 else
400 strbuf_add(out, s->plain.buf + hunk->start,
401 hunk->end - hunk->start);
f6aa7ecc
JS
402}
403
5906d5de
JS
404static void render_diff_header(struct add_p_state *s,
405 struct file_diff *file_diff, int colored,
406 struct strbuf *out)
407{
408 /*
409 * If there was a mode change, the first hunk is a pseudo hunk that
410 * corresponds to the mode line in the header. If the user did not want
411 * to stage that "hunk", we actually have to cut it out from the header.
412 */
413 int skip_mode_change =
414 file_diff->mode_change && file_diff->hunk->use != USE_HUNK;
415 struct hunk *head = &file_diff->head, *first = file_diff->hunk;
416
417 if (!skip_mode_change) {
418 render_hunk(s, head, 0, colored, out);
419 return;
420 }
421
422 if (colored) {
423 const char *p = s->colored.buf;
424
425 strbuf_add(out, p + head->colored_start,
426 first->colored_start - head->colored_start);
427 strbuf_add(out, p + first->colored_end,
428 head->colored_end - first->colored_end);
429 } else {
430 const char *p = s->plain.buf;
431
432 strbuf_add(out, p + head->start, first->start - head->start);
433 strbuf_add(out, p + first->end, head->end - first->end);
434 }
435}
436
11f2c0da
JS
437/* Coalesce hunks again that were split */
438static int merge_hunks(struct add_p_state *s, struct file_diff *file_diff,
bcdd297b 439 size_t *hunk_index, int use_all, struct hunk *merged)
11f2c0da 440{
bcdd297b 441 size_t i = *hunk_index, delta;
11f2c0da
JS
442 struct hunk *hunk = file_diff->hunk + i;
443 /* `header` corresponds to the merged hunk */
444 struct hunk_header *header = &merged->header, *next;
445
bcdd297b 446 if (!use_all && hunk->use != USE_HUNK)
11f2c0da
JS
447 return 0;
448
449 *merged = *hunk;
450 /* We simply skip the colored part (if any) when merging hunks */
451 merged->colored_start = merged->colored_end = 0;
452
453 for (; i + 1 < file_diff->hunk_nr; i++) {
454 hunk++;
455 next = &hunk->header;
456
457 /*
458 * Stop merging hunks when:
459 *
460 * - the hunk is not selected for use, or
461 * - the hunk does not overlap with the already-merged hunk(s)
462 */
bcdd297b
JS
463 if ((!use_all && hunk->use != USE_HUNK) ||
464 header->new_offset >= next->new_offset + merged->delta ||
465 header->new_offset + header->new_count
466 < next->new_offset + merged->delta)
11f2c0da
JS
467 break;
468
bcdd297b
JS
469 /*
470 * If the hunks were not edited, and overlap, we can simply
471 * extend the line range.
472 */
473 if (merged->start < hunk->start && merged->end > hunk->start) {
474 merged->end = hunk->end;
475 merged->colored_end = hunk->colored_end;
476 delta = 0;
477 } else {
478 const char *plain = s->plain.buf;
479 size_t overlapping_line_count = header->new_offset
480 + header->new_count - merged->delta
481 - next->new_offset;
482 size_t overlap_end = hunk->start;
483 size_t overlap_start = overlap_end;
484 size_t overlap_next, len, j;
485
486 /*
487 * One of the hunks was edited: the modified hunk was
488 * appended to the strbuf `s->plain`.
489 *
490 * Let's ensure that at least the last context line of
491 * the first hunk overlaps with the corresponding line
492 * of the second hunk, and then merge.
493 */
494 for (j = 0; j < overlapping_line_count; j++) {
495 overlap_next = find_next_line(&s->plain,
496 overlap_end);
497
498 if (overlap_next > hunk->end)
499 BUG("failed to find %d context lines "
500 "in:\n%.*s",
501 (int)overlapping_line_count,
502 (int)(hunk->end - hunk->start),
503 plain + hunk->start);
504
505 if (plain[overlap_end] != ' ')
506 return error(_("expected context line "
507 "#%d in\n%.*s"),
508 (int)(j + 1),
509 (int)(hunk->end
510 - hunk->start),
511 plain + hunk->start);
512
513 overlap_start = overlap_end;
514 overlap_end = overlap_next;
515 }
516 len = overlap_end - overlap_start;
517
518 if (len > merged->end - merged->start ||
519 memcmp(plain + merged->end - len,
520 plain + overlap_start, len))
521 return error(_("hunks do not overlap:\n%.*s\n"
522 "\tdoes not end with:\n%.*s"),
523 (int)(merged->end - merged->start),
524 plain + merged->start,
525 (int)len, plain + overlap_start);
526
527 /*
528 * Since the start-end ranges are not adjacent, we
529 * cannot simply take the union of the ranges. To
530 * address that, we temporarily append the union of the
531 * lines to the `plain` strbuf.
532 */
533 if (merged->end != s->plain.len) {
534 size_t start = s->plain.len;
535
536 strbuf_add(&s->plain, plain + merged->start,
537 merged->end - merged->start);
538 plain = s->plain.buf;
539 merged->start = start;
540 merged->end = s->plain.len;
541 }
542
543 strbuf_add(&s->plain,
544 plain + overlap_end,
545 hunk->end - overlap_end);
546 merged->end = s->plain.len;
547 merged->splittable_into += hunk->splittable_into;
548 delta = merged->delta;
549 merged->delta += hunk->delta;
550 }
11f2c0da
JS
551
552 header->old_count = next->old_offset + next->old_count
553 - header->old_offset;
bcdd297b
JS
554 header->new_count = next->new_offset + delta
555 + next->new_count - header->new_offset;
11f2c0da
JS
556 }
557
558 if (i == *hunk_index)
559 return 0;
560
561 *hunk_index = i;
562 return 1;
563}
564
80399aec 565static void reassemble_patch(struct add_p_state *s,
bcdd297b
JS
566 struct file_diff *file_diff, int use_all,
567 struct strbuf *out)
f6aa7ecc
JS
568{
569 struct hunk *hunk;
bcdd297b 570 size_t save_len = s->plain.len, i;
25ea47af 571 ssize_t delta = 0;
f6aa7ecc 572
5906d5de 573 render_diff_header(s, file_diff, 0, out);
f6aa7ecc 574
5906d5de 575 for (i = file_diff->mode_change; i < file_diff->hunk_nr; i++) {
11f2c0da
JS
576 struct hunk merged = { 0 };
577
80399aec 578 hunk = file_diff->hunk + i;
bcdd297b 579 if (!use_all && hunk->use != USE_HUNK)
25ea47af
JS
580 delta += hunk->header.old_count
581 - hunk->header.new_count;
11f2c0da
JS
582 else {
583 /* merge overlapping hunks into a temporary hunk */
bcdd297b 584 if (merge_hunks(s, file_diff, &i, use_all, &merged))
11f2c0da
JS
585 hunk = &merged;
586
25ea47af 587 render_hunk(s, hunk, delta, 0, out);
bcdd297b
JS
588
589 /*
590 * In case `merge_hunks()` used `plain` as a scratch
591 * pad (this happens when an edited hunk had to be
592 * coalesced with another hunk).
593 */
594 strbuf_setlen(&s->plain, save_len);
595
596 delta += hunk->delta;
11f2c0da 597 }
f6aa7ecc
JS
598 }
599}
600
510aeca1
JS
601static int split_hunk(struct add_p_state *s, struct file_diff *file_diff,
602 size_t hunk_index)
603{
604 int colored = !!s->colored.len, first = 1;
605 struct hunk *hunk = file_diff->hunk + hunk_index;
606 size_t splittable_into;
607 size_t end, colored_end, current, colored_current = 0, context_line_count;
608 struct hunk_header remaining, *header;
609 char marker, ch;
610
611 if (hunk_index >= file_diff->hunk_nr)
612 BUG("invalid hunk index: %d (must be >= 0 and < %d)",
613 (int)hunk_index, (int)file_diff->hunk_nr);
614
615 if (hunk->splittable_into < 2)
616 return 0;
617 splittable_into = hunk->splittable_into;
618
619 end = hunk->end;
620 colored_end = hunk->colored_end;
621
622 remaining = hunk->header;
623
624 file_diff->hunk_nr += splittable_into - 1;
625 ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr, file_diff->hunk_alloc);
626 if (hunk_index + splittable_into < file_diff->hunk_nr)
627 memmove(file_diff->hunk + hunk_index + splittable_into,
628 file_diff->hunk + hunk_index + 1,
629 (file_diff->hunk_nr - hunk_index - splittable_into)
630 * sizeof(*hunk));
631 hunk = file_diff->hunk + hunk_index;
632 hunk->splittable_into = 1;
633 memset(hunk + 1, 0, (splittable_into - 1) * sizeof(*hunk));
634
635 header = &hunk->header;
636 header->old_count = header->new_count = 0;
637
638 current = hunk->start;
639 if (colored)
640 colored_current = hunk->colored_start;
641 marker = '\0';
642 context_line_count = 0;
643
644 while (splittable_into > 1) {
645 ch = s->plain.buf[current];
646
647 if (!ch)
648 BUG("buffer overrun while splitting hunks");
649
650 /*
651 * Is this the first context line after a chain of +/- lines?
652 * Then record the start of the next split hunk.
653 */
654 if ((marker == '-' || marker == '+') && ch == ' ') {
655 first = 0;
656 hunk[1].start = current;
657 if (colored)
658 hunk[1].colored_start = colored_current;
659 context_line_count = 0;
660 }
661
662 /*
663 * Was the previous line a +/- one? Alternatively, is this the
664 * first line (and not a +/- one)?
665 *
666 * Then just increment the appropriate counter and continue
667 * with the next line.
668 */
669 if (marker != ' ' || (ch != '-' && ch != '+')) {
670next_hunk_line:
671 /* Comment lines are attached to the previous line */
672 if (ch == '\\')
673 ch = marker ? marker : ' ';
674
675 /* current hunk not done yet */
676 if (ch == ' ')
677 context_line_count++;
678 else if (ch == '-')
679 header->old_count++;
680 else if (ch == '+')
681 header->new_count++;
682 else
683 BUG("unhandled diff marker: '%c'", ch);
684 marker = ch;
685 current = find_next_line(&s->plain, current);
686 if (colored)
687 colored_current =
688 find_next_line(&s->colored,
689 colored_current);
690 continue;
691 }
692
693 /*
694 * We got us the start of a new hunk!
695 *
696 * This is a context line, so it is shared with the previous
697 * hunk, if any.
698 */
699
700 if (first) {
701 if (header->old_count || header->new_count)
702 BUG("counts are off: %d/%d",
703 (int)header->old_count,
704 (int)header->new_count);
705
706 header->old_count = context_line_count;
707 header->new_count = context_line_count;
708 context_line_count = 0;
709 first = 0;
710 goto next_hunk_line;
711 }
712
713 remaining.old_offset += header->old_count;
714 remaining.old_count -= header->old_count;
715 remaining.new_offset += header->new_count;
716 remaining.new_count -= header->new_count;
717
718 /* initialize next hunk header's offsets */
719 hunk[1].header.old_offset =
720 header->old_offset + header->old_count;
721 hunk[1].header.new_offset =
722 header->new_offset + header->new_count;
723
724 /* add one split hunk */
725 header->old_count += context_line_count;
726 header->new_count += context_line_count;
727
728 hunk->end = current;
729 if (colored)
730 hunk->colored_end = colored_current;
731
732 hunk++;
733 hunk->splittable_into = 1;
734 hunk->use = hunk[-1].use;
735 header = &hunk->header;
736
737 header->old_count = header->new_count = context_line_count;
738 context_line_count = 0;
739
740 splittable_into--;
741 marker = ch;
742 }
743
744 /* last hunk simply gets the rest */
745 if (header->old_offset != remaining.old_offset)
746 BUG("miscounted old_offset: %lu != %lu",
747 header->old_offset, remaining.old_offset);
748 if (header->new_offset != remaining.new_offset)
749 BUG("miscounted new_offset: %lu != %lu",
750 header->new_offset, remaining.new_offset);
751 header->old_count = remaining.old_count;
752 header->new_count = remaining.new_count;
753 hunk->end = end;
754 if (colored)
755 hunk->colored_end = colored_end;
756
757 return 0;
758}
759
bcdd297b
JS
760static void recolor_hunk(struct add_p_state *s, struct hunk *hunk)
761{
762 const char *plain = s->plain.buf;
763 size_t current, eol, next;
764
765 if (!s->colored.len)
766 return;
767
768 hunk->colored_start = s->colored.len;
769 for (current = hunk->start; current < hunk->end; ) {
770 for (eol = current; eol < hunk->end; eol++)
771 if (plain[eol] == '\n')
772 break;
773 next = eol + (eol < hunk->end);
774 if (eol > current && plain[eol - 1] == '\r')
775 eol--;
776
777 strbuf_addstr(&s->colored,
778 plain[current] == '-' ?
779 s->s.file_old_color :
780 plain[current] == '+' ?
781 s->s.file_new_color :
782 s->s.context_color);
783 strbuf_add(&s->colored, plain + current, eol - current);
784 strbuf_addstr(&s->colored, GIT_COLOR_RESET);
785 if (next > eol)
786 strbuf_add(&s->colored, plain + eol, next - eol);
787 current = next;
788 }
789 hunk->colored_end = s->colored.len;
790}
791
792static int edit_hunk_manually(struct add_p_state *s, struct hunk *hunk)
793{
794 size_t i;
795
796 strbuf_reset(&s->buf);
797 strbuf_commented_addf(&s->buf, _("Manual hunk edit mode -- see bottom for "
798 "a quick guide.\n"));
799 render_hunk(s, hunk, 0, 0, &s->buf);
800 strbuf_commented_addf(&s->buf,
801 _("---\n"
802 "To remove '%c' lines, make them ' ' lines "
803 "(context).\n"
804 "To remove '%c' lines, delete them.\n"
805 "Lines starting with %c will be removed.\n"),
806 '-', '+', comment_line_char);
807 strbuf_commented_addf(&s->buf,
808 _("If the patch applies cleanly, the edited hunk "
809 "will immediately be\n"
810 "marked for staging.\n"));
811 /*
812 * TRANSLATORS: 'it' refers to the patch mentioned in the previous
813 * messages.
814 */
815 strbuf_commented_addf(&s->buf,
816 _("If it does not apply cleanly, you will be "
817 "given an opportunity to\n"
818 "edit again. If all lines of the hunk are "
819 "removed, then the edit is\n"
820 "aborted and the hunk is left unchanged.\n"));
821
822 if (strbuf_edit_interactively(&s->buf, "addp-hunk-edit.diff", NULL) < 0)
823 return -1;
824
825 /* strip out commented lines */
826 hunk->start = s->plain.len;
827 for (i = 0; i < s->buf.len; ) {
828 size_t next = find_next_line(&s->buf, i);
829
830 if (s->buf.buf[i] != comment_line_char)
831 strbuf_add(&s->plain, s->buf.buf + i, next - i);
832 i = next;
833 }
834
835 hunk->end = s->plain.len;
836 if (hunk->end == hunk->start)
837 /* The user aborted editing by deleting everything */
838 return 0;
839
840 recolor_hunk(s, hunk);
841
842 /*
843 * If the hunk header is intact, parse it, otherwise simply use the
844 * hunk header prior to editing (which will adjust `hunk->start` to
845 * skip the hunk header).
846 */
847 if (s->plain.buf[hunk->start] == '@' &&
848 parse_hunk_header(s, hunk) < 0)
849 return error(_("could not parse hunk header"));
850
851 return 1;
852}
853
854static ssize_t recount_edited_hunk(struct add_p_state *s, struct hunk *hunk,
855 size_t orig_old_count, size_t orig_new_count)
856{
857 struct hunk_header *header = &hunk->header;
858 size_t i;
859
860 header->old_count = header->new_count = 0;
861 for (i = hunk->start; i < hunk->end; ) {
862 switch (s->plain.buf[i]) {
863 case '-':
864 header->old_count++;
865 break;
866 case '+':
867 header->new_count++;
868 break;
869 case ' ': case '\r': case '\n':
870 header->old_count++;
871 header->new_count++;
872 break;
873 }
874
875 i = find_next_line(&s->plain, i);
876 }
877
878 return orig_old_count - orig_new_count
879 - header->old_count + header->new_count;
880}
881
882static int run_apply_check(struct add_p_state *s,
883 struct file_diff *file_diff)
884{
885 struct child_process cp = CHILD_PROCESS_INIT;
886
887 strbuf_reset(&s->buf);
888 reassemble_patch(s, file_diff, 1, &s->buf);
889
890 setup_child_process(s, &cp,
891 "apply", "--cached", "--check", NULL);
892 if (pipe_command(&cp, s->buf.buf, s->buf.len, NULL, 0, NULL, 0))
893 return error(_("'git apply --cached' failed"));
894
895 return 0;
896}
897
898static int prompt_yesno(struct add_p_state *s, const char *prompt)
899{
900 for (;;) {
901 color_fprintf(stdout, s->s.prompt_color, "%s", _(prompt));
902 fflush(stdout);
903 if (strbuf_getline(&s->answer, stdin) == EOF)
904 return -1;
905 strbuf_trim_trailing_newline(&s->answer);
906 switch (tolower(s->answer.buf[0])) {
907 case 'n': return 0;
908 case 'y': return 1;
909 }
910 }
911}
912
913static int edit_hunk_loop(struct add_p_state *s,
914 struct file_diff *file_diff, struct hunk *hunk)
915{
916 size_t plain_len = s->plain.len, colored_len = s->colored.len;
917 struct hunk backup;
918
919 backup = *hunk;
920
921 for (;;) {
922 int res = edit_hunk_manually(s, hunk);
923 if (res == 0) {
924 /* abandonded */
925 *hunk = backup;
926 return -1;
927 }
928
929 if (res > 0) {
930 hunk->delta +=
931 recount_edited_hunk(s, hunk,
932 backup.header.old_count,
933 backup.header.new_count);
934 if (!run_apply_check(s, file_diff))
935 return 0;
936 }
937
938 /* Drop edits (they were appended to s->plain) */
939 strbuf_setlen(&s->plain, plain_len);
940 strbuf_setlen(&s->colored, colored_len);
941 *hunk = backup;
942
943 /*
944 * TRANSLATORS: do not translate [y/n]
945 * The program will only accept that input at this point.
946 * Consider translating (saying "no" discards!) as
947 * (saying "n" for "no" discards!) if the translation
948 * of the word "no" does not start with n.
949 */
950 res = prompt_yesno(s, _("Your edited hunk does not apply. "
951 "Edit again (saying \"no\" discards!) "
952 "[y/n]? "));
953 if (res < 1)
954 return -1;
955 }
956}
957
9254bdfb
JS
958#define SUMMARY_HEADER_WIDTH 20
959#define SUMMARY_LINE_WIDTH 80
960static void summarize_hunk(struct add_p_state *s, struct hunk *hunk,
961 struct strbuf *out)
962{
963 struct hunk_header *header = &hunk->header;
964 struct strbuf *plain = &s->plain;
965 size_t len = out->len, i;
966
967 strbuf_addf(out, " -%lu,%lu +%lu,%lu ",
968 header->old_offset, header->old_count,
969 header->new_offset, header->new_count);
970 if (out->len - len < SUMMARY_HEADER_WIDTH)
971 strbuf_addchars(out, ' ',
972 SUMMARY_HEADER_WIDTH + len - out->len);
973 for (i = hunk->start; i < hunk->end; i = find_next_line(plain, i))
974 if (plain->buf[i] != ' ')
975 break;
976 if (i < hunk->end)
977 strbuf_add(out, plain->buf + i, find_next_line(plain, i) - i);
978 if (out->len - len > SUMMARY_LINE_WIDTH)
979 strbuf_setlen(out, len + SUMMARY_LINE_WIDTH);
980 strbuf_complete_line(out);
981}
982
983#define DISPLAY_HUNKS_LINES 20
984static size_t display_hunks(struct add_p_state *s,
985 struct file_diff *file_diff, size_t start_index)
986{
987 size_t end_index = start_index + DISPLAY_HUNKS_LINES;
988
989 if (end_index > file_diff->hunk_nr)
990 end_index = file_diff->hunk_nr;
991
992 while (start_index < end_index) {
993 struct hunk *hunk = file_diff->hunk + start_index++;
994
995 strbuf_reset(&s->buf);
996 strbuf_addf(&s->buf, "%c%2d: ", hunk->use == USE_HUNK ? '+'
997 : hunk->use == SKIP_HUNK ? '-' : ' ',
998 (int)start_index);
999 summarize_hunk(s, hunk, &s->buf);
1000 fputs(s->buf.buf, stdout);
1001 }
1002
1003 return end_index;
1004}
1005
f6aa7ecc
JS
1006static const char help_patch_text[] =
1007N_("y - stage this hunk\n"
1008 "n - do not stage this hunk\n"
1009 "a - stage this and all the remaining hunks\n"
1010 "d - do not stage this hunk nor any of the remaining hunks\n"
1011 "j - leave this hunk undecided, see next undecided hunk\n"
1012 "J - leave this hunk undecided, see next hunk\n"
1013 "k - leave this hunk undecided, see previous undecided hunk\n"
1014 "K - leave this hunk undecided, see previous hunk\n"
9254bdfb 1015 "g - select a hunk to go to\n"
510aeca1 1016 "s - split the current hunk into smaller hunks\n"
bcdd297b 1017 "e - manually edit the current hunk\n"
f6aa7ecc
JS
1018 "? - print help\n");
1019
80399aec
JS
1020static int patch_update_file(struct add_p_state *s,
1021 struct file_diff *file_diff)
f6aa7ecc
JS
1022{
1023 size_t hunk_index = 0;
1024 ssize_t i, undecided_previous, undecided_next;
1025 struct hunk *hunk;
1026 char ch;
1027 struct child_process cp = CHILD_PROCESS_INIT;
e3bd11b4 1028 int colored = !!s->colored.len;
0ecd9d27 1029 enum prompt_mode_type prompt_mode_type;
f6aa7ecc 1030
80399aec 1031 if (!file_diff->hunk_nr)
f6aa7ecc
JS
1032 return 0;
1033
1034 strbuf_reset(&s->buf);
5906d5de 1035 render_diff_header(s, file_diff, colored, &s->buf);
f6aa7ecc
JS
1036 fputs(s->buf.buf, stdout);
1037 for (;;) {
80399aec 1038 if (hunk_index >= file_diff->hunk_nr)
f6aa7ecc 1039 hunk_index = 0;
80399aec 1040 hunk = file_diff->hunk + hunk_index;
f6aa7ecc
JS
1041
1042 undecided_previous = -1;
1043 for (i = hunk_index - 1; i >= 0; i--)
80399aec 1044 if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
f6aa7ecc
JS
1045 undecided_previous = i;
1046 break;
1047 }
1048
1049 undecided_next = -1;
80399aec
JS
1050 for (i = hunk_index + 1; i < file_diff->hunk_nr; i++)
1051 if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
f6aa7ecc
JS
1052 undecided_next = i;
1053 break;
1054 }
1055
1056 /* Everything decided? */
1057 if (undecided_previous < 0 && undecided_next < 0 &&
1058 hunk->use != UNDECIDED_HUNK)
1059 break;
1060
1061 strbuf_reset(&s->buf);
25ea47af 1062 render_hunk(s, hunk, 0, colored, &s->buf);
f6aa7ecc
JS
1063 fputs(s->buf.buf, stdout);
1064
1065 strbuf_reset(&s->buf);
1066 if (undecided_previous >= 0)
1067 strbuf_addstr(&s->buf, ",k");
1068 if (hunk_index)
1069 strbuf_addstr(&s->buf, ",K");
1070 if (undecided_next >= 0)
1071 strbuf_addstr(&s->buf, ",j");
80399aec 1072 if (hunk_index + 1 < file_diff->hunk_nr)
f6aa7ecc 1073 strbuf_addstr(&s->buf, ",J");
9254bdfb
JS
1074 if (file_diff->hunk_nr > 1)
1075 strbuf_addstr(&s->buf, ",g");
510aeca1
JS
1076 if (hunk->splittable_into > 1)
1077 strbuf_addstr(&s->buf, ",s");
bcdd297b
JS
1078 if (hunk_index + 1 > file_diff->mode_change &&
1079 !file_diff->deleted)
1080 strbuf_addstr(&s->buf, ",e");
0ecd9d27
JS
1081
1082 if (file_diff->deleted)
1083 prompt_mode_type = PROMPT_DELETION;
1084 else if (file_diff->mode_change && !hunk_index)
1085 prompt_mode_type = PROMPT_MODE_CHANGE;
1086 else
1087 prompt_mode_type = PROMPT_HUNK;
1088
12c24cf8
JS
1089 color_fprintf(stdout, s->s.prompt_color,
1090 "(%"PRIuMAX"/%"PRIuMAX") ",
80399aec
JS
1091 (uintmax_t)hunk_index + 1,
1092 (uintmax_t)file_diff->hunk_nr);
12c24cf8 1093 color_fprintf(stdout, s->s.prompt_color,
0ecd9d27 1094 _(prompt_mode[prompt_mode_type]), s->buf.buf);
f6aa7ecc
JS
1095 fflush(stdout);
1096 if (strbuf_getline(&s->answer, stdin) == EOF)
1097 break;
1098 strbuf_trim_trailing_newline(&s->answer);
1099
1100 if (!s->answer.len)
1101 continue;
1102 ch = tolower(s->answer.buf[0]);
1103 if (ch == 'y') {
1104 hunk->use = USE_HUNK;
1105soft_increment:
1106 hunk_index = undecided_next < 0 ?
80399aec 1107 file_diff->hunk_nr : undecided_next;
f6aa7ecc
JS
1108 } else if (ch == 'n') {
1109 hunk->use = SKIP_HUNK;
1110 goto soft_increment;
1111 } else if (ch == 'a') {
80399aec
JS
1112 for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
1113 hunk = file_diff->hunk + hunk_index;
f6aa7ecc
JS
1114 if (hunk->use == UNDECIDED_HUNK)
1115 hunk->use = USE_HUNK;
1116 }
1117 } else if (ch == 'd') {
80399aec
JS
1118 for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
1119 hunk = file_diff->hunk + hunk_index;
f6aa7ecc
JS
1120 if (hunk->use == UNDECIDED_HUNK)
1121 hunk->use = SKIP_HUNK;
1122 }
7584dd3c
JS
1123 } else if (s->answer.buf[0] == 'K') {
1124 if (hunk_index)
1125 hunk_index--;
1126 else
1127 err(s, _("No previous hunk"));
1128 } else if (s->answer.buf[0] == 'J') {
80399aec 1129 if (hunk_index + 1 < file_diff->hunk_nr)
7584dd3c
JS
1130 hunk_index++;
1131 else
1132 err(s, _("No next hunk"));
1133 } else if (s->answer.buf[0] == 'k') {
1134 if (undecided_previous >= 0)
1135 hunk_index = undecided_previous;
1136 else
1137 err(s, _("No previous hunk"));
1138 } else if (s->answer.buf[0] == 'j') {
1139 if (undecided_next >= 0)
1140 hunk_index = undecided_next;
1141 else
1142 err(s, _("No next hunk"));
9254bdfb
JS
1143 } else if (s->answer.buf[0] == 'g') {
1144 char *pend;
1145 unsigned long response;
1146
1147 if (file_diff->hunk_nr < 2) {
1148 err(s, _("No other hunks to goto"));
1149 continue;
1150 }
1151 strbuf_remove(&s->answer, 0, 1);
1152 strbuf_trim(&s->answer);
1153 i = hunk_index - DISPLAY_HUNKS_LINES / 2;
1154 if (i < file_diff->mode_change)
1155 i = file_diff->mode_change;
1156 while (s->answer.len == 0) {
1157 i = display_hunks(s, file_diff, i);
1158 printf("%s", i < file_diff->hunk_nr ?
1159 _("go to which hunk (<ret> to see "
1160 "more)? ") : _("go to which hunk? "));
1161 fflush(stdout);
1162 if (strbuf_getline(&s->answer,
1163 stdin) == EOF)
1164 break;
1165 strbuf_trim_trailing_newline(&s->answer);
1166 }
1167
1168 strbuf_trim(&s->answer);
1169 response = strtoul(s->answer.buf, &pend, 10);
1170 if (*pend || pend == s->answer.buf)
1171 err(s, _("Invalid number: '%s'"),
1172 s->answer.buf);
1173 else if (0 < response && response <= file_diff->hunk_nr)
1174 hunk_index = response - 1;
1175 else
1176 err(s, Q_("Sorry, only %d hunk available.",
1177 "Sorry, only %d hunks available.",
1178 file_diff->hunk_nr),
1179 (int)file_diff->hunk_nr);
510aeca1
JS
1180 } else if (s->answer.buf[0] == 's') {
1181 size_t splittable_into = hunk->splittable_into;
1182 if (splittable_into < 2)
1183 err(s, _("Sorry, cannot split this hunk"));
1184 else if (!split_hunk(s, file_diff,
1185 hunk - file_diff->hunk))
1186 color_fprintf_ln(stdout, s->s.header_color,
1187 _("Split into %d hunks."),
1188 (int)splittable_into);
bcdd297b
JS
1189 } else if (s->answer.buf[0] == 'e') {
1190 if (hunk_index + 1 == file_diff->mode_change)
1191 err(s, _("Sorry, cannot edit this hunk"));
1192 else if (edit_hunk_loop(s, file_diff, hunk) >= 0) {
1193 hunk->use = USE_HUNK;
1194 goto soft_increment;
1195 }
7584dd3c 1196 } else
12c24cf8
JS
1197 color_fprintf(stdout, s->s.help_color,
1198 _(help_patch_text));
f6aa7ecc
JS
1199 }
1200
1201 /* Any hunk to be used? */
80399aec
JS
1202 for (i = 0; i < file_diff->hunk_nr; i++)
1203 if (file_diff->hunk[i].use == USE_HUNK)
f6aa7ecc
JS
1204 break;
1205
80399aec 1206 if (i < file_diff->hunk_nr) {
f6aa7ecc
JS
1207 /* At least one hunk selected: apply */
1208 strbuf_reset(&s->buf);
bcdd297b 1209 reassemble_patch(s, file_diff, 0, &s->buf);
f6aa7ecc 1210
25ea47af 1211 discard_index(s->s.r->index);
f6aa7ecc
JS
1212 setup_child_process(s, &cp, "apply", "--cached", NULL);
1213 if (pipe_command(&cp, s->buf.buf, s->buf.len,
1214 NULL, 0, NULL, 0))
1215 error(_("'git apply --cached' failed"));
25ea47af
JS
1216 if (!repo_read_index(s->s.r))
1217 repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
f6aa7ecc
JS
1218 1, NULL, NULL, NULL);
1219 }
1220
1221 putchar('\n');
1222 return 0;
1223}
1224
1225int run_add_p(struct repository *r, const struct pathspec *ps)
1226{
25ea47af
JS
1227 struct add_p_state s = {
1228 { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
1229 };
80399aec 1230 size_t i;
25ea47af
JS
1231
1232 init_add_i_state(&s.s, r);
f6aa7ecc
JS
1233
1234 if (discard_index(r->index) < 0 || repo_read_index(r) < 0 ||
1235 repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
1236 NULL, NULL, NULL) < 0 ||
1237 parse_diff(&s, ps) < 0) {
1238 strbuf_release(&s.plain);
e3bd11b4 1239 strbuf_release(&s.colored);
f6aa7ecc
JS
1240 return -1;
1241 }
1242
80399aec
JS
1243 for (i = 0; i < s.file_diff_nr; i++)
1244 if (patch_update_file(&s, s.file_diff + i))
1245 break;
f6aa7ecc
JS
1246
1247 strbuf_release(&s.answer);
1248 strbuf_release(&s.buf);
1249 strbuf_release(&s.plain);
e3bd11b4 1250 strbuf_release(&s.colored);
f6aa7ecc
JS
1251 return 0;
1252}