dfa99bfd5d8b615b16de831b7cbd9eae89c93bdd
   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 "refs.h"
  14#include "string-list.h"
  15#include "quote.h"
  16#include "column.h"
  17#include "color.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
  53static int parse_clean_color_slot(const char *var)
  54{
  55        if (!strcasecmp(var, "reset"))
  56                return CLEAN_COLOR_RESET;
  57        if (!strcasecmp(var, "plain"))
  58                return CLEAN_COLOR_PLAIN;
  59        if (!strcasecmp(var, "prompt"))
  60                return CLEAN_COLOR_PROMPT;
  61        if (!strcasecmp(var, "header"))
  62                return CLEAN_COLOR_HEADER;
  63        if (!strcasecmp(var, "help"))
  64                return CLEAN_COLOR_HELP;
  65        if (!strcasecmp(var, "error"))
  66                return CLEAN_COLOR_ERROR;
  67        return -1;
  68}
  69
  70static int git_clean_config(const char *var, const char *value, void *cb)
  71{
  72        if (!prefixcmp(var, "column."))
  73                return git_column_config(var, value, "clean", &colopts);
  74
  75        /* honors the color.interactive* config variables which also
  76           applied in git-add--interactive and git-stash */
  77        if (!strcmp(var, "color.interactive")) {
  78                clean_use_color = git_config_colorbool(var, value);
  79                return 0;
  80        }
  81        if (!prefixcmp(var, "color.interactive.")) {
  82                int slot = parse_clean_color_slot(var +
  83                                                  strlen("color.interactive."));
  84                if (slot < 0)
  85                        return 0;
  86                if (!value)
  87                        return config_error_nonbool(var);
  88                color_parse(value, var, clean_colors[slot]);
  89                return 0;
  90        }
  91
  92        if (!strcmp(var, "clean.requireforce")) {
  93                force = !git_config_bool(var, value);
  94                return 0;
  95        }
  96
  97        /* inspect the color.ui config variable and others */
  98        return git_color_default_config(var, value, cb);
  99}
 100
 101static const char *clean_get_color(enum color_clean ix)
 102{
 103        if (want_color(clean_use_color))
 104                return clean_colors[ix];
 105        return "";
 106}
 107
 108static void clean_print_color(enum color_clean ix)
 109{
 110        printf("%s", clean_get_color(ix));
 111}
 112
 113static int exclude_cb(const struct option *opt, const char *arg, int unset)
 114{
 115        struct string_list *exclude_list = opt->value;
 116        string_list_append(exclude_list, arg);
 117        return 0;
 118}
 119
 120static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
 121                int dry_run, int quiet, int *dir_gone)
 122{
 123        DIR *dir;
 124        struct strbuf quoted = STRBUF_INIT;
 125        struct dirent *e;
 126        int res = 0, ret = 0, gone = 1, original_len = path->len, len, i;
 127        unsigned char submodule_head[20];
 128        struct string_list dels = STRING_LIST_INIT_DUP;
 129
 130        *dir_gone = 1;
 131
 132        if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
 133                        !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
 134                if (!quiet) {
 135                        quote_path_relative(path->buf, prefix, &quoted);
 136                        printf(dry_run ?  _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
 137                                        quoted.buf);
 138                }
 139
 140                *dir_gone = 0;
 141                return 0;
 142        }
 143
 144        dir = opendir(path->buf);
 145        if (!dir) {
 146                /* an empty dir could be removed even if it is unreadble */
 147                res = dry_run ? 0 : rmdir(path->buf);
 148                if (res) {
 149                        quote_path_relative(path->buf, prefix, &quoted);
 150                        warning(_(msg_warn_remove_failed), quoted.buf);
 151                        *dir_gone = 0;
 152                }
 153                return res;
 154        }
 155
 156        if (path->buf[original_len - 1] != '/')
 157                strbuf_addch(path, '/');
 158
 159        len = path->len;
 160        while ((e = readdir(dir)) != NULL) {
 161                struct stat st;
 162                if (is_dot_or_dotdot(e->d_name))
 163                        continue;
 164
 165                strbuf_setlen(path, len);
 166                strbuf_addstr(path, e->d_name);
 167                if (lstat(path->buf, &st))
 168                        ; /* fall thru */
 169                else if (S_ISDIR(st.st_mode)) {
 170                        if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))
 171                                ret = 1;
 172                        if (gone) {
 173                                quote_path_relative(path->buf, prefix, &quoted);
 174                                string_list_append(&dels, quoted.buf);
 175                        } else
 176                                *dir_gone = 0;
 177                        continue;
 178                } else {
 179                        res = dry_run ? 0 : unlink(path->buf);
 180                        if (!res) {
 181                                quote_path_relative(path->buf, prefix, &quoted);
 182                                string_list_append(&dels, quoted.buf);
 183                        } else {
 184                                quote_path_relative(path->buf, prefix, &quoted);
 185                                warning(_(msg_warn_remove_failed), quoted.buf);
 186                                *dir_gone = 0;
 187                                ret = 1;
 188                        }
 189                        continue;
 190                }
 191
 192                /* path too long, stat fails, or non-directory still exists */
 193                *dir_gone = 0;
 194                ret = 1;
 195                break;
 196        }
 197        closedir(dir);
 198
 199        strbuf_setlen(path, original_len);
 200
 201        if (*dir_gone) {
 202                res = dry_run ? 0 : rmdir(path->buf);
 203                if (!res)
 204                        *dir_gone = 1;
 205                else {
 206                        quote_path_relative(path->buf, prefix, &quoted);
 207                        warning(_(msg_warn_remove_failed), quoted.buf);
 208                        *dir_gone = 0;
 209                        ret = 1;
 210                }
 211        }
 212
 213        if (!*dir_gone && !quiet) {
 214                for (i = 0; i < dels.nr; i++)
 215                        printf(dry_run ?  _(msg_would_remove) : _(msg_remove), dels.items[i].string);
 216        }
 217        string_list_clear(&dels, 0);
 218        return ret;
 219}
 220
 221static void pretty_print_dels(void)
 222{
 223        struct string_list list = STRING_LIST_INIT_DUP;
 224        struct string_list_item *item;
 225        struct strbuf buf = STRBUF_INIT;
 226        const char *qname;
 227        struct column_options copts;
 228
 229        for_each_string_list_item(item, &del_list) {
 230                qname = quote_path_relative(item->string, NULL, &buf);
 231                string_list_append(&list, qname);
 232        }
 233
 234        /*
 235         * always enable column display, we only consult column.*
 236         * about layout strategy and stuff
 237         */
 238        colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
 239        memset(&copts, 0, sizeof(copts));
 240        copts.indent = "  ";
 241        copts.padding = 2;
 242        print_columns(&list, colopts, &copts);
 243        putchar('\n');
 244        strbuf_release(&buf);
 245        string_list_clear(&list, 0);
 246}
 247
 248static void interactive_main_loop(void)
 249{
 250        struct strbuf confirm = STRBUF_INIT;
 251
 252        while (del_list.nr) {
 253                putchar('\n');
 254                clean_print_color(CLEAN_COLOR_HEADER);
 255                printf_ln(Q_("Would remove the following item:",
 256                             "Would remove the following items:",
 257                             del_list.nr));
 258                clean_print_color(CLEAN_COLOR_RESET);
 259                putchar('\n');
 260
 261                pretty_print_dels();
 262
 263                clean_print_color(CLEAN_COLOR_PROMPT);
 264                printf(_("Remove [y/n]? "));
 265                clean_print_color(CLEAN_COLOR_RESET);
 266                if (strbuf_getline(&confirm, stdin, '\n') != EOF) {
 267                        strbuf_trim(&confirm);
 268                } else {
 269                        /* Ctrl-D is the same as "quit" */
 270                        string_list_clear(&del_list, 0);
 271                        putchar('\n');
 272                        printf_ln("Bye.");
 273                        break;
 274                }
 275
 276                if (confirm.len) {
 277                        if (!strncasecmp(confirm.buf, "yes", confirm.len)) {
 278                                break;
 279                        } else if (!strncasecmp(confirm.buf, "no", confirm.len) ||
 280                                   !strncasecmp(confirm.buf, "quit", confirm.len)) {
 281                                string_list_clear(&del_list, 0);
 282                                printf_ln("Bye.");
 283                                break;
 284                        } else {
 285                                continue;
 286                        }
 287                }
 288        }
 289
 290        strbuf_release(&confirm);
 291}
 292
 293int cmd_clean(int argc, const char **argv, const char *prefix)
 294{
 295        int i, res;
 296        int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;
 297        int ignored_only = 0, config_set = 0, errors = 0, gone = 1;
 298        int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
 299        struct strbuf abs_path = STRBUF_INIT;
 300        struct dir_struct dir;
 301        static const char **pathspec;
 302        struct strbuf buf = STRBUF_INIT;
 303        struct string_list exclude_list = STRING_LIST_INIT_NODUP;
 304        struct exclude_list *el;
 305        struct string_list_item *item;
 306        const char *qname;
 307        char *seen = NULL;
 308        struct option options[] = {
 309                OPT__QUIET(&quiet, N_("do not print names of files removed")),
 310                OPT__DRY_RUN(&dry_run, N_("dry run")),
 311                OPT__FORCE(&force, N_("force")),
 312                OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")),
 313                OPT_BOOLEAN('d', NULL, &remove_directories,
 314                                N_("remove whole directories")),
 315                { OPTION_CALLBACK, 'e', "exclude", &exclude_list, N_("pattern"),
 316                  N_("add <pattern> to ignore rules"), PARSE_OPT_NONEG, exclude_cb },
 317                OPT_BOOLEAN('x', NULL, &ignored, N_("remove ignored files, too")),
 318                OPT_BOOLEAN('X', NULL, &ignored_only,
 319                                N_("remove only ignored files")),
 320                OPT_END()
 321        };
 322
 323        git_config(git_clean_config, NULL);
 324        if (force < 0)
 325                force = 0;
 326        else
 327                config_set = 1;
 328
 329        argc = parse_options(argc, argv, prefix, options, builtin_clean_usage,
 330                             0);
 331
 332        memset(&dir, 0, sizeof(dir));
 333        if (ignored_only)
 334                dir.flags |= DIR_SHOW_IGNORED;
 335
 336        if (ignored && ignored_only)
 337                die(_("-x and -X cannot be used together"));
 338
 339        if (!interactive && !dry_run && !force) {
 340                if (config_set)
 341                        die(_("clean.requireForce set to true and neither -i, -n nor -f given; "
 342                                  "refusing to clean"));
 343                else
 344                        die(_("clean.requireForce defaults to true and neither -i, -n nor -f given; "
 345                                  "refusing to clean"));
 346        }
 347
 348        if (force > 1)
 349                rm_flags = 0;
 350
 351        dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
 352
 353        if (read_cache() < 0)
 354                die(_("index file corrupt"));
 355
 356        if (!ignored)
 357                setup_standard_excludes(&dir);
 358
 359        el = add_exclude_list(&dir, EXC_CMDL, "--exclude option");
 360        for (i = 0; i < exclude_list.nr; i++)
 361                add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1));
 362
 363        pathspec = get_pathspec(prefix, argv);
 364
 365        fill_directory(&dir, pathspec);
 366
 367        if (pathspec)
 368                seen = xmalloc(argc > 0 ? argc : 1);
 369
 370        for (i = 0; i < dir.nr; i++) {
 371                struct dir_entry *ent = dir.entries[i];
 372                int len, pos;
 373                int matches = 0;
 374                struct cache_entry *ce;
 375                struct stat st;
 376                const char *rel;
 377
 378                /*
 379                 * Remove the '/' at the end that directory
 380                 * walking adds for directory entries.
 381                 */
 382                len = ent->len;
 383                if (len && ent->name[len-1] == '/')
 384                        len--;
 385                pos = cache_name_pos(ent->name, len);
 386                if (0 <= pos)
 387                        continue;       /* exact match */
 388                pos = -pos - 1;
 389                if (pos < active_nr) {
 390                        ce = active_cache[pos];
 391                        if (ce_namelen(ce) == len &&
 392                            !memcmp(ce->name, ent->name, len))
 393                                continue; /* Yup, this one exists unmerged */
 394                }
 395
 396                if (lstat(ent->name, &st))
 397                        die_errno("Cannot lstat '%s'", ent->name);
 398
 399                if (pathspec) {
 400                        memset(seen, 0, argc > 0 ? argc : 1);
 401                        matches = match_pathspec(pathspec, ent->name, len,
 402                                                 0, seen);
 403                }
 404
 405                if (S_ISDIR(st.st_mode)) {
 406                        if (remove_directories || (matches == MATCHED_EXACTLY)) {
 407                                rel = relative_path(ent->name, prefix, &buf);
 408                                string_list_append(&del_list, rel);
 409                        }
 410                } else {
 411                        if (pathspec && !matches)
 412                                continue;
 413                        rel = relative_path(ent->name, prefix, &buf);
 414                        string_list_append(&del_list, rel);
 415                }
 416        }
 417
 418        if (interactive && del_list.nr > 0)
 419                interactive_main_loop();
 420
 421        for_each_string_list_item(item, &del_list) {
 422                struct stat st;
 423
 424                if (prefix)
 425                        strbuf_addstr(&abs_path, prefix);
 426
 427                strbuf_addstr(&abs_path, item->string);
 428
 429                /*
 430                 * we might have removed this as part of earlier
 431                 * recursive directory removal, so lstat() here could
 432                 * fail with ENOENT.
 433                 */
 434                if (lstat(abs_path.buf, &st))
 435                        continue;
 436
 437                if (S_ISDIR(st.st_mode)) {
 438                        if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone))
 439                                errors++;
 440                        if (gone && !quiet) {
 441                                qname = quote_path_relative(item->string, NULL, &buf);
 442                                printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
 443                        }
 444                } else {
 445                        res = dry_run ? 0 : unlink(abs_path.buf);
 446                        if (res) {
 447                                qname = quote_path_relative(item->string, NULL, &buf);
 448                                warning(_(msg_warn_remove_failed), qname);
 449                                errors++;
 450                        } else if (!quiet) {
 451                                qname = quote_path_relative(item->string, NULL, &buf);
 452                                printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
 453                        }
 454                }
 455                strbuf_reset(&abs_path);
 456        }
 457        free(seen);
 458
 459        strbuf_release(&abs_path);
 460        strbuf_release(&buf);
 461        string_list_clear(&del_list, 0);
 462        string_list_clear(&exclude_list, 0);
 463        return (errors != 0);
 464}