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