built-in add -i: implement the main loop
[git] / add-interactive.c
1 #include "cache.h"
2 #include "add-interactive.h"
3 #include "color.h"
4 #include "config.h"
5 #include "diffcore.h"
6 #include "revision.h"
7 #include "refs.h"
8 #include "string-list.h"
9
10 struct add_i_state {
11         struct repository *r;
12         int use_color;
13         char header_color[COLOR_MAXLEN];
14 };
15
16 static void init_color(struct repository *r, struct add_i_state *s,
17                        const char *slot_name, char *dst,
18                        const char *default_color)
19 {
20         char *key = xstrfmt("color.interactive.%s", slot_name);
21         const char *value;
22
23         if (!s->use_color)
24                 dst[0] = '\0';
25         else if (repo_config_get_value(r, key, &value) ||
26                  color_parse(value, dst))
27                 strlcpy(dst, default_color, COLOR_MAXLEN);
28
29         free(key);
30 }
31
32 static void init_add_i_state(struct add_i_state *s, struct repository *r)
33 {
34         const char *value;
35
36         s->r = r;
37
38         if (repo_config_get_value(r, "color.interactive", &value))
39                 s->use_color = -1;
40         else
41                 s->use_color =
42                         git_config_colorbool("color.interactive", value);
43         s->use_color = want_color(s->use_color);
44
45         init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
46 }
47
48 struct list_options {
49         int columns;
50         const char *header;
51         void (*print_item)(int i, struct string_list_item *item, void *print_item_data);
52         void *print_item_data;
53 };
54
55 static void list(struct add_i_state *s, struct string_list *list,
56                  struct list_options *opts)
57 {
58         int i, last_lf = 0;
59
60         if (!list->nr)
61                 return;
62
63         if (opts->header)
64                 color_fprintf_ln(stdout, s->header_color,
65                                  "%s", opts->header);
66
67         for (i = 0; i < list->nr; i++) {
68                 opts->print_item(i, list->items + i, opts->print_item_data);
69
70                 if ((opts->columns) && ((i + 1) % (opts->columns))) {
71                         putchar('\t');
72                         last_lf = 0;
73                 }
74                 else {
75                         putchar('\n');
76                         last_lf = 1;
77                 }
78         }
79
80         if (!last_lf)
81                 putchar('\n');
82 }
83 struct list_and_choose_options {
84         struct list_options list_opts;
85
86         const char *prompt;
87 };
88
89 #define LIST_AND_CHOOSE_ERROR (-1)
90 #define LIST_AND_CHOOSE_QUIT  (-2)
91
92 /*
93  * Returns the selected index.
94  *
95  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
96  * `LIST_AND_CHOOSE_QUIT` is returned.
97  */
98 static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
99                                struct list_and_choose_options *opts)
100 {
101         struct strbuf input = STRBUF_INIT;
102         ssize_t res = LIST_AND_CHOOSE_ERROR;
103
104         for (;;) {
105                 char *p;
106
107                 strbuf_reset(&input);
108
109                 list(s, items, &opts->list_opts);
110
111                 printf("%s%s", opts->prompt, "> ");
112                 fflush(stdout);
113
114                 if (strbuf_getline(&input, stdin) == EOF) {
115                         putchar('\n');
116                         res = LIST_AND_CHOOSE_QUIT;
117                         break;
118                 }
119                 strbuf_trim(&input);
120
121                 if (!input.len)
122                         break;
123
124                 p = input.buf;
125                 for (;;) {
126                         size_t sep = strcspn(p, " \t\r\n,");
127                         ssize_t index = -1;
128
129                         if (!sep) {
130                                 if (!*p)
131                                         break;
132                                 p++;
133                                 continue;
134                         }
135
136                         if (isdigit(*p)) {
137                                 char *endp;
138                                 index = strtoul(p, &endp, 10) - 1;
139                                 if (endp != p + sep)
140                                         index = -1;
141                         }
142
143                         if (p[sep])
144                                 p[sep++] = '\0';
145                         if (index < 0 || index >= items->nr)
146                                 printf(_("Huh (%s)?\n"), p);
147                         else {
148                                 res = index;
149                                 break;
150                         }
151
152                         p += sep;
153                 }
154
155                 if (res != LIST_AND_CHOOSE_ERROR)
156                         break;
157         }
158
159         strbuf_release(&input);
160         return res;
161 }
162
163 struct adddel {
164         uintmax_t add, del;
165         unsigned seen:1, binary:1;
166 };
167
168 struct file_item {
169         struct adddel index, worktree;
170 };
171
172 static void add_file_item(struct string_list *files, const char *name)
173 {
174         struct file_item *item = xcalloc(sizeof(*item), 1);
175
176         string_list_append(files, name)->util = item;
177 }
178
179 struct pathname_entry {
180         struct hashmap_entry ent;
181         const char *name;
182         struct file_item *item;
183 };
184
185 static int pathname_entry_cmp(const void *unused_cmp_data,
186                               const struct hashmap_entry *he1,
187                               const struct hashmap_entry *he2,
188                               const void *name)
189 {
190         const struct pathname_entry *e1 =
191                 container_of(he1, const struct pathname_entry, ent);
192         const struct pathname_entry *e2 =
193                 container_of(he2, const struct pathname_entry, ent);
194
195         return strcmp(e1->name, name ? (const char *)name : e2->name);
196 }
197
198 struct collection_status {
199         enum { FROM_WORKTREE = 0, FROM_INDEX = 1 } phase;
200
201         const char *reference;
202
203         struct string_list *files;
204         struct hashmap file_map;
205 };
206
207 static void collect_changes_cb(struct diff_queue_struct *q,
208                                struct diff_options *options,
209                                void *data)
210 {
211         struct collection_status *s = data;
212         struct diffstat_t stat = { 0 };
213         int i;
214
215         if (!q->nr)
216                 return;
217
218         compute_diffstat(options, &stat, q);
219
220         for (i = 0; i < stat.nr; i++) {
221                 const char *name = stat.files[i]->name;
222                 int hash = strhash(name);
223                 struct pathname_entry *entry;
224                 struct file_item *file_item;
225                 struct adddel *adddel;
226
227                 entry = hashmap_get_entry_from_hash(&s->file_map, hash, name,
228                                                     struct pathname_entry, ent);
229                 if (!entry) {
230                         add_file_item(s->files, name);
231
232                         entry = xcalloc(sizeof(*entry), 1);
233                         hashmap_entry_init(&entry->ent, hash);
234                         entry->name = s->files->items[s->files->nr - 1].string;
235                         entry->item = s->files->items[s->files->nr - 1].util;
236                         hashmap_add(&s->file_map, &entry->ent);
237                 }
238
239                 file_item = entry->item;
240                 adddel = s->phase == FROM_INDEX ?
241                         &file_item->index : &file_item->worktree;
242                 adddel->seen = 1;
243                 adddel->add = stat.files[i]->added;
244                 adddel->del = stat.files[i]->deleted;
245                 if (stat.files[i]->is_binary)
246                         adddel->binary = 1;
247         }
248         free_diffstat_info(&stat);
249 }
250
251 static int get_modified_files(struct repository *r, struct string_list *files,
252                               const struct pathspec *ps)
253 {
254         struct object_id head_oid;
255         int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
256                                              &head_oid, NULL);
257         struct collection_status s = { FROM_WORKTREE };
258
259         if (discard_index(r->index) < 0 ||
260             repo_read_index_preload(r, ps, 0) < 0)
261                 return error(_("could not read index"));
262
263         string_list_clear(files, 1);
264         s.files = files;
265         hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);
266
267         for (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {
268                 struct rev_info rev;
269                 struct setup_revision_opt opt = { 0 };
270
271                 opt.def = is_initial ?
272                         empty_tree_oid_hex() : oid_to_hex(&head_oid);
273
274                 init_revisions(&rev, NULL);
275                 setup_revisions(0, NULL, &rev, &opt);
276
277                 rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
278                 rev.diffopt.format_callback = collect_changes_cb;
279                 rev.diffopt.format_callback_data = &s;
280
281                 if (ps)
282                         copy_pathspec(&rev.prune_data, ps);
283
284                 if (s.phase == FROM_INDEX)
285                         run_diff_index(&rev, 1);
286                 else {
287                         rev.diffopt.flags.ignore_dirty_submodules = 1;
288                         run_diff_files(&rev, 0);
289                 }
290         }
291         hashmap_free_entries(&s.file_map, struct pathname_entry, ent);
292
293         /* While the diffs are ordered already, we ran *two* diffs... */
294         string_list_sort(files);
295
296         return 0;
297 }
298
299 static void render_adddel(struct strbuf *buf,
300                                 struct adddel *ad, const char *no_changes)
301 {
302         if (ad->binary)
303                 strbuf_addstr(buf, _("binary"));
304         else if (ad->seen)
305                 strbuf_addf(buf, "+%"PRIuMAX"/-%"PRIuMAX,
306                             (uintmax_t)ad->add, (uintmax_t)ad->del);
307         else
308                 strbuf_addstr(buf, no_changes);
309 }
310
311 struct print_file_item_data {
312         const char *modified_fmt;
313         struct strbuf buf, index, worktree;
314 };
315
316 static void print_file_item(int i, struct string_list_item *item,
317                             void *print_file_item_data)
318 {
319         struct file_item *c = item->util;
320         struct print_file_item_data *d = print_file_item_data;
321
322         strbuf_reset(&d->index);
323         strbuf_reset(&d->worktree);
324         strbuf_reset(&d->buf);
325
326         render_adddel(&d->worktree, &c->worktree, _("nothing"));
327         render_adddel(&d->index, &c->index, _("unchanged"));
328         strbuf_addf(&d->buf, d->modified_fmt,
329                     d->index.buf, d->worktree.buf, item->string);
330
331         printf(" %2d: %s", i + 1, d->buf.buf);
332 }
333
334 static int run_status(struct add_i_state *s, const struct pathspec *ps,
335                       struct string_list *files, struct list_options *opts)
336 {
337         if (get_modified_files(s->r, files, ps) < 0)
338                 return -1;
339
340         list(s, files, opts);
341         putchar('\n');
342
343         return 0;
344 }
345
346 typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
347                          struct string_list *files,
348                          struct list_options *opts);
349
350 static void print_command_item(int i, struct string_list_item *item,
351                                void *print_command_item_data)
352 {
353         printf(" %2d: %s", i + 1, item->string);
354 }
355
356 int run_add_i(struct repository *r, const struct pathspec *ps)
357 {
358         struct add_i_state s = { NULL };
359         struct list_and_choose_options main_loop_opts = {
360                 { 4, N_("*** Commands ***"), print_command_item, NULL },
361                 N_("What now")
362         };
363         struct {
364                 const char *string;
365                 command_t command;
366         } command_list[] = {
367                 { "status", run_status },
368         };
369         struct string_list commands = STRING_LIST_INIT_NODUP;
370
371         struct print_file_item_data print_file_item_data = {
372                 "%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
373         };
374         struct list_options opts = {
375                 0, NULL, print_file_item, &print_file_item_data
376         };
377         struct strbuf header = STRBUF_INIT;
378         struct string_list files = STRING_LIST_INIT_DUP;
379         ssize_t i;
380         int res = 0;
381
382         for (i = 0; i < ARRAY_SIZE(command_list); i++)
383                 string_list_append(&commands, command_list[i].string)
384                         ->util = command_list[i].command;
385
386         init_add_i_state(&s, r);
387
388         strbuf_addstr(&header, "      ");
389         strbuf_addf(&header, print_file_item_data.modified_fmt,
390                     _("staged"), _("unstaged"), _("path"));
391         opts.header = header.buf;
392
393         if (discard_index(r->index) < 0 ||
394             repo_read_index(r) < 0 ||
395             repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
396                                          NULL, NULL, NULL) < 0)
397                 warning(_("could not refresh index"));
398
399         res = run_status(&s, ps, &files, &opts);
400
401         for (;;) {
402                 i = list_and_choose(&s, &commands, &main_loop_opts);
403                 if (i == LIST_AND_CHOOSE_QUIT) {
404                         printf(_("Bye.\n"));
405                         res = 0;
406                         break;
407                 }
408                 if (i != LIST_AND_CHOOSE_ERROR) {
409                         command_t command = commands.items[i].util;
410                         res = command(&s, ps, &files, &opts);
411                 }
412         }
413
414         string_list_clear(&files, 1);
415         strbuf_release(&print_file_item_data.buf);
416         strbuf_release(&print_file_item_data.index);
417         strbuf_release(&print_file_item_data.worktree);
418         strbuf_release(&header);
419         string_list_clear(&commands, 0);
420
421         return res;
422 }