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