]> git.ipfire.org Git - thirdparty/git.git/blame - add-patch.c
built-in add -p: implement the '/' ("search regex") 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"
d6cf8733 1016 "/ - search for a hunk matching the given regex\n"
510aeca1 1017 "s - split the current hunk into smaller hunks\n"
bcdd297b 1018 "e - manually edit the current hunk\n"
f6aa7ecc
JS
1019 "? - print help\n");
1020
80399aec
JS
1021static int patch_update_file(struct add_p_state *s,
1022 struct file_diff *file_diff)
f6aa7ecc
JS
1023{
1024 size_t hunk_index = 0;
1025 ssize_t i, undecided_previous, undecided_next;
1026 struct hunk *hunk;
1027 char ch;
1028 struct child_process cp = CHILD_PROCESS_INIT;
e3bd11b4 1029 int colored = !!s->colored.len;
0ecd9d27 1030 enum prompt_mode_type prompt_mode_type;
f6aa7ecc 1031
80399aec 1032 if (!file_diff->hunk_nr)
f6aa7ecc
JS
1033 return 0;
1034
1035 strbuf_reset(&s->buf);
5906d5de 1036 render_diff_header(s, file_diff, colored, &s->buf);
f6aa7ecc
JS
1037 fputs(s->buf.buf, stdout);
1038 for (;;) {
80399aec 1039 if (hunk_index >= file_diff->hunk_nr)
f6aa7ecc 1040 hunk_index = 0;
80399aec 1041 hunk = file_diff->hunk + hunk_index;
f6aa7ecc
JS
1042
1043 undecided_previous = -1;
1044 for (i = hunk_index - 1; i >= 0; i--)
80399aec 1045 if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
f6aa7ecc
JS
1046 undecided_previous = i;
1047 break;
1048 }
1049
1050 undecided_next = -1;
80399aec
JS
1051 for (i = hunk_index + 1; i < file_diff->hunk_nr; i++)
1052 if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
f6aa7ecc
JS
1053 undecided_next = i;
1054 break;
1055 }
1056
1057 /* Everything decided? */
1058 if (undecided_previous < 0 && undecided_next < 0 &&
1059 hunk->use != UNDECIDED_HUNK)
1060 break;
1061
1062 strbuf_reset(&s->buf);
25ea47af 1063 render_hunk(s, hunk, 0, colored, &s->buf);
f6aa7ecc
JS
1064 fputs(s->buf.buf, stdout);
1065
1066 strbuf_reset(&s->buf);
1067 if (undecided_previous >= 0)
1068 strbuf_addstr(&s->buf, ",k");
1069 if (hunk_index)
1070 strbuf_addstr(&s->buf, ",K");
1071 if (undecided_next >= 0)
1072 strbuf_addstr(&s->buf, ",j");
80399aec 1073 if (hunk_index + 1 < file_diff->hunk_nr)
f6aa7ecc 1074 strbuf_addstr(&s->buf, ",J");
9254bdfb 1075 if (file_diff->hunk_nr > 1)
d6cf8733 1076 strbuf_addstr(&s->buf, ",g,/");
510aeca1
JS
1077 if (hunk->splittable_into > 1)
1078 strbuf_addstr(&s->buf, ",s");
bcdd297b
JS
1079 if (hunk_index + 1 > file_diff->mode_change &&
1080 !file_diff->deleted)
1081 strbuf_addstr(&s->buf, ",e");
0ecd9d27
JS
1082
1083 if (file_diff->deleted)
1084 prompt_mode_type = PROMPT_DELETION;
1085 else if (file_diff->mode_change && !hunk_index)
1086 prompt_mode_type = PROMPT_MODE_CHANGE;
1087 else
1088 prompt_mode_type = PROMPT_HUNK;
1089
12c24cf8
JS
1090 color_fprintf(stdout, s->s.prompt_color,
1091 "(%"PRIuMAX"/%"PRIuMAX") ",
80399aec
JS
1092 (uintmax_t)hunk_index + 1,
1093 (uintmax_t)file_diff->hunk_nr);
12c24cf8 1094 color_fprintf(stdout, s->s.prompt_color,
0ecd9d27 1095 _(prompt_mode[prompt_mode_type]), s->buf.buf);
f6aa7ecc
JS
1096 fflush(stdout);
1097 if (strbuf_getline(&s->answer, stdin) == EOF)
1098 break;
1099 strbuf_trim_trailing_newline(&s->answer);
1100
1101 if (!s->answer.len)
1102 continue;
1103 ch = tolower(s->answer.buf[0]);
1104 if (ch == 'y') {
1105 hunk->use = USE_HUNK;
1106soft_increment:
1107 hunk_index = undecided_next < 0 ?
80399aec 1108 file_diff->hunk_nr : undecided_next;
f6aa7ecc
JS
1109 } else if (ch == 'n') {
1110 hunk->use = SKIP_HUNK;
1111 goto soft_increment;
1112 } else if (ch == 'a') {
80399aec
JS
1113 for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
1114 hunk = file_diff->hunk + hunk_index;
f6aa7ecc
JS
1115 if (hunk->use == UNDECIDED_HUNK)
1116 hunk->use = USE_HUNK;
1117 }
1118 } else if (ch == 'd') {
80399aec
JS
1119 for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
1120 hunk = file_diff->hunk + hunk_index;
f6aa7ecc
JS
1121 if (hunk->use == UNDECIDED_HUNK)
1122 hunk->use = SKIP_HUNK;
1123 }
7584dd3c
JS
1124 } else if (s->answer.buf[0] == 'K') {
1125 if (hunk_index)
1126 hunk_index--;
1127 else
1128 err(s, _("No previous hunk"));
1129 } else if (s->answer.buf[0] == 'J') {
80399aec 1130 if (hunk_index + 1 < file_diff->hunk_nr)
7584dd3c
JS
1131 hunk_index++;
1132 else
1133 err(s, _("No next hunk"));
1134 } else if (s->answer.buf[0] == 'k') {
1135 if (undecided_previous >= 0)
1136 hunk_index = undecided_previous;
1137 else
1138 err(s, _("No previous hunk"));
1139 } else if (s->answer.buf[0] == 'j') {
1140 if (undecided_next >= 0)
1141 hunk_index = undecided_next;
1142 else
1143 err(s, _("No next hunk"));
9254bdfb
JS
1144 } else if (s->answer.buf[0] == 'g') {
1145 char *pend;
1146 unsigned long response;
1147
1148 if (file_diff->hunk_nr < 2) {
1149 err(s, _("No other hunks to goto"));
1150 continue;
1151 }
1152 strbuf_remove(&s->answer, 0, 1);
1153 strbuf_trim(&s->answer);
1154 i = hunk_index - DISPLAY_HUNKS_LINES / 2;
1155 if (i < file_diff->mode_change)
1156 i = file_diff->mode_change;
1157 while (s->answer.len == 0) {
1158 i = display_hunks(s, file_diff, i);
1159 printf("%s", i < file_diff->hunk_nr ?
1160 _("go to which hunk (<ret> to see "
1161 "more)? ") : _("go to which hunk? "));
1162 fflush(stdout);
1163 if (strbuf_getline(&s->answer,
1164 stdin) == EOF)
1165 break;
1166 strbuf_trim_trailing_newline(&s->answer);
1167 }
1168
1169 strbuf_trim(&s->answer);
1170 response = strtoul(s->answer.buf, &pend, 10);
1171 if (*pend || pend == s->answer.buf)
1172 err(s, _("Invalid number: '%s'"),
1173 s->answer.buf);
1174 else if (0 < response && response <= file_diff->hunk_nr)
1175 hunk_index = response - 1;
1176 else
1177 err(s, Q_("Sorry, only %d hunk available.",
1178 "Sorry, only %d hunks available.",
1179 file_diff->hunk_nr),
1180 (int)file_diff->hunk_nr);
d6cf8733
JS
1181 } else if (s->answer.buf[0] == '/') {
1182 regex_t regex;
1183 int ret;
1184
1185 if (file_diff->hunk_nr < 2) {
1186 err(s, _("No other hunks to search"));
1187 continue;
1188 }
1189 strbuf_remove(&s->answer, 0, 1);
1190 strbuf_trim_trailing_newline(&s->answer);
1191 if (s->answer.len == 0) {
1192 printf("%s", _("search for regex? "));
1193 fflush(stdout);
1194 if (strbuf_getline(&s->answer,
1195 stdin) == EOF)
1196 break;
1197 strbuf_trim_trailing_newline(&s->answer);
1198 if (s->answer.len == 0)
1199 continue;
1200 }
1201 ret = regcomp(&regex, s->answer.buf,
1202 REG_EXTENDED | REG_NOSUB | REG_NEWLINE);
1203 if (ret) {
1204 char errbuf[1024];
1205
1206 regerror(ret, &regex, errbuf, sizeof(errbuf));
1207 err(s, _("Malformed search regexp %s: %s"),
1208 s->answer.buf, errbuf);
1209 continue;
1210 }
1211 i = hunk_index;
1212 for (;;) {
1213 /* render the hunk into a scratch buffer */
1214 render_hunk(s, file_diff->hunk + i, 0, 0,
1215 &s->buf);
1216 if (regexec(&regex, s->buf.buf, 0, NULL, 0)
1217 != REG_NOMATCH)
1218 break;
1219 i++;
1220 if (i == file_diff->hunk_nr)
1221 i = 0;
1222 if (i != hunk_index)
1223 continue;
1224 err(s, _("No hunk matches the given pattern"));
1225 break;
1226 }
1227 hunk_index = i;
510aeca1
JS
1228 } else if (s->answer.buf[0] == 's') {
1229 size_t splittable_into = hunk->splittable_into;
1230 if (splittable_into < 2)
1231 err(s, _("Sorry, cannot split this hunk"));
1232 else if (!split_hunk(s, file_diff,
1233 hunk - file_diff->hunk))
1234 color_fprintf_ln(stdout, s->s.header_color,
1235 _("Split into %d hunks."),
1236 (int)splittable_into);
bcdd297b
JS
1237 } else if (s->answer.buf[0] == 'e') {
1238 if (hunk_index + 1 == file_diff->mode_change)
1239 err(s, _("Sorry, cannot edit this hunk"));
1240 else if (edit_hunk_loop(s, file_diff, hunk) >= 0) {
1241 hunk->use = USE_HUNK;
1242 goto soft_increment;
1243 }
7584dd3c 1244 } else
12c24cf8
JS
1245 color_fprintf(stdout, s->s.help_color,
1246 _(help_patch_text));
f6aa7ecc
JS
1247 }
1248
1249 /* Any hunk to be used? */
80399aec
JS
1250 for (i = 0; i < file_diff->hunk_nr; i++)
1251 if (file_diff->hunk[i].use == USE_HUNK)
f6aa7ecc
JS
1252 break;
1253
80399aec 1254 if (i < file_diff->hunk_nr) {
f6aa7ecc
JS
1255 /* At least one hunk selected: apply */
1256 strbuf_reset(&s->buf);
bcdd297b 1257 reassemble_patch(s, file_diff, 0, &s->buf);
f6aa7ecc 1258
25ea47af 1259 discard_index(s->s.r->index);
f6aa7ecc
JS
1260 setup_child_process(s, &cp, "apply", "--cached", NULL);
1261 if (pipe_command(&cp, s->buf.buf, s->buf.len,
1262 NULL, 0, NULL, 0))
1263 error(_("'git apply --cached' failed"));
25ea47af
JS
1264 if (!repo_read_index(s->s.r))
1265 repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
f6aa7ecc
JS
1266 1, NULL, NULL, NULL);
1267 }
1268
1269 putchar('\n');
1270 return 0;
1271}
1272
1273int run_add_p(struct repository *r, const struct pathspec *ps)
1274{
25ea47af
JS
1275 struct add_p_state s = {
1276 { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
1277 };
80399aec 1278 size_t i;
25ea47af
JS
1279
1280 init_add_i_state(&s.s, r);
f6aa7ecc
JS
1281
1282 if (discard_index(r->index) < 0 || repo_read_index(r) < 0 ||
1283 repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
1284 NULL, NULL, NULL) < 0 ||
1285 parse_diff(&s, ps) < 0) {
1286 strbuf_release(&s.plain);
e3bd11b4 1287 strbuf_release(&s.colored);
f6aa7ecc
JS
1288 return -1;
1289 }
1290
80399aec
JS
1291 for (i = 0; i < s.file_diff_nr; i++)
1292 if (patch_update_file(&s, s.file_diff + i))
1293 break;
f6aa7ecc
JS
1294
1295 strbuf_release(&s.answer);
1296 strbuf_release(&s.buf);
1297 strbuf_release(&s.plain);
e3bd11b4 1298 strbuf_release(&s.colored);
f6aa7ecc
JS
1299 return 0;
1300}