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