]>
Commit | Line | Data |
---|---|---|
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 | |
9 | struct 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 | ||
14 | struct 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 | ||
25 | static 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 | ||
41 | static 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 | ||
132 | static 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 | ||
143 | static 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 | ||
157 | static const char help_patch_text[] = | |
158 | N_("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 | ||
168 | static 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; | |
233 | soft_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 | ||
289 | int 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 | } |