]> git.ipfire.org Git - thirdparty/git.git/blob - add-patch.c
2007f55e0431cfc43c69827105dd87ebf7d915d4
[thirdparty/git.git] / add-patch.c
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"
7 #include "color.h"
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 };
19
20 struct hunk {
21 size_t start, end, colored_start, colored_end;
22 enum { UNDECIDED_HUNK = 0, SKIP_HUNK, USE_HUNK } use;
23 struct hunk_header header;
24 };
25
26 struct add_p_state {
27 struct add_i_state s;
28 struct strbuf answer, buf;
29
30 /* parsed diff */
31 struct strbuf plain, colored;
32 struct file_diff {
33 struct hunk head;
34 struct hunk *hunk;
35 size_t hunk_nr, hunk_alloc;
36 unsigned deleted:1, mode_change:1;
37 } *file_diff;
38 size_t file_diff_nr;
39 };
40
41 static void err(struct add_p_state *s, const char *fmt, ...)
42 {
43 va_list args;
44
45 va_start(args, fmt);
46 fputs(s->s.error_color, stderr);
47 vfprintf(stderr, fmt, args);
48 fputs(s->s.reset_color, stderr);
49 fputc('\n', stderr);
50 va_end(args);
51 }
52
53 static void setup_child_process(struct add_p_state *s,
54 struct child_process *cp, ...)
55 {
56 va_list ap;
57 const char *arg;
58
59 va_start(ap, cp);
60 while ((arg = va_arg(ap, const char *)))
61 argv_array_push(&cp->args, arg);
62 va_end(ap);
63
64 cp->git_cmd = 1;
65 argv_array_pushf(&cp->env_array,
66 INDEX_ENVIRONMENT "=%s", s->s.r->index_file);
67 }
68
69 static int parse_range(const char **p,
70 unsigned long *offset, unsigned long *count)
71 {
72 char *pend;
73
74 *offset = strtoul(*p, &pend, 10);
75 if (pend == *p)
76 return -1;
77 if (*pend != ',') {
78 *count = 1;
79 *p = pend;
80 return 0;
81 }
82 *count = strtoul(pend + 1, (char **)p, 10);
83 return *p == pend + 1 ? -1 : 0;
84 }
85
86 static int parse_hunk_header(struct add_p_state *s, struct hunk *hunk)
87 {
88 struct hunk_header *header = &hunk->header;
89 const char *line = s->plain.buf + hunk->start, *p = line;
90 char *eol = memchr(p, '\n', s->plain.len - hunk->start);
91
92 if (!eol)
93 eol = s->plain.buf + s->plain.len;
94
95 if (!skip_prefix(p, "@@ -", &p) ||
96 parse_range(&p, &header->old_offset, &header->old_count) < 0 ||
97 !skip_prefix(p, " +", &p) ||
98 parse_range(&p, &header->new_offset, &header->new_count) < 0 ||
99 !skip_prefix(p, " @@", &p))
100 return error(_("could not parse hunk header '%.*s'"),
101 (int)(eol - line), line);
102
103 hunk->start = eol - s->plain.buf + (*eol == '\n');
104 header->extra_start = p - s->plain.buf;
105 header->extra_end = hunk->start;
106
107 if (!s->colored.len) {
108 header->colored_extra_start = header->colored_extra_end = 0;
109 return 0;
110 }
111
112 /* Now find the extra text in the colored diff */
113 line = s->colored.buf + hunk->colored_start;
114 eol = memchr(line, '\n', s->colored.len - hunk->colored_start);
115 if (!eol)
116 eol = s->colored.buf + s->colored.len;
117 p = memmem(line, eol - line, "@@ -", 4);
118 if (!p)
119 return error(_("could not parse colored hunk header '%.*s'"),
120 (int)(eol - line), line);
121 p = memmem(p + 4, eol - p - 4, " @@", 3);
122 if (!p)
123 return error(_("could not parse colored hunk header '%.*s'"),
124 (int)(eol - line), line);
125 hunk->colored_start = eol - s->colored.buf + (*eol == '\n');
126 header->colored_extra_start = p + 3 - s->colored.buf;
127 header->colored_extra_end = hunk->colored_start;
128
129 return 0;
130 }
131
132 static int is_octal(const char *p, size_t len)
133 {
134 if (!len)
135 return 0;
136
137 while (len--)
138 if (*p < '0' || *(p++) > '7')
139 return 0;
140 return 1;
141 }
142
143 static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
144 {
145 struct argv_array args = ARGV_ARRAY_INIT;
146 struct strbuf *plain = &s->plain, *colored = NULL;
147 struct child_process cp = CHILD_PROCESS_INIT;
148 char *p, *pend, *colored_p = NULL, *colored_pend = NULL;
149 size_t file_diff_alloc = 0, i, color_arg_index;
150 struct file_diff *file_diff = NULL;
151 struct hunk *hunk = NULL;
152 int res;
153
154 /* Use `--no-color` explicitly, just in case `diff.color = always`. */
155 argv_array_pushl(&args, "diff-files", "-p", "--no-color", "--", NULL);
156 color_arg_index = args.argc - 2;
157 for (i = 0; i < ps->nr; i++)
158 argv_array_push(&args, ps->items[i].original);
159
160 setup_child_process(s, &cp, NULL);
161 cp.argv = args.argv;
162 res = capture_command(&cp, plain, 0);
163 if (res) {
164 argv_array_clear(&args);
165 return error(_("could not parse diff"));
166 }
167 if (!plain->len) {
168 argv_array_clear(&args);
169 return 0;
170 }
171 strbuf_complete_line(plain);
172
173 if (want_color_fd(1, -1)) {
174 struct child_process colored_cp = CHILD_PROCESS_INIT;
175
176 setup_child_process(s, &colored_cp, NULL);
177 xsnprintf((char *)args.argv[color_arg_index], 8, "--color");
178 colored_cp.argv = args.argv;
179 colored = &s->colored;
180 res = capture_command(&colored_cp, colored, 0);
181 argv_array_clear(&args);
182 if (res)
183 return error(_("could not parse colored diff"));
184 strbuf_complete_line(colored);
185 colored_p = colored->buf;
186 colored_pend = colored_p + colored->len;
187 }
188 argv_array_clear(&args);
189
190 /* parse files and hunks */
191 p = plain->buf;
192 pend = p + plain->len;
193 while (p != pend) {
194 char *eol = memchr(p, '\n', pend - p);
195 const char *deleted = NULL, *mode_change = NULL;
196
197 if (!eol)
198 eol = pend;
199
200 if (starts_with(p, "diff ")) {
201 s->file_diff_nr++;
202 ALLOC_GROW(s->file_diff, s->file_diff_nr,
203 file_diff_alloc);
204 file_diff = s->file_diff + s->file_diff_nr - 1;
205 memset(file_diff, 0, sizeof(*file_diff));
206 hunk = &file_diff->head;
207 hunk->start = p - plain->buf;
208 if (colored_p)
209 hunk->colored_start = colored_p - colored->buf;
210 } else if (p == plain->buf)
211 BUG("diff starts with unexpected line:\n"
212 "%.*s\n", (int)(eol - p), p);
213 else if (file_diff->deleted)
214 ; /* keep the rest of the file in a single "hunk" */
215 else if (starts_with(p, "@@ ") ||
216 (hunk == &file_diff->head &&
217 skip_prefix(p, "deleted file", &deleted))) {
218 file_diff->hunk_nr++;
219 ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr,
220 file_diff->hunk_alloc);
221 hunk = file_diff->hunk + file_diff->hunk_nr - 1;
222 memset(hunk, 0, sizeof(*hunk));
223
224 hunk->start = p - plain->buf;
225 if (colored)
226 hunk->colored_start = colored_p - colored->buf;
227
228 if (deleted)
229 file_diff->deleted = 1;
230 else if (parse_hunk_header(s, hunk) < 0)
231 return -1;
232 } else if (hunk == &file_diff->head &&
233 skip_prefix(p, "old mode ", &mode_change) &&
234 is_octal(mode_change, eol - mode_change)) {
235 if (file_diff->mode_change)
236 BUG("double mode change?\n\n%.*s",
237 (int)(eol - plain->buf), plain->buf);
238 if (file_diff->hunk_nr++)
239 BUG("mode change in the middle?\n\n%.*s",
240 (int)(eol - plain->buf), plain->buf);
241
242 /*
243 * Do *not* change `hunk`: the mode change pseudo-hunk
244 * is _part of_ the header "hunk".
245 */
246 file_diff->mode_change = 1;
247 ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr,
248 file_diff->hunk_alloc);
249 memset(file_diff->hunk, 0, sizeof(struct hunk));
250 file_diff->hunk->start = p - plain->buf;
251 if (colored_p)
252 file_diff->hunk->colored_start =
253 colored_p - colored->buf;
254 } else if (hunk == &file_diff->head &&
255 skip_prefix(p, "new mode ", &mode_change) &&
256 is_octal(mode_change, eol - mode_change)) {
257
258 /*
259 * Extend the "mode change" pseudo-hunk to include also
260 * the "new mode" line.
261 */
262 if (!file_diff->mode_change)
263 BUG("'new mode' without 'old mode'?\n\n%.*s",
264 (int)(eol - plain->buf), plain->buf);
265 if (file_diff->hunk_nr != 1)
266 BUG("mode change in the middle?\n\n%.*s",
267 (int)(eol - plain->buf), plain->buf);
268 if (p - plain->buf != file_diff->hunk->end)
269 BUG("'new mode' does not immediately follow "
270 "'old mode'?\n\n%.*s",
271 (int)(eol - plain->buf), plain->buf);
272 }
273
274 if (file_diff->deleted && file_diff->mode_change)
275 BUG("diff contains delete *and* a mode change?!?\n%.*s",
276 (int)(eol - (plain->buf + file_diff->head.start)),
277 plain->buf + file_diff->head.start);
278
279 p = eol == pend ? pend : eol + 1;
280 hunk->end = p - plain->buf;
281
282 if (colored) {
283 char *colored_eol = memchr(colored_p, '\n',
284 colored_pend - colored_p);
285 if (colored_eol)
286 colored_p = colored_eol + 1;
287 else
288 colored_p = colored_pend;
289
290 hunk->colored_end = colored_p - colored->buf;
291 }
292
293 if (mode_change) {
294 if (file_diff->hunk_nr != 1)
295 BUG("mode change in hunk #%d???",
296 (int)file_diff->hunk_nr);
297 /* Adjust the end of the "mode change" pseudo-hunk */
298 file_diff->hunk->end = hunk->end;
299 if (colored)
300 file_diff->hunk->colored_end = hunk->colored_end;
301 }
302 }
303
304 return 0;
305 }
306
307 static void render_hunk(struct add_p_state *s, struct hunk *hunk,
308 ssize_t delta, int colored, struct strbuf *out)
309 {
310 struct hunk_header *header = &hunk->header;
311
312 if (hunk->header.old_offset != 0 || hunk->header.new_offset != 0) {
313 /*
314 * Generate the hunk header dynamically, except for special
315 * hunks (such as the diff header).
316 */
317 const char *p;
318 size_t len;
319 unsigned long old_offset = header->old_offset;
320 unsigned long new_offset = header->new_offset;
321
322 if (!colored) {
323 p = s->plain.buf + header->extra_start;
324 len = header->extra_end - header->extra_start;
325 } else {
326 strbuf_addstr(out, s->s.fraginfo_color);
327 p = s->colored.buf + header->colored_extra_start;
328 len = header->colored_extra_end
329 - header->colored_extra_start;
330 }
331
332 new_offset += delta;
333
334 strbuf_addf(out, "@@ -%lu,%lu +%lu,%lu @@",
335 old_offset, header->old_count,
336 new_offset, header->new_count);
337 if (len)
338 strbuf_add(out, p, len);
339 else if (colored)
340 strbuf_addf(out, "%s\n", GIT_COLOR_RESET);
341 else
342 strbuf_addch(out, '\n');
343 }
344
345 if (colored)
346 strbuf_add(out, s->colored.buf + hunk->colored_start,
347 hunk->colored_end - hunk->colored_start);
348 else
349 strbuf_add(out, s->plain.buf + hunk->start,
350 hunk->end - hunk->start);
351 }
352
353 static void render_diff_header(struct add_p_state *s,
354 struct file_diff *file_diff, int colored,
355 struct strbuf *out)
356 {
357 /*
358 * If there was a mode change, the first hunk is a pseudo hunk that
359 * corresponds to the mode line in the header. If the user did not want
360 * to stage that "hunk", we actually have to cut it out from the header.
361 */
362 int skip_mode_change =
363 file_diff->mode_change && file_diff->hunk->use != USE_HUNK;
364 struct hunk *head = &file_diff->head, *first = file_diff->hunk;
365
366 if (!skip_mode_change) {
367 render_hunk(s, head, 0, colored, out);
368 return;
369 }
370
371 if (colored) {
372 const char *p = s->colored.buf;
373
374 strbuf_add(out, p + head->colored_start,
375 first->colored_start - head->colored_start);
376 strbuf_add(out, p + first->colored_end,
377 head->colored_end - first->colored_end);
378 } else {
379 const char *p = s->plain.buf;
380
381 strbuf_add(out, p + head->start, first->start - head->start);
382 strbuf_add(out, p + first->end, head->end - first->end);
383 }
384 }
385
386 static void reassemble_patch(struct add_p_state *s,
387 struct file_diff *file_diff, struct strbuf *out)
388 {
389 struct hunk *hunk;
390 size_t i;
391 ssize_t delta = 0;
392
393 render_diff_header(s, file_diff, 0, out);
394
395 for (i = file_diff->mode_change; i < file_diff->hunk_nr; i++) {
396 hunk = file_diff->hunk + i;
397 if (hunk->use != USE_HUNK)
398 delta += hunk->header.old_count
399 - hunk->header.new_count;
400 else
401 render_hunk(s, hunk, delta, 0, out);
402 }
403 }
404
405 static const char help_patch_text[] =
406 N_("y - stage this hunk\n"
407 "n - do not stage this hunk\n"
408 "a - stage this and all the remaining hunks\n"
409 "d - do not stage this hunk nor any of the remaining hunks\n"
410 "j - leave this hunk undecided, see next undecided hunk\n"
411 "J - leave this hunk undecided, see next hunk\n"
412 "k - leave this hunk undecided, see previous undecided hunk\n"
413 "K - leave this hunk undecided, see previous hunk\n"
414 "? - print help\n");
415
416 static int patch_update_file(struct add_p_state *s,
417 struct file_diff *file_diff)
418 {
419 size_t hunk_index = 0;
420 ssize_t i, undecided_previous, undecided_next;
421 struct hunk *hunk;
422 char ch;
423 struct child_process cp = CHILD_PROCESS_INIT;
424 int colored = !!s->colored.len;
425
426 if (!file_diff->hunk_nr)
427 return 0;
428
429 strbuf_reset(&s->buf);
430 render_diff_header(s, file_diff, colored, &s->buf);
431 fputs(s->buf.buf, stdout);
432 for (;;) {
433 if (hunk_index >= file_diff->hunk_nr)
434 hunk_index = 0;
435 hunk = file_diff->hunk + hunk_index;
436
437 undecided_previous = -1;
438 for (i = hunk_index - 1; i >= 0; i--)
439 if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
440 undecided_previous = i;
441 break;
442 }
443
444 undecided_next = -1;
445 for (i = hunk_index + 1; i < file_diff->hunk_nr; i++)
446 if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
447 undecided_next = i;
448 break;
449 }
450
451 /* Everything decided? */
452 if (undecided_previous < 0 && undecided_next < 0 &&
453 hunk->use != UNDECIDED_HUNK)
454 break;
455
456 strbuf_reset(&s->buf);
457 render_hunk(s, hunk, 0, colored, &s->buf);
458 fputs(s->buf.buf, stdout);
459
460 strbuf_reset(&s->buf);
461 if (undecided_previous >= 0)
462 strbuf_addstr(&s->buf, ",k");
463 if (hunk_index)
464 strbuf_addstr(&s->buf, ",K");
465 if (undecided_next >= 0)
466 strbuf_addstr(&s->buf, ",j");
467 if (hunk_index + 1 < file_diff->hunk_nr)
468 strbuf_addstr(&s->buf, ",J");
469 color_fprintf(stdout, s->s.prompt_color,
470 "(%"PRIuMAX"/%"PRIuMAX") ",
471 (uintmax_t)hunk_index + 1,
472 (uintmax_t)file_diff->hunk_nr);
473 color_fprintf(stdout, s->s.prompt_color,
474 _("Stage this hunk [y,n,a,d%s,?]? "),
475 s->buf.buf);
476 fflush(stdout);
477 if (strbuf_getline(&s->answer, stdin) == EOF)
478 break;
479 strbuf_trim_trailing_newline(&s->answer);
480
481 if (!s->answer.len)
482 continue;
483 ch = tolower(s->answer.buf[0]);
484 if (ch == 'y') {
485 hunk->use = USE_HUNK;
486 soft_increment:
487 hunk_index = undecided_next < 0 ?
488 file_diff->hunk_nr : undecided_next;
489 } else if (ch == 'n') {
490 hunk->use = SKIP_HUNK;
491 goto soft_increment;
492 } else if (ch == 'a') {
493 for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
494 hunk = file_diff->hunk + hunk_index;
495 if (hunk->use == UNDECIDED_HUNK)
496 hunk->use = USE_HUNK;
497 }
498 } else if (ch == 'd') {
499 for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
500 hunk = file_diff->hunk + hunk_index;
501 if (hunk->use == UNDECIDED_HUNK)
502 hunk->use = SKIP_HUNK;
503 }
504 } else if (s->answer.buf[0] == 'K') {
505 if (hunk_index)
506 hunk_index--;
507 else
508 err(s, _("No previous hunk"));
509 } else if (s->answer.buf[0] == 'J') {
510 if (hunk_index + 1 < file_diff->hunk_nr)
511 hunk_index++;
512 else
513 err(s, _("No next hunk"));
514 } else if (s->answer.buf[0] == 'k') {
515 if (undecided_previous >= 0)
516 hunk_index = undecided_previous;
517 else
518 err(s, _("No previous hunk"));
519 } else if (s->answer.buf[0] == 'j') {
520 if (undecided_next >= 0)
521 hunk_index = undecided_next;
522 else
523 err(s, _("No next hunk"));
524 } else
525 color_fprintf(stdout, s->s.help_color,
526 _(help_patch_text));
527 }
528
529 /* Any hunk to be used? */
530 for (i = 0; i < file_diff->hunk_nr; i++)
531 if (file_diff->hunk[i].use == USE_HUNK)
532 break;
533
534 if (i < file_diff->hunk_nr) {
535 /* At least one hunk selected: apply */
536 strbuf_reset(&s->buf);
537 reassemble_patch(s, file_diff, &s->buf);
538
539 discard_index(s->s.r->index);
540 setup_child_process(s, &cp, "apply", "--cached", NULL);
541 if (pipe_command(&cp, s->buf.buf, s->buf.len,
542 NULL, 0, NULL, 0))
543 error(_("'git apply --cached' failed"));
544 if (!repo_read_index(s->s.r))
545 repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
546 1, NULL, NULL, NULL);
547 }
548
549 putchar('\n');
550 return 0;
551 }
552
553 int run_add_p(struct repository *r, const struct pathspec *ps)
554 {
555 struct add_p_state s = {
556 { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
557 };
558 size_t i;
559
560 init_add_i_state(&s.s, r);
561
562 if (discard_index(r->index) < 0 || repo_read_index(r) < 0 ||
563 repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
564 NULL, NULL, NULL) < 0 ||
565 parse_diff(&s, ps) < 0) {
566 strbuf_release(&s.plain);
567 strbuf_release(&s.colored);
568 return -1;
569 }
570
571 for (i = 0; i < s.file_diff_nr; i++)
572 if (patch_update_file(&s, s.file_diff + i))
573 break;
574
575 strbuf_release(&s.answer);
576 strbuf_release(&s.buf);
577 strbuf_release(&s.plain);
578 strbuf_release(&s.colored);
579 return 0;
580 }