Line | Count | Source (jump to first uncovered line) |
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 "abspath.h" |
11 | | #include "config.h" |
12 | | #include "dir.h" |
13 | | #include "gettext.h" |
14 | | #include "parse-options.h" |
15 | | #include "path.h" |
16 | | #include "read-cache-ll.h" |
17 | | #include "repository.h" |
18 | | #include "setup.h" |
19 | | #include "string-list.h" |
20 | | #include "quote.h" |
21 | | #include "column.h" |
22 | | #include "color.h" |
23 | | #include "pathspec.h" |
24 | | #include "help.h" |
25 | | #include "prompt.h" |
26 | | |
27 | | static int require_force = -1; /* unset */ |
28 | | static int interactive; |
29 | | static struct string_list del_list = STRING_LIST_INIT_DUP; |
30 | | static unsigned int colopts; |
31 | | |
32 | | static const char *const builtin_clean_usage[] = { |
33 | | N_("git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] [<pathspec>...]"), |
34 | | NULL |
35 | | }; |
36 | | |
37 | | static const char *msg_remove = N_("Removing %s\n"); |
38 | | static const char *msg_would_remove = N_("Would remove %s\n"); |
39 | | static const char *msg_skip_git_dir = N_("Skipping repository %s\n"); |
40 | | static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n"); |
41 | | static const char *msg_warn_remove_failed = N_("failed to remove %s"); |
42 | | static const char *msg_warn_lstat_failed = N_("could not lstat %s\n"); |
43 | | static const char *msg_skip_cwd = N_("Refusing to remove current working directory\n"); |
44 | | static const char *msg_would_skip_cwd = N_("Would refuse to remove current working directory\n"); |
45 | | |
46 | | enum color_clean { |
47 | | CLEAN_COLOR_RESET = 0, |
48 | | CLEAN_COLOR_PLAIN = 1, |
49 | | CLEAN_COLOR_PROMPT = 2, |
50 | | CLEAN_COLOR_HEADER = 3, |
51 | | CLEAN_COLOR_HELP = 4, |
52 | | CLEAN_COLOR_ERROR = 5 |
53 | | }; |
54 | | |
55 | | static const char *color_interactive_slots[] = { |
56 | | [CLEAN_COLOR_ERROR] = "error", |
57 | | [CLEAN_COLOR_HEADER] = "header", |
58 | | [CLEAN_COLOR_HELP] = "help", |
59 | | [CLEAN_COLOR_PLAIN] = "plain", |
60 | | [CLEAN_COLOR_PROMPT] = "prompt", |
61 | | [CLEAN_COLOR_RESET] = "reset", |
62 | | }; |
63 | | |
64 | | static int clean_use_color = -1; |
65 | | static char clean_colors[][COLOR_MAXLEN] = { |
66 | | [CLEAN_COLOR_ERROR] = GIT_COLOR_BOLD_RED, |
67 | | [CLEAN_COLOR_HEADER] = GIT_COLOR_BOLD, |
68 | | [CLEAN_COLOR_HELP] = GIT_COLOR_BOLD_RED, |
69 | | [CLEAN_COLOR_PLAIN] = GIT_COLOR_NORMAL, |
70 | | [CLEAN_COLOR_PROMPT] = GIT_COLOR_BOLD_BLUE, |
71 | | [CLEAN_COLOR_RESET] = GIT_COLOR_RESET, |
72 | | }; |
73 | | |
74 | 0 | #define MENU_OPTS_SINGLETON 01 |
75 | 0 | #define MENU_OPTS_IMMEDIATE 02 |
76 | 0 | #define MENU_OPTS_LIST_ONLY 04 |
77 | | |
78 | | struct menu_opts { |
79 | | const char *header; |
80 | | const char *prompt; |
81 | | int flags; |
82 | | }; |
83 | | |
84 | 0 | #define MENU_RETURN_NO_LOOP 10 |
85 | | |
86 | | struct menu_item { |
87 | | char hotkey; |
88 | | const char *title; |
89 | | int selected; |
90 | | int (*fn)(void); |
91 | | }; |
92 | | |
93 | | enum menu_stuff_type { |
94 | | MENU_STUFF_TYPE_STRING_LIST = 1, |
95 | | MENU_STUFF_TYPE_MENU_ITEM |
96 | | }; |
97 | | |
98 | | struct menu_stuff { |
99 | | enum menu_stuff_type type; |
100 | | int nr; |
101 | | void *stuff; |
102 | | }; |
103 | | |
104 | | define_list_config_array(color_interactive_slots); |
105 | | |
106 | | static int git_clean_config(const char *var, const char *value, |
107 | | const struct config_context *ctx, void *cb) |
108 | 0 | { |
109 | 0 | const char *slot_name; |
110 | |
|
111 | 0 | if (starts_with(var, "column.")) |
112 | 0 | return git_column_config(var, value, "clean", &colopts); |
113 | | |
114 | | /* honors the color.interactive* config variables which also |
115 | | applied in git-add--interactive and git-stash */ |
116 | 0 | if (!strcmp(var, "color.interactive")) { |
117 | 0 | clean_use_color = git_config_colorbool(var, value); |
118 | 0 | return 0; |
119 | 0 | } |
120 | 0 | if (skip_prefix(var, "color.interactive.", &slot_name)) { |
121 | 0 | int slot = LOOKUP_CONFIG(color_interactive_slots, slot_name); |
122 | 0 | if (slot < 0) |
123 | 0 | return 0; |
124 | 0 | if (!value) |
125 | 0 | return config_error_nonbool(var); |
126 | 0 | return color_parse(value, clean_colors[slot]); |
127 | 0 | } |
128 | | |
129 | 0 | if (!strcmp(var, "clean.requireforce")) { |
130 | 0 | require_force = git_config_bool(var, value); |
131 | 0 | return 0; |
132 | 0 | } |
133 | | |
134 | 0 | if (git_color_config(var, value, cb) < 0) |
135 | 0 | return -1; |
136 | | |
137 | 0 | return git_default_config(var, value, ctx, cb); |
138 | 0 | } |
139 | | |
140 | | static const char *clean_get_color(enum color_clean ix) |
141 | 0 | { |
142 | 0 | if (want_color(clean_use_color)) |
143 | 0 | return clean_colors[ix]; |
144 | 0 | return ""; |
145 | 0 | } |
146 | | |
147 | | static void clean_print_color(enum color_clean ix) |
148 | 0 | { |
149 | 0 | printf("%s", clean_get_color(ix)); |
150 | 0 | } |
151 | | |
152 | | static int exclude_cb(const struct option *opt, const char *arg, int unset) |
153 | 0 | { |
154 | 0 | struct string_list *exclude_list = opt->value; |
155 | 0 | BUG_ON_OPT_NEG(unset); |
156 | 0 | string_list_append(exclude_list, arg); |
157 | 0 | return 0; |
158 | 0 | } |
159 | | |
160 | | static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, |
161 | | int dry_run, int quiet, int *dir_gone) |
162 | 0 | { |
163 | 0 | DIR *dir; |
164 | 0 | struct strbuf quoted = STRBUF_INIT; |
165 | 0 | struct strbuf realpath = STRBUF_INIT; |
166 | 0 | struct strbuf real_ocwd = STRBUF_INIT; |
167 | 0 | struct dirent *e; |
168 | 0 | int res = 0, ret = 0, gone = 1, original_len = path->len, len; |
169 | 0 | struct string_list dels = STRING_LIST_INIT_DUP; |
170 | |
|
171 | 0 | *dir_gone = 1; |
172 | |
|
173 | 0 | if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && |
174 | 0 | is_nonbare_repository_dir(path)) { |
175 | 0 | if (!quiet) { |
176 | 0 | quote_path(path->buf, prefix, "ed, 0); |
177 | 0 | printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir), |
178 | 0 | quoted.buf); |
179 | 0 | } |
180 | |
|
181 | 0 | *dir_gone = 0; |
182 | 0 | goto out; |
183 | 0 | } |
184 | | |
185 | 0 | dir = opendir(path->buf); |
186 | 0 | if (!dir) { |
187 | | /* an empty dir could be removed even if it is unreadble */ |
188 | 0 | res = dry_run ? 0 : rmdir(path->buf); |
189 | 0 | if (res) { |
190 | 0 | int saved_errno = errno; |
191 | 0 | quote_path(path->buf, prefix, "ed, 0); |
192 | 0 | errno = saved_errno; |
193 | 0 | warning_errno(_(msg_warn_remove_failed), quoted.buf); |
194 | 0 | *dir_gone = 0; |
195 | 0 | } |
196 | 0 | ret = res; |
197 | 0 | goto out; |
198 | 0 | } |
199 | | |
200 | 0 | strbuf_complete(path, '/'); |
201 | |
|
202 | 0 | len = path->len; |
203 | 0 | while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) { |
204 | 0 | struct stat st; |
205 | |
|
206 | 0 | strbuf_setlen(path, len); |
207 | 0 | strbuf_addstr(path, e->d_name); |
208 | 0 | if (lstat(path->buf, &st)) |
209 | 0 | warning_errno(_(msg_warn_lstat_failed), path->buf); |
210 | 0 | else if (S_ISDIR(st.st_mode)) { |
211 | 0 | if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone)) |
212 | 0 | ret = 1; |
213 | 0 | if (gone) { |
214 | 0 | quote_path(path->buf, prefix, "ed, 0); |
215 | 0 | string_list_append(&dels, quoted.buf); |
216 | 0 | } else |
217 | 0 | *dir_gone = 0; |
218 | 0 | continue; |
219 | 0 | } else { |
220 | 0 | res = dry_run ? 0 : unlink(path->buf); |
221 | 0 | if (!res) { |
222 | 0 | quote_path(path->buf, prefix, "ed, 0); |
223 | 0 | string_list_append(&dels, quoted.buf); |
224 | 0 | } else { |
225 | 0 | int saved_errno = errno; |
226 | 0 | quote_path(path->buf, prefix, "ed, 0); |
227 | 0 | errno = saved_errno; |
228 | 0 | warning_errno(_(msg_warn_remove_failed), quoted.buf); |
229 | 0 | *dir_gone = 0; |
230 | 0 | ret = 1; |
231 | 0 | } |
232 | 0 | continue; |
233 | 0 | } |
234 | | |
235 | | /* path too long, stat fails, or non-directory still exists */ |
236 | 0 | *dir_gone = 0; |
237 | 0 | ret = 1; |
238 | 0 | break; |
239 | 0 | } |
240 | 0 | closedir(dir); |
241 | |
|
242 | 0 | strbuf_setlen(path, original_len); |
243 | |
|
244 | 0 | if (*dir_gone) { |
245 | | /* |
246 | | * Normalize path components in path->buf, e.g. change '\' to |
247 | | * '/' on Windows. |
248 | | */ |
249 | 0 | strbuf_realpath(&realpath, path->buf, 1); |
250 | | |
251 | | /* |
252 | | * path and realpath are absolute; for comparison, we would |
253 | | * like to transform startup_info->original_cwd to an absolute |
254 | | * path too. |
255 | | */ |
256 | 0 | if (startup_info->original_cwd) |
257 | 0 | strbuf_realpath(&real_ocwd, |
258 | 0 | startup_info->original_cwd, 1); |
259 | |
|
260 | 0 | if (!strbuf_cmp(&realpath, &real_ocwd)) { |
261 | 0 | printf("%s", dry_run ? _(msg_would_skip_cwd) : _(msg_skip_cwd)); |
262 | 0 | *dir_gone = 0; |
263 | 0 | } else { |
264 | 0 | res = dry_run ? 0 : rmdir(path->buf); |
265 | 0 | if (!res) |
266 | 0 | *dir_gone = 1; |
267 | 0 | else { |
268 | 0 | int saved_errno = errno; |
269 | 0 | quote_path(path->buf, prefix, "ed, 0); |
270 | 0 | errno = saved_errno; |
271 | 0 | warning_errno(_(msg_warn_remove_failed), quoted.buf); |
272 | 0 | *dir_gone = 0; |
273 | 0 | ret = 1; |
274 | 0 | } |
275 | 0 | } |
276 | 0 | } |
277 | |
|
278 | 0 | if (!*dir_gone && !quiet) { |
279 | 0 | int i; |
280 | 0 | for (i = 0; i < dels.nr; i++) |
281 | 0 | printf(dry_run ? _(msg_would_remove) : _(msg_remove), dels.items[i].string); |
282 | 0 | } |
283 | 0 | out: |
284 | 0 | strbuf_release(&realpath); |
285 | 0 | strbuf_release(&real_ocwd); |
286 | 0 | strbuf_release("ed); |
287 | 0 | string_list_clear(&dels, 0); |
288 | 0 | return ret; |
289 | 0 | } |
290 | | |
291 | | static void pretty_print_dels(void) |
292 | 0 | { |
293 | 0 | struct string_list list = STRING_LIST_INIT_DUP; |
294 | 0 | struct string_list_item *item; |
295 | 0 | struct strbuf buf = STRBUF_INIT; |
296 | 0 | const char *qname; |
297 | 0 | struct column_options copts; |
298 | |
|
299 | 0 | for_each_string_list_item(item, &del_list) { |
300 | 0 | qname = quote_path(item->string, NULL, &buf, 0); |
301 | 0 | string_list_append(&list, qname); |
302 | 0 | } |
303 | | |
304 | | /* |
305 | | * always enable column display, we only consult column.* |
306 | | * about layout strategy and stuff |
307 | | */ |
308 | 0 | colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED; |
309 | 0 | memset(&copts, 0, sizeof(copts)); |
310 | 0 | copts.indent = " "; |
311 | 0 | copts.padding = 2; |
312 | 0 | print_columns(&list, colopts, &copts); |
313 | 0 | strbuf_release(&buf); |
314 | 0 | string_list_clear(&list, 0); |
315 | 0 | } |
316 | | |
317 | | static void pretty_print_menus(struct string_list *menu_list) |
318 | 0 | { |
319 | 0 | unsigned int local_colopts = 0; |
320 | 0 | struct column_options copts; |
321 | |
|
322 | 0 | local_colopts = COL_ENABLED | COL_ROW; |
323 | 0 | memset(&copts, 0, sizeof(copts)); |
324 | 0 | copts.indent = " "; |
325 | 0 | copts.padding = 2; |
326 | 0 | print_columns(menu_list, local_colopts, &copts); |
327 | 0 | } |
328 | | |
329 | | static void prompt_help_cmd(int singleton) |
330 | 0 | { |
331 | 0 | clean_print_color(CLEAN_COLOR_HELP); |
332 | 0 | printf(singleton ? |
333 | 0 | _("Prompt help:\n" |
334 | 0 | "1 - select a numbered item\n" |
335 | 0 | "foo - select item based on unique prefix\n" |
336 | 0 | " - (empty) select nothing\n") : |
337 | 0 | _("Prompt help:\n" |
338 | 0 | "1 - select a single item\n" |
339 | 0 | "3-5 - select a range of items\n" |
340 | 0 | "2-3,6-9 - select multiple ranges\n" |
341 | 0 | "foo - select item based on unique prefix\n" |
342 | 0 | "-... - unselect specified items\n" |
343 | 0 | "* - choose all items\n" |
344 | 0 | " - (empty) finish selecting\n")); |
345 | 0 | clean_print_color(CLEAN_COLOR_RESET); |
346 | 0 | } |
347 | | |
348 | | /* |
349 | | * display menu stuff with number prefix and hotkey highlight |
350 | | */ |
351 | | static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen) |
352 | 0 | { |
353 | 0 | struct string_list menu_list = STRING_LIST_INIT_DUP; |
354 | 0 | struct strbuf menu = STRBUF_INIT; |
355 | 0 | struct menu_item *menu_item; |
356 | 0 | struct string_list_item *string_list_item; |
357 | 0 | int i; |
358 | |
|
359 | 0 | switch (stuff->type) { |
360 | 0 | default: |
361 | 0 | die("Bad type of menu_stuff when print menu"); |
362 | 0 | case MENU_STUFF_TYPE_MENU_ITEM: |
363 | 0 | menu_item = (struct menu_item *)stuff->stuff; |
364 | 0 | for (i = 0; i < stuff->nr; i++, menu_item++) { |
365 | 0 | const char *p; |
366 | 0 | int highlighted = 0; |
367 | |
|
368 | 0 | p = menu_item->title; |
369 | 0 | if ((*chosen)[i] < 0) |
370 | 0 | (*chosen)[i] = menu_item->selected ? 1 : 0; |
371 | 0 | strbuf_addf(&menu, "%s%2d: ", (*chosen)[i] ? "*" : " ", i+1); |
372 | 0 | for (; *p; p++) { |
373 | 0 | if (!highlighted && *p == menu_item->hotkey) { |
374 | 0 | strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_PROMPT)); |
375 | 0 | strbuf_addch(&menu, *p); |
376 | 0 | strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_RESET)); |
377 | 0 | highlighted = 1; |
378 | 0 | } else { |
379 | 0 | strbuf_addch(&menu, *p); |
380 | 0 | } |
381 | 0 | } |
382 | 0 | string_list_append(&menu_list, menu.buf); |
383 | 0 | strbuf_reset(&menu); |
384 | 0 | } |
385 | 0 | break; |
386 | 0 | case MENU_STUFF_TYPE_STRING_LIST: |
387 | 0 | i = 0; |
388 | 0 | for_each_string_list_item(string_list_item, (struct string_list *)stuff->stuff) { |
389 | 0 | if ((*chosen)[i] < 0) |
390 | 0 | (*chosen)[i] = 0; |
391 | 0 | strbuf_addf(&menu, "%s%2d: %s", |
392 | 0 | (*chosen)[i] ? "*" : " ", i+1, string_list_item->string); |
393 | 0 | string_list_append(&menu_list, menu.buf); |
394 | 0 | strbuf_reset(&menu); |
395 | 0 | i++; |
396 | 0 | } |
397 | 0 | break; |
398 | 0 | } |
399 | | |
400 | 0 | pretty_print_menus(&menu_list); |
401 | |
|
402 | 0 | strbuf_release(&menu); |
403 | 0 | string_list_clear(&menu_list, 0); |
404 | 0 | } |
405 | | |
406 | | static int find_unique(const char *choice, struct menu_stuff *menu_stuff) |
407 | 0 | { |
408 | 0 | struct menu_item *menu_item; |
409 | 0 | struct string_list_item *string_list_item; |
410 | 0 | int i, len, found = 0; |
411 | |
|
412 | 0 | len = strlen(choice); |
413 | 0 | switch (menu_stuff->type) { |
414 | 0 | default: |
415 | 0 | die("Bad type of menu_stuff when parse choice"); |
416 | 0 | case MENU_STUFF_TYPE_MENU_ITEM: |
417 | |
|
418 | 0 | menu_item = (struct menu_item *)menu_stuff->stuff; |
419 | 0 | for (i = 0; i < menu_stuff->nr; i++, menu_item++) { |
420 | 0 | if (len == 1 && *choice == menu_item->hotkey) { |
421 | 0 | found = i + 1; |
422 | 0 | break; |
423 | 0 | } |
424 | 0 | if (!strncasecmp(choice, menu_item->title, len)) { |
425 | 0 | if (found) { |
426 | 0 | if (len == 1) { |
427 | | /* continue for hotkey matching */ |
428 | 0 | found = -1; |
429 | 0 | } else { |
430 | 0 | found = 0; |
431 | 0 | break; |
432 | 0 | } |
433 | 0 | } else { |
434 | 0 | found = i + 1; |
435 | 0 | } |
436 | 0 | } |
437 | 0 | } |
438 | 0 | break; |
439 | 0 | case MENU_STUFF_TYPE_STRING_LIST: |
440 | 0 | string_list_item = ((struct string_list *)menu_stuff->stuff)->items; |
441 | 0 | for (i = 0; i < menu_stuff->nr; i++, string_list_item++) { |
442 | 0 | if (!strncasecmp(choice, string_list_item->string, len)) { |
443 | 0 | if (found) { |
444 | 0 | found = 0; |
445 | 0 | break; |
446 | 0 | } |
447 | 0 | found = i + 1; |
448 | 0 | } |
449 | 0 | } |
450 | 0 | break; |
451 | 0 | } |
452 | 0 | return found; |
453 | 0 | } |
454 | | |
455 | | /* |
456 | | * Parse user input, and return choice(s) for menu (menu_stuff). |
457 | | * |
458 | | * Input |
459 | | * (for single choice) |
460 | | * 1 - select a numbered item |
461 | | * foo - select item based on menu title |
462 | | * - (empty) select nothing |
463 | | * |
464 | | * (for multiple choice) |
465 | | * 1 - select a single item |
466 | | * 3-5 - select a range of items |
467 | | * 2-3,6-9 - select multiple ranges |
468 | | * foo - select item based on menu title |
469 | | * -... - unselect specified items |
470 | | * * - choose all items |
471 | | * - (empty) finish selecting |
472 | | * |
473 | | * The parse result will be saved in array **chosen, and |
474 | | * return number of total selections. |
475 | | */ |
476 | | static int parse_choice(struct menu_stuff *menu_stuff, |
477 | | int is_single, |
478 | | struct strbuf input, |
479 | | int **chosen) |
480 | 0 | { |
481 | 0 | struct strbuf **choice_list, **ptr; |
482 | 0 | int nr = 0; |
483 | 0 | int i; |
484 | |
|
485 | 0 | if (is_single) { |
486 | 0 | choice_list = strbuf_split_max(&input, '\n', 0); |
487 | 0 | } else { |
488 | 0 | char *p = input.buf; |
489 | 0 | do { |
490 | 0 | if (*p == ',') |
491 | 0 | *p = ' '; |
492 | 0 | } while (*p++); |
493 | 0 | choice_list = strbuf_split_max(&input, ' ', 0); |
494 | 0 | } |
495 | |
|
496 | 0 | for (ptr = choice_list; *ptr; ptr++) { |
497 | 0 | char *p; |
498 | 0 | int choose = 1; |
499 | 0 | int bottom = 0, top = 0; |
500 | 0 | int is_range, is_number; |
501 | |
|
502 | 0 | strbuf_trim(*ptr); |
503 | 0 | if (!(*ptr)->len) |
504 | 0 | continue; |
505 | | |
506 | | /* Input that begins with '-'; unchoose */ |
507 | 0 | if (*(*ptr)->buf == '-') { |
508 | 0 | choose = 0; |
509 | 0 | strbuf_remove((*ptr), 0, 1); |
510 | 0 | } |
511 | |
|
512 | 0 | is_range = 0; |
513 | 0 | is_number = 1; |
514 | 0 | for (p = (*ptr)->buf; *p; p++) { |
515 | 0 | if ('-' == *p) { |
516 | 0 | if (!is_range) { |
517 | 0 | is_range = 1; |
518 | 0 | is_number = 0; |
519 | 0 | } else { |
520 | 0 | is_number = 0; |
521 | 0 | is_range = 0; |
522 | 0 | break; |
523 | 0 | } |
524 | 0 | } else if (!isdigit(*p)) { |
525 | 0 | is_number = 0; |
526 | 0 | is_range = 0; |
527 | 0 | break; |
528 | 0 | } |
529 | 0 | } |
530 | |
|
531 | 0 | if (is_number) { |
532 | 0 | bottom = atoi((*ptr)->buf); |
533 | 0 | top = bottom; |
534 | 0 | } else if (is_range) { |
535 | 0 | bottom = atoi((*ptr)->buf); |
536 | | /* a range can be specified like 5-7 or 5- */ |
537 | 0 | if (!*(strchr((*ptr)->buf, '-') + 1)) |
538 | 0 | top = menu_stuff->nr; |
539 | 0 | else |
540 | 0 | top = atoi(strchr((*ptr)->buf, '-') + 1); |
541 | 0 | } else if (!strcmp((*ptr)->buf, "*")) { |
542 | 0 | bottom = 1; |
543 | 0 | top = menu_stuff->nr; |
544 | 0 | } else { |
545 | 0 | bottom = find_unique((*ptr)->buf, menu_stuff); |
546 | 0 | top = bottom; |
547 | 0 | } |
548 | |
|
549 | 0 | if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top || |
550 | 0 | (is_single && bottom != top)) { |
551 | 0 | clean_print_color(CLEAN_COLOR_ERROR); |
552 | 0 | printf(_("Huh (%s)?\n"), (*ptr)->buf); |
553 | 0 | clean_print_color(CLEAN_COLOR_RESET); |
554 | 0 | continue; |
555 | 0 | } |
556 | | |
557 | 0 | for (i = bottom; i <= top; i++) |
558 | 0 | (*chosen)[i-1] = choose; |
559 | 0 | } |
560 | |
|
561 | 0 | strbuf_list_free(choice_list); |
562 | |
|
563 | 0 | for (i = 0; i < menu_stuff->nr; i++) |
564 | 0 | nr += (*chosen)[i]; |
565 | 0 | return nr; |
566 | 0 | } |
567 | | |
568 | | /* |
569 | | * Implement a git-add-interactive compatible UI, which is borrowed |
570 | | * from add-interactive.c. |
571 | | * |
572 | | * Return value: |
573 | | * |
574 | | * - Return an array of integers |
575 | | * - , and it is up to you to free the allocated memory. |
576 | | * - The array ends with EOF. |
577 | | * - If user pressed CTRL-D (i.e. EOF), no selection returned. |
578 | | */ |
579 | | static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff) |
580 | 0 | { |
581 | 0 | struct strbuf choice = STRBUF_INIT; |
582 | 0 | int *chosen, *result; |
583 | 0 | int nr = 0; |
584 | 0 | int eof = 0; |
585 | 0 | int i; |
586 | |
|
587 | 0 | ALLOC_ARRAY(chosen, stuff->nr); |
588 | | /* set chosen as uninitialized */ |
589 | 0 | for (i = 0; i < stuff->nr; i++) |
590 | 0 | chosen[i] = -1; |
591 | |
|
592 | 0 | for (;;) { |
593 | 0 | if (opts->header) { |
594 | 0 | printf_ln("%s%s%s", |
595 | 0 | clean_get_color(CLEAN_COLOR_HEADER), |
596 | 0 | _(opts->header), |
597 | 0 | clean_get_color(CLEAN_COLOR_RESET)); |
598 | 0 | } |
599 | | |
600 | | /* chosen will be initialized by print_highlight_menu_stuff */ |
601 | 0 | print_highlight_menu_stuff(stuff, &chosen); |
602 | |
|
603 | 0 | if (opts->flags & MENU_OPTS_LIST_ONLY) |
604 | 0 | break; |
605 | | |
606 | 0 | if (opts->prompt) { |
607 | 0 | printf("%s%s%s%s", |
608 | 0 | clean_get_color(CLEAN_COLOR_PROMPT), |
609 | 0 | _(opts->prompt), |
610 | 0 | opts->flags & MENU_OPTS_SINGLETON ? "> " : ">> ", |
611 | 0 | clean_get_color(CLEAN_COLOR_RESET)); |
612 | 0 | } |
613 | |
|
614 | 0 | if (git_read_line_interactively(&choice) == EOF) { |
615 | 0 | eof = 1; |
616 | 0 | break; |
617 | 0 | } |
618 | | |
619 | | /* help for prompt */ |
620 | 0 | if (!strcmp(choice.buf, "?")) { |
621 | 0 | prompt_help_cmd(opts->flags & MENU_OPTS_SINGLETON); |
622 | 0 | continue; |
623 | 0 | } |
624 | | |
625 | | /* for a multiple-choice menu, press ENTER (empty) will return back */ |
626 | 0 | if (!(opts->flags & MENU_OPTS_SINGLETON) && !choice.len) |
627 | 0 | break; |
628 | | |
629 | 0 | nr = parse_choice(stuff, |
630 | 0 | opts->flags & MENU_OPTS_SINGLETON, |
631 | 0 | choice, |
632 | 0 | &chosen); |
633 | |
|
634 | 0 | if (opts->flags & MENU_OPTS_SINGLETON) { |
635 | 0 | if (nr) |
636 | 0 | break; |
637 | 0 | } else if (opts->flags & MENU_OPTS_IMMEDIATE) { |
638 | 0 | break; |
639 | 0 | } |
640 | 0 | } |
641 | |
|
642 | 0 | if (eof) { |
643 | 0 | result = xmalloc(sizeof(int)); |
644 | 0 | *result = EOF; |
645 | 0 | } else { |
646 | 0 | int j = 0; |
647 | | |
648 | | /* |
649 | | * recalculate nr, if return back from menu directly with |
650 | | * default selections. |
651 | | */ |
652 | 0 | if (!nr) { |
653 | 0 | for (i = 0; i < stuff->nr; i++) |
654 | 0 | nr += chosen[i]; |
655 | 0 | } |
656 | |
|
657 | 0 | CALLOC_ARRAY(result, st_add(nr, 1)); |
658 | 0 | for (i = 0; i < stuff->nr && j < nr; i++) { |
659 | 0 | if (chosen[i]) |
660 | 0 | result[j++] = i; |
661 | 0 | } |
662 | 0 | result[j] = EOF; |
663 | 0 | } |
664 | |
|
665 | 0 | free(chosen); |
666 | 0 | strbuf_release(&choice); |
667 | 0 | return result; |
668 | 0 | } |
669 | | |
670 | | static int clean_cmd(void) |
671 | 0 | { |
672 | 0 | return MENU_RETURN_NO_LOOP; |
673 | 0 | } |
674 | | |
675 | | static int filter_by_patterns_cmd(void) |
676 | 0 | { |
677 | 0 | struct dir_struct dir = DIR_INIT; |
678 | 0 | struct strbuf confirm = STRBUF_INIT; |
679 | 0 | struct strbuf **ignore_list; |
680 | 0 | struct string_list_item *item; |
681 | 0 | struct pattern_list *pl; |
682 | 0 | int changed = -1, i; |
683 | |
|
684 | 0 | for (;;) { |
685 | 0 | if (!del_list.nr) |
686 | 0 | break; |
687 | | |
688 | 0 | if (changed) |
689 | 0 | pretty_print_dels(); |
690 | |
|
691 | 0 | clean_print_color(CLEAN_COLOR_PROMPT); |
692 | 0 | printf(_("Input ignore patterns>> ")); |
693 | 0 | clean_print_color(CLEAN_COLOR_RESET); |
694 | 0 | if (git_read_line_interactively(&confirm) == EOF) |
695 | 0 | putchar('\n'); |
696 | | |
697 | | /* quit filter_by_pattern mode if press ENTER or Ctrl-D */ |
698 | 0 | if (!confirm.len) |
699 | 0 | break; |
700 | | |
701 | 0 | pl = add_pattern_list(&dir, EXC_CMDL, "manual exclude"); |
702 | 0 | ignore_list = strbuf_split_max(&confirm, ' ', 0); |
703 | |
|
704 | 0 | for (i = 0; ignore_list[i]; i++) { |
705 | 0 | strbuf_trim(ignore_list[i]); |
706 | 0 | if (!ignore_list[i]->len) |
707 | 0 | continue; |
708 | | |
709 | 0 | add_pattern(ignore_list[i]->buf, "", 0, pl, -(i+1)); |
710 | 0 | } |
711 | |
|
712 | 0 | changed = 0; |
713 | 0 | for_each_string_list_item(item, &del_list) { |
714 | 0 | int dtype = DT_UNKNOWN; |
715 | |
|
716 | 0 | if (is_excluded(&dir, the_repository->index, item->string, &dtype)) { |
717 | 0 | *item->string = '\0'; |
718 | 0 | changed++; |
719 | 0 | } |
720 | 0 | } |
721 | |
|
722 | 0 | if (changed) { |
723 | 0 | string_list_remove_empty_items(&del_list, 0); |
724 | 0 | } else { |
725 | 0 | clean_print_color(CLEAN_COLOR_ERROR); |
726 | 0 | printf_ln(_("WARNING: Cannot find items matched by: %s"), confirm.buf); |
727 | 0 | clean_print_color(CLEAN_COLOR_RESET); |
728 | 0 | } |
729 | |
|
730 | 0 | strbuf_list_free(ignore_list); |
731 | 0 | dir_clear(&dir); |
732 | 0 | } |
733 | |
|
734 | 0 | strbuf_release(&confirm); |
735 | 0 | return 0; |
736 | 0 | } |
737 | | |
738 | | static int select_by_numbers_cmd(void) |
739 | 0 | { |
740 | 0 | struct menu_opts menu_opts; |
741 | 0 | struct menu_stuff menu_stuff; |
742 | 0 | struct string_list_item *items; |
743 | 0 | int *chosen; |
744 | 0 | int i, j; |
745 | |
|
746 | 0 | menu_opts.header = NULL; |
747 | 0 | menu_opts.prompt = N_("Select items to delete"); |
748 | 0 | menu_opts.flags = 0; |
749 | |
|
750 | 0 | menu_stuff.type = MENU_STUFF_TYPE_STRING_LIST; |
751 | 0 | menu_stuff.stuff = &del_list; |
752 | 0 | menu_stuff.nr = del_list.nr; |
753 | |
|
754 | 0 | chosen = list_and_choose(&menu_opts, &menu_stuff); |
755 | 0 | items = del_list.items; |
756 | 0 | for (i = 0, j = 0; i < del_list.nr; i++) { |
757 | 0 | if (i < chosen[j]) { |
758 | 0 | *(items[i].string) = '\0'; |
759 | 0 | } else if (i == chosen[j]) { |
760 | | /* delete selected item */ |
761 | 0 | j++; |
762 | 0 | continue; |
763 | 0 | } else { |
764 | | /* end of chosen (chosen[j] == EOF), won't delete */ |
765 | 0 | *(items[i].string) = '\0'; |
766 | 0 | } |
767 | 0 | } |
768 | |
|
769 | 0 | string_list_remove_empty_items(&del_list, 0); |
770 | |
|
771 | 0 | free(chosen); |
772 | 0 | return 0; |
773 | 0 | } |
774 | | |
775 | | static int ask_each_cmd(void) |
776 | 0 | { |
777 | 0 | struct strbuf confirm = STRBUF_INIT; |
778 | 0 | struct strbuf buf = STRBUF_INIT; |
779 | 0 | struct string_list_item *item; |
780 | 0 | const char *qname; |
781 | 0 | int changed = 0, eof = 0; |
782 | |
|
783 | 0 | for_each_string_list_item(item, &del_list) { |
784 | | /* Ctrl-D should stop removing files */ |
785 | 0 | if (!eof) { |
786 | 0 | qname = quote_path(item->string, NULL, &buf, 0); |
787 | | /* TRANSLATORS: Make sure to keep [y/N] as is */ |
788 | 0 | printf(_("Remove %s [y/N]? "), qname); |
789 | 0 | if (git_read_line_interactively(&confirm) == EOF) { |
790 | 0 | putchar('\n'); |
791 | 0 | eof = 1; |
792 | 0 | } |
793 | 0 | } |
794 | 0 | if (!confirm.len || strncasecmp(confirm.buf, "yes", confirm.len)) { |
795 | 0 | *item->string = '\0'; |
796 | 0 | changed++; |
797 | 0 | } |
798 | 0 | } |
799 | |
|
800 | 0 | if (changed) |
801 | 0 | string_list_remove_empty_items(&del_list, 0); |
802 | |
|
803 | 0 | strbuf_release(&buf); |
804 | 0 | strbuf_release(&confirm); |
805 | 0 | return MENU_RETURN_NO_LOOP; |
806 | 0 | } |
807 | | |
808 | | static int quit_cmd(void) |
809 | 0 | { |
810 | 0 | string_list_clear(&del_list, 0); |
811 | 0 | printf(_("Bye.\n")); |
812 | 0 | return MENU_RETURN_NO_LOOP; |
813 | 0 | } |
814 | | |
815 | | static int help_cmd(void) |
816 | 0 | { |
817 | 0 | clean_print_color(CLEAN_COLOR_HELP); |
818 | 0 | printf_ln(_( |
819 | 0 | "clean - start cleaning\n" |
820 | 0 | "filter by pattern - exclude items from deletion\n" |
821 | 0 | "select by numbers - select items to be deleted by numbers\n" |
822 | 0 | "ask each - confirm each deletion (like \"rm -i\")\n" |
823 | 0 | "quit - stop cleaning\n" |
824 | 0 | "help - this screen\n" |
825 | 0 | "? - help for prompt selection" |
826 | 0 | )); |
827 | 0 | clean_print_color(CLEAN_COLOR_RESET); |
828 | 0 | return 0; |
829 | 0 | } |
830 | | |
831 | | static void interactive_main_loop(void) |
832 | 0 | { |
833 | 0 | while (del_list.nr) { |
834 | 0 | struct menu_opts menu_opts; |
835 | 0 | struct menu_stuff menu_stuff; |
836 | 0 | struct menu_item menus[] = { |
837 | 0 | {'c', "clean", 0, clean_cmd}, |
838 | 0 | {'f', "filter by pattern", 0, filter_by_patterns_cmd}, |
839 | 0 | {'s', "select by numbers", 0, select_by_numbers_cmd}, |
840 | 0 | {'a', "ask each", 0, ask_each_cmd}, |
841 | 0 | {'q', "quit", 0, quit_cmd}, |
842 | 0 | {'h', "help", 0, help_cmd}, |
843 | 0 | }; |
844 | 0 | int *chosen; |
845 | |
|
846 | 0 | menu_opts.header = N_("*** Commands ***"); |
847 | 0 | menu_opts.prompt = N_("What now"); |
848 | 0 | menu_opts.flags = MENU_OPTS_SINGLETON; |
849 | |
|
850 | 0 | menu_stuff.type = MENU_STUFF_TYPE_MENU_ITEM; |
851 | 0 | menu_stuff.stuff = menus; |
852 | 0 | menu_stuff.nr = sizeof(menus) / sizeof(struct menu_item); |
853 | |
|
854 | 0 | clean_print_color(CLEAN_COLOR_HEADER); |
855 | 0 | printf_ln(Q_("Would remove the following item:", |
856 | 0 | "Would remove the following items:", |
857 | 0 | del_list.nr)); |
858 | 0 | clean_print_color(CLEAN_COLOR_RESET); |
859 | |
|
860 | 0 | pretty_print_dels(); |
861 | |
|
862 | 0 | chosen = list_and_choose(&menu_opts, &menu_stuff); |
863 | |
|
864 | 0 | if (*chosen != EOF) { |
865 | 0 | int ret; |
866 | 0 | ret = menus[*chosen].fn(); |
867 | 0 | if (ret != MENU_RETURN_NO_LOOP) { |
868 | 0 | FREE_AND_NULL(chosen); |
869 | 0 | if (!del_list.nr) { |
870 | 0 | clean_print_color(CLEAN_COLOR_ERROR); |
871 | 0 | printf_ln(_("No more files to clean, exiting.")); |
872 | 0 | clean_print_color(CLEAN_COLOR_RESET); |
873 | 0 | break; |
874 | 0 | } |
875 | 0 | continue; |
876 | 0 | } |
877 | 0 | } else { |
878 | 0 | quit_cmd(); |
879 | 0 | } |
880 | | |
881 | 0 | FREE_AND_NULL(chosen); |
882 | 0 | break; |
883 | 0 | } |
884 | 0 | } |
885 | | |
886 | | static void correct_untracked_entries(struct dir_struct *dir) |
887 | 0 | { |
888 | 0 | int src, dst, ign; |
889 | |
|
890 | 0 | for (src = dst = ign = 0; src < dir->nr; src++) { |
891 | | /* skip paths in ignored[] that cannot be inside entries[src] */ |
892 | 0 | while (ign < dir->ignored_nr && |
893 | 0 | 0 <= cmp_dir_entry(&dir->entries[src], &dir->ignored[ign])) |
894 | 0 | ign++; |
895 | |
|
896 | 0 | if (ign < dir->ignored_nr && |
897 | 0 | check_dir_entry_contains(dir->entries[src], dir->ignored[ign])) { |
898 | | /* entries[src] contains an ignored path, so we drop it */ |
899 | 0 | free(dir->entries[src]); |
900 | 0 | } else { |
901 | 0 | struct dir_entry *ent = dir->entries[src++]; |
902 | | |
903 | | /* entries[src] does not contain an ignored path, so we keep it */ |
904 | 0 | dir->entries[dst++] = ent; |
905 | | |
906 | | /* then discard paths in entries[] contained inside entries[src] */ |
907 | 0 | while (src < dir->nr && |
908 | 0 | check_dir_entry_contains(ent, dir->entries[src])) |
909 | 0 | free(dir->entries[src++]); |
910 | | |
911 | | /* compensate for the outer loop's loop control */ |
912 | 0 | src--; |
913 | 0 | } |
914 | 0 | } |
915 | 0 | dir->nr = dst; |
916 | 0 | } |
917 | | |
918 | | int cmd_clean(int argc, const char **argv, const char *prefix) |
919 | 0 | { |
920 | 0 | int i, res; |
921 | 0 | int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0; |
922 | 0 | int ignored_only = 0, force = 0, errors = 0, gone = 1; |
923 | 0 | int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; |
924 | 0 | struct strbuf abs_path = STRBUF_INIT; |
925 | 0 | struct dir_struct dir = DIR_INIT; |
926 | 0 | struct pathspec pathspec; |
927 | 0 | struct strbuf buf = STRBUF_INIT; |
928 | 0 | struct string_list exclude_list = STRING_LIST_INIT_NODUP; |
929 | 0 | struct pattern_list *pl; |
930 | 0 | struct string_list_item *item; |
931 | 0 | const char *qname; |
932 | 0 | struct option options[] = { |
933 | 0 | OPT__QUIET(&quiet, N_("do not print names of files removed")), |
934 | 0 | OPT__DRY_RUN(&dry_run, N_("dry run")), |
935 | 0 | OPT__FORCE(&force, N_("force"), PARSE_OPT_NOCOMPLETE), |
936 | 0 | OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")), |
937 | 0 | OPT_BOOL('d', NULL, &remove_directories, |
938 | 0 | N_("remove whole directories")), |
939 | 0 | OPT_CALLBACK_F('e', "exclude", &exclude_list, N_("pattern"), |
940 | 0 | N_("add <pattern> to ignore rules"), PARSE_OPT_NONEG, exclude_cb), |
941 | 0 | OPT_BOOL('x', NULL, &ignored, N_("remove ignored files, too")), |
942 | 0 | OPT_BOOL('X', NULL, &ignored_only, |
943 | 0 | N_("remove only ignored files")), |
944 | 0 | OPT_END() |
945 | 0 | }; |
946 | |
|
947 | 0 | git_config(git_clean_config, NULL); |
948 | |
|
949 | 0 | argc = parse_options(argc, argv, prefix, options, builtin_clean_usage, |
950 | 0 | 0); |
951 | |
|
952 | 0 | if (require_force != 0 && !force && !interactive && !dry_run) |
953 | 0 | die(_("clean.requireForce is true and -f not given: refusing to clean")); |
954 | | |
955 | 0 | if (force > 1) |
956 | 0 | rm_flags = 0; |
957 | 0 | else |
958 | 0 | dir.flags |= DIR_SKIP_NESTED_GIT; |
959 | |
|
960 | 0 | dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; |
961 | |
|
962 | 0 | if (ignored && ignored_only) |
963 | 0 | die(_("options '%s' and '%s' cannot be used together"), "-x", "-X"); |
964 | 0 | if (!ignored) |
965 | 0 | setup_standard_excludes(&dir); |
966 | 0 | if (ignored_only) |
967 | 0 | dir.flags |= DIR_SHOW_IGNORED; |
968 | |
|
969 | 0 | if (argc) { |
970 | | /* |
971 | | * Remaining args implies pathspecs specified, and we should |
972 | | * recurse within those. |
973 | | */ |
974 | 0 | remove_directories = 1; |
975 | 0 | } |
976 | |
|
977 | 0 | if (remove_directories && !ignored_only) { |
978 | | /* |
979 | | * We need to know about ignored files too: |
980 | | * |
981 | | * If (ignored), then we will delete ignored files as well. |
982 | | * |
983 | | * If (!ignored), then even though we not are doing |
984 | | * anything with ignored files, we need to know about them |
985 | | * so that we can avoid deleting a directory of untracked |
986 | | * files that also contains an ignored file within it. |
987 | | * |
988 | | * For the (!ignored) case, since we only need to avoid |
989 | | * deleting ignored files, we can set |
990 | | * DIR_SHOW_IGNORED_TOO_MODE_MATCHING in order to avoid |
991 | | * recursing into a directory which is itself ignored. |
992 | | */ |
993 | 0 | dir.flags |= DIR_SHOW_IGNORED_TOO; |
994 | 0 | if (!ignored) |
995 | 0 | dir.flags |= DIR_SHOW_IGNORED_TOO_MODE_MATCHING; |
996 | | |
997 | | /* |
998 | | * Let the fill_directory() machinery know that we aren't |
999 | | * just recursing to collect the ignored files; we want all |
1000 | | * the untracked ones so that we can delete them. (Note: |
1001 | | * we could also set DIR_KEEP_UNTRACKED_CONTENTS when |
1002 | | * ignored_only is true, since DIR_KEEP_UNTRACKED_CONTENTS |
1003 | | * only has effect in combination with DIR_SHOW_IGNORED_TOO. It makes |
1004 | | * the code clearer to exclude it, though. |
1005 | | */ |
1006 | 0 | dir.flags |= DIR_KEEP_UNTRACKED_CONTENTS; |
1007 | 0 | } |
1008 | |
|
1009 | 0 | prepare_repo_settings(the_repository); |
1010 | 0 | the_repository->settings.command_requires_full_index = 0; |
1011 | |
|
1012 | 0 | if (repo_read_index(the_repository) < 0) |
1013 | 0 | die(_("index file corrupt")); |
1014 | | |
1015 | 0 | pl = add_pattern_list(&dir, EXC_CMDL, "--exclude option"); |
1016 | 0 | for (i = 0; i < exclude_list.nr; i++) |
1017 | 0 | add_pattern(exclude_list.items[i].string, "", 0, pl, -(i+1)); |
1018 | |
|
1019 | 0 | parse_pathspec(&pathspec, 0, |
1020 | 0 | PATHSPEC_PREFER_CWD, |
1021 | 0 | prefix, argv); |
1022 | |
|
1023 | 0 | fill_directory(&dir, the_repository->index, &pathspec); |
1024 | 0 | correct_untracked_entries(&dir); |
1025 | |
|
1026 | 0 | for (i = 0; i < dir.nr; i++) { |
1027 | 0 | struct dir_entry *ent = dir.entries[i]; |
1028 | 0 | struct stat st; |
1029 | 0 | const char *rel; |
1030 | |
|
1031 | 0 | if (!index_name_is_other(the_repository->index, ent->name, ent->len)) |
1032 | 0 | continue; |
1033 | | |
1034 | 0 | if (lstat(ent->name, &st)) |
1035 | 0 | die_errno("Cannot lstat '%s'", ent->name); |
1036 | | |
1037 | 0 | if (S_ISDIR(st.st_mode) && !remove_directories) |
1038 | 0 | continue; |
1039 | | |
1040 | 0 | rel = relative_path(ent->name, prefix, &buf); |
1041 | 0 | string_list_append(&del_list, rel); |
1042 | 0 | } |
1043 | | |
1044 | 0 | dir_clear(&dir); |
1045 | |
|
1046 | 0 | if (interactive && del_list.nr > 0) |
1047 | 0 | interactive_main_loop(); |
1048 | |
|
1049 | 0 | for_each_string_list_item(item, &del_list) { |
1050 | 0 | struct stat st; |
1051 | |
|
1052 | 0 | strbuf_reset(&abs_path); |
1053 | 0 | if (prefix) |
1054 | 0 | strbuf_addstr(&abs_path, prefix); |
1055 | |
|
1056 | 0 | strbuf_addstr(&abs_path, item->string); |
1057 | | |
1058 | | /* |
1059 | | * we might have removed this as part of earlier |
1060 | | * recursive directory removal, so lstat() here could |
1061 | | * fail with ENOENT. |
1062 | | */ |
1063 | 0 | if (lstat(abs_path.buf, &st)) |
1064 | 0 | continue; |
1065 | | |
1066 | 0 | if (S_ISDIR(st.st_mode)) { |
1067 | 0 | if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone)) |
1068 | 0 | errors++; |
1069 | 0 | if (gone && !quiet) { |
1070 | 0 | qname = quote_path(item->string, NULL, &buf, 0); |
1071 | 0 | printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); |
1072 | 0 | } |
1073 | 0 | } else { |
1074 | 0 | res = dry_run ? 0 : unlink(abs_path.buf); |
1075 | 0 | if (res) { |
1076 | 0 | int saved_errno = errno; |
1077 | 0 | qname = quote_path(item->string, NULL, &buf, 0); |
1078 | 0 | errno = saved_errno; |
1079 | 0 | warning_errno(_(msg_warn_remove_failed), qname); |
1080 | 0 | errors++; |
1081 | 0 | } else if (!quiet) { |
1082 | 0 | qname = quote_path(item->string, NULL, &buf, 0); |
1083 | 0 | printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); |
1084 | 0 | } |
1085 | 0 | } |
1086 | 0 | } |
1087 | |
|
1088 | 0 | strbuf_release(&abs_path); |
1089 | 0 | strbuf_release(&buf); |
1090 | 0 | string_list_clear(&del_list, 0); |
1091 | 0 | string_list_clear(&exclude_list, 0); |
1092 | 0 | clear_pathspec(&pathspec); |
1093 | 0 | return (errors != 0); |
1094 | 0 | } |