git

Форк
0
/
clean.c 
1094 строки · 27.2 Кб
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
#define MENU_OPTS_SINGLETON		01
75
#define MENU_OPTS_IMMEDIATE		02
76
#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
#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
{
109
	const char *slot_name;
110

111
	if (starts_with(var, "column."))
112
		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
	if (!strcmp(var, "color.interactive")) {
117
		clean_use_color = git_config_colorbool(var, value);
118
		return 0;
119
	}
120
	if (skip_prefix(var, "color.interactive.", &slot_name)) {
121
		int slot = LOOKUP_CONFIG(color_interactive_slots, slot_name);
122
		if (slot < 0)
123
			return 0;
124
		if (!value)
125
			return config_error_nonbool(var);
126
		return color_parse(value, clean_colors[slot]);
127
	}
128

129
	if (!strcmp(var, "clean.requireforce")) {
130
		require_force = git_config_bool(var, value);
131
		return 0;
132
	}
133

134
	if (git_color_config(var, value, cb) < 0)
135
		return -1;
136

137
	return git_default_config(var, value, ctx, cb);
138
}
139

140
static const char *clean_get_color(enum color_clean ix)
141
{
142
	if (want_color(clean_use_color))
143
		return clean_colors[ix];
144
	return "";
145
}
146

147
static void clean_print_color(enum color_clean ix)
148
{
149
	printf("%s", clean_get_color(ix));
150
}
151

152
static int exclude_cb(const struct option *opt, const char *arg, int unset)
153
{
154
	struct string_list *exclude_list = opt->value;
155
	BUG_ON_OPT_NEG(unset);
156
	string_list_append(exclude_list, arg);
157
	return 0;
158
}
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
{
163
	DIR *dir;
164
	struct strbuf quoted = STRBUF_INIT;
165
	struct strbuf realpath = STRBUF_INIT;
166
	struct strbuf real_ocwd = STRBUF_INIT;
167
	struct dirent *e;
168
	int res = 0, ret = 0, gone = 1, original_len = path->len, len;
169
	struct string_list dels = STRING_LIST_INIT_DUP;
170

171
	*dir_gone = 1;
172

173
	if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
174
	    is_nonbare_repository_dir(path)) {
175
		if (!quiet) {
176
			quote_path(path->buf, prefix, &quoted, 0);
177
			printf(dry_run ?  _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
178
					quoted.buf);
179
		}
180

181
		*dir_gone = 0;
182
		goto out;
183
	}
184

185
	dir = opendir(path->buf);
186
	if (!dir) {
187
		/* an empty dir could be removed even if it is unreadble */
188
		res = dry_run ? 0 : rmdir(path->buf);
189
		if (res) {
190
			int saved_errno = errno;
191
			quote_path(path->buf, prefix, &quoted, 0);
192
			errno = saved_errno;
193
			warning_errno(_(msg_warn_remove_failed), quoted.buf);
194
			*dir_gone = 0;
195
		}
196
		ret = res;
197
		goto out;
198
	}
199

200
	strbuf_complete(path, '/');
201

202
	len = path->len;
203
	while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) {
204
		struct stat st;
205

206
		strbuf_setlen(path, len);
207
		strbuf_addstr(path, e->d_name);
208
		if (lstat(path->buf, &st))
209
			warning_errno(_(msg_warn_lstat_failed), path->buf);
210
		else if (S_ISDIR(st.st_mode)) {
211
			if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))
212
				ret = 1;
213
			if (gone) {
214
				quote_path(path->buf, prefix, &quoted, 0);
215
				string_list_append(&dels, quoted.buf);
216
			} else
217
				*dir_gone = 0;
218
			continue;
219
		} else {
220
			res = dry_run ? 0 : unlink(path->buf);
221
			if (!res) {
222
				quote_path(path->buf, prefix, &quoted, 0);
223
				string_list_append(&dels, quoted.buf);
224
			} else {
225
				int saved_errno = errno;
226
				quote_path(path->buf, prefix, &quoted, 0);
227
				errno = saved_errno;
228
				warning_errno(_(msg_warn_remove_failed), quoted.buf);
229
				*dir_gone = 0;
230
				ret = 1;
231
			}
232
			continue;
233
		}
234

235
		/* path too long, stat fails, or non-directory still exists */
236
		*dir_gone = 0;
237
		ret = 1;
238
		break;
239
	}
240
	closedir(dir);
241

242
	strbuf_setlen(path, original_len);
243

244
	if (*dir_gone) {
245
		/*
246
		 * Normalize path components in path->buf, e.g. change '\' to
247
		 * '/' on Windows.
248
		 */
249
		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
		 if (startup_info->original_cwd)
257
			 strbuf_realpath(&real_ocwd,
258
					 startup_info->original_cwd, 1);
259

260
		if (!strbuf_cmp(&realpath, &real_ocwd)) {
261
			printf("%s", dry_run ? _(msg_would_skip_cwd) : _(msg_skip_cwd));
262
			*dir_gone = 0;
263
		} else {
264
			res = dry_run ? 0 : rmdir(path->buf);
265
			if (!res)
266
				*dir_gone = 1;
267
			else {
268
				int saved_errno = errno;
269
				quote_path(path->buf, prefix, &quoted, 0);
270
				errno = saved_errno;
271
				warning_errno(_(msg_warn_remove_failed), quoted.buf);
272
				*dir_gone = 0;
273
				ret = 1;
274
			}
275
		}
276
	}
277

278
	if (!*dir_gone && !quiet) {
279
		int i;
280
		for (i = 0; i < dels.nr; i++)
281
			printf(dry_run ?  _(msg_would_remove) : _(msg_remove), dels.items[i].string);
282
	}
283
out:
284
	strbuf_release(&realpath);
285
	strbuf_release(&real_ocwd);
286
	strbuf_release(&quoted);
287
	string_list_clear(&dels, 0);
288
	return ret;
289
}
290

291
static void pretty_print_dels(void)
292
{
293
	struct string_list list = STRING_LIST_INIT_DUP;
294
	struct string_list_item *item;
295
	struct strbuf buf = STRBUF_INIT;
296
	const char *qname;
297
	struct column_options copts;
298

299
	for_each_string_list_item(item, &del_list) {
300
		qname = quote_path(item->string, NULL, &buf, 0);
301
		string_list_append(&list, qname);
302
	}
303

304
	/*
305
	 * always enable column display, we only consult column.*
306
	 * about layout strategy and stuff
307
	 */
308
	colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
309
	memset(&copts, 0, sizeof(copts));
310
	copts.indent = "  ";
311
	copts.padding = 2;
312
	print_columns(&list, colopts, &copts);
313
	strbuf_release(&buf);
314
	string_list_clear(&list, 0);
315
}
316

317
static void pretty_print_menus(struct string_list *menu_list)
318
{
319
	unsigned int local_colopts = 0;
320
	struct column_options copts;
321

322
	local_colopts = COL_ENABLED | COL_ROW;
323
	memset(&copts, 0, sizeof(copts));
324
	copts.indent = "  ";
325
	copts.padding = 2;
326
	print_columns(menu_list, local_colopts, &copts);
327
}
328

329
static void prompt_help_cmd(int singleton)
330
{
331
	clean_print_color(CLEAN_COLOR_HELP);
332
	printf(singleton ?
333
		  _("Prompt help:\n"
334
		    "1          - select a numbered item\n"
335
		    "foo        - select item based on unique prefix\n"
336
		    "           - (empty) select nothing\n") :
337
		  _("Prompt help:\n"
338
		    "1          - select a single item\n"
339
		    "3-5        - select a range of items\n"
340
		    "2-3,6-9    - select multiple ranges\n"
341
		    "foo        - select item based on unique prefix\n"
342
		    "-...       - unselect specified items\n"
343
		    "*          - choose all items\n"
344
		    "           - (empty) finish selecting\n"));
345
	clean_print_color(CLEAN_COLOR_RESET);
346
}
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
{
353
	struct string_list menu_list = STRING_LIST_INIT_DUP;
354
	struct strbuf menu = STRBUF_INIT;
355
	struct menu_item *menu_item;
356
	struct string_list_item *string_list_item;
357
	int i;
358

359
	switch (stuff->type) {
360
	default:
361
		die("Bad type of menu_stuff when print menu");
362
	case MENU_STUFF_TYPE_MENU_ITEM:
363
		menu_item = (struct menu_item *)stuff->stuff;
364
		for (i = 0; i < stuff->nr; i++, menu_item++) {
365
			const char *p;
366
			int highlighted = 0;
367

368
			p = menu_item->title;
369
			if ((*chosen)[i] < 0)
370
				(*chosen)[i] = menu_item->selected ? 1 : 0;
371
			strbuf_addf(&menu, "%s%2d: ", (*chosen)[i] ? "*" : " ", i+1);
372
			for (; *p; p++) {
373
				if (!highlighted && *p == menu_item->hotkey) {
374
					strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_PROMPT));
375
					strbuf_addch(&menu, *p);
376
					strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_RESET));
377
					highlighted = 1;
378
				} else {
379
					strbuf_addch(&menu, *p);
380
				}
381
			}
382
			string_list_append(&menu_list, menu.buf);
383
			strbuf_reset(&menu);
384
		}
385
		break;
386
	case MENU_STUFF_TYPE_STRING_LIST:
387
		i = 0;
388
		for_each_string_list_item(string_list_item, (struct string_list *)stuff->stuff) {
389
			if ((*chosen)[i] < 0)
390
				(*chosen)[i] = 0;
391
			strbuf_addf(&menu, "%s%2d: %s",
392
				    (*chosen)[i] ? "*" : " ", i+1, string_list_item->string);
393
			string_list_append(&menu_list, menu.buf);
394
			strbuf_reset(&menu);
395
			i++;
396
		}
397
		break;
398
	}
399

400
	pretty_print_menus(&menu_list);
401

402
	strbuf_release(&menu);
403
	string_list_clear(&menu_list, 0);
404
}
405

406
static int find_unique(const char *choice, struct menu_stuff *menu_stuff)
407
{
408
	struct menu_item *menu_item;
409
	struct string_list_item *string_list_item;
410
	int i, len, found = 0;
411

412
	len = strlen(choice);
413
	switch (menu_stuff->type) {
414
	default:
415
		die("Bad type of menu_stuff when parse choice");
416
	case MENU_STUFF_TYPE_MENU_ITEM:
417

418
		menu_item = (struct menu_item *)menu_stuff->stuff;
419
		for (i = 0; i < menu_stuff->nr; i++, menu_item++) {
420
			if (len == 1 && *choice == menu_item->hotkey) {
421
				found = i + 1;
422
				break;
423
			}
424
			if (!strncasecmp(choice, menu_item->title, len)) {
425
				if (found) {
426
					if (len == 1) {
427
						/* continue for hotkey matching */
428
						found = -1;
429
					} else {
430
						found = 0;
431
						break;
432
					}
433
				} else {
434
					found = i + 1;
435
				}
436
			}
437
		}
438
		break;
439
	case MENU_STUFF_TYPE_STRING_LIST:
440
		string_list_item = ((struct string_list *)menu_stuff->stuff)->items;
441
		for (i = 0; i < menu_stuff->nr; i++, string_list_item++) {
442
			if (!strncasecmp(choice, string_list_item->string, len)) {
443
				if (found) {
444
					found = 0;
445
					break;
446
				}
447
				found = i + 1;
448
			}
449
		}
450
		break;
451
	}
452
	return found;
453
}
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
{
481
	struct strbuf **choice_list, **ptr;
482
	int nr = 0;
483
	int i;
484

485
	if (is_single) {
486
		choice_list = strbuf_split_max(&input, '\n', 0);
487
	} else {
488
		char *p = input.buf;
489
		do {
490
			if (*p == ',')
491
				*p = ' ';
492
		} while (*p++);
493
		choice_list = strbuf_split_max(&input, ' ', 0);
494
	}
495

496
	for (ptr = choice_list; *ptr; ptr++) {
497
		char *p;
498
		int choose = 1;
499
		int bottom = 0, top = 0;
500
		int is_range, is_number;
501

502
		strbuf_trim(*ptr);
503
		if (!(*ptr)->len)
504
			continue;
505

506
		/* Input that begins with '-'; unchoose */
507
		if (*(*ptr)->buf == '-') {
508
			choose = 0;
509
			strbuf_remove((*ptr), 0, 1);
510
		}
511

512
		is_range = 0;
513
		is_number = 1;
514
		for (p = (*ptr)->buf; *p; p++) {
515
			if ('-' == *p) {
516
				if (!is_range) {
517
					is_range = 1;
518
					is_number = 0;
519
				} else {
520
					is_number = 0;
521
					is_range = 0;
522
					break;
523
				}
524
			} else if (!isdigit(*p)) {
525
				is_number = 0;
526
				is_range = 0;
527
				break;
528
			}
529
		}
530

531
		if (is_number) {
532
			bottom = atoi((*ptr)->buf);
533
			top = bottom;
534
		} else if (is_range) {
535
			bottom = atoi((*ptr)->buf);
536
			/* a range can be specified like 5-7 or 5- */
537
			if (!*(strchr((*ptr)->buf, '-') + 1))
538
				top = menu_stuff->nr;
539
			else
540
				top = atoi(strchr((*ptr)->buf, '-') + 1);
541
		} else if (!strcmp((*ptr)->buf, "*")) {
542
			bottom = 1;
543
			top = menu_stuff->nr;
544
		} else {
545
			bottom = find_unique((*ptr)->buf, menu_stuff);
546
			top = bottom;
547
		}
548

549
		if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top ||
550
		    (is_single && bottom != top)) {
551
			clean_print_color(CLEAN_COLOR_ERROR);
552
			printf(_("Huh (%s)?\n"), (*ptr)->buf);
553
			clean_print_color(CLEAN_COLOR_RESET);
554
			continue;
555
		}
556

557
		for (i = bottom; i <= top; i++)
558
			(*chosen)[i-1] = choose;
559
	}
560

561
	strbuf_list_free(choice_list);
562

563
	for (i = 0; i < menu_stuff->nr; i++)
564
		nr += (*chosen)[i];
565
	return nr;
566
}
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
{
581
	struct strbuf choice = STRBUF_INIT;
582
	int *chosen, *result;
583
	int nr = 0;
584
	int eof = 0;
585
	int i;
586

587
	ALLOC_ARRAY(chosen, stuff->nr);
588
	/* set chosen as uninitialized */
589
	for (i = 0; i < stuff->nr; i++)
590
		chosen[i] = -1;
591

592
	for (;;) {
593
		if (opts->header) {
594
			printf_ln("%s%s%s",
595
				  clean_get_color(CLEAN_COLOR_HEADER),
596
				  _(opts->header),
597
				  clean_get_color(CLEAN_COLOR_RESET));
598
		}
599

600
		/* chosen will be initialized by print_highlight_menu_stuff */
601
		print_highlight_menu_stuff(stuff, &chosen);
602

603
		if (opts->flags & MENU_OPTS_LIST_ONLY)
604
			break;
605

606
		if (opts->prompt) {
607
			printf("%s%s%s%s",
608
			       clean_get_color(CLEAN_COLOR_PROMPT),
609
			       _(opts->prompt),
610
			       opts->flags & MENU_OPTS_SINGLETON ? "> " : ">> ",
611
			       clean_get_color(CLEAN_COLOR_RESET));
612
		}
613

614
		if (git_read_line_interactively(&choice) == EOF) {
615
			eof = 1;
616
			break;
617
		}
618

619
		/* help for prompt */
620
		if (!strcmp(choice.buf, "?")) {
621
			prompt_help_cmd(opts->flags & MENU_OPTS_SINGLETON);
622
			continue;
623
		}
624

625
		/* for a multiple-choice menu, press ENTER (empty) will return back */
626
		if (!(opts->flags & MENU_OPTS_SINGLETON) && !choice.len)
627
			break;
628

629
		nr = parse_choice(stuff,
630
				  opts->flags & MENU_OPTS_SINGLETON,
631
				  choice,
632
				  &chosen);
633

634
		if (opts->flags & MENU_OPTS_SINGLETON) {
635
			if (nr)
636
				break;
637
		} else if (opts->flags & MENU_OPTS_IMMEDIATE) {
638
			break;
639
		}
640
	}
641

642
	if (eof) {
643
		result = xmalloc(sizeof(int));
644
		*result = EOF;
645
	} else {
646
		int j = 0;
647

648
		/*
649
		 * recalculate nr, if return back from menu directly with
650
		 * default selections.
651
		 */
652
		if (!nr) {
653
			for (i = 0; i < stuff->nr; i++)
654
				nr += chosen[i];
655
		}
656

657
		CALLOC_ARRAY(result, st_add(nr, 1));
658
		for (i = 0; i < stuff->nr && j < nr; i++) {
659
			if (chosen[i])
660
				result[j++] = i;
661
		}
662
		result[j] = EOF;
663
	}
664

665
	free(chosen);
666
	strbuf_release(&choice);
667
	return result;
668
}
669

670
static int clean_cmd(void)
671
{
672
	return MENU_RETURN_NO_LOOP;
673
}
674

675
static int filter_by_patterns_cmd(void)
676
{
677
	struct dir_struct dir = DIR_INIT;
678
	struct strbuf confirm = STRBUF_INIT;
679
	struct strbuf **ignore_list;
680
	struct string_list_item *item;
681
	struct pattern_list *pl;
682
	int changed = -1, i;
683

684
	for (;;) {
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
		ignore_list = strbuf_split_max(&confirm, ' ', 0);
703

704
		for (i = 0; ignore_list[i]; i++) {
705
			strbuf_trim(ignore_list[i]);
706
			if (!ignore_list[i]->len)
707
				continue;
708

709
			add_pattern(ignore_list[i]->buf, "", 0, pl, -(i+1));
710
		}
711

712
		changed = 0;
713
		for_each_string_list_item(item, &del_list) {
714
			int dtype = DT_UNKNOWN;
715

716
			if (is_excluded(&dir, the_repository->index, item->string, &dtype)) {
717
				*item->string = '\0';
718
				changed++;
719
			}
720
		}
721

722
		if (changed) {
723
			string_list_remove_empty_items(&del_list, 0);
724
		} else {
725
			clean_print_color(CLEAN_COLOR_ERROR);
726
			printf_ln(_("WARNING: Cannot find items matched by: %s"), confirm.buf);
727
			clean_print_color(CLEAN_COLOR_RESET);
728
		}
729

730
		strbuf_list_free(ignore_list);
731
		dir_clear(&dir);
732
	}
733

734
	strbuf_release(&confirm);
735
	return 0;
736
}
737

738
static int select_by_numbers_cmd(void)
739
{
740
	struct menu_opts menu_opts;
741
	struct menu_stuff menu_stuff;
742
	struct string_list_item *items;
743
	int *chosen;
744
	int i, j;
745

746
	menu_opts.header = NULL;
747
	menu_opts.prompt = N_("Select items to delete");
748
	menu_opts.flags = 0;
749

750
	menu_stuff.type = MENU_STUFF_TYPE_STRING_LIST;
751
	menu_stuff.stuff = &del_list;
752
	menu_stuff.nr = del_list.nr;
753

754
	chosen = list_and_choose(&menu_opts, &menu_stuff);
755
	items = del_list.items;
756
	for (i = 0, j = 0; i < del_list.nr; i++) {
757
		if (i < chosen[j]) {
758
			*(items[i].string) = '\0';
759
		} else if (i == chosen[j]) {
760
			/* delete selected item */
761
			j++;
762
			continue;
763
		} else {
764
			/* end of chosen (chosen[j] == EOF), won't delete */
765
			*(items[i].string) = '\0';
766
		}
767
	}
768

769
	string_list_remove_empty_items(&del_list, 0);
770

771
	free(chosen);
772
	return 0;
773
}
774

775
static int ask_each_cmd(void)
776
{
777
	struct strbuf confirm = STRBUF_INIT;
778
	struct strbuf buf = STRBUF_INIT;
779
	struct string_list_item *item;
780
	const char *qname;
781
	int changed = 0, eof = 0;
782

783
	for_each_string_list_item(item, &del_list) {
784
		/* Ctrl-D should stop removing files */
785
		if (!eof) {
786
			qname = quote_path(item->string, NULL, &buf, 0);
787
			/* TRANSLATORS: Make sure to keep [y/N] as is */
788
			printf(_("Remove %s [y/N]? "), qname);
789
			if (git_read_line_interactively(&confirm) == EOF) {
790
				putchar('\n');
791
				eof = 1;
792
			}
793
		}
794
		if (!confirm.len || strncasecmp(confirm.buf, "yes", confirm.len)) {
795
			*item->string = '\0';
796
			changed++;
797
		}
798
	}
799

800
	if (changed)
801
		string_list_remove_empty_items(&del_list, 0);
802

803
	strbuf_release(&buf);
804
	strbuf_release(&confirm);
805
	return MENU_RETURN_NO_LOOP;
806
}
807

808
static int quit_cmd(void)
809
{
810
	string_list_clear(&del_list, 0);
811
	printf(_("Bye.\n"));
812
	return MENU_RETURN_NO_LOOP;
813
}
814

815
static int help_cmd(void)
816
{
817
	clean_print_color(CLEAN_COLOR_HELP);
818
	printf_ln(_(
819
		    "clean               - start cleaning\n"
820
		    "filter by pattern   - exclude items from deletion\n"
821
		    "select by numbers   - select items to be deleted by numbers\n"
822
		    "ask each            - confirm each deletion (like \"rm -i\")\n"
823
		    "quit                - stop cleaning\n"
824
		    "help                - this screen\n"
825
		    "?                   - help for prompt selection"
826
		   ));
827
	clean_print_color(CLEAN_COLOR_RESET);
828
	return 0;
829
}
830

831
static void interactive_main_loop(void)
832
{
833
	while (del_list.nr) {
834
		struct menu_opts menu_opts;
835
		struct menu_stuff menu_stuff;
836
		struct menu_item menus[] = {
837
			{'c', "clean",			0, clean_cmd},
838
			{'f', "filter by pattern",	0, filter_by_patterns_cmd},
839
			{'s', "select by numbers",	0, select_by_numbers_cmd},
840
			{'a', "ask each",		0, ask_each_cmd},
841
			{'q', "quit",			0, quit_cmd},
842
			{'h', "help",			0, help_cmd},
843
		};
844
		int *chosen;
845

846
		menu_opts.header = N_("*** Commands ***");
847
		menu_opts.prompt = N_("What now");
848
		menu_opts.flags = MENU_OPTS_SINGLETON;
849

850
		menu_stuff.type = MENU_STUFF_TYPE_MENU_ITEM;
851
		menu_stuff.stuff = menus;
852
		menu_stuff.nr = sizeof(menus) / sizeof(struct menu_item);
853

854
		clean_print_color(CLEAN_COLOR_HEADER);
855
		printf_ln(Q_("Would remove the following item:",
856
			     "Would remove the following items:",
857
			     del_list.nr));
858
		clean_print_color(CLEAN_COLOR_RESET);
859

860
		pretty_print_dels();
861

862
		chosen = list_and_choose(&menu_opts, &menu_stuff);
863

864
		if (*chosen != EOF) {
865
			int ret;
866
			ret = menus[*chosen].fn();
867
			if (ret != MENU_RETURN_NO_LOOP) {
868
				FREE_AND_NULL(chosen);
869
				if (!del_list.nr) {
870
					clean_print_color(CLEAN_COLOR_ERROR);
871
					printf_ln(_("No more files to clean, exiting."));
872
					clean_print_color(CLEAN_COLOR_RESET);
873
					break;
874
				}
875
				continue;
876
			}
877
		} else {
878
			quit_cmd();
879
		}
880

881
		FREE_AND_NULL(chosen);
882
		break;
883
	}
884
}
885

886
static void correct_untracked_entries(struct dir_struct *dir)
887
{
888
	int src, dst, ign;
889

890
	for (src = dst = ign = 0; src < dir->nr; src++) {
891
		/* skip paths in ignored[] that cannot be inside entries[src] */
892
		while (ign < dir->ignored_nr &&
893
		       0 <= cmp_dir_entry(&dir->entries[src], &dir->ignored[ign]))
894
			ign++;
895

896
		if (ign < dir->ignored_nr &&
897
		    check_dir_entry_contains(dir->entries[src], dir->ignored[ign])) {
898
			/* entries[src] contains an ignored path, so we drop it */
899
			free(dir->entries[src]);
900
		} else {
901
			struct dir_entry *ent = dir->entries[src++];
902

903
			/* entries[src] does not contain an ignored path, so we keep it */
904
			dir->entries[dst++] = ent;
905

906
			/* then discard paths in entries[] contained inside entries[src] */
907
			while (src < dir->nr &&
908
			       check_dir_entry_contains(ent, dir->entries[src]))
909
				free(dir->entries[src++]);
910

911
			/* compensate for the outer loop's loop control */
912
			src--;
913
		}
914
	}
915
	dir->nr = dst;
916
}
917

918
int cmd_clean(int argc, const char **argv, const char *prefix)
919
{
920
	int i, res;
921
	int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;
922
	int ignored_only = 0, force = 0, errors = 0, gone = 1;
923
	int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
924
	struct strbuf abs_path = STRBUF_INIT;
925
	struct dir_struct dir = DIR_INIT;
926
	struct pathspec pathspec;
927
	struct strbuf buf = STRBUF_INIT;
928
	struct string_list exclude_list = STRING_LIST_INIT_NODUP;
929
	struct pattern_list *pl;
930
	struct string_list_item *item;
931
	const char *qname;
932
	struct option options[] = {
933
		OPT__QUIET(&quiet, N_("do not print names of files removed")),
934
		OPT__DRY_RUN(&dry_run, N_("dry run")),
935
		OPT__FORCE(&force, N_("force"), PARSE_OPT_NOCOMPLETE),
936
		OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")),
937
		OPT_BOOL('d', NULL, &remove_directories,
938
				N_("remove whole directories")),
939
		OPT_CALLBACK_F('e', "exclude", &exclude_list, N_("pattern"),
940
		  N_("add <pattern> to ignore rules"), PARSE_OPT_NONEG, exclude_cb),
941
		OPT_BOOL('x', NULL, &ignored, N_("remove ignored files, too")),
942
		OPT_BOOL('X', NULL, &ignored_only,
943
				N_("remove only ignored files")),
944
		OPT_END()
945
	};
946

947
	git_config(git_clean_config, NULL);
948

949
	argc = parse_options(argc, argv, prefix, options, builtin_clean_usage,
950
			     0);
951

952
	if (require_force != 0 && !force && !interactive && !dry_run)
953
		die(_("clean.requireForce is true and -f not given: refusing to clean"));
954

955
	if (force > 1)
956
		rm_flags = 0;
957
	else
958
		dir.flags |= DIR_SKIP_NESTED_GIT;
959

960
	dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
961

962
	if (ignored && ignored_only)
963
		die(_("options '%s' and '%s' cannot be used together"), "-x", "-X");
964
	if (!ignored)
965
		setup_standard_excludes(&dir);
966
	if (ignored_only)
967
		dir.flags |= DIR_SHOW_IGNORED;
968

969
	if (argc) {
970
		/*
971
		 * Remaining args implies pathspecs specified, and we should
972
		 * recurse within those.
973
		 */
974
		remove_directories = 1;
975
	}
976

977
	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
		dir.flags |= DIR_SHOW_IGNORED_TOO;
994
		if (!ignored)
995
			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
		dir.flags |= DIR_KEEP_UNTRACKED_CONTENTS;
1007
	}
1008

1009
	prepare_repo_settings(the_repository);
1010
	the_repository->settings.command_requires_full_index = 0;
1011

1012
	if (repo_read_index(the_repository) < 0)
1013
		die(_("index file corrupt"));
1014

1015
	pl = add_pattern_list(&dir, EXC_CMDL, "--exclude option");
1016
	for (i = 0; i < exclude_list.nr; i++)
1017
		add_pattern(exclude_list.items[i].string, "", 0, pl, -(i+1));
1018

1019
	parse_pathspec(&pathspec, 0,
1020
		       PATHSPEC_PREFER_CWD,
1021
		       prefix, argv);
1022

1023
	fill_directory(&dir, the_repository->index, &pathspec);
1024
	correct_untracked_entries(&dir);
1025

1026
	for (i = 0; i < dir.nr; i++) {
1027
		struct dir_entry *ent = dir.entries[i];
1028
		struct stat st;
1029
		const char *rel;
1030

1031
		if (!index_name_is_other(the_repository->index, ent->name, ent->len))
1032
			continue;
1033

1034
		if (lstat(ent->name, &st))
1035
			die_errno("Cannot lstat '%s'", ent->name);
1036

1037
		if (S_ISDIR(st.st_mode) && !remove_directories)
1038
			continue;
1039

1040
		rel = relative_path(ent->name, prefix, &buf);
1041
		string_list_append(&del_list, rel);
1042
	}
1043

1044
	dir_clear(&dir);
1045

1046
	if (interactive && del_list.nr > 0)
1047
		interactive_main_loop();
1048

1049
	for_each_string_list_item(item, &del_list) {
1050
		struct stat st;
1051

1052
		strbuf_reset(&abs_path);
1053
		if (prefix)
1054
			strbuf_addstr(&abs_path, prefix);
1055

1056
		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
		if (lstat(abs_path.buf, &st))
1064
			continue;
1065

1066
		if (S_ISDIR(st.st_mode)) {
1067
			if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone))
1068
				errors++;
1069
			if (gone && !quiet) {
1070
				qname = quote_path(item->string, NULL, &buf, 0);
1071
				printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
1072
			}
1073
		} else {
1074
			res = dry_run ? 0 : unlink(abs_path.buf);
1075
			if (res) {
1076
				int saved_errno = errno;
1077
				qname = quote_path(item->string, NULL, &buf, 0);
1078
				errno = saved_errno;
1079
				warning_errno(_(msg_warn_remove_failed), qname);
1080
				errors++;
1081
			} else if (!quiet) {
1082
				qname = quote_path(item->string, NULL, &buf, 0);
1083
				printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
1084
			}
1085
		}
1086
	}
1087

1088
	strbuf_release(&abs_path);
1089
	strbuf_release(&buf);
1090
	string_list_clear(&del_list, 0);
1091
	string_list_clear(&exclude_list, 0);
1092
	clear_pathspec(&pathspec);
1093
	return (errors != 0);
1094
}
1095

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.