git-clean: show items of del_list in columns
[git] / builtin / clean.c
1 /*
2  * "git clean" builtin command
3  *
4  * Copyright (C) 2007 Shawn Bohrer
5  *
6  * Based on git-clean.sh by Pavel Roskin
7  */
8
9 #include "builtin.h"
10 #include "cache.h"
11 #include "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
18 static int force = -1; /* unset */
19 static int interactive;
20 static struct string_list del_list = STRING_LIST_INIT_DUP;
21 static unsigned int colopts;
22
23 static const char *const builtin_clean_usage[] = {
24         N_("git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
25         NULL
26 };
27
28 static const char *msg_remove = N_("Removing %s\n");
29 static const char *msg_would_remove = N_("Would remove %s\n");
30 static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
31 static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
32 static const char *msg_warn_remove_failed = N_("failed to remove %s");
33
34 static 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
46 static 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
53 static 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
154 static 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
181 static 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
222 int 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 }