Add and use generic name->id mapping code for color slot parsing
[git] / builtin / clean.c
1 /*
2  * "git clean" builtin command
3  *
4  * Copyright (C) 2007 Shawn Bohrer
5  *
6  * Based on git-clean.sh by Pavel Roskin
7  */
8
9 #include "builtin.h"
10 #include "cache.h"
11 #include "config.h"
12 #include "dir.h"
13 #include "parse-options.h"
14 #include "string-list.h"
15 #include "quote.h"
16 #include "column.h"
17 #include "color.h"
18 #include "pathspec.h"
19
20 static int force = -1; /* unset */
21 static int interactive;
22 static struct string_list del_list = STRING_LIST_INIT_DUP;
23 static unsigned int colopts;
24
25 static const char *const builtin_clean_usage[] = {
26         N_("git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
27         NULL
28 };
29
30 static const char *msg_remove = N_("Removing %s\n");
31 static const char *msg_would_remove = N_("Would remove %s\n");
32 static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
33 static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
34 static const char *msg_warn_remove_failed = N_("failed to remove %s");
35
36 enum color_clean {
37         CLEAN_COLOR_RESET = 0,
38         CLEAN_COLOR_PLAIN = 1,
39         CLEAN_COLOR_PROMPT = 2,
40         CLEAN_COLOR_HEADER = 3,
41         CLEAN_COLOR_HELP = 4,
42         CLEAN_COLOR_ERROR = 5
43 };
44
45 static const char *color_interactive_slots[] = {
46         [CLEAN_COLOR_ERROR]  = "error",
47         [CLEAN_COLOR_HEADER] = "header",
48         [CLEAN_COLOR_HELP]   = "help",
49         [CLEAN_COLOR_PLAIN]  = "plain",
50         [CLEAN_COLOR_PROMPT] = "prompt",
51         [CLEAN_COLOR_RESET]  = "reset",
52 };
53
54 static int clean_use_color = -1;
55 static char clean_colors[][COLOR_MAXLEN] = {
56         [CLEAN_COLOR_ERROR] = GIT_COLOR_BOLD_RED,
57         [CLEAN_COLOR_HEADER] = GIT_COLOR_BOLD,
58         [CLEAN_COLOR_HELP] = GIT_COLOR_BOLD_RED,
59         [CLEAN_COLOR_PLAIN] = GIT_COLOR_NORMAL,
60         [CLEAN_COLOR_PROMPT] = GIT_COLOR_BOLD_BLUE,
61         [CLEAN_COLOR_RESET] = GIT_COLOR_RESET,
62 };
63
64 #define MENU_OPTS_SINGLETON             01
65 #define MENU_OPTS_IMMEDIATE             02
66 #define MENU_OPTS_LIST_ONLY             04
67
68 struct menu_opts {
69         const char *header;
70         const char *prompt;
71         int flags;
72 };
73
74 #define MENU_RETURN_NO_LOOP             10
75
76 struct menu_item {
77         char hotkey;
78         const char *title;
79         int selected;
80         int (*fn)(void);
81 };
82
83 enum menu_stuff_type {
84         MENU_STUFF_TYPE_STRING_LIST = 1,
85         MENU_STUFF_TYPE_MENU_ITEM
86 };
87
88 struct menu_stuff {
89         enum menu_stuff_type type;
90         int nr;
91         void *stuff;
92 };
93
94 static int git_clean_config(const char *var, const char *value, void *cb)
95 {
96         const char *slot_name;
97
98         if (starts_with(var, "column."))
99                 return git_column_config(var, value, "clean", &colopts);
100
101         /* honors the color.interactive* config variables which also
102            applied in git-add--interactive and git-stash */
103         if (!strcmp(var, "color.interactive")) {
104                 clean_use_color = git_config_colorbool(var, value);
105                 return 0;
106         }
107         if (skip_prefix(var, "color.interactive.", &slot_name)) {
108                 int slot = LOOKUP_CONFIG(color_interactive_slots, slot_name);
109                 if (slot < 0)
110                         return 0;
111                 if (!value)
112                         return config_error_nonbool(var);
113                 return color_parse(value, clean_colors[slot]);
114         }
115
116         if (!strcmp(var, "clean.requireforce")) {
117                 force = !git_config_bool(var, value);
118                 return 0;
119         }
120
121         /* inspect the color.ui config variable and others */
122         return git_color_default_config(var, value, cb);
123 }
124
125 static const char *clean_get_color(enum color_clean ix)
126 {
127         if (want_color(clean_use_color))
128                 return clean_colors[ix];
129         return "";
130 }
131
132 static void clean_print_color(enum color_clean ix)
133 {
134         printf("%s", clean_get_color(ix));
135 }
136
137 static int exclude_cb(const struct option *opt, const char *arg, int unset)
138 {
139         struct string_list *exclude_list = opt->value;
140         string_list_append(exclude_list, arg);
141         return 0;
142 }
143
144 static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
145                 int dry_run, int quiet, int *dir_gone)
146 {
147         DIR *dir;
148         struct strbuf quoted = STRBUF_INIT;
149         struct dirent *e;
150         int res = 0, ret = 0, gone = 1, original_len = path->len, len;
151         struct string_list dels = STRING_LIST_INIT_DUP;
152
153         *dir_gone = 1;
154
155         if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && is_nonbare_repository_dir(path)) {
156                 if (!quiet) {
157                         quote_path_relative(path->buf, prefix, &quoted);
158                         printf(dry_run ?  _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
159                                         quoted.buf);
160                 }
161
162                 *dir_gone = 0;
163                 goto out;
164         }
165
166         dir = opendir(path->buf);
167         if (!dir) {
168                 /* an empty dir could be removed even if it is unreadble */
169                 res = dry_run ? 0 : rmdir(path->buf);
170                 if (res) {
171                         int saved_errno = errno;
172                         quote_path_relative(path->buf, prefix, &quoted);
173                         errno = saved_errno;
174                         warning_errno(_(msg_warn_remove_failed), quoted.buf);
175                         *dir_gone = 0;
176                 }
177                 ret = res;
178                 goto out;
179         }
180
181         strbuf_complete(path, '/');
182
183         len = path->len;
184         while ((e = readdir(dir)) != NULL) {
185                 struct stat st;
186                 if (is_dot_or_dotdot(e->d_name))
187                         continue;
188
189                 strbuf_setlen(path, len);
190                 strbuf_addstr(path, e->d_name);
191                 if (lstat(path->buf, &st))
192                         ; /* fall thru */
193                 else if (S_ISDIR(st.st_mode)) {
194                         if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))
195                                 ret = 1;
196                         if (gone) {
197                                 quote_path_relative(path->buf, prefix, &quoted);
198                                 string_list_append(&dels, quoted.buf);
199                         } else
200                                 *dir_gone = 0;
201                         continue;
202                 } else {
203                         res = dry_run ? 0 : unlink(path->buf);
204                         if (!res) {
205                                 quote_path_relative(path->buf, prefix, &quoted);
206                                 string_list_append(&dels, quoted.buf);
207                         } else {
208                                 int saved_errno = errno;
209                                 quote_path_relative(path->buf, prefix, &quoted);
210                                 errno = saved_errno;
211                                 warning_errno(_(msg_warn_remove_failed), quoted.buf);
212                                 *dir_gone = 0;
213                                 ret = 1;
214                         }
215                         continue;
216                 }
217
218                 /* path too long, stat fails, or non-directory still exists */
219                 *dir_gone = 0;
220                 ret = 1;
221                 break;
222         }
223         closedir(dir);
224
225         strbuf_setlen(path, original_len);
226
227         if (*dir_gone) {
228                 res = dry_run ? 0 : rmdir(path->buf);
229                 if (!res)
230                         *dir_gone = 1;
231                 else {
232                         int saved_errno = errno;
233                         quote_path_relative(path->buf, prefix, &quoted);
234                         errno = saved_errno;
235                         warning_errno(_(msg_warn_remove_failed), quoted.buf);
236                         *dir_gone = 0;
237                         ret = 1;
238                 }
239         }
240
241         if (!*dir_gone && !quiet) {
242                 int i;
243                 for (i = 0; i < dels.nr; i++)
244                         printf(dry_run ?  _(msg_would_remove) : _(msg_remove), dels.items[i].string);
245         }
246 out:
247         strbuf_release(&quoted);
248         string_list_clear(&dels, 0);
249         return ret;
250 }
251
252 static void pretty_print_dels(void)
253 {
254         struct string_list list = STRING_LIST_INIT_DUP;
255         struct string_list_item *item;
256         struct strbuf buf = STRBUF_INIT;
257         const char *qname;
258         struct column_options copts;
259
260         for_each_string_list_item(item, &del_list) {
261                 qname = quote_path_relative(item->string, NULL, &buf);
262                 string_list_append(&list, qname);
263         }
264
265         /*
266          * always enable column display, we only consult column.*
267          * about layout strategy and stuff
268          */
269         colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
270         memset(&copts, 0, sizeof(copts));
271         copts.indent = "  ";
272         copts.padding = 2;
273         print_columns(&list, colopts, &copts);
274         strbuf_release(&buf);
275         string_list_clear(&list, 0);
276 }
277
278 static void pretty_print_menus(struct string_list *menu_list)
279 {
280         unsigned int local_colopts = 0;
281         struct column_options copts;
282
283         local_colopts = COL_ENABLED | COL_ROW;
284         memset(&copts, 0, sizeof(copts));
285         copts.indent = "  ";
286         copts.padding = 2;
287         print_columns(menu_list, local_colopts, &copts);
288 }
289
290 static void prompt_help_cmd(int singleton)
291 {
292         clean_print_color(CLEAN_COLOR_HELP);
293         printf(singleton ?
294                   _("Prompt help:\n"
295                     "1          - select a numbered item\n"
296                     "foo        - select item based on unique prefix\n"
297                     "           - (empty) select nothing\n") :
298                   _("Prompt help:\n"
299                     "1          - select a single item\n"
300                     "3-5        - select a range of items\n"
301                     "2-3,6-9    - select multiple ranges\n"
302                     "foo        - select item based on unique prefix\n"
303                     "-...       - unselect specified items\n"
304                     "*          - choose all items\n"
305                     "           - (empty) finish selecting\n"));
306         clean_print_color(CLEAN_COLOR_RESET);
307 }
308
309 /*
310  * display menu stuff with number prefix and hotkey highlight
311  */
312 static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)
313 {
314         struct string_list menu_list = STRING_LIST_INIT_DUP;
315         struct strbuf menu = STRBUF_INIT;
316         struct menu_item *menu_item;
317         struct string_list_item *string_list_item;
318         int i;
319
320         switch (stuff->type) {
321         default:
322                 die("Bad type of menu_stuff when print menu");
323         case MENU_STUFF_TYPE_MENU_ITEM:
324                 menu_item = (struct menu_item *)stuff->stuff;
325                 for (i = 0; i < stuff->nr; i++, menu_item++) {
326                         const char *p;
327                         int highlighted = 0;
328
329                         p = menu_item->title;
330                         if ((*chosen)[i] < 0)
331                                 (*chosen)[i] = menu_item->selected ? 1 : 0;
332                         strbuf_addf(&menu, "%s%2d: ", (*chosen)[i] ? "*" : " ", i+1);
333                         for (; *p; p++) {
334                                 if (!highlighted && *p == menu_item->hotkey) {
335                                         strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_PROMPT));
336                                         strbuf_addch(&menu, *p);
337                                         strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_RESET));
338                                         highlighted = 1;
339                                 } else {
340                                         strbuf_addch(&menu, *p);
341                                 }
342                         }
343                         string_list_append(&menu_list, menu.buf);
344                         strbuf_reset(&menu);
345                 }
346                 break;
347         case MENU_STUFF_TYPE_STRING_LIST:
348                 i = 0;
349                 for_each_string_list_item(string_list_item, (struct string_list *)stuff->stuff) {
350                         if ((*chosen)[i] < 0)
351                                 (*chosen)[i] = 0;
352                         strbuf_addf(&menu, "%s%2d: %s",
353                                     (*chosen)[i] ? "*" : " ", i+1, string_list_item->string);
354                         string_list_append(&menu_list, menu.buf);
355                         strbuf_reset(&menu);
356                         i++;
357                 }
358                 break;
359         }
360
361         pretty_print_menus(&menu_list);
362
363         strbuf_release(&menu);
364         string_list_clear(&menu_list, 0);
365 }
366
367 static int find_unique(const char *choice, struct menu_stuff *menu_stuff)
368 {
369         struct menu_item *menu_item;
370         struct string_list_item *string_list_item;
371         int i, len, found = 0;
372
373         len = strlen(choice);
374         switch (menu_stuff->type) {
375         default:
376                 die("Bad type of menu_stuff when parse choice");
377         case MENU_STUFF_TYPE_MENU_ITEM:
378
379                 menu_item = (struct menu_item *)menu_stuff->stuff;
380                 for (i = 0; i < menu_stuff->nr; i++, menu_item++) {
381                         if (len == 1 && *choice == menu_item->hotkey) {
382                                 found = i + 1;
383                                 break;
384                         }
385                         if (!strncasecmp(choice, menu_item->title, len)) {
386                                 if (found) {
387                                         if (len == 1) {
388                                                 /* continue for hotkey matching */
389                                                 found = -1;
390                                         } else {
391                                                 found = 0;
392                                                 break;
393                                         }
394                                 } else {
395                                         found = i + 1;
396                                 }
397                         }
398                 }
399                 break;
400         case MENU_STUFF_TYPE_STRING_LIST:
401                 string_list_item = ((struct string_list *)menu_stuff->stuff)->items;
402                 for (i = 0; i < menu_stuff->nr; i++, string_list_item++) {
403                         if (!strncasecmp(choice, string_list_item->string, len)) {
404                                 if (found) {
405                                         found = 0;
406                                         break;
407                                 }
408                                 found = i + 1;
409                         }
410                 }
411                 break;
412         }
413         return found;
414 }
415
416
417 /*
418  * Parse user input, and return choice(s) for menu (menu_stuff).
419  *
420  * Input
421  *     (for single choice)
422  *         1          - select a numbered item
423  *         foo        - select item based on menu title
424  *                    - (empty) select nothing
425  *
426  *     (for multiple choice)
427  *         1          - select a single item
428  *         3-5        - select a range of items
429  *         2-3,6-9    - select multiple ranges
430  *         foo        - select item based on menu title
431  *         -...       - unselect specified items
432  *         *          - choose all items
433  *                    - (empty) finish selecting
434  *
435  * The parse result will be saved in array **chosen, and
436  * return number of total selections.
437  */
438 static int parse_choice(struct menu_stuff *menu_stuff,
439                         int is_single,
440                         struct strbuf input,
441                         int **chosen)
442 {
443         struct strbuf **choice_list, **ptr;
444         int nr = 0;
445         int i;
446
447         if (is_single) {
448                 choice_list = strbuf_split_max(&input, '\n', 0);
449         } else {
450                 char *p = input.buf;
451                 do {
452                         if (*p == ',')
453                                 *p = ' ';
454                 } while (*p++);
455                 choice_list = strbuf_split_max(&input, ' ', 0);
456         }
457
458         for (ptr = choice_list; *ptr; ptr++) {
459                 char *p;
460                 int choose = 1;
461                 int bottom = 0, top = 0;
462                 int is_range, is_number;
463
464                 strbuf_trim(*ptr);
465                 if (!(*ptr)->len)
466                         continue;
467
468                 /* Input that begins with '-'; unchoose */
469                 if (*(*ptr)->buf == '-') {
470                         choose = 0;
471                         strbuf_remove((*ptr), 0, 1);
472                 }
473
474                 is_range = 0;
475                 is_number = 1;
476                 for (p = (*ptr)->buf; *p; p++) {
477                         if ('-' == *p) {
478                                 if (!is_range) {
479                                         is_range = 1;
480                                         is_number = 0;
481                                 } else {
482                                         is_number = 0;
483                                         is_range = 0;
484                                         break;
485                                 }
486                         } else if (!isdigit(*p)) {
487                                 is_number = 0;
488                                 is_range = 0;
489                                 break;
490                         }
491                 }
492
493                 if (is_number) {
494                         bottom = atoi((*ptr)->buf);
495                         top = bottom;
496                 } else if (is_range) {
497                         bottom = atoi((*ptr)->buf);
498                         /* a range can be specified like 5-7 or 5- */
499                         if (!*(strchr((*ptr)->buf, '-') + 1))
500                                 top = menu_stuff->nr;
501                         else
502                                 top = atoi(strchr((*ptr)->buf, '-') + 1);
503                 } else if (!strcmp((*ptr)->buf, "*")) {
504                         bottom = 1;
505                         top = menu_stuff->nr;
506                 } else {
507                         bottom = find_unique((*ptr)->buf, menu_stuff);
508                         top = bottom;
509                 }
510
511                 if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top ||
512                     (is_single && bottom != top)) {
513                         clean_print_color(CLEAN_COLOR_ERROR);
514                         printf(_("Huh (%s)?\n"), (*ptr)->buf);
515                         clean_print_color(CLEAN_COLOR_RESET);
516                         continue;
517                 }
518
519                 for (i = bottom; i <= top; i++)
520                         (*chosen)[i-1] = choose;
521         }
522
523         strbuf_list_free(choice_list);
524
525         for (i = 0; i < menu_stuff->nr; i++)
526                 nr += (*chosen)[i];
527         return nr;
528 }
529
530 /*
531  * Implement a git-add-interactive compatible UI, which is borrowed
532  * from git-add--interactive.perl.
533  *
534  * Return value:
535  *
536  *   - Return an array of integers
537  *   - , and it is up to you to free the allocated memory.
538  *   - The array ends with EOF.
539  *   - If user pressed CTRL-D (i.e. EOF), no selection returned.
540  */
541 static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
542 {
543         struct strbuf choice = STRBUF_INIT;
544         int *chosen, *result;
545         int nr = 0;
546         int eof = 0;
547         int i;
548
549         ALLOC_ARRAY(chosen, stuff->nr);
550         /* set chosen as uninitialized */
551         for (i = 0; i < stuff->nr; i++)
552                 chosen[i] = -1;
553
554         for (;;) {
555                 if (opts->header) {
556                         printf_ln("%s%s%s",
557                                   clean_get_color(CLEAN_COLOR_HEADER),
558                                   _(opts->header),
559                                   clean_get_color(CLEAN_COLOR_RESET));
560                 }
561
562                 /* chosen will be initialized by print_highlight_menu_stuff */
563                 print_highlight_menu_stuff(stuff, &chosen);
564
565                 if (opts->flags & MENU_OPTS_LIST_ONLY)
566                         break;
567
568                 if (opts->prompt) {
569                         printf("%s%s%s%s",
570                                clean_get_color(CLEAN_COLOR_PROMPT),
571                                _(opts->prompt),
572                                opts->flags & MENU_OPTS_SINGLETON ? "> " : ">> ",
573                                clean_get_color(CLEAN_COLOR_RESET));
574                 }
575
576                 if (strbuf_getline_lf(&choice, stdin) != EOF) {
577                         strbuf_trim(&choice);
578                 } else {
579                         eof = 1;
580                         break;
581                 }
582
583                 /* help for prompt */
584                 if (!strcmp(choice.buf, "?")) {
585                         prompt_help_cmd(opts->flags & MENU_OPTS_SINGLETON);
586                         continue;
587                 }
588
589                 /* for a multiple-choice menu, press ENTER (empty) will return back */
590                 if (!(opts->flags & MENU_OPTS_SINGLETON) && !choice.len)
591                         break;
592
593                 nr = parse_choice(stuff,
594                                   opts->flags & MENU_OPTS_SINGLETON,
595                                   choice,
596                                   &chosen);
597
598                 if (opts->flags & MENU_OPTS_SINGLETON) {
599                         if (nr)
600                                 break;
601                 } else if (opts->flags & MENU_OPTS_IMMEDIATE) {
602                         break;
603                 }
604         }
605
606         if (eof) {
607                 result = xmalloc(sizeof(int));
608                 *result = EOF;
609         } else {
610                 int j = 0;
611
612                 /*
613                  * recalculate nr, if return back from menu directly with
614                  * default selections.
615                  */
616                 if (!nr) {
617                         for (i = 0; i < stuff->nr; i++)
618                                 nr += chosen[i];
619                 }
620
621                 result = xcalloc(st_add(nr, 1), sizeof(int));
622                 for (i = 0; i < stuff->nr && j < nr; i++) {
623                         if (chosen[i])
624                                 result[j++] = i;
625                 }
626                 result[j] = EOF;
627         }
628
629         free(chosen);
630         strbuf_release(&choice);
631         return result;
632 }
633
634 static int clean_cmd(void)
635 {
636         return MENU_RETURN_NO_LOOP;
637 }
638
639 static int filter_by_patterns_cmd(void)
640 {
641         struct dir_struct dir;
642         struct strbuf confirm = STRBUF_INIT;
643         struct strbuf **ignore_list;
644         struct string_list_item *item;
645         struct exclude_list *el;
646         int changed = -1, i;
647
648         for (;;) {
649                 if (!del_list.nr)
650                         break;
651
652                 if (changed)
653                         pretty_print_dels();
654
655                 clean_print_color(CLEAN_COLOR_PROMPT);
656                 printf(_("Input ignore patterns>> "));
657                 clean_print_color(CLEAN_COLOR_RESET);
658                 if (strbuf_getline_lf(&confirm, stdin) != EOF)
659                         strbuf_trim(&confirm);
660                 else
661                         putchar('\n');
662
663                 /* quit filter_by_pattern mode if press ENTER or Ctrl-D */
664                 if (!confirm.len)
665                         break;
666
667                 memset(&dir, 0, sizeof(dir));
668                 el = add_exclude_list(&dir, EXC_CMDL, "manual exclude");
669                 ignore_list = strbuf_split_max(&confirm, ' ', 0);
670
671                 for (i = 0; ignore_list[i]; i++) {
672                         strbuf_trim(ignore_list[i]);
673                         if (!ignore_list[i]->len)
674                                 continue;
675
676                         add_exclude(ignore_list[i]->buf, "", 0, el, -(i+1));
677                 }
678
679                 changed = 0;
680                 for_each_string_list_item(item, &del_list) {
681                         int dtype = DT_UNKNOWN;
682
683                         if (is_excluded(&dir, &the_index, item->string, &dtype)) {
684                                 *item->string = '\0';
685                                 changed++;
686                         }
687                 }
688
689                 if (changed) {
690                         string_list_remove_empty_items(&del_list, 0);
691                 } else {
692                         clean_print_color(CLEAN_COLOR_ERROR);
693                         printf_ln(_("WARNING: Cannot find items matched by: %s"), confirm.buf);
694                         clean_print_color(CLEAN_COLOR_RESET);
695                 }
696
697                 strbuf_list_free(ignore_list);
698                 clear_directory(&dir);
699         }
700
701         strbuf_release(&confirm);
702         return 0;
703 }
704
705 static int select_by_numbers_cmd(void)
706 {
707         struct menu_opts menu_opts;
708         struct menu_stuff menu_stuff;
709         struct string_list_item *items;
710         int *chosen;
711         int i, j;
712
713         menu_opts.header = NULL;
714         menu_opts.prompt = N_("Select items to delete");
715         menu_opts.flags = 0;
716
717         menu_stuff.type = MENU_STUFF_TYPE_STRING_LIST;
718         menu_stuff.stuff = &del_list;
719         menu_stuff.nr = del_list.nr;
720
721         chosen = list_and_choose(&menu_opts, &menu_stuff);
722         items = del_list.items;
723         for (i = 0, j = 0; i < del_list.nr; i++) {
724                 if (i < chosen[j]) {
725                         *(items[i].string) = '\0';
726                 } else if (i == chosen[j]) {
727                         /* delete selected item */
728                         j++;
729                         continue;
730                 } else {
731                         /* end of chosen (chosen[j] == EOF), won't delete */
732                         *(items[i].string) = '\0';
733                 }
734         }
735
736         string_list_remove_empty_items(&del_list, 0);
737
738         free(chosen);
739         return 0;
740 }
741
742 static int ask_each_cmd(void)
743 {
744         struct strbuf confirm = STRBUF_INIT;
745         struct strbuf buf = STRBUF_INIT;
746         struct string_list_item *item;
747         const char *qname;
748         int changed = 0, eof = 0;
749
750         for_each_string_list_item(item, &del_list) {
751                 /* Ctrl-D should stop removing files */
752                 if (!eof) {
753                         qname = quote_path_relative(item->string, NULL, &buf);
754                         /* TRANSLATORS: Make sure to keep [y/N] as is */
755                         printf(_("Remove %s [y/N]? "), qname);
756                         if (strbuf_getline_lf(&confirm, stdin) != EOF) {
757                                 strbuf_trim(&confirm);
758                         } else {
759                                 putchar('\n');
760                                 eof = 1;
761                         }
762                 }
763                 if (!confirm.len || strncasecmp(confirm.buf, "yes", confirm.len)) {
764                         *item->string = '\0';
765                         changed++;
766                 }
767         }
768
769         if (changed)
770                 string_list_remove_empty_items(&del_list, 0);
771
772         strbuf_release(&buf);
773         strbuf_release(&confirm);
774         return MENU_RETURN_NO_LOOP;
775 }
776
777 static int quit_cmd(void)
778 {
779         string_list_clear(&del_list, 0);
780         printf(_("Bye.\n"));
781         return MENU_RETURN_NO_LOOP;
782 }
783
784 static int help_cmd(void)
785 {
786         clean_print_color(CLEAN_COLOR_HELP);
787         printf_ln(_(
788                     "clean               - start cleaning\n"
789                     "filter by pattern   - exclude items from deletion\n"
790                     "select by numbers   - select items to be deleted by numbers\n"
791                     "ask each            - confirm each deletion (like \"rm -i\")\n"
792                     "quit                - stop cleaning\n"
793                     "help                - this screen\n"
794                     "?                   - help for prompt selection"
795                    ));
796         clean_print_color(CLEAN_COLOR_RESET);
797         return 0;
798 }
799
800 static void interactive_main_loop(void)
801 {
802         while (del_list.nr) {
803                 struct menu_opts menu_opts;
804                 struct menu_stuff menu_stuff;
805                 struct menu_item menus[] = {
806                         {'c', "clean",                  0, clean_cmd},
807                         {'f', "filter by pattern",      0, filter_by_patterns_cmd},
808                         {'s', "select by numbers",      0, select_by_numbers_cmd},
809                         {'a', "ask each",               0, ask_each_cmd},
810                         {'q', "quit",                   0, quit_cmd},
811                         {'h', "help",                   0, help_cmd},
812                 };
813                 int *chosen;
814
815                 menu_opts.header = N_("*** Commands ***");
816                 menu_opts.prompt = N_("What now");
817                 menu_opts.flags = MENU_OPTS_SINGLETON;
818
819                 menu_stuff.type = MENU_STUFF_TYPE_MENU_ITEM;
820                 menu_stuff.stuff = menus;
821                 menu_stuff.nr = sizeof(menus) / sizeof(struct menu_item);
822
823                 clean_print_color(CLEAN_COLOR_HEADER);
824                 printf_ln(Q_("Would remove the following item:",
825                              "Would remove the following items:",
826                              del_list.nr));
827                 clean_print_color(CLEAN_COLOR_RESET);
828
829                 pretty_print_dels();
830
831                 chosen = list_and_choose(&menu_opts, &menu_stuff);
832
833                 if (*chosen != EOF) {
834                         int ret;
835                         ret = menus[*chosen].fn();
836                         if (ret != MENU_RETURN_NO_LOOP) {
837                                 FREE_AND_NULL(chosen);
838                                 if (!del_list.nr) {
839                                         clean_print_color(CLEAN_COLOR_ERROR);
840                                         printf_ln(_("No more files to clean, exiting."));
841                                         clean_print_color(CLEAN_COLOR_RESET);
842                                         break;
843                                 }
844                                 continue;
845                         }
846                 } else {
847                         quit_cmd();
848                 }
849
850                 FREE_AND_NULL(chosen);
851                 break;
852         }
853 }
854
855 static void correct_untracked_entries(struct dir_struct *dir)
856 {
857         int src, dst, ign;
858
859         for (src = dst = ign = 0; src < dir->nr; src++) {
860                 /* skip paths in ignored[] that cannot be inside entries[src] */
861                 while (ign < dir->ignored_nr &&
862                        0 <= cmp_dir_entry(&dir->entries[src], &dir->ignored[ign]))
863                         ign++;
864
865                 if (ign < dir->ignored_nr &&
866                     check_dir_entry_contains(dir->entries[src], dir->ignored[ign])) {
867                         /* entries[src] contains an ignored path, so we drop it */
868                         free(dir->entries[src]);
869                 } else {
870                         struct dir_entry *ent = dir->entries[src++];
871
872                         /* entries[src] does not contain an ignored path, so we keep it */
873                         dir->entries[dst++] = ent;
874
875                         /* then discard paths in entries[] contained inside entries[src] */
876                         while (src < dir->nr &&
877                                check_dir_entry_contains(ent, dir->entries[src]))
878                                 free(dir->entries[src++]);
879
880                         /* compensate for the outer loop's loop control */
881                         src--;
882                 }
883         }
884         dir->nr = dst;
885 }
886
887 int cmd_clean(int argc, const char **argv, const char *prefix)
888 {
889         int i, res;
890         int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;
891         int ignored_only = 0, config_set = 0, errors = 0, gone = 1;
892         int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
893         struct strbuf abs_path = STRBUF_INIT;
894         struct dir_struct dir;
895         struct pathspec pathspec;
896         struct strbuf buf = STRBUF_INIT;
897         struct string_list exclude_list = STRING_LIST_INIT_NODUP;
898         struct exclude_list *el;
899         struct string_list_item *item;
900         const char *qname;
901         struct option options[] = {
902                 OPT__QUIET(&quiet, N_("do not print names of files removed")),
903                 OPT__DRY_RUN(&dry_run, N_("dry run")),
904                 OPT__FORCE(&force, N_("force"), PARSE_OPT_NOCOMPLETE),
905                 OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")),
906                 OPT_BOOL('d', NULL, &remove_directories,
907                                 N_("remove whole directories")),
908                 { OPTION_CALLBACK, 'e', "exclude", &exclude_list, N_("pattern"),
909                   N_("add <pattern> to ignore rules"), PARSE_OPT_NONEG, exclude_cb },
910                 OPT_BOOL('x', NULL, &ignored, N_("remove ignored files, too")),
911                 OPT_BOOL('X', NULL, &ignored_only,
912                                 N_("remove only ignored files")),
913                 OPT_END()
914         };
915
916         git_config(git_clean_config, NULL);
917         if (force < 0)
918                 force = 0;
919         else
920                 config_set = 1;
921
922         argc = parse_options(argc, argv, prefix, options, builtin_clean_usage,
923                              0);
924
925         memset(&dir, 0, sizeof(dir));
926         if (ignored_only)
927                 dir.flags |= DIR_SHOW_IGNORED;
928
929         if (ignored && ignored_only)
930                 die(_("-x and -X cannot be used together"));
931
932         if (!interactive && !dry_run && !force) {
933                 if (config_set)
934                         die(_("clean.requireForce set to true and neither -i, -n, nor -f given; "
935                                   "refusing to clean"));
936                 else
937                         die(_("clean.requireForce defaults to true and neither -i, -n, nor -f given;"
938                                   " refusing to clean"));
939         }
940
941         if (force > 1)
942                 rm_flags = 0;
943
944         dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
945
946         if (remove_directories)
947                 dir.flags |= DIR_SHOW_IGNORED_TOO | DIR_KEEP_UNTRACKED_CONTENTS;
948
949         if (read_cache() < 0)
950                 die(_("index file corrupt"));
951
952         if (!ignored)
953                 setup_standard_excludes(&dir);
954
955         el = add_exclude_list(&dir, EXC_CMDL, "--exclude option");
956         for (i = 0; i < exclude_list.nr; i++)
957                 add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1));
958
959         parse_pathspec(&pathspec, 0,
960                        PATHSPEC_PREFER_CWD,
961                        prefix, argv);
962
963         fill_directory(&dir, &the_index, &pathspec);
964         correct_untracked_entries(&dir);
965
966         for (i = 0; i < dir.nr; i++) {
967                 struct dir_entry *ent = dir.entries[i];
968                 int matches = 0;
969                 struct stat st;
970                 const char *rel;
971
972                 if (!cache_name_is_other(ent->name, ent->len))
973                         continue;
974
975                 if (pathspec.nr)
976                         matches = dir_path_match(ent, &pathspec, 0, NULL);
977
978                 if (pathspec.nr && !matches)
979                         continue;
980
981                 if (lstat(ent->name, &st))
982                         die_errno("Cannot lstat '%s'", ent->name);
983
984                 if (S_ISDIR(st.st_mode) && !remove_directories &&
985                     matches != MATCHED_EXACTLY)
986                         continue;
987
988                 rel = relative_path(ent->name, prefix, &buf);
989                 string_list_append(&del_list, rel);
990         }
991
992         for (i = 0; i < dir.nr; i++)
993                 free(dir.entries[i]);
994
995         for (i = 0; i < dir.ignored_nr; i++)
996                 free(dir.ignored[i]);
997
998         if (interactive && del_list.nr > 0)
999                 interactive_main_loop();
1000
1001         for_each_string_list_item(item, &del_list) {
1002                 struct stat st;
1003
1004                 if (prefix)
1005                         strbuf_addstr(&abs_path, prefix);
1006
1007                 strbuf_addstr(&abs_path, item->string);
1008
1009                 /*
1010                  * we might have removed this as part of earlier
1011                  * recursive directory removal, so lstat() here could
1012                  * fail with ENOENT.
1013                  */
1014                 if (lstat(abs_path.buf, &st))
1015                         continue;
1016
1017                 if (S_ISDIR(st.st_mode)) {
1018                         if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone))
1019                                 errors++;
1020                         if (gone && !quiet) {
1021                                 qname = quote_path_relative(item->string, NULL, &buf);
1022                                 printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
1023                         }
1024                 } else {
1025                         res = dry_run ? 0 : unlink(abs_path.buf);
1026                         if (res) {
1027                                 int saved_errno = errno;
1028                                 qname = quote_path_relative(item->string, NULL, &buf);
1029                                 errno = saved_errno;
1030                                 warning_errno(_(msg_warn_remove_failed), qname);
1031                                 errors++;
1032                         } else if (!quiet) {
1033                                 qname = quote_path_relative(item->string, NULL, &buf);
1034                                 printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
1035                         }
1036                 }
1037                 strbuf_reset(&abs_path);
1038         }
1039
1040         strbuf_release(&abs_path);
1041         strbuf_release(&buf);
1042         string_list_clear(&del_list, 0);
1043         string_list_clear(&exclude_list, 0);
1044         return (errors != 0);
1045 }