]> git.ipfire.org Git - thirdparty/git.git/blame - add-patch.c
built-in add -p: show colored hunks by default
[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"
f6aa7ecc
JS
8
9struct hunk {
e3bd11b4 10 size_t start, end, colored_start, colored_end;
f6aa7ecc
JS
11 enum { UNDECIDED_HUNK = 0, SKIP_HUNK, USE_HUNK } use;
12};
13
14struct add_p_state {
15 struct repository *r;
16 struct strbuf answer, buf;
17
18 /* parsed diff */
e3bd11b4 19 struct strbuf plain, colored;
f6aa7ecc
JS
20 struct hunk head;
21 struct hunk *hunk;
22 size_t hunk_nr, hunk_alloc;
23};
24
25static void setup_child_process(struct add_p_state *s,
26 struct child_process *cp, ...)
27{
28 va_list ap;
29 const char *arg;
30
31 va_start(ap, cp);
32 while ((arg = va_arg(ap, const char *)))
33 argv_array_push(&cp->args, arg);
34 va_end(ap);
35
36 cp->git_cmd = 1;
37 argv_array_pushf(&cp->env_array,
38 INDEX_ENVIRONMENT "=%s", s->r->index_file);
39}
40
41static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
42{
e3bd11b4
JS
43 struct argv_array args = ARGV_ARRAY_INIT;
44 struct strbuf *plain = &s->plain, *colored = NULL;
f6aa7ecc 45 struct child_process cp = CHILD_PROCESS_INIT;
e3bd11b4
JS
46 char *p, *pend, *colored_p = NULL, *colored_pend = NULL;
47 size_t i, color_arg_index;
f6aa7ecc
JS
48 struct hunk *hunk = NULL;
49 int res;
50
51 /* Use `--no-color` explicitly, just in case `diff.color = always`. */
e3bd11b4
JS
52 argv_array_pushl(&args, "diff-files", "-p", "--no-color", "--", NULL);
53 color_arg_index = args.argc - 2;
f6aa7ecc 54 for (i = 0; i < ps->nr; i++)
e3bd11b4 55 argv_array_push(&args, ps->items[i].original);
f6aa7ecc 56
e3bd11b4
JS
57 setup_child_process(s, &cp, NULL);
58 cp.argv = args.argv;
f6aa7ecc 59 res = capture_command(&cp, plain, 0);
e3bd11b4
JS
60 if (res) {
61 argv_array_clear(&args);
f6aa7ecc 62 return error(_("could not parse diff"));
e3bd11b4
JS
63 }
64 if (!plain->len) {
65 argv_array_clear(&args);
f6aa7ecc 66 return 0;
e3bd11b4 67 }
f6aa7ecc
JS
68 strbuf_complete_line(plain);
69
e3bd11b4
JS
70 if (want_color_fd(1, -1)) {
71 struct child_process colored_cp = CHILD_PROCESS_INIT;
72
73 setup_child_process(s, &colored_cp, NULL);
74 xsnprintf((char *)args.argv[color_arg_index], 8, "--color");
75 colored_cp.argv = args.argv;
76 colored = &s->colored;
77 res = capture_command(&colored_cp, colored, 0);
78 argv_array_clear(&args);
79 if (res)
80 return error(_("could not parse colored diff"));
81 strbuf_complete_line(colored);
82 colored_p = colored->buf;
83 colored_pend = colored_p + colored->len;
84 }
85 argv_array_clear(&args);
86
f6aa7ecc
JS
87 /* parse hunks */
88 p = plain->buf;
89 pend = p + plain->len;
90 while (p != pend) {
91 char *eol = memchr(p, '\n', pend - p);
92 if (!eol)
93 eol = pend;
94
95 if (starts_with(p, "diff ")) {
96 if (p != plain->buf)
97 BUG("multi-file diff not yet handled");
98 hunk = &s->head;
99 } else if (p == plain->buf)
100 BUG("diff starts with unexpected line:\n"
101 "%.*s\n", (int)(eol - p), p);
102 else if (starts_with(p, "@@ ")) {
103 s->hunk_nr++;
104 ALLOC_GROW(s->hunk, s->hunk_nr,
105 s->hunk_alloc);
106 hunk = s->hunk + s->hunk_nr - 1;
107 memset(hunk, 0, sizeof(*hunk));
108
109 hunk->start = p - plain->buf;
e3bd11b4
JS
110 if (colored)
111 hunk->colored_start = colored_p - colored->buf;
f6aa7ecc
JS
112 }
113
114 p = eol == pend ? pend : eol + 1;
115 hunk->end = p - plain->buf;
e3bd11b4
JS
116
117 if (colored) {
118 char *colored_eol = memchr(colored_p, '\n',
119 colored_pend - colored_p);
120 if (colored_eol)
121 colored_p = colored_eol + 1;
122 else
123 colored_p = colored_pend;
124
125 hunk->colored_end = colored_p - colored->buf;
126 }
f6aa7ecc
JS
127 }
128
129 return 0;
130}
131
132static void render_hunk(struct add_p_state *s, struct hunk *hunk,
e3bd11b4 133 int colored, struct strbuf *out)
f6aa7ecc 134{
e3bd11b4
JS
135 if (colored)
136 strbuf_add(out, s->colored.buf + hunk->colored_start,
137 hunk->colored_end - hunk->colored_start);
138 else
139 strbuf_add(out, s->plain.buf + hunk->start,
140 hunk->end - hunk->start);
f6aa7ecc
JS
141}
142
143static void reassemble_patch(struct add_p_state *s, struct strbuf *out)
144{
145 struct hunk *hunk;
146 size_t i;
147
e3bd11b4 148 render_hunk(s, &s->head, 0, out);
f6aa7ecc
JS
149
150 for (i = 0; i < s->hunk_nr; i++) {
151 hunk = s->hunk + i;
152 if (hunk->use == USE_HUNK)
e3bd11b4 153 render_hunk(s, hunk, 0, out);
f6aa7ecc
JS
154 }
155}
156
157static const char help_patch_text[] =
158N_("y - stage this hunk\n"
159 "n - do not stage this hunk\n"
160 "a - stage this and all the remaining hunks\n"
161 "d - do not stage this hunk nor any of the remaining hunks\n"
162 "j - leave this hunk undecided, see next undecided hunk\n"
163 "J - leave this hunk undecided, see next hunk\n"
164 "k - leave this hunk undecided, see previous undecided hunk\n"
165 "K - leave this hunk undecided, see previous hunk\n"
166 "? - print help\n");
167
168static int patch_update_file(struct add_p_state *s)
169{
170 size_t hunk_index = 0;
171 ssize_t i, undecided_previous, undecided_next;
172 struct hunk *hunk;
173 char ch;
174 struct child_process cp = CHILD_PROCESS_INIT;
e3bd11b4 175 int colored = !!s->colored.len;
f6aa7ecc
JS
176
177 if (!s->hunk_nr)
178 return 0;
179
180 strbuf_reset(&s->buf);
e3bd11b4 181 render_hunk(s, &s->head, colored, &s->buf);
f6aa7ecc
JS
182 fputs(s->buf.buf, stdout);
183 for (;;) {
184 if (hunk_index >= s->hunk_nr)
185 hunk_index = 0;
186 hunk = s->hunk + hunk_index;
187
188 undecided_previous = -1;
189 for (i = hunk_index - 1; i >= 0; i--)
190 if (s->hunk[i].use == UNDECIDED_HUNK) {
191 undecided_previous = i;
192 break;
193 }
194
195 undecided_next = -1;
196 for (i = hunk_index + 1; i < s->hunk_nr; i++)
197 if (s->hunk[i].use == UNDECIDED_HUNK) {
198 undecided_next = i;
199 break;
200 }
201
202 /* Everything decided? */
203 if (undecided_previous < 0 && undecided_next < 0 &&
204 hunk->use != UNDECIDED_HUNK)
205 break;
206
207 strbuf_reset(&s->buf);
e3bd11b4 208 render_hunk(s, hunk, colored, &s->buf);
f6aa7ecc
JS
209 fputs(s->buf.buf, stdout);
210
211 strbuf_reset(&s->buf);
212 if (undecided_previous >= 0)
213 strbuf_addstr(&s->buf, ",k");
214 if (hunk_index)
215 strbuf_addstr(&s->buf, ",K");
216 if (undecided_next >= 0)
217 strbuf_addstr(&s->buf, ",j");
218 if (hunk_index + 1 < s->hunk_nr)
219 strbuf_addstr(&s->buf, ",J");
220 printf("(%"PRIuMAX"/%"PRIuMAX") ",
221 (uintmax_t)hunk_index + 1, (uintmax_t)s->hunk_nr);
222 printf(_("Stage this hunk [y,n,a,d%s,?]? "), s->buf.buf);
223 fflush(stdout);
224 if (strbuf_getline(&s->answer, stdin) == EOF)
225 break;
226 strbuf_trim_trailing_newline(&s->answer);
227
228 if (!s->answer.len)
229 continue;
230 ch = tolower(s->answer.buf[0]);
231 if (ch == 'y') {
232 hunk->use = USE_HUNK;
233soft_increment:
234 hunk_index = undecided_next < 0 ?
235 s->hunk_nr : undecided_next;
236 } else if (ch == 'n') {
237 hunk->use = SKIP_HUNK;
238 goto soft_increment;
239 } else if (ch == 'a') {
240 for (; hunk_index < s->hunk_nr; hunk_index++) {
241 hunk = s->hunk + hunk_index;
242 if (hunk->use == UNDECIDED_HUNK)
243 hunk->use = USE_HUNK;
244 }
245 } else if (ch == 'd') {
246 for (; hunk_index < s->hunk_nr; hunk_index++) {
247 hunk = s->hunk + hunk_index;
248 if (hunk->use == UNDECIDED_HUNK)
249 hunk->use = SKIP_HUNK;
250 }
251 } else if (hunk_index && s->answer.buf[0] == 'K')
252 hunk_index--;
253 else if (hunk_index + 1 < s->hunk_nr &&
254 s->answer.buf[0] == 'J')
255 hunk_index++;
256 else if (undecided_previous >= 0 &&
257 s->answer.buf[0] == 'k')
258 hunk_index = undecided_previous;
259 else if (undecided_next >= 0 && s->answer.buf[0] == 'j')
260 hunk_index = undecided_next;
261 else
262 puts(_(help_patch_text));
263 }
264
265 /* Any hunk to be used? */
266 for (i = 0; i < s->hunk_nr; i++)
267 if (s->hunk[i].use == USE_HUNK)
268 break;
269
270 if (i < s->hunk_nr) {
271 /* At least one hunk selected: apply */
272 strbuf_reset(&s->buf);
273 reassemble_patch(s, &s->buf);
274
275 discard_index(s->r->index);
276 setup_child_process(s, &cp, "apply", "--cached", NULL);
277 if (pipe_command(&cp, s->buf.buf, s->buf.len,
278 NULL, 0, NULL, 0))
279 error(_("'git apply --cached' failed"));
280 if (!repo_read_index(s->r))
281 repo_refresh_and_write_index(s->r, REFRESH_QUIET, 0,
282 1, NULL, NULL, NULL);
283 }
284
285 putchar('\n');
286 return 0;
287}
288
289int run_add_p(struct repository *r, const struct pathspec *ps)
290{
291 struct add_p_state s = { r, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };
292
293 if (discard_index(r->index) < 0 || repo_read_index(r) < 0 ||
294 repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
295 NULL, NULL, NULL) < 0 ||
296 parse_diff(&s, ps) < 0) {
297 strbuf_release(&s.plain);
e3bd11b4 298 strbuf_release(&s.colored);
f6aa7ecc
JS
299 return -1;
300 }
301
302 if (s.hunk_nr)
303 patch_update_file(&s);
304
305 strbuf_release(&s.answer);
306 strbuf_release(&s.buf);
307 strbuf_release(&s.plain);
e3bd11b4 308 strbuf_release(&s.colored);
f6aa7ecc
JS
309 return 0;
310}