built-in add -i: start implementing the `patch` functionality in C
[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 }