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