builtin / clean.con commit git-clean: show items of del_list in columns (1b8fd46)
   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
  18static int force = -1; /* unset */
  19static int interactive;
  20static struct string_list del_list = STRING_LIST_INIT_DUP;
  21static unsigned int colopts;
  22
  23static const char *const builtin_clean_usage[] = {
  24        N_("git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
  25        NULL
  26};
  27
  28static const char *msg_remove = N_("Removing %s\n");
  29static const char *msg_would_remove = N_("Would remove %s\n");
  30static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
  31static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
  32static const char *msg_warn_remove_failed = N_("failed to remove %s");
  33
  34static int git_clean_config(const char *var, const char *value, void *cb)
  35{
  36        if (!prefixcmp(var, "column."))
  37                return git_column_config(var, value, "clean", &colopts);
  38
  39        if (!strcmp(var, "clean.requireforce")) {
  40                force = !git_config_bool(var, value);
  41                return 0;
  42        }
  43        return git_default_config(var, value, cb);
  44}
  45
  46static int exclude_cb(const struct option *opt, const char *arg, int unset)
  47{
  48        struct string_list *exclude_list = opt->value;
  49        string_list_append(exclude_list, arg);
  50        return 0;
  51}
  52
  53static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
  54                int dry_run, int quiet, int *dir_gone)
  55{
  56        DIR *dir;
  57        struct strbuf quoted = STRBUF_INIT;
  58        struct dirent *e;
  59        int res = 0, ret = 0, gone = 1, original_len = path->len, len, i;
  60        unsigned char submodule_head[20];
  61        struct string_list dels = STRING_LIST_INIT_DUP;
  62
  63        *dir_gone = 1;
  64
  65        if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
  66                        !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
  67                if (!quiet) {
  68                        quote_path_relative(path->buf, prefix, &quoted);
  69                        printf(dry_run ?  _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
  70                                        quoted.buf);
  71                }
  72
  73                *dir_gone = 0;
  74                return 0;
  75        }
  76
  77        dir = opendir(path->buf);
  78        if (!dir) {
  79                /* an empty dir could be removed even if it is unreadble */
  80                res = dry_run ? 0 : rmdir(path->buf);
  81                if (res) {
  82                        quote_path_relative(path->buf, prefix, &quoted);
  83                        warning(_(msg_warn_remove_failed), quoted.buf);
  84                        *dir_gone = 0;
  85                }
  86                return res;
  87        }
  88
  89        if (path->buf[original_len - 1] != '/')
  90                strbuf_addch(path, '/');
  91
  92        len = path->len;
  93        while ((e = readdir(dir)) != NULL) {
  94                struct stat st;
  95                if (is_dot_or_dotdot(e->d_name))
  96                        continue;
  97
  98                strbuf_setlen(path, len);
  99                strbuf_addstr(path, e->d_name);
 100                if (lstat(path->buf, &st))
 101                        ; /* fall thru */
 102                else if (S_ISDIR(st.st_mode)) {
 103                        if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))
 104                                ret = 1;
 105                        if (gone) {
 106                                quote_path_relative(path->buf, prefix, &quoted);
 107                                string_list_append(&dels, quoted.buf);
 108                        } else
 109                                *dir_gone = 0;
 110                        continue;
 111                } else {
 112                        res = dry_run ? 0 : unlink(path->buf);
 113                        if (!res) {
 114                                quote_path_relative(path->buf, prefix, &quoted);
 115                                string_list_append(&dels, quoted.buf);
 116                        } else {
 117                                quote_path_relative(path->buf, prefix, &quoted);
 118                                warning(_(msg_warn_remove_failed), quoted.buf);
 119                                *dir_gone = 0;
 120                                ret = 1;
 121                        }
 122                        continue;
 123                }
 124
 125                /* path too long, stat fails, or non-directory still exists */
 126                *dir_gone = 0;
 127                ret = 1;
 128                break;
 129        }
 130        closedir(dir);
 131
 132        strbuf_setlen(path, original_len);
 133
 134        if (*dir_gone) {
 135                res = dry_run ? 0 : rmdir(path->buf);
 136                if (!res)
 137                        *dir_gone = 1;
 138                else {
 139                        quote_path_relative(path->buf, prefix, &quoted);
 140                        warning(_(msg_warn_remove_failed), quoted.buf);
 141                        *dir_gone = 0;
 142                        ret = 1;
 143                }
 144        }
 145
 146        if (!*dir_gone && !quiet) {
 147                for (i = 0; i < dels.nr; i++)
 148                        printf(dry_run ?  _(msg_would_remove) : _(msg_remove), dels.items[i].string);
 149        }
 150        string_list_clear(&dels, 0);
 151        return ret;
 152}
 153
 154static void pretty_print_dels(void)
 155{
 156        struct string_list list = STRING_LIST_INIT_DUP;
 157        struct string_list_item *item;
 158        struct strbuf buf = STRBUF_INIT;
 159        const char *qname;
 160        struct column_options copts;
 161
 162        for_each_string_list_item(item, &del_list) {
 163                qname = quote_path_relative(item->string, NULL, &buf);
 164                string_list_append(&list, qname);
 165        }
 166
 167        /*
 168         * always enable column display, we only consult column.*
 169         * about layout strategy and stuff
 170         */
 171        colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
 172        memset(&copts, 0, sizeof(copts));
 173        copts.indent = "  ";
 174        copts.padding = 2;
 175        print_columns(&list, colopts, &copts);
 176        putchar('\n');
 177        strbuf_release(&buf);
 178        string_list_clear(&list, 0);
 179}
 180
 181static void interactive_main_loop(void)
 182{
 183        struct strbuf confirm = STRBUF_INIT;
 184
 185        while (del_list.nr) {
 186                putchar('\n');
 187                printf_ln(Q_("Would remove the following item:",
 188                             "Would remove the following items:",
 189                             del_list.nr));
 190                putchar('\n');
 191
 192                pretty_print_dels();
 193
 194                printf(_("Remove [y/n]? "));
 195                if (strbuf_getline(&confirm, stdin, '\n') != EOF) {
 196                        strbuf_trim(&confirm);
 197                } else {
 198                        /* Ctrl-D is the same as "quit" */
 199                        string_list_clear(&del_list, 0);
 200                        putchar('\n');
 201                        printf_ln("Bye.");
 202                        break;
 203                }
 204
 205                if (confirm.len) {
 206                        if (!strncasecmp(confirm.buf, "yes", confirm.len)) {
 207                                break;
 208                        } else if (!strncasecmp(confirm.buf, "no", confirm.len) ||
 209                                   !strncasecmp(confirm.buf, "quit", confirm.len)) {
 210                                string_list_clear(&del_list, 0);
 211                                printf_ln("Bye.");
 212                                break;
 213                        } else {
 214                                continue;
 215                        }
 216                }
 217        }
 218
 219        strbuf_release(&confirm);
 220}
 221
 222int cmd_clean(int argc, const char **argv, const char *prefix)
 223{
 224        int i, res;
 225        int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;
 226        int ignored_only = 0, config_set = 0, errors = 0, gone = 1;
 227        int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
 228        struct strbuf abs_path = STRBUF_INIT;
 229        struct dir_struct dir;
 230        static const char **pathspec;
 231        struct strbuf buf = STRBUF_INIT;
 232        struct string_list exclude_list = STRING_LIST_INIT_NODUP;
 233        struct exclude_list *el;
 234        struct string_list_item *item;
 235        const char *qname;
 236        char *seen = NULL;
 237        struct option options[] = {
 238                OPT__QUIET(&quiet, N_("do not print names of files removed")),
 239                OPT__DRY_RUN(&dry_run, N_("dry run")),
 240                OPT__FORCE(&force, N_("force")),
 241                OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")),
 242                OPT_BOOLEAN('d', NULL, &remove_directories,
 243                                N_("remove whole directories")),
 244                { OPTION_CALLBACK, 'e', "exclude", &exclude_list, N_("pattern"),
 245                  N_("add <pattern> to ignore rules"), PARSE_OPT_NONEG, exclude_cb },
 246                OPT_BOOLEAN('x', NULL, &ignored, N_("remove ignored files, too")),
 247                OPT_BOOLEAN('X', NULL, &ignored_only,
 248                                N_("remove only ignored files")),
 249                OPT_END()
 250        };
 251
 252        git_config(git_clean_config, NULL);
 253        if (force < 0)
 254                force = 0;
 255        else
 256                config_set = 1;
 257
 258        argc = parse_options(argc, argv, prefix, options, builtin_clean_usage,
 259                             0);
 260
 261        memset(&dir, 0, sizeof(dir));
 262        if (ignored_only)
 263                dir.flags |= DIR_SHOW_IGNORED;
 264
 265        if (ignored && ignored_only)
 266                die(_("-x and -X cannot be used together"));
 267
 268        if (!interactive && !dry_run && !force) {
 269                if (config_set)
 270                        die(_("clean.requireForce set to true and neither -i, -n nor -f given; "
 271                                  "refusing to clean"));
 272                else
 273                        die(_("clean.requireForce defaults to true and neither -i, -n nor -f given; "
 274                                  "refusing to clean"));
 275        }
 276
 277        if (force > 1)
 278                rm_flags = 0;
 279
 280        dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
 281
 282        if (read_cache() < 0)
 283                die(_("index file corrupt"));
 284
 285        if (!ignored)
 286                setup_standard_excludes(&dir);
 287
 288        el = add_exclude_list(&dir, EXC_CMDL, "--exclude option");
 289        for (i = 0; i < exclude_list.nr; i++)
 290                add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1));
 291
 292        pathspec = get_pathspec(prefix, argv);
 293
 294        fill_directory(&dir, pathspec);
 295
 296        if (pathspec)
 297                seen = xmalloc(argc > 0 ? argc : 1);
 298
 299        for (i = 0; i < dir.nr; i++) {
 300                struct dir_entry *ent = dir.entries[i];
 301                int len, pos;
 302                int matches = 0;
 303                struct cache_entry *ce;
 304                struct stat st;
 305                const char *rel;
 306
 307                /*
 308                 * Remove the '/' at the end that directory
 309                 * walking adds for directory entries.
 310                 */
 311                len = ent->len;
 312                if (len && ent->name[len-1] == '/')
 313                        len--;
 314                pos = cache_name_pos(ent->name, len);
 315                if (0 <= pos)
 316                        continue;       /* exact match */
 317                pos = -pos - 1;
 318                if (pos < active_nr) {
 319                        ce = active_cache[pos];
 320                        if (ce_namelen(ce) == len &&
 321                            !memcmp(ce->name, ent->name, len))
 322                                continue; /* Yup, this one exists unmerged */
 323                }
 324
 325                if (lstat(ent->name, &st))
 326                        die_errno("Cannot lstat '%s'", ent->name);
 327
 328                if (pathspec) {
 329                        memset(seen, 0, argc > 0 ? argc : 1);
 330                        matches = match_pathspec(pathspec, ent->name, len,
 331                                                 0, seen);
 332                }
 333
 334                if (S_ISDIR(st.st_mode)) {
 335                        if (remove_directories || (matches == MATCHED_EXACTLY)) {
 336                                rel = relative_path(ent->name, prefix, &buf);
 337                                string_list_append(&del_list, rel);
 338                        }
 339                } else {
 340                        if (pathspec && !matches)
 341                                continue;
 342                        rel = relative_path(ent->name, prefix, &buf);
 343                        string_list_append(&del_list, rel);
 344                }
 345        }
 346
 347        if (interactive && del_list.nr > 0)
 348                interactive_main_loop();
 349
 350        for_each_string_list_item(item, &del_list) {
 351                struct stat st;
 352
 353                if (prefix)
 354                        strbuf_addstr(&abs_path, prefix);
 355
 356                strbuf_addstr(&abs_path, item->string);
 357
 358                /*
 359                 * we might have removed this as part of earlier
 360                 * recursive directory removal, so lstat() here could
 361                 * fail with ENOENT.
 362                 */
 363                if (lstat(abs_path.buf, &st))
 364                        continue;
 365
 366                if (S_ISDIR(st.st_mode)) {
 367                        if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone))
 368                                errors++;
 369                        if (gone && !quiet) {
 370                                qname = quote_path_relative(item->string, NULL, &buf);
 371                                printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
 372                        }
 373                } else {
 374                        res = dry_run ? 0 : unlink(abs_path.buf);
 375                        if (res) {
 376                                qname = quote_path_relative(item->string, NULL, &buf);
 377                                warning(_(msg_warn_remove_failed), qname);
 378                                errors++;
 379                        } else if (!quiet) {
 380                                qname = quote_path_relative(item->string, NULL, &buf);
 381                                printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
 382                        }
 383                }
 384                strbuf_reset(&abs_path);
 385        }
 386        free(seen);
 387
 388        strbuf_release(&abs_path);
 389        strbuf_release(&buf);
 390        string_list_clear(&del_list, 0);
 391        string_list_clear(&exclude_list, 0);
 392        return (errors != 0);
 393}