]>
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" | |
07de4eba | 13 | #include "string-list.h" |
1fb32894 | 14 | #include "quote.h" |
113f10f2 | 15 | |
625db1b7 | 16 | static int force = -1; /* unset */ |
113f10f2 SB |
17 | |
18 | static const char *const builtin_clean_usage[] = { | |
07de4eba | 19 | "git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...", |
113f10f2 SB |
20 | NULL |
21 | }; | |
22 | ||
ef90d6d4 | 23 | static int git_clean_config(const char *var, const char *value, void *cb) |
113f10f2 SB |
24 | { |
25 | if (!strcmp(var, "clean.requireforce")) | |
26 | force = !git_config_bool(var, value); | |
ef90d6d4 | 27 | return git_default_config(var, value, cb); |
113f10f2 SB |
28 | } |
29 | ||
07de4eba JH |
30 | static int exclude_cb(const struct option *opt, const char *arg, int unset) |
31 | { | |
32 | struct string_list *exclude_list = opt->value; | |
33 | string_list_append(exclude_list, arg); | |
34 | return 0; | |
35 | } | |
36 | ||
113f10f2 SB |
37 | int cmd_clean(int argc, const char **argv, const char *prefix) |
38 | { | |
d871c869 | 39 | int i; |
113f10f2 | 40 | int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0; |
1c7d402b | 41 | int ignored_only = 0, config_set = 0, errors = 0; |
a0f4afbe | 42 | int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; |
f285a2d7 | 43 | struct strbuf directory = STRBUF_INIT; |
113f10f2 | 44 | struct dir_struct dir; |
113f10f2 | 45 | static const char **pathspec; |
f285a2d7 | 46 | struct strbuf buf = STRBUF_INIT; |
bdab6a59 | 47 | struct string_list exclude_list = STRING_LIST_INIT_NODUP; |
1fb32894 | 48 | const char *qname; |
d871c869 | 49 | char *seen = NULL; |
113f10f2 | 50 | struct option options[] = { |
8c839683 | 51 | OPT__QUIET(&quiet, "do not print names of files removed"), |
e21adb8c | 52 | OPT__DRY_RUN(&show_only, "dry run"), |
76946b76 | 53 | OPT__FORCE(&force, "force"), |
113f10f2 SB |
54 | OPT_BOOLEAN('d', NULL, &remove_directories, |
55 | "remove whole directories"), | |
07de4eba | 56 | { OPTION_CALLBACK, 'e', "exclude", &exclude_list, "pattern", |
b6194678 | 57 | "add <pattern> to ignore rules", PARSE_OPT_NONEG, exclude_cb }, |
113f10f2 SB |
58 | OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"), |
59 | OPT_BOOLEAN('X', NULL, &ignored_only, | |
60 | "remove only ignored files"), | |
61 | OPT_END() | |
62 | }; | |
63 | ||
ef90d6d4 | 64 | git_config(git_clean_config, NULL); |
625db1b7 JH |
65 | if (force < 0) |
66 | force = 0; | |
67 | else | |
68 | config_set = 1; | |
69 | ||
37782920 SB |
70 | argc = parse_options(argc, argv, prefix, options, builtin_clean_usage, |
71 | 0); | |
113f10f2 SB |
72 | |
73 | memset(&dir, 0, sizeof(dir)); | |
1617adc7 | 74 | if (ignored_only) |
7c4c97c0 | 75 | dir.flags |= DIR_SHOW_IGNORED; |
113f10f2 SB |
76 | |
77 | if (ignored && ignored_only) | |
2da57add | 78 | die(_("-x and -X cannot be used together")); |
113f10f2 | 79 | |
a66f9b2a ÆAB |
80 | if (!show_only && !force) { |
81 | if (config_set) | |
82 | die(_("clean.requireForce set to true and neither -n nor -f given; " | |
83 | "refusing to clean")); | |
84 | else | |
85 | die(_("clean.requireForce defaults to true and neither -n nor -f given; " | |
86 | "refusing to clean")); | |
87 | } | |
113f10f2 | 88 | |
a0f4afbe JH |
89 | if (force > 1) |
90 | rm_flags = 0; | |
91 | ||
7c4c97c0 | 92 | dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; |
113f10f2 | 93 | |
c28b3d6e | 94 | if (read_cache() < 0) |
2da57add | 95 | die(_("index file corrupt")); |
c28b3d6e | 96 | |
1617adc7 SB |
97 | if (!ignored) |
98 | setup_standard_excludes(&dir); | |
113f10f2 | 99 | |
07de4eba | 100 | for (i = 0; i < exclude_list.nr; i++) |
b6194678 JH |
101 | add_exclude(exclude_list.items[i].string, "", 0, |
102 | &dir.exclude_list[EXC_CMDL]); | |
07de4eba | 103 | |
113f10f2 | 104 | pathspec = get_pathspec(prefix, argv); |
113f10f2 | 105 | |
1d8842d9 | 106 | fill_directory(&dir, pathspec); |
113f10f2 | 107 | |
d871c869 | 108 | if (pathspec) |
a8db80c2 | 109 | seen = xmalloc(argc > 0 ? argc : 1); |
d871c869 JH |
110 | |
111 | for (i = 0; i < dir.nr; i++) { | |
112 | struct dir_entry *ent = dir.entries[i]; | |
f2d0df71 SB |
113 | int len, pos; |
114 | int matches = 0; | |
113f10f2 SB |
115 | struct cache_entry *ce; |
116 | struct stat st; | |
113f10f2 SB |
117 | |
118 | /* | |
119 | * Remove the '/' at the end that directory | |
120 | * walking adds for directory entries. | |
121 | */ | |
122 | len = ent->len; | |
123 | if (len && ent->name[len-1] == '/') | |
124 | len--; | |
125 | pos = cache_name_pos(ent->name, len); | |
126 | if (0 <= pos) | |
127 | continue; /* exact match */ | |
128 | pos = -pos - 1; | |
129 | if (pos < active_nr) { | |
130 | ce = active_cache[pos]; | |
131 | if (ce_namelen(ce) == len && | |
132 | !memcmp(ce->name, ent->name, len)) | |
133 | continue; /* Yup, this one exists unmerged */ | |
134 | } | |
135 | ||
d871c869 JH |
136 | /* |
137 | * we might have removed this as part of earlier | |
138 | * recursive directory removal, so lstat() here could | |
139 | * fail with ENOENT. | |
140 | */ | |
141 | if (lstat(ent->name, &st)) | |
142 | continue; | |
143 | ||
144 | if (pathspec) { | |
a8db80c2 | 145 | memset(seen, 0, argc > 0 ? argc : 1); |
f2d0df71 | 146 | matches = match_pathspec(pathspec, ent->name, len, |
1c7d402b | 147 | 0, seen); |
d871c869 JH |
148 | } |
149 | ||
150 | if (S_ISDIR(st.st_mode)) { | |
113f10f2 | 151 | strbuf_addstr(&directory, ent->name); |
1fb32894 | 152 | qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); |
f2d0df71 SB |
153 | if (show_only && (remove_directories || |
154 | (matches == MATCHED_EXACTLY))) { | |
2da57add | 155 | printf(_("Would remove %s\n"), qname); |
f2d0df71 SB |
156 | } else if (remove_directories || |
157 | (matches == MATCHED_EXACTLY)) { | |
aa9c83c2 | 158 | if (!quiet) |
2da57add | 159 | printf(_("Removing %s\n"), qname); |
a0f4afbe JH |
160 | if (remove_dir_recursively(&directory, |
161 | rm_flags) != 0) { | |
2da57add | 162 | warning(_("failed to remove %s"), qname); |
aa9c83c2 MV |
163 | errors++; |
164 | } | |
113f10f2 | 165 | } else if (show_only) { |
2da57add | 166 | printf(_("Would not remove %s\n"), qname); |
113f10f2 | 167 | } else { |
2da57add | 168 | printf(_("Not removing %s\n"), qname); |
113f10f2 SB |
169 | } |
170 | strbuf_reset(&directory); | |
171 | } else { | |
d871c869 JH |
172 | if (pathspec && !matches) |
173 | continue; | |
1fb32894 | 174 | qname = quote_path_relative(ent->name, -1, &buf, prefix); |
113f10f2 | 175 | if (show_only) { |
2da57add | 176 | printf(_("Would remove %s\n"), qname); |
113f10f2 SB |
177 | continue; |
178 | } else if (!quiet) { | |
2da57add | 179 | printf(_("Removing %s\n"), qname); |
113f10f2 | 180 | } |
aa9c83c2 | 181 | if (unlink(ent->name) != 0) { |
2da57add | 182 | warning(_("failed to remove %s"), qname); |
aa9c83c2 MV |
183 | errors++; |
184 | } | |
113f10f2 SB |
185 | } |
186 | } | |
d871c869 | 187 | free(seen); |
113f10f2 SB |
188 | |
189 | strbuf_release(&directory); | |
07de4eba | 190 | string_list_clear(&exclude_list, 0); |
aa9c83c2 | 191 | return (errors != 0); |
113f10f2 | 192 | } |