]> git.ipfire.org Git - thirdparty/git.git/blob - add-patch.c
built-in add -i: start implementing the `patch` functionality in C
[thirdparty/git.git] / add-patch.c
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 }