]>
git.ipfire.org Git - thirdparty/git.git/blob - add-patch.c
2 #include "add-interactive.h"
4 #include "run-command.h"
5 #include "argv-array.h"
11 unsigned long old_offset
, old_count
, new_offset
, new_count
;
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.
17 size_t extra_start
, extra_end
, colored_extra_start
, colored_extra_end
;
21 size_t start
, end
, colored_start
, colored_end
;
22 enum { UNDECIDED_HUNK
= 0, SKIP_HUNK
, USE_HUNK
} use
;
23 struct hunk_header header
;
28 struct strbuf answer
, buf
;
31 struct strbuf plain
, colored
;
34 size_t hunk_nr
, hunk_alloc
;
37 static void err(struct add_p_state
*s
, const char *fmt
, ...)
42 fputs(s
->s
.error_color
, stderr
);
43 vfprintf(stderr
, fmt
, args
);
44 fputs(s
->s
.reset_color
, stderr
);
49 static void setup_child_process(struct add_p_state
*s
,
50 struct child_process
*cp
, ...)
56 while ((arg
= va_arg(ap
, const char *)))
57 argv_array_push(&cp
->args
, arg
);
61 argv_array_pushf(&cp
->env_array
,
62 INDEX_ENVIRONMENT
"=%s", s
->s
.r
->index_file
);
65 static int parse_range(const char **p
,
66 unsigned long *offset
, unsigned long *count
)
70 *offset
= strtoul(*p
, &pend
, 10);
78 *count
= strtoul(pend
+ 1, (char **)p
, 10);
79 return *p
== pend
+ 1 ? -1 : 0;
82 static int parse_hunk_header(struct add_p_state
*s
, struct hunk
*hunk
)
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
);
89 eol
= s
->plain
.buf
+ s
->plain
.len
;
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
);
99 hunk
->start
= eol
- s
->plain
.buf
+ (*eol
== '\n');
100 header
->extra_start
= p
- s
->plain
.buf
;
101 header
->extra_end
= hunk
->start
;
103 if (!s
->colored
.len
) {
104 header
->colored_extra_start
= header
->colored_extra_end
= 0;
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
);
112 eol
= s
->colored
.buf
+ s
->colored
.len
;
113 p
= memmem(line
, eol
- line
, "@@ -", 4);
115 return error(_("could not parse colored hunk header '%.*s'"),
116 (int)(eol
- line
), line
);
117 p
= memmem(p
+ 4, eol
- p
- 4, " @@", 3);
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
;
128 static int parse_diff(struct add_p_state
*s
, const struct pathspec
*ps
)
130 struct argv_array args
= ARGV_ARRAY_INIT
;
131 struct strbuf
*plain
= &s
->plain
, *colored
= NULL
;
132 struct child_process cp
= CHILD_PROCESS_INIT
;
133 char *p
, *pend
, *colored_p
= NULL
, *colored_pend
= NULL
;
134 size_t i
, color_arg_index
;
135 struct hunk
*hunk
= NULL
;
138 /* Use `--no-color` explicitly, just in case `diff.color = always`. */
139 argv_array_pushl(&args
, "diff-files", "-p", "--no-color", "--", NULL
);
140 color_arg_index
= args
.argc
- 2;
141 for (i
= 0; i
< ps
->nr
; i
++)
142 argv_array_push(&args
, ps
->items
[i
].original
);
144 setup_child_process(s
, &cp
, NULL
);
146 res
= capture_command(&cp
, plain
, 0);
148 argv_array_clear(&args
);
149 return error(_("could not parse diff"));
152 argv_array_clear(&args
);
155 strbuf_complete_line(plain
);
157 if (want_color_fd(1, -1)) {
158 struct child_process colored_cp
= CHILD_PROCESS_INIT
;
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
);
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
;
172 argv_array_clear(&args
);
176 pend
= p
+ plain
->len
;
178 char *eol
= memchr(p
, '\n', pend
- p
);
182 if (starts_with(p
, "diff ")) {
184 BUG("multi-file diff not yet handled");
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
, "@@ ")) {
191 ALLOC_GROW(s
->hunk
, s
->hunk_nr
,
193 hunk
= s
->hunk
+ s
->hunk_nr
- 1;
194 memset(hunk
, 0, sizeof(*hunk
));
196 hunk
->start
= p
- plain
->buf
;
198 hunk
->colored_start
= colored_p
- colored
->buf
;
200 if (parse_hunk_header(s
, hunk
) < 0)
204 p
= eol
== pend
? pend
: eol
+ 1;
205 hunk
->end
= p
- plain
->buf
;
208 char *colored_eol
= memchr(colored_p
, '\n',
209 colored_pend
- colored_p
);
211 colored_p
= colored_eol
+ 1;
213 colored_p
= colored_pend
;
215 hunk
->colored_end
= colored_p
- colored
->buf
;
222 static void render_hunk(struct add_p_state
*s
, struct hunk
*hunk
,
223 ssize_t delta
, int colored
, struct strbuf
*out
)
225 struct hunk_header
*header
= &hunk
->header
;
227 if (hunk
->header
.old_offset
!= 0 || hunk
->header
.new_offset
!= 0) {
229 * Generate the hunk header dynamically, except for special
230 * hunks (such as the diff header).
234 unsigned long old_offset
= header
->old_offset
;
235 unsigned long new_offset
= header
->new_offset
;
238 p
= s
->plain
.buf
+ header
->extra_start
;
239 len
= header
->extra_end
- header
->extra_start
;
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
;
249 strbuf_addf(out
, "@@ -%lu,%lu +%lu,%lu @@",
250 old_offset
, header
->old_count
,
251 new_offset
, header
->new_count
);
253 strbuf_add(out
, p
, len
);
255 strbuf_addf(out
, "%s\n", GIT_COLOR_RESET
);
257 strbuf_addch(out
, '\n');
261 strbuf_add(out
, s
->colored
.buf
+ hunk
->colored_start
,
262 hunk
->colored_end
- hunk
->colored_start
);
264 strbuf_add(out
, s
->plain
.buf
+ hunk
->start
,
265 hunk
->end
- hunk
->start
);
268 static void reassemble_patch(struct add_p_state
*s
, struct strbuf
*out
)
274 render_hunk(s
, &s
->head
, 0, 0, out
);
276 for (i
= 0; i
< s
->hunk_nr
; i
++) {
278 if (hunk
->use
!= USE_HUNK
)
279 delta
+= hunk
->header
.old_count
280 - hunk
->header
.new_count
;
282 render_hunk(s
, hunk
, delta
, 0, out
);
286 static const char help_patch_text
[] =
287 N_("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"
297 static int patch_update_file(struct add_p_state
*s
)
299 size_t hunk_index
= 0;
300 ssize_t i
, undecided_previous
, undecided_next
;
303 struct child_process cp
= CHILD_PROCESS_INIT
;
304 int colored
= !!s
->colored
.len
;
309 strbuf_reset(&s
->buf
);
310 render_hunk(s
, &s
->head
, 0, colored
, &s
->buf
);
311 fputs(s
->buf
.buf
, stdout
);
313 if (hunk_index
>= s
->hunk_nr
)
315 hunk
= s
->hunk
+ hunk_index
;
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
;
325 for (i
= hunk_index
+ 1; i
< s
->hunk_nr
; i
++)
326 if (s
->hunk
[i
].use
== UNDECIDED_HUNK
) {
331 /* Everything decided? */
332 if (undecided_previous
< 0 && undecided_next
< 0 &&
333 hunk
->use
!= UNDECIDED_HUNK
)
336 strbuf_reset(&s
->buf
);
337 render_hunk(s
, hunk
, 0, colored
, &s
->buf
);
338 fputs(s
->buf
.buf
, stdout
);
340 strbuf_reset(&s
->buf
);
341 if (undecided_previous
>= 0)
342 strbuf_addstr(&s
->buf
, ",k");
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");
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,?]? "),
356 if (strbuf_getline(&s
->answer
, stdin
) == EOF
)
358 strbuf_trim_trailing_newline(&s
->answer
);
362 ch
= tolower(s
->answer
.buf
[0]);
364 hunk
->use
= USE_HUNK
;
366 hunk_index
= undecided_next
< 0 ?
367 s
->hunk_nr
: undecided_next
;
368 } else if (ch
== 'n') {
369 hunk
->use
= SKIP_HUNK
;
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
;
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
;
383 } else if (s
->answer
.buf
[0] == 'K') {
387 err(s
, _("No previous hunk"));
388 } else if (s
->answer
.buf
[0] == 'J') {
389 if (hunk_index
+ 1 < s
->hunk_nr
)
392 err(s
, _("No next hunk"));
393 } else if (s
->answer
.buf
[0] == 'k') {
394 if (undecided_previous
>= 0)
395 hunk_index
= undecided_previous
;
397 err(s
, _("No previous hunk"));
398 } else if (s
->answer
.buf
[0] == 'j') {
399 if (undecided_next
>= 0)
400 hunk_index
= undecided_next
;
402 err(s
, _("No next hunk"));
404 color_fprintf(stdout
, s
->s
.help_color
,
408 /* Any hunk to be used? */
409 for (i
= 0; i
< s
->hunk_nr
; i
++)
410 if (s
->hunk
[i
].use
== USE_HUNK
)
413 if (i
< s
->hunk_nr
) {
414 /* At least one hunk selected: apply */
415 strbuf_reset(&s
->buf
);
416 reassemble_patch(s
, &s
->buf
);
418 discard_index(s
->s
.r
->index
);
419 setup_child_process(s
, &cp
, "apply", "--cached", NULL
);
420 if (pipe_command(&cp
, s
->buf
.buf
, s
->buf
.len
,
422 error(_("'git apply --cached' failed"));
423 if (!repo_read_index(s
->s
.r
))
424 repo_refresh_and_write_index(s
->s
.r
, REFRESH_QUIET
, 0,
425 1, NULL
, NULL
, NULL
);
432 int run_add_p(struct repository
*r
, const struct pathspec
*ps
)
434 struct add_p_state s
= {
435 { r
}, STRBUF_INIT
, STRBUF_INIT
, STRBUF_INIT
, STRBUF_INIT
438 init_add_i_state(&s
.s
, r
);
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
);
445 strbuf_release(&s
.colored
);
450 patch_update_file(&s
);
452 strbuf_release(&s
.answer
);
453 strbuf_release(&s
.buf
);
454 strbuf_release(&s
.plain
);
455 strbuf_release(&s
.colored
);