Commit | Line | Data |
---|---|---|
113f10f2 SB |
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" | |
f538a91e | 13 | #include "refs.h" |
07de4eba | 14 | #include "string-list.h" |
1fb32894 | 15 | #include "quote.h" |
1b8fd467 | 16 | #include "column.h" |
113f10f2 | 17 | |
625db1b7 | 18 | static int force = -1; /* unset */ |
17696002 | 19 | static int interactive; |
396049e5 | 20 | static struct string_list del_list = STRING_LIST_INIT_DUP; |
1b8fd467 | 21 | static unsigned int colopts; |
113f10f2 SB |
22 | |
23 | static const char *const builtin_clean_usage[] = { | |
17696002 | 24 | N_("git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."), |
113f10f2 SB |
25 | NULL |
26 | }; | |
27 | ||
f538a91e ZK |
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 | ||
ef90d6d4 | 34 | static int git_clean_config(const char *var, const char *value, void *cb) |
113f10f2 | 35 | { |
1b8fd467 JX |
36 | if (!prefixcmp(var, "column.")) |
37 | return git_column_config(var, value, "clean", &colopts); | |
38 | ||
39 | if (!strcmp(var, "clean.requireforce")) { | |
113f10f2 | 40 | force = !git_config_bool(var, value); |
1b8fd467 JX |
41 | return 0; |
42 | } | |
ef90d6d4 | 43 | return git_default_config(var, value, cb); |
113f10f2 SB |
44 | } |
45 | ||
07de4eba JH |
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 | ||
f538a91e ZK |
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) { | |
39598f99 | 68 | quote_path_relative(path->buf, prefix, "ed); |
f538a91e ZK |
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) { | |
39598f99 | 82 | quote_path_relative(path->buf, prefix, "ed); |
f538a91e ZK |
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) { | |
39598f99 | 106 | quote_path_relative(path->buf, prefix, "ed); |
f538a91e ZK |
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) { | |
39598f99 | 114 | quote_path_relative(path->buf, prefix, "ed); |
f538a91e ZK |
115 | string_list_append(&dels, quoted.buf); |
116 | } else { | |
39598f99 | 117 | quote_path_relative(path->buf, prefix, "ed); |
f538a91e ZK |
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 { | |
39598f99 | 139 | quote_path_relative(path->buf, prefix, "ed); |
f538a91e ZK |
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 | ||
1b8fd467 | 154 | static void pretty_print_dels(void) |
17696002 | 155 | { |
1b8fd467 | 156 | struct string_list list = STRING_LIST_INIT_DUP; |
17696002 | 157 | struct string_list_item *item; |
1b8fd467 | 158 | struct strbuf buf = STRBUF_INIT; |
17696002 | 159 | const char *qname; |
1b8fd467 JX |
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; | |
17696002 JX |
184 | |
185 | while (del_list.nr) { | |
186 | putchar('\n'); | |
1b8fd467 JX |
187 | printf_ln(Q_("Would remove the following item:", |
188 | "Would remove the following items:", | |
189 | del_list.nr)); | |
17696002 JX |
190 | putchar('\n'); |
191 | ||
1b8fd467 JX |
192 | pretty_print_dels(); |
193 | ||
17696002 JX |
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 | ||
17696002 JX |
219 | strbuf_release(&confirm); |
220 | } | |
221 | ||
113f10f2 SB |
222 | int cmd_clean(int argc, const char **argv, const char *prefix) |
223 | { | |
f538a91e ZK |
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; | |
a0f4afbe | 227 | int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; |
396049e5 | 228 | struct strbuf abs_path = STRBUF_INIT; |
113f10f2 | 229 | struct dir_struct dir; |
113f10f2 | 230 | static const char **pathspec; |
f285a2d7 | 231 | struct strbuf buf = STRBUF_INIT; |
bdab6a59 | 232 | struct string_list exclude_list = STRING_LIST_INIT_NODUP; |
72aeb187 | 233 | struct exclude_list *el; |
396049e5 | 234 | struct string_list_item *item; |
1fb32894 | 235 | const char *qname; |
d871c869 | 236 | char *seen = NULL; |
113f10f2 | 237 | struct option options[] = { |
145f9c81 | 238 | OPT__QUIET(&quiet, N_("do not print names of files removed")), |
f538a91e | 239 | OPT__DRY_RUN(&dry_run, N_("dry run")), |
145f9c81 | 240 | OPT__FORCE(&force, N_("force")), |
17696002 | 241 | OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")), |
113f10f2 | 242 | OPT_BOOLEAN('d', NULL, &remove_directories, |
145f9c81 NTND |
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")), | |
113f10f2 | 247 | OPT_BOOLEAN('X', NULL, &ignored_only, |
145f9c81 | 248 | N_("remove only ignored files")), |
113f10f2 SB |
249 | OPT_END() |
250 | }; | |
251 | ||
ef90d6d4 | 252 | git_config(git_clean_config, NULL); |
625db1b7 JH |
253 | if (force < 0) |
254 | force = 0; | |
255 | else | |
256 | config_set = 1; | |
257 | ||
37782920 SB |
258 | argc = parse_options(argc, argv, prefix, options, builtin_clean_usage, |
259 | 0); | |
113f10f2 SB |
260 | |
261 | memset(&dir, 0, sizeof(dir)); | |
1617adc7 | 262 | if (ignored_only) |
7c4c97c0 | 263 | dir.flags |= DIR_SHOW_IGNORED; |
113f10f2 SB |
264 | |
265 | if (ignored && ignored_only) | |
2da57add | 266 | die(_("-x and -X cannot be used together")); |
113f10f2 | 267 | |
17696002 | 268 | if (!interactive && !dry_run && !force) { |
a66f9b2a | 269 | if (config_set) |
17696002 | 270 | die(_("clean.requireForce set to true and neither -i, -n nor -f given; " |
a66f9b2a ÆAB |
271 | "refusing to clean")); |
272 | else | |
17696002 | 273 | die(_("clean.requireForce defaults to true and neither -i, -n nor -f given; " |
a66f9b2a ÆAB |
274 | "refusing to clean")); |
275 | } | |
113f10f2 | 276 | |
a0f4afbe JH |
277 | if (force > 1) |
278 | rm_flags = 0; | |
279 | ||
7c4c97c0 | 280 | dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; |
113f10f2 | 281 | |
c28b3d6e | 282 | if (read_cache() < 0) |
2da57add | 283 | die(_("index file corrupt")); |
c28b3d6e | 284 | |
1617adc7 SB |
285 | if (!ignored) |
286 | setup_standard_excludes(&dir); | |
113f10f2 | 287 | |
72aeb187 | 288 | el = add_exclude_list(&dir, EXC_CMDL, "--exclude option"); |
07de4eba | 289 | for (i = 0; i < exclude_list.nr; i++) |
72aeb187 | 290 | add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1)); |
07de4eba | 291 | |
113f10f2 | 292 | pathspec = get_pathspec(prefix, argv); |
113f10f2 | 293 | |
1d8842d9 | 294 | fill_directory(&dir, pathspec); |
113f10f2 | 295 | |
d871c869 | 296 | if (pathspec) |
a8db80c2 | 297 | seen = xmalloc(argc > 0 ? argc : 1); |
d871c869 JH |
298 | |
299 | for (i = 0; i < dir.nr; i++) { | |
300 | struct dir_entry *ent = dir.entries[i]; | |
f2d0df71 SB |
301 | int len, pos; |
302 | int matches = 0; | |
113f10f2 SB |
303 | struct cache_entry *ce; |
304 | struct stat st; | |
396049e5 | 305 | const char *rel; |
113f10f2 SB |
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 | ||
d871c869 | 325 | if (lstat(ent->name, &st)) |
396049e5 | 326 | die_errno("Cannot lstat '%s'", ent->name); |
d871c869 JH |
327 | |
328 | if (pathspec) { | |
a8db80c2 | 329 | memset(seen, 0, argc > 0 ? argc : 1); |
f2d0df71 | 330 | matches = match_pathspec(pathspec, ent->name, len, |
1c7d402b | 331 | 0, seen); |
d871c869 JH |
332 | } |
333 | ||
334 | if (S_ISDIR(st.st_mode)) { | |
f538a91e | 335 | if (remove_directories || (matches == MATCHED_EXACTLY)) { |
396049e5 JX |
336 | rel = relative_path(ent->name, prefix, &buf); |
337 | string_list_append(&del_list, rel); | |
113f10f2 | 338 | } |
113f10f2 | 339 | } else { |
d871c869 JH |
340 | if (pathspec && !matches) |
341 | continue; | |
396049e5 JX |
342 | rel = relative_path(ent->name, prefix, &buf); |
343 | string_list_append(&del_list, rel); | |
344 | } | |
345 | } | |
346 | ||
17696002 JX |
347 | if (interactive && del_list.nr > 0) |
348 | interactive_main_loop(); | |
396049e5 JX |
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); | |
f538a91e | 375 | if (res) { |
396049e5 | 376 | qname = quote_path_relative(item->string, NULL, &buf); |
f538a91e | 377 | warning(_(msg_warn_remove_failed), qname); |
aa9c83c2 | 378 | errors++; |
f538a91e | 379 | } else if (!quiet) { |
396049e5 | 380 | qname = quote_path_relative(item->string, NULL, &buf); |
f538a91e | 381 | printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); |
aa9c83c2 | 382 | } |
113f10f2 | 383 | } |
396049e5 | 384 | strbuf_reset(&abs_path); |
113f10f2 | 385 | } |
d871c869 | 386 | free(seen); |
113f10f2 | 387 | |
396049e5 JX |
388 | strbuf_release(&abs_path); |
389 | strbuf_release(&buf); | |
390 | string_list_clear(&del_list, 0); | |
07de4eba | 391 | string_list_clear(&exclude_list, 0); |
aa9c83c2 | 392 | return (errors != 0); |
113f10f2 | 393 | } |