built-in add -i: prepare for multi-selection commands
[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         char help_color[COLOR_MAXLEN];
15         char prompt_color[COLOR_MAXLEN];
16         char error_color[COLOR_MAXLEN];
17         char reset_color[COLOR_MAXLEN];
18 };
19
20 static void init_color(struct repository *r, struct add_i_state *s,
21                        const char *slot_name, char *dst,
22                        const char *default_color)
23 {
24         char *key = xstrfmt("color.interactive.%s", slot_name);
25         const char *value;
26
27         if (!s->use_color)
28                 dst[0] = '\0';
29         else if (repo_config_get_value(r, key, &value) ||
30                  color_parse(value, dst))
31                 strlcpy(dst, default_color, COLOR_MAXLEN);
32
33         free(key);
34 }
35
36 static void init_add_i_state(struct add_i_state *s, struct repository *r)
37 {
38         const char *value;
39
40         s->r = r;
41
42         if (repo_config_get_value(r, "color.interactive", &value))
43                 s->use_color = -1;
44         else
45                 s->use_color =
46                         git_config_colorbool("color.interactive", value);
47         s->use_color = want_color(s->use_color);
48
49         init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
50         init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
51         init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE);
52         init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED);
53         init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET);
54 }
55
56 /*
57  * A "prefix item list" is a list of items that are identified by a string, and
58  * a unique prefix (if any) is determined for each item.
59  *
60  * It is implemented in the form of a pair of `string_list`s, the first one
61  * duplicating the strings, with the `util` field pointing at a structure whose
62  * first field must be `size_t prefix_length`.
63  *
64  * That `prefix_length` field will be computed by `find_unique_prefixes()`; It
65  * will be set to zero if no valid, unique prefix could be found.
66  *
67  * The second `string_list` is called `sorted` and does _not_ duplicate the
68  * strings but simply reuses the first one's, with the `util` field pointing at
69  * the `string_item_list` of the first `string_list`. It  will be populated and
70  * sorted by `find_unique_prefixes()`.
71  */
72 struct prefix_item_list {
73         struct string_list items;
74         struct string_list sorted;
75         int *selected; /* for multi-selections */
76         size_t min_length, max_length;
77 };
78 #define PREFIX_ITEM_LIST_INIT \
79         { STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, NULL, 1, 4 }
80
81 static void prefix_item_list_clear(struct prefix_item_list *list)
82 {
83         string_list_clear(&list->items, 1);
84         string_list_clear(&list->sorted, 0);
85         FREE_AND_NULL(list->selected);
86 }
87
88 static void extend_prefix_length(struct string_list_item *p,
89                                  const char *other_string, size_t max_length)
90 {
91         size_t *len = p->util;
92
93         if (!*len || memcmp(p->string, other_string, *len))
94                 return;
95
96         for (;;) {
97                 char c = p->string[*len];
98
99                 /*
100                  * Is `p` a strict prefix of `other`? Or have we exhausted the
101                  * maximal length of the prefix? Or is the current character a
102                  * multi-byte UTF-8 one? If so, there is no valid, unique
103                  * prefix.
104                  */
105                 if (!c || ++*len > max_length || !isascii(c)) {
106                         *len = 0;
107                         break;
108                 }
109
110                 if (c != other_string[*len - 1])
111                         break;
112         }
113 }
114
115 static void find_unique_prefixes(struct prefix_item_list *list)
116 {
117         size_t i;
118
119         if (list->sorted.nr == list->items.nr)
120                 return;
121
122         string_list_clear(&list->sorted, 0);
123         /* Avoid reallocating incrementally */
124         list->sorted.items = xmalloc(st_mult(sizeof(*list->sorted.items),
125                                              list->items.nr));
126         list->sorted.nr = list->sorted.alloc = list->items.nr;
127
128         for (i = 0; i < list->items.nr; i++) {
129                 list->sorted.items[i].string = list->items.items[i].string;
130                 list->sorted.items[i].util = list->items.items + i;
131         }
132
133         string_list_sort(&list->sorted);
134
135         for (i = 0; i < list->sorted.nr; i++) {
136                 struct string_list_item *sorted_item = list->sorted.items + i;
137                 struct string_list_item *item = sorted_item->util;
138                 size_t *len = item->util;
139
140                 *len = 0;
141                 while (*len < list->min_length) {
142                         char c = item->string[(*len)++];
143
144                         if (!c || !isascii(c)) {
145                                 *len = 0;
146                                 break;
147                         }
148                 }
149
150                 if (i > 0)
151                         extend_prefix_length(item, sorted_item[-1].string,
152                                              list->max_length);
153                 if (i + 1 < list->sorted.nr)
154                         extend_prefix_length(item, sorted_item[1].string,
155                                              list->max_length);
156         }
157 }
158
159 static ssize_t find_unique(const char *string, struct prefix_item_list *list)
160 {
161         int index = string_list_find_insert_index(&list->sorted, string, 1);
162         struct string_list_item *item;
163
164         if (list->items.nr != list->sorted.nr)
165                 BUG("prefix_item_list in inconsistent state (%"PRIuMAX
166                     " vs %"PRIuMAX")",
167                     (uintmax_t)list->items.nr, (uintmax_t)list->sorted.nr);
168
169         if (index < 0)
170                 item = list->sorted.items[-1 - index].util;
171         else if (index > 0 &&
172                  starts_with(list->sorted.items[index - 1].string, string))
173                 return -1;
174         else if (index + 1 < list->sorted.nr &&
175                  starts_with(list->sorted.items[index + 1].string, string))
176                 return -1;
177         else if (index < list->sorted.nr)
178                 item = list->sorted.items[index].util;
179         else
180                 return -1;
181         return item - list->items.items;
182 }
183
184 struct list_options {
185         int columns;
186         const char *header;
187         void (*print_item)(int i, int selected, struct string_list_item *item,
188                            void *print_item_data);
189         void *print_item_data;
190 };
191
192 static void list(struct add_i_state *s, struct string_list *list, int *selected,
193                  struct list_options *opts)
194 {
195         int i, last_lf = 0;
196
197         if (!list->nr)
198                 return;
199
200         if (opts->header)
201                 color_fprintf_ln(stdout, s->header_color,
202                                  "%s", opts->header);
203
204         for (i = 0; i < list->nr; i++) {
205                 opts->print_item(i, selected ? selected[i] : 0, list->items + i,
206                                  opts->print_item_data);
207
208                 if ((opts->columns) && ((i + 1) % (opts->columns))) {
209                         putchar('\t');
210                         last_lf = 0;
211                 }
212                 else {
213                         putchar('\n');
214                         last_lf = 1;
215                 }
216         }
217
218         if (!last_lf)
219                 putchar('\n');
220 }
221 struct list_and_choose_options {
222         struct list_options list_opts;
223
224         const char *prompt;
225         enum {
226                 SINGLETON = (1<<0),
227                 IMMEDIATE = (1<<1),
228         } flags;
229         void (*print_help)(struct add_i_state *s);
230 };
231
232 #define LIST_AND_CHOOSE_ERROR (-1)
233 #define LIST_AND_CHOOSE_QUIT  (-2)
234
235 /*
236  * Returns the selected index in singleton mode, the number of selected items
237  * otherwise.
238  *
239  * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
240  * `LIST_AND_CHOOSE_QUIT` is returned.
241  */
242 static ssize_t list_and_choose(struct add_i_state *s,
243                                struct prefix_item_list *items,
244                                struct list_and_choose_options *opts)
245 {
246         int singleton = opts->flags & SINGLETON;
247         int immediate = opts->flags & IMMEDIATE;
248
249         struct strbuf input = STRBUF_INIT;
250         ssize_t res = singleton ? LIST_AND_CHOOSE_ERROR : 0;
251
252         if (!singleton) {
253                 free(items->selected);
254                 CALLOC_ARRAY(items->selected, items->items.nr);
255         }
256
257         if (singleton && !immediate)
258                 BUG("singleton requires immediate");
259
260         find_unique_prefixes(items);
261
262         for (;;) {
263                 char *p;
264
265                 strbuf_reset(&input);
266
267                 list(s, &items->items, items->selected, &opts->list_opts);
268
269                 color_fprintf(stdout, s->prompt_color, "%s", opts->prompt);
270                 fputs(singleton ? "> " : ">> ", stdout);
271                 fflush(stdout);
272
273                 if (strbuf_getline(&input, stdin) == EOF) {
274                         putchar('\n');
275                         if (immediate)
276                                 res = LIST_AND_CHOOSE_QUIT;
277                         break;
278                 }
279                 strbuf_trim(&input);
280
281                 if (!input.len)
282                         break;
283
284                 if (!strcmp(input.buf, "?")) {
285                         opts->print_help(s);
286                         continue;
287                 }
288
289                 p = input.buf;
290                 for (;;) {
291                         size_t sep = strcspn(p, " \t\r\n,");
292                         int choose = 1;
293                         /* `from` is inclusive, `to` is exclusive */
294                         ssize_t from = -1, to = -1;
295
296                         if (!sep) {
297                                 if (!*p)
298                                         break;
299                                 p++;
300                                 continue;
301                         }
302
303                         /* Input that begins with '-'; de-select */
304                         if (*p == '-') {
305                                 choose = 0;
306                                 p++;
307                                 sep--;
308                         }
309
310                         if (sep == 1 && *p == '*') {
311                                 from = 0;
312                                 to = items->items.nr;
313                         } else if (isdigit(*p)) {
314                                 char *endp;
315                                 /*
316                                  * A range can be specified like 5-7 or 5-.
317                                  *
318                                  * Note: `from` is 0-based while the user input
319                                  * is 1-based, hence we have to decrement by
320                                  * one. We do not have to decrement `to` even
321                                  * if it is 0-based because it is an exclusive
322                                  * boundary.
323                                  */
324                                 from = strtoul(p, &endp, 10) - 1;
325                                 if (endp == p + sep)
326                                         to = from + 1;
327                                 else if (*endp == '-') {
328                                         to = strtoul(++endp, &endp, 10);
329                                         /* extra characters after the range? */
330                                         if (endp != p + sep)
331                                                 from = -1;
332                                 }
333                         }
334
335                         if (p[sep])
336                                 p[sep++] = '\0';
337                         if (from < 0) {
338                                 from = find_unique(p, items);
339                                 if (from >= 0)
340                                         to = from + 1;
341                         }
342
343                         if (from < 0 || from >= items->items.nr ||
344                             (singleton && from + 1 != to)) {
345                                 color_fprintf_ln(stdout, s->error_color,
346                                                  _("Huh (%s)?"), p);
347                                 break;
348                         } else if (singleton) {
349                                 res = from;
350                                 break;
351                         }
352
353                         if (to > items->items.nr)
354                                 to = items->items.nr;
355
356                         for (; from < to; from++)
357                                 if (items->selected[from] != choose) {
358                                         items->selected[from] = choose;
359                                         res += choose ? +1 : -1;
360                                 }
361
362                         p += sep;
363                 }
364
365                 if ((immediate && res != LIST_AND_CHOOSE_ERROR) ||
366                     !strcmp(input.buf, "*"))
367                         break;
368         }
369
370         strbuf_release(&input);
371         return res;
372 }
373
374 struct adddel {
375         uintmax_t add, del;
376         unsigned seen:1, binary:1;
377 };
378
379 struct file_item {
380         struct adddel index, worktree;
381 };
382
383 static void add_file_item(struct string_list *files, const char *name)
384 {
385         struct file_item *item = xcalloc(sizeof(*item), 1);
386
387         string_list_append(files, name)->util = item;
388 }
389
390 struct pathname_entry {
391         struct hashmap_entry ent;
392         const char *name;
393         struct file_item *item;
394 };
395
396 static int pathname_entry_cmp(const void *unused_cmp_data,
397                               const struct hashmap_entry *he1,
398                               const struct hashmap_entry *he2,
399                               const void *name)
400 {
401         const struct pathname_entry *e1 =
402                 container_of(he1, const struct pathname_entry, ent);
403         const struct pathname_entry *e2 =
404                 container_of(he2, const struct pathname_entry, ent);
405
406         return strcmp(e1->name, name ? (const char *)name : e2->name);
407 }
408
409 struct collection_status {
410         enum { FROM_WORKTREE = 0, FROM_INDEX = 1 } mode;
411
412         const char *reference;
413
414         unsigned skip_unseen:1;
415         struct string_list *files;
416         struct hashmap file_map;
417 };
418
419 static void collect_changes_cb(struct diff_queue_struct *q,
420                                struct diff_options *options,
421                                void *data)
422 {
423         struct collection_status *s = data;
424         struct diffstat_t stat = { 0 };
425         int i;
426
427         if (!q->nr)
428                 return;
429
430         compute_diffstat(options, &stat, q);
431
432         for (i = 0; i < stat.nr; i++) {
433                 const char *name = stat.files[i]->name;
434                 int hash = strhash(name);
435                 struct pathname_entry *entry;
436                 struct file_item *file_item;
437                 struct adddel *adddel;
438
439                 entry = hashmap_get_entry_from_hash(&s->file_map, hash, name,
440                                                     struct pathname_entry, ent);
441                 if (!entry) {
442                         if (s->skip_unseen)
443                                 continue;
444
445                         add_file_item(s->files, name);
446
447                         entry = xcalloc(sizeof(*entry), 1);
448                         hashmap_entry_init(&entry->ent, hash);
449                         entry->name = s->files->items[s->files->nr - 1].string;
450                         entry->item = s->files->items[s->files->nr - 1].util;
451                         hashmap_add(&s->file_map, &entry->ent);
452                 }
453
454                 file_item = entry->item;
455                 adddel = s->mode == FROM_INDEX ?
456                         &file_item->index : &file_item->worktree;
457                 adddel->seen = 1;
458                 adddel->add = stat.files[i]->added;
459                 adddel->del = stat.files[i]->deleted;
460                 if (stat.files[i]->is_binary)
461                         adddel->binary = 1;
462         }
463         free_diffstat_info(&stat);
464 }
465
466 enum modified_files_filter {
467         NO_FILTER = 0,
468         WORKTREE_ONLY = 1,
469         INDEX_ONLY = 2,
470 };
471
472 static int get_modified_files(struct repository *r,
473                               enum modified_files_filter filter,
474                               struct string_list *files,
475                               const struct pathspec *ps)
476 {
477         struct object_id head_oid;
478         int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
479                                              &head_oid, NULL);
480         struct collection_status s = { 0 };
481         int i;
482
483         if (discard_index(r->index) < 0 ||
484             repo_read_index_preload(r, ps, 0) < 0)
485                 return error(_("could not read index"));
486
487         string_list_clear(files, 1);
488         s.files = files;
489         hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);
490
491         for (i = 0; i < 2; i++) {
492                 struct rev_info rev;
493                 struct setup_revision_opt opt = { 0 };
494
495                 if (filter == INDEX_ONLY)
496                         s.mode = (i == 0) ? FROM_INDEX : FROM_WORKTREE;
497                 else
498                         s.mode = (i == 0) ? FROM_WORKTREE : FROM_INDEX;
499                 s.skip_unseen = filter && i;
500
501                 opt.def = is_initial ?
502                         empty_tree_oid_hex() : oid_to_hex(&head_oid);
503
504                 init_revisions(&rev, NULL);
505                 setup_revisions(0, NULL, &rev, &opt);
506
507                 rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
508                 rev.diffopt.format_callback = collect_changes_cb;
509                 rev.diffopt.format_callback_data = &s;
510
511                 if (ps)
512                         copy_pathspec(&rev.prune_data, ps);
513
514                 if (s.mode == FROM_INDEX)
515                         run_diff_index(&rev, 1);
516                 else {
517                         rev.diffopt.flags.ignore_dirty_submodules = 1;
518                         run_diff_files(&rev, 0);
519                 }
520
521                 if (ps)
522                         clear_pathspec(&rev.prune_data);
523         }
524         hashmap_free_entries(&s.file_map, struct pathname_entry, ent);
525
526         /* While the diffs are ordered already, we ran *two* diffs... */
527         string_list_sort(files);
528
529         return 0;
530 }
531
532 static void render_adddel(struct strbuf *buf,
533                                 struct adddel *ad, const char *no_changes)
534 {
535         if (ad->binary)
536                 strbuf_addstr(buf, _("binary"));
537         else if (ad->seen)
538                 strbuf_addf(buf, "+%"PRIuMAX"/-%"PRIuMAX,
539                             (uintmax_t)ad->add, (uintmax_t)ad->del);
540         else
541                 strbuf_addstr(buf, no_changes);
542 }
543
544 /* filters out prefixes which have special meaning to list_and_choose() */
545 static int is_valid_prefix(const char *prefix, size_t prefix_len)
546 {
547         return prefix_len && prefix &&
548                 /*
549                  * We expect `prefix` to be NUL terminated, therefore this
550                  * `strcspn()` call is okay, even if it might do much more
551                  * work than strictly necessary.
552                  */
553                 strcspn(prefix, " \t\r\n,") >= prefix_len &&    /* separators */
554                 *prefix != '-' &&                               /* deselection */
555                 !isdigit(*prefix) &&                            /* selection */
556                 (prefix_len != 1 ||
557                  (*prefix != '*' &&                             /* "all" wildcard */
558                   *prefix != '?'));                             /* prompt help */
559 }
560
561 struct print_file_item_data {
562         const char *modified_fmt;
563         struct strbuf buf, index, worktree;
564 };
565
566 static void print_file_item(int i, int selected, struct string_list_item *item,
567                             void *print_file_item_data)
568 {
569         struct file_item *c = item->util;
570         struct print_file_item_data *d = print_file_item_data;
571
572         strbuf_reset(&d->index);
573         strbuf_reset(&d->worktree);
574         strbuf_reset(&d->buf);
575
576         render_adddel(&d->worktree, &c->worktree, _("nothing"));
577         render_adddel(&d->index, &c->index, _("unchanged"));
578         strbuf_addf(&d->buf, d->modified_fmt,
579                     d->index.buf, d->worktree.buf, item->string);
580
581         printf("%c%2d: %s", selected ? '*' : ' ', i + 1, d->buf.buf);
582 }
583
584 static int run_status(struct add_i_state *s, const struct pathspec *ps,
585                       struct string_list *files, struct list_options *opts)
586 {
587         if (get_modified_files(s->r, NO_FILTER, files, ps) < 0)
588                 return -1;
589
590         list(s, files, NULL, opts);
591         putchar('\n');
592
593         return 0;
594 }
595
596 static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,
597                     struct string_list *unused_files,
598                     struct list_options *unused_opts)
599 {
600         color_fprintf_ln(stdout, s->help_color, "status        - %s",
601                          _("show paths with changes"));
602         color_fprintf_ln(stdout, s->help_color, "update        - %s",
603                          _("add working tree state to the staged set of changes"));
604         color_fprintf_ln(stdout, s->help_color, "revert        - %s",
605                          _("revert staged set of changes back to the HEAD version"));
606         color_fprintf_ln(stdout, s->help_color, "patch         - %s",
607                          _("pick hunks and update selectively"));
608         color_fprintf_ln(stdout, s->help_color, "diff          - %s",
609                          _("view diff between HEAD and index"));
610         color_fprintf_ln(stdout, s->help_color, "add untracked - %s",
611                          _("add contents of untracked files to the staged set of changes"));
612
613         return 0;
614 }
615
616 typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
617                          struct string_list *files,
618                          struct list_options *opts);
619
620 struct command_item {
621         size_t prefix_length;
622         command_t command;
623 };
624
625 struct print_command_item_data {
626         const char *color, *reset;
627 };
628
629 static void print_command_item(int i, int selected,
630                                struct string_list_item *item,
631                                void *print_command_item_data)
632 {
633         struct print_command_item_data *d = print_command_item_data;
634         struct command_item *util = item->util;
635
636         if (!util->prefix_length ||
637             !is_valid_prefix(item->string, util->prefix_length))
638                 printf(" %2d: %s", i + 1, item->string);
639         else
640                 printf(" %2d: %s%.*s%s%s", i + 1,
641                        d->color, (int)util->prefix_length, item->string,
642                        d->reset, item->string + util->prefix_length);
643 }
644
645 static void command_prompt_help(struct add_i_state *s)
646 {
647         const char *help_color = s->help_color;
648         color_fprintf_ln(stdout, help_color, "%s", _("Prompt help:"));
649         color_fprintf_ln(stdout, help_color, "1          - %s",
650                          _("select a numbered item"));
651         color_fprintf_ln(stdout, help_color, "foo        - %s",
652                          _("select item based on unique prefix"));
653         color_fprintf_ln(stdout, help_color, "           - %s",
654                          _("(empty) select nothing"));
655 }
656
657 int run_add_i(struct repository *r, const struct pathspec *ps)
658 {
659         struct add_i_state s = { NULL };
660         struct print_command_item_data data = { "[", "]" };
661         struct list_and_choose_options main_loop_opts = {
662                 { 4, N_("*** Commands ***"), print_command_item, &data },
663                 N_("What now"), SINGLETON | IMMEDIATE, command_prompt_help
664         };
665         struct {
666                 const char *string;
667                 command_t command;
668         } command_list[] = {
669                 { "status", run_status },
670                 { "help", run_help },
671         };
672         struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
673
674         struct print_file_item_data print_file_item_data = {
675                 "%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
676         };
677         struct list_options opts = {
678                 0, NULL, print_file_item, &print_file_item_data
679         };
680         struct strbuf header = STRBUF_INIT;
681         struct string_list files = STRING_LIST_INIT_DUP;
682         ssize_t i;
683         int res = 0;
684
685         for (i = 0; i < ARRAY_SIZE(command_list); i++) {
686                 struct command_item *util = xcalloc(sizeof(*util), 1);
687                 util->command = command_list[i].command;
688                 string_list_append(&commands.items, command_list[i].string)
689                         ->util = util;
690         }
691
692         init_add_i_state(&s, r);
693
694         /*
695          * When color was asked for, use the prompt color for
696          * highlighting, otherwise use square brackets.
697          */
698         if (s.use_color) {
699                 data.color = s.prompt_color;
700                 data.reset = s.reset_color;
701         }
702
703         strbuf_addstr(&header, "      ");
704         strbuf_addf(&header, print_file_item_data.modified_fmt,
705                     _("staged"), _("unstaged"), _("path"));
706         opts.header = header.buf;
707
708         if (discard_index(r->index) < 0 ||
709             repo_read_index(r) < 0 ||
710             repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
711                                          NULL, NULL, NULL) < 0)
712                 warning(_("could not refresh index"));
713
714         res = run_status(&s, ps, &files, &opts);
715
716         for (;;) {
717                 i = list_and_choose(&s, &commands, &main_loop_opts);
718                 if (i == LIST_AND_CHOOSE_QUIT) {
719                         printf(_("Bye.\n"));
720                         res = 0;
721                         break;
722                 }
723                 if (i != LIST_AND_CHOOSE_ERROR) {
724                         struct command_item *util =
725                                 commands.items.items[i].util;
726                         res = util->command(&s, ps, &files, &opts);
727                 }
728         }
729
730         string_list_clear(&files, 1);
731         strbuf_release(&print_file_item_data.buf);
732         strbuf_release(&print_file_item_data.index);
733         strbuf_release(&print_file_item_data.worktree);
734         strbuf_release(&header);
735         prefix_item_list_clear(&commands);
736
737         return res;
738 }