Coverage Report

Created: 2024-09-08 06:23

/src/git/builtin/clean.c
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, &quoted, 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, &quoted, 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, &quoted, 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, &quoted, 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, &quoted, 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, &quoted, 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(&quoted);
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
}