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