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