repack_without_refs(): make the refnames argument a string_list
[gitweb.git] / builtin / clean.c
index 95b41279c0dcc6781e210519183090d38d54c77a..77846762b55bd42f4ab97823ee1b6850dff81714 100644 (file)
@@ -15,6 +15,7 @@
 #include "quote.h"
 #include "column.h"
 #include "color.h"
+#include "pathspec.h"
 
 static int force = -1; /* unset */
 static int interactive;
@@ -47,7 +48,7 @@ enum color_clean {
        CLEAN_COLOR_PROMPT = 2,
        CLEAN_COLOR_HEADER = 3,
        CLEAN_COLOR_HELP = 4,
-       CLEAN_COLOR_ERROR = 5,
+       CLEAN_COLOR_ERROR = 5
 };
 
 #define MENU_OPTS_SINGLETON            01
@@ -99,7 +100,9 @@ static int parse_clean_color_slot(const char *var)
 
 static int git_clean_config(const char *var, const char *value, void *cb)
 {
-       if (!prefixcmp(var, "column."))
+       const char *slot_name;
+
+       if (starts_with(var, "column."))
                return git_column_config(var, value, "clean", &colopts);
 
        /* honors the color.interactive* config variables which also
@@ -108,15 +111,13 @@ static int git_clean_config(const char *var, const char *value, void *cb)
                clean_use_color = git_config_colorbool(var, value);
                return 0;
        }
-       if (!prefixcmp(var, "color.interactive.")) {
-               int slot = parse_clean_color_slot(var +
-                                                 strlen("color.interactive."));
+       if (skip_prefix(var, "color.interactive.", &slot_name)) {
+               int slot = parse_clean_color_slot(slot_name);
                if (slot < 0)
                        return 0;
                if (!value)
                        return config_error_nonbool(var);
-               color_parse(value, var, clean_colors[slot]);
-               return 0;
+               return color_parse(value, clean_colors[slot]);
        }
 
        if (!strcmp(var, "clean.requireforce")) {
@@ -153,7 +154,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
        DIR *dir;
        struct strbuf quoted = STRBUF_INIT;
        struct dirent *e;
-       int res = 0, ret = 0, gone = 1, original_len = path->len, len, i;
+       int res = 0, ret = 0, gone = 1, original_len = path->len, len;
        unsigned char submodule_head[20];
        struct string_list dels = STRING_LIST_INIT_DUP;
 
@@ -241,6 +242,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
        }
 
        if (!*dir_gone && !quiet) {
+               int i;
                for (i = 0; i < dels.nr; i++)
                        printf(dry_run ?  _(msg_would_remove) : _(msg_remove), dels.items[i].string);
        }
@@ -365,6 +367,56 @@ static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)
        string_list_clear(&menu_list, 0);
 }
 
+static int find_unique(const char *choice, struct menu_stuff *menu_stuff)
+{
+       struct menu_item *menu_item;
+       struct string_list_item *string_list_item;
+       int i, len, found = 0;
+
+       len = strlen(choice);
+       switch (menu_stuff->type) {
+       default:
+               die("Bad type of menu_stuff when parse choice");
+       case MENU_STUFF_TYPE_MENU_ITEM:
+
+               menu_item = (struct menu_item *)menu_stuff->stuff;
+               for (i = 0; i < menu_stuff->nr; i++, menu_item++) {
+                       if (len == 1 && *choice == menu_item->hotkey) {
+                               found = i + 1;
+                               break;
+                       }
+                       if (!strncasecmp(choice, menu_item->title, len)) {
+                               if (found) {
+                                       if (len == 1) {
+                                               /* continue for hotkey matching */
+                                               found = -1;
+                                       } else {
+                                               found = 0;
+                                               break;
+                                       }
+                               } else {
+                                       found = i + 1;
+                               }
+                       }
+               }
+               break;
+       case MENU_STUFF_TYPE_STRING_LIST:
+               string_list_item = ((struct string_list *)menu_stuff->stuff)->items;
+               for (i = 0; i < menu_stuff->nr; i++, string_list_item++) {
+                       if (!strncasecmp(choice, string_list_item->string, len)) {
+                               if (found) {
+                                       found = 0;
+                                       break;
+                               }
+                               found = i + 1;
+                       }
+               }
+               break;
+       }
+       return found;
+}
+
+
 /*
  * Parse user input, and return choice(s) for menu (menu_stuff).
  *
@@ -392,8 +444,6 @@ static int parse_choice(struct menu_stuff *menu_stuff,
                        int **chosen)
 {
        struct strbuf **choice_list, **ptr;
-       struct menu_item *menu_item;
-       struct string_list_item *string_list_item;
        int nr = 0;
        int i;
 
@@ -457,32 +507,8 @@ static int parse_choice(struct menu_stuff *menu_stuff,
                        bottom = 1;
                        top = menu_stuff->nr;
                } else {
-                       switch (menu_stuff->type) {
-                       default:
-                               die("Bad type of menu_stuff when parse choice");
-                       case MENU_STUFF_TYPE_MENU_ITEM:
-                               menu_item = (struct menu_item *)menu_stuff->stuff;
-                               for (i = 0; i < menu_stuff->nr; i++, menu_item++) {
-                                       if (((*ptr)->len == 1 &&
-                                            *(*ptr)->buf == menu_item->hotkey) ||
-                                           !strcasecmp((*ptr)->buf, menu_item->title)) {
-                                               bottom = i + 1;
-                                               top = bottom;
-                                               break;
-                                       }
-                               }
-                               break;
-                       case MENU_STUFF_TYPE_STRING_LIST:
-                               string_list_item = ((struct string_list *)menu_stuff->stuff)->items;
-                               for (i = 0; i < menu_stuff->nr; i++, string_list_item++) {
-                                       if (!strcasecmp((*ptr)->buf, string_list_item->string)) {
-                                               bottom = i + 1;
-                                               top = bottom;
-                                               break;
-                                       }
-                               }
-                               break;
-                       }
+                       bottom = find_unique((*ptr)->buf, menu_stuff);
+                       top = bottom;
                }
 
                if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top ||
@@ -595,8 +621,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
                                nr += chosen[i];
                }
 
-               result = xmalloc(sizeof(int) * (nr + 1));
-               memset(result, 0, sizeof(int) * (nr + 1));
+               result = xcalloc(nr + 1, sizeof(int));
                for (i = 0; i < stuff->nr && j < nr; i++) {
                        if (chosen[i])
                                result[j++] = i;
@@ -614,6 +639,143 @@ static int clean_cmd(void)
        return MENU_RETURN_NO_LOOP;
 }
 
+static int filter_by_patterns_cmd(void)
+{
+       struct dir_struct dir;
+       struct strbuf confirm = STRBUF_INIT;
+       struct strbuf **ignore_list;
+       struct string_list_item *item;
+       struct exclude_list *el;
+       int changed = -1, i;
+
+       for (;;) {
+               if (!del_list.nr)
+                       break;
+
+               if (changed)
+                       pretty_print_dels();
+
+               clean_print_color(CLEAN_COLOR_PROMPT);
+               printf(_("Input ignore patterns>> "));
+               clean_print_color(CLEAN_COLOR_RESET);
+               if (strbuf_getline(&confirm, stdin, '\n') != EOF)
+                       strbuf_trim(&confirm);
+               else
+                       putchar('\n');
+
+               /* quit filter_by_pattern mode if press ENTER or Ctrl-D */
+               if (!confirm.len)
+                       break;
+
+               memset(&dir, 0, sizeof(dir));
+               el = add_exclude_list(&dir, EXC_CMDL, "manual exclude");
+               ignore_list = strbuf_split_max(&confirm, ' ', 0);
+
+               for (i = 0; ignore_list[i]; i++) {
+                       strbuf_trim(ignore_list[i]);
+                       if (!ignore_list[i]->len)
+                               continue;
+
+                       add_exclude(ignore_list[i]->buf, "", 0, el, -(i+1));
+               }
+
+               changed = 0;
+               for_each_string_list_item(item, &del_list) {
+                       int dtype = DT_UNKNOWN;
+
+                       if (is_excluded(&dir, item->string, &dtype)) {
+                               *item->string = '\0';
+                               changed++;
+                       }
+               }
+
+               if (changed) {
+                       string_list_remove_empty_items(&del_list, 0);
+               } else {
+                       clean_print_color(CLEAN_COLOR_ERROR);
+                       printf_ln(_("WARNING: Cannot find items matched by: %s"), confirm.buf);
+                       clean_print_color(CLEAN_COLOR_RESET);
+               }
+
+               strbuf_list_free(ignore_list);
+               clear_directory(&dir);
+       }
+
+       strbuf_release(&confirm);
+       return 0;
+}
+
+static int select_by_numbers_cmd(void)
+{
+       struct menu_opts menu_opts;
+       struct menu_stuff menu_stuff;
+       struct string_list_item *items;
+       int *chosen;
+       int i, j;
+
+       menu_opts.header = NULL;
+       menu_opts.prompt = N_("Select items to delete");
+       menu_opts.flags = 0;
+
+       menu_stuff.type = MENU_STUFF_TYPE_STRING_LIST;
+       menu_stuff.stuff = &del_list;
+       menu_stuff.nr = del_list.nr;
+
+       chosen = list_and_choose(&menu_opts, &menu_stuff);
+       items = del_list.items;
+       for (i = 0, j = 0; i < del_list.nr; i++) {
+               if (i < chosen[j]) {
+                       *(items[i].string) = '\0';
+               } else if (i == chosen[j]) {
+                       /* delete selected item */
+                       j++;
+                       continue;
+               } else {
+                       /* end of chosen (chosen[j] == EOF), won't delete */
+                       *(items[i].string) = '\0';
+               }
+       }
+
+       string_list_remove_empty_items(&del_list, 0);
+
+       free(chosen);
+       return 0;
+}
+
+static int ask_each_cmd(void)
+{
+       struct strbuf confirm = STRBUF_INIT;
+       struct strbuf buf = STRBUF_INIT;
+       struct string_list_item *item;
+       const char *qname;
+       int changed = 0, eof = 0;
+
+       for_each_string_list_item(item, &del_list) {
+               /* Ctrl-D should stop removing files */
+               if (!eof) {
+                       qname = quote_path_relative(item->string, NULL, &buf);
+                       printf(_("remove %s? "), qname);
+                       if (strbuf_getline(&confirm, stdin, '\n') != EOF) {
+                               strbuf_trim(&confirm);
+                       } else {
+                               putchar('\n');
+                               eof = 1;
+                       }
+               }
+               if (!confirm.len || strncasecmp(confirm.buf, "yes", confirm.len)) {
+                       *item->string = '\0';
+                       changed++;
+               }
+       }
+
+       if (changed)
+               string_list_remove_empty_items(&del_list, 0);
+
+       strbuf_release(&buf);
+       strbuf_release(&confirm);
+       return MENU_RETURN_NO_LOOP;
+}
+
 static int quit_cmd(void)
 {
        string_list_clear(&del_list, 0);
@@ -626,6 +788,9 @@ static int help_cmd(void)
        clean_print_color(CLEAN_COLOR_HELP);
        printf_ln(_(
                    "clean               - start cleaning\n"
+                   "filter by pattern   - exclude items from deletion\n"
+                   "select by numbers   - select items to be deleted by numbers\n"
+                   "ask each            - confirm each deletion (like \"rm -i\")\n"
                    "quit                - stop cleaning\n"
                    "help                - this screen\n"
                    "?                   - help for prompt selection"
@@ -641,6 +806,9 @@ static void interactive_main_loop(void)
                struct menu_stuff menu_stuff;
                struct menu_item menus[] = {
                        {'c', "clean",                  0, clean_cmd},
+                       {'f', "filter by pattern",      0, filter_by_patterns_cmd},
+                       {'s', "select by numbers",      0, select_by_numbers_cmd},
+                       {'a', "ask each",               0, ask_each_cmd},
                        {'q', "quit",                   0, quit_cmd},
                        {'h', "help",                   0, help_cmd},
                };
@@ -696,24 +864,23 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
        int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
        struct strbuf abs_path = STRBUF_INIT;
        struct dir_struct dir;
-       static const char **pathspec;
+       struct pathspec pathspec;
        struct strbuf buf = STRBUF_INIT;
        struct string_list exclude_list = STRING_LIST_INIT_NODUP;
        struct exclude_list *el;
        struct string_list_item *item;
        const char *qname;
-       char *seen = NULL;
        struct option options[] = {
                OPT__QUIET(&quiet, N_("do not print names of files removed")),
                OPT__DRY_RUN(&dry_run, N_("dry run")),
                OPT__FORCE(&force, N_("force")),
                OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")),
-               OPT_BOOLEAN('d', NULL, &remove_directories,
+               OPT_BOOL('d', NULL, &remove_directories,
                                N_("remove whole directories")),
                { OPTION_CALLBACK, 'e', "exclude", &exclude_list, N_("pattern"),
                  N_("add <pattern> to ignore rules"), PARSE_OPT_NONEG, exclude_cb },
-               OPT_BOOLEAN('x', NULL, &ignored, N_("remove ignored files, too")),
-               OPT_BOOLEAN('X', NULL, &ignored_only,
+               OPT_BOOL('x', NULL, &ignored, N_("remove ignored files, too")),
+               OPT_BOOL('X', NULL, &ignored_only,
                                N_("remove only ignored files")),
                OPT_END()
        };
@@ -736,11 +903,11 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 
        if (!interactive && !dry_run && !force) {
                if (config_set)
-                       die(_("clean.requireForce set to true and neither -i, -n nor -f given; "
+                       die(_("clean.requireForce set to true and neither -i, -n, nor -f given; "
                                  "refusing to clean"));
                else
-                       die(_("clean.requireForce defaults to true and neither -i, -n nor -f given; "
-                                 "refusing to clean"));
+                       die(_("clean.requireForce defaults to true and neither -i, -n, nor -f given;"
+                                 " refusing to clean"));
        }
 
        if (force > 1)
@@ -758,59 +925,36 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
        for (i = 0; i < exclude_list.nr; i++)
                add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1));
 
-       pathspec = get_pathspec(prefix, argv);
-
-       fill_directory(&dir, pathspec);
+       parse_pathspec(&pathspec, 0,
+                      PATHSPEC_PREFER_CWD,
+                      prefix, argv);
 
-       if (pathspec)
-               seen = xmalloc(argc > 0 ? argc : 1);
+       fill_directory(&dir, &pathspec);
 
        for (i = 0; i < dir.nr; i++) {
                struct dir_entry *ent = dir.entries[i];
-               int len, pos;
                int matches = 0;
-               struct cache_entry *ce;
                struct stat st;
                const char *rel;
 
-               /*
-                * Remove the '/' at the end that directory
-                * walking adds for directory entries.
-                */
-               len = ent->len;
-               if (len && ent->name[len-1] == '/')
-                       len--;
-               pos = cache_name_pos(ent->name, len);
-               if (0 <= pos)
-                       continue;       /* exact match */
-               pos = -pos - 1;
-               if (pos < active_nr) {
-                       ce = active_cache[pos];
-                       if (ce_namelen(ce) == len &&
-                           !memcmp(ce->name, ent->name, len))
-                               continue; /* Yup, this one exists unmerged */
-               }
+               if (!cache_name_is_other(ent->name, ent->len))
+                       continue;
 
                if (lstat(ent->name, &st))
                        die_errno("Cannot lstat '%s'", ent->name);
 
-               if (pathspec) {
-                       memset(seen, 0, argc > 0 ? argc : 1);
-                       matches = match_pathspec(pathspec, ent->name, len,
-                                                0, seen);
-               }
+               if (pathspec.nr)
+                       matches = dir_path_match(ent, &pathspec, 0, NULL);
 
-               if (S_ISDIR(st.st_mode)) {
-                       if (remove_directories || (matches == MATCHED_EXACTLY)) {
-                               rel = relative_path(ent->name, prefix, &buf);
-                               string_list_append(&del_list, rel);
-                       }
-               } else {
-                       if (pathspec && !matches)
-                               continue;
-                       rel = relative_path(ent->name, prefix, &buf);
-                       string_list_append(&del_list, rel);
-               }
+               if (pathspec.nr && !matches)
+                       continue;
+
+               if (S_ISDIR(st.st_mode) && !remove_directories &&
+                   matches != MATCHED_EXACTLY)
+                       continue;
+
+               rel = relative_path(ent->name, prefix, &buf);
+               string_list_append(&del_list, rel);
        }
 
        if (interactive && del_list.nr > 0)
@@ -852,7 +996,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                }
                strbuf_reset(&abs_path);
        }
-       free(seen);
 
        strbuf_release(&abs_path);
        strbuf_release(&buf);