From: Junio C Hamano <hidden> Date: 2016-06-15 22:57:10
Jiang Xin [off-list ref] writes:
Show what would be done and the user must confirm before actually
cleaning. In the confirmation dialog, the user has three choices:
* y/yes: Start to do cleaning.
* n/no: Nothing will be deleted.
* e/edit: Exclude items from deletion using ignore patterns.
When the user chooses the edit mode, the user can input space-
separated patterns (the same syntax as gitignore), and each clean
candidate that matches with one of the patterns will be excluded
from cleaning. When the user feels it's OK, presses ENTER and back
to the confirmation dialog.
Signed-off-by: Jiang Xin <redacted>
Suggested-by: Junio C Hamano <redacted>
Spelling-checked-by: Eric Sunshine [off-list ref]
Comments-by: Matthieu Moy [off-list ref]
Suggested-by: Eric Sunshine <redacted>
Listing everybody who has ever said anything in the review thread?
I can understand that you may want to give credit to those who
significantly helped, but please do not overdo it.
In any case, with the help of their inputs, you brought the patch
into its final shape. Please sign-off at the _end_.
@@ -34,7 +34,18 @@ OPTIONS -f:: --force:: If the Git configuration variable clean.requireForce is not set- to false, 'git clean' will refuse to run unless given -f or -n.+ to false, 'git clean' will refuse to run unless given -f, -n or+ -i.++-i::+--interactive::+ Show what would be done and the user must confirm before actually+ cleaning. In the confirmation dialog, the user can choose to abort+ the cleaning, or enter into an edit mode. In the edit mode, the+ user can input space-separated patterns (the same syntax as+ gitignore), and each clean candidate that matches with one of the+ patterns will be excluded from cleaning. When the user feels it's+ OK, presses ENTER and back to the confirmation dialog. -n:: --dry-run::
@@ -142,6 +145,139 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, return ret; }+static void edit_by_patterns_cmd()
static void edit_by_patterns_cmd(void)
+{
+ struct dir_struct dir;
+ struct strbuf confirm = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf **ignore_list;
+ struct string_list_item *item;
+ struct exclude_list *el;
+ const char *qname;
+ int changed = -1, i;
+
+ while (1) {
+ /* dels list may become empty when we run string_list_remove_empty_items later */
An unnecessary and overlong comment. The message shown already
tells the reader what is going on anyway, no?
+ if (!del_list.nr) {
+ printf_ln(_("No more files to clean, exiting."));
+ break;
+ }
+
+ if (changed) {
+ putchar('\n');
+
+ /* Display dels in "Would remove ..." format */
+ for_each_string_list_item(item, &del_list) {
+ qname = quote_path_relative(item->string, -1, &buf, *the_prefix);
+ printf(_(msg_would_remove), qname);
+ }
+ putchar('\n');
+ }
+
+ printf(_("Input ignore patterns>> "));
+ if (strbuf_getline(&confirm, stdin, '\n') != EOF) {
+ strbuf_trim(&confirm);
+ } else {
+ putchar('\n');
+ break;
Why break here? If we got nothing, wouldn't confirm.len be zero?
If we did get something but the input got flushed without line-end,
sending '\n' to the terminal may be justified, but in that case you
would may have something useful, and asking confirm.len if it is
empty would be the consistent way to check between two cases, no?
A few points:
* Pass prefix as a parameter to this function, just like how
remove_dirs() gets called, and get rid of the_prefix.
* The result of quote_* is designed to avoid ambiguities, by
applying C-style quotes like HT => \t and adding "" pair around
it as necessary. I doubt feeding it to is_excluded() makes any
sense. You probably meant path_relative(), but I am not sure.
quoted hunk
+ }
+
+ if (changed) {
+ string_list_remove_empty_items(&del_list, 0);
+ } else {
+ printf_ln(_("WARNING: Cannot find items matched by: %s"), confirm.buf);
+ }
+
+ strbuf_list_free(ignore_list);
+ clear_directory(&dir);
+ }
+
+ strbuf_release(&buf);
+ strbuf_release(&confirm);
+}
+
+static void interactive_main_loop()
+{
+ struct strbuf confirm = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT;
+ struct string_list_item *item;
+ const char *qname;
+
+ /* dels list may become empty after return back from edit mode */
+ while (del_list.nr) {
+ printf_ln(Q_("Would remove the following item:",
+ "Would remove the following items:",
+ del_list.nr));
+ putchar('\n');
+
+ /* Display dels in "Would remove ..." format */
+ for_each_string_list_item(item, &del_list) {
+ qname = quote_path_relative(item->string, -1, &buf, *the_prefix);
+ printf(_(msg_would_remove), qname);
+ }
+ putchar('\n');
+
+ /* Confirmation dialog */
+ printf(_("Remove ([y]es/[n]o/[e]dit) ? "));
+ if (strbuf_getline(&confirm, stdin, '\n') != EOF) {
+ strbuf_trim(&confirm);
+ } else {
+ /* Ctrl-D is the same as "quit" */
+ string_list_clear(&del_list, 0);
+ putchar('\n');
+ printf_ln("Bye.");
+ break;
+ }
+
+ if (confirm.len) {
+ if (!strncasecmp(confirm.buf, "yes", confirm.len)) {
+ break;
+ } else if (!strncasecmp(confirm.buf, "no", confirm.len) ||
+ !strncasecmp(confirm.buf, "quit", confirm.len)) {
+ string_list_clear(&del_list, 0);
+ printf_ln("Bye.");
+ break;
+ } else if (!strncasecmp(confirm.buf, "edit", confirm.len)) {
+ edit_by_patterns_cmd();
+ } else {
+ continue;
+ }
+ }
+ }
+
+ strbuf_release(&buf);
+ strbuf_release(&confirm);
+}
+
int cmd_clean(int argc, const char **argv, const char *prefix)
{
int i, res;
None of these changes s/prefix/*the_prefix/ looks remotely
justifiable. Does anybody change *the_prefix after it is set and if
so how?
quoted hunk
memset(&dir, 0, sizeof(dir));
@@ -186,12 +326,16 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (ignored && ignored_only) die(_("-x and -X cannot be used together"));- if (!dry_run && !force) {+ if (interactive) {+ if (!isatty(0) || !isatty(1))+ die(_("interactive clean can not run without a valid tty; "+ "refusing to clean"));+ } else if (!dry_run && !force) { if (config_set)- die(_("clean.requireForce set to true and neither -n nor -f given; "+ die(_("clean.requireForce set to true and neither -i, -n nor -f given; " "refusing to clean")); else- die(_("clean.requireForce defaults to true and neither -n nor -f given; "+ die(_("clean.requireForce defaults to true and neither -i, -n nor -f given; " "refusing to clean")); }
@@ -210,7 +354,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) for (i = 0; i < exclude_list.nr; i++) add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1));- pathspec = get_pathspec(prefix, argv);+ pathspec = get_pathspec(*the_prefix, argv); fill_directory(&dir, pathspec);
@@ -257,26 +401,41 @@ int cmd_clean(int argc, const char **argv, const char *prefix) } if (S_ISDIR(st.st_mode)) {- strbuf_addstr(&directory, ent->name);- if (remove_directories || (matches == MATCHED_EXACTLY)) {- if (remove_dirs(&directory, prefix, rm_flags, dry_run, quiet, &gone))- errors++;- if (gone && !quiet) {- qname = quote_path_relative(directory.buf, directory.len, &buf, prefix);- printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);- }- }- strbuf_reset(&directory);+ if (remove_directories || (matches == MATCHED_EXACTLY))+ string_list_append(&del_list, ent->name); } else { if (pathspec && !matches) continue;- res = dry_run ? 0 : unlink(ent->name);+ string_list_append(&del_list, ent->name);+ }+ }++ if (interactive && !dry_run && del_list.nr > 0)+ interactive_main_loop();++ for_each_string_list_item(item, &del_list) {+ struct stat st;++ if (lstat(item->string, &st))+ continue;
Ignoring errors silently?
With the "interactive" stuff, can you get into a situation where you
originally propose to remove D and D/F but the user tells you to
remove D (editing D/F away), or vice versa?
I think this patch should be in at least two parts:
- Introduce the two-phase "collect in del_list, remove in a
separate loop at the end" restructuring.
- (optional, if you are feeling ambitious) Change the path that is
stored in del_list relative to the prefix, so that all functions
that operate on the string in the del_list do not have to do
*_relative() thing. Some functions may instead have to prepend
prefix but if they are minority compared to the users of
*_relative(), it may be an overall win from the readability's
point of view.
- Add the "interactively allow you to reduce the del_list" bit
between the two phases.
Why break here? If we got nothing, wouldn't confirm.len be zero?
If we did get something but the input got flushed without line-end,
sending '\n' to the terminal may be justified, but in that case you
would may have something useful, and asking confirm.len if it is
empty would be the consistent way to check between two cases, no?
Yes, this break is unnecessary, it left from pervious revision.
A few points:
* Pass prefix as a parameter to this function, just like how
remove_dirs() gets called, and get rid of the_prefix.
* The result of quote_* is designed to avoid ambiguities, by
applying C-style quotes like HT => \t and adding "" pair around
it as necessary. I doubt feeding it to is_excluded() makes any
sense. You probably meant path_relative(), but I am not sure.
Appreciated, that is what I need. I write a local version of path_relative,
a combination of path_relative (in quote.c) and relative_path (in path.c),
like this:
static const char *path_relative(const char *in, const char *prefix)
quoted
+ for_each_string_list_item(item, &del_list) {
+ struct stat st;
+
+ if (lstat(item->string, &st))
+ continue;
Ignoring errors silently?
With the "interactive" stuff, can you get into a situation where you
originally propose to remove D and D/F but the user tells you to
remove D (editing D/F away), or vice versa?
I can not find out such a case, that remove parent directory D,
while left file in it, such as D/F.
I think this patch should be in at least two parts:
- Introduce the two-phase "collect in del_list, remove in a
separate loop at the end" restructuring.
- (optional, if you are feeling ambitious) Change the path that is
stored in del_list relative to the prefix, so that all functions
that operate on the string in the del_list do not have to do
*_relative() thing. Some functions may instead have to prepend
prefix but if they are minority compared to the users of
*_relative(), it may be an overall win from the readability's
point of view.
- Add the "interactively allow you to reduce the del_list" bit
between the two phases.
Updates since v7 series:
* Eliminate global variable "**the_prefix".
* Save relative paths in del_list.
* Split 1/10 of v7 into 3 patches for readability.
* Change orders of patches, thanks to Eric.
* Update menu and hotkeys with the help of Eric.
Usage:
When the command enters the interactive mode, it shows the
files and directories to be cleaned, and goes into its
interactive command loop.
The command loop shows the list of subcommands available, and
gives a prompt "What now> ". In general, when the prompt ends
with a single '>', you can pick only one of the choices given
and type return, like this:
*** Commands ***
1: clean 2: filter by pattern 3: select by numbers
4. ask each 5. toggle flags: none 6. quit
7: help
What now> 2
You also could say `c` or `clean` above as long as the choice is unique.
The main command loop has 7 subcommands.
clean::
Start cleaning files and directories, and then quit.
filter by pattern::
This shows the files and directories to be deleted and issues an
"Input ignore patterns>>" prompt. You can input space-seperated
patterns to exclude files and directories from deletion.
E.g. "*.c *.h" will excludes files end with ".c" and ".h" from
deletion. When you are satisfied with the filtered result, press
ENTER (empty) back to the main menu.
select by numbers::
This shows the files and directories to be deleted and issues an
"Select items to delete>>" prompt. When the prompt ends with double
'>>' like this, you can make more than one selection, concatenated
with whitespace or comma. Also you can say ranges. E.g. "2-5 7,9"
to choose 2,3,4,5,7,9 from the list. If the second number in a
range is omitted, all remaining patches are taken. E.g. "7-" to
choose 7,8,9 from the list. You can say '*' to choose everything.
Also when you are satisfied with the filtered result, press ENTER
(empty) back to the main menu.
ask each::
This will start to clean, and you must confirm one by one in order
to delete items. Please note that this action is not as efficient
as the above two actions.
toggle flags::
This lets you change the flags for git-clean, such as -x/-X/-d/-ff,
and refresh the cleaning candidates list automatically.
quit::
This lets you quit without do cleaning.
help::
Show brief usage of interactive git-clean.
Jiang Xin (12):
git-clean refactor: hold cleaning items in del_list
git-clean: add support for -i/--interactive
git-clean: show items of del_list in columns
git-clean: add colors to interactive git-clean
git-clean: use a git-add-interactive compatible UI
git-clean: add filter by pattern interactive action
git-clean: add select by numbers interactive action
git-clean: add ask each interactive action
git-clean refactor: save some options in clean_flags
git-clean refactor: add wrapper scan_clean_candidates
git-clean: add toggle flags interactive action
git-clean: update document for interactive git-clean
Documentation/config.txt | 4 +
Documentation/git-clean.txt | 77 +++-
builtin/clean.c | 1019 +++++++++++++++++++++++++++++++++++++++----
3 files changed, 1023 insertions(+), 77 deletions(-)
--
1.8.3.rc1.341.g24a8a0f
Refactor git-clean operations into two phases:
* collect cleaning candidates in del_list,
* and remove them in a separate loop at the end.
We will introduce an interactive git-clean between the two phases.
The interactive git-clean will show what would be done and confirm
before do real cleaning.
Signed-off-by: Jiang Xin <redacted>
---
builtin/clean.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 85 insertions(+), 18 deletions(-)
Show what would be done and the user must confirm before actually
cleaning.
Would remove ...
Would remove ...
Would remove ...
Remove (y/n) ?
Press "y" to start cleaning, and press "n" if you want to abort.
Signed-off-by: Jiang Xin <redacted>
---
Documentation/git-clean.txt | 10 ++++++--
builtin/clean.c | 61 +++++++++++++++++++++++++++++++++++++++++----
2 files changed, 64 insertions(+), 7 deletions(-)
@@ -34,7 +34,13 @@ OPTIONS -f:: --force:: If the Git configuration variable clean.requireForce is not set- to false, 'git clean' will refuse to run unless given -f or -n.+ to false, 'git clean' will refuse to run unless given -f, -n or+ -i.++-i::+--interactive::+ Show what would be done and the user must confirm before actually+ cleaning. -n:: --dry-run::
@@ -185,6 +186,50 @@ static const char *path_relative(const char *in, const char *prefix)returnbuf;}+staticvoidinteractive_main_loop(void)+{+structstrbufconfirm=STRBUF_INIT;+structstrbufbuf=STRBUF_INIT;+structstring_list_item*item;+constchar*qname;++while(del_list.nr){+putchar('\n');+for_each_string_list_item(item,&del_list){+qname=quote_path_relative(item->string,-1,&buf,NULL);+printf(_(msg_would_remove),qname);+}+putchar('\n');++printf(_("Remove (y/n) ? "));+if(strbuf_getline(&confirm,stdin,'\n')!=EOF){+strbuf_trim(&confirm);+}else{+/* Ctrl-D is the same as "quit" */+string_list_clear(&del_list,0);+putchar('\n');+printf_ln("Bye.");+break;+}++if(confirm.len){+if(!strncasecmp(confirm.buf,"yes",confirm.len)){+break;+}elseif(!strncasecmp(confirm.buf,"no",confirm.len)||+!strncasecmp(confirm.buf,"quit",confirm.len)){+string_list_clear(&del_list,0);+printf_ln("Bye.");+break;+}else{+continue;+}+}+}++strbuf_release(&buf);+strbuf_release(&confirm);+}+intcmd_clean(intargc,constchar**argv,constchar*prefix){inti,res;
@@ -204,6 +249,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)OPT__QUIET(&quiet,N_("do not print names of files removed")),OPT__DRY_RUN(&dry_run,N_("dry run")),OPT__FORCE(&force,N_("force")),+OPT_BOOL('i',"interactive",&interactive,N_("interactive cleaning")),OPT_BOOLEAN('d',NULL,&remove_directories,N_("remove whole directories")),{OPTION_CALLBACK,'e',"exclude",&exclude_list,N_("pattern"),
@@ -230,12 +276,16 @@ int cmd_clean(int argc, const char **argv, const char *prefix)if(ignored&&ignored_only)die(_("-x and -X cannot be used together"));-if(!dry_run&&!force){+if(interactive){+if(!isatty(0)||!isatty(1))+die(_("interactive clean can not run without a valid tty; "+"refusing to clean"));+}elseif(!dry_run&&!force){if(config_set)-die(_("clean.requireForce set to true and neither -n nor -f given; "+die(_("clean.requireForce set to true and neither -i, -n nor -f given; ""refusing to clean"));else-die(_("clean.requireForce defaults to true and neither -n nor -f given; "+die(_("clean.requireForce defaults to true and neither -i, -n nor -f given; ""refusing to clean"));}
@@ -309,7 +359,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)}}-/* TODO: do interactive git-clean here, which will modify del_list */+if(interactive&&!dry_run&&del_list.nr>0)+interactive_main_loop();for_each_string_list_item(item,&del_list){structstatst;
When there are lots of items to be cleaned, it is hard to see them all
in one screen. Show them in columns instead of in one column will solve
this problem.
Signed-off-by: Jiang Xin <redacted>
Comments-by: Matthieu Moy [off-list ref]
---
Documentation/config.txt | 4 ++++
builtin/clean.c | 49 +++++++++++++++++++++++++++++++++++++++---------
2 files changed, 44 insertions(+), 9 deletions(-)
@@ -955,6 +955,10 @@ column.branch:: Specify whether to output branch listing in `git branch` in columns. See `column.ui` for details.+column.clean::+ Specify whether to output cleaning files in `git clean -i` in columns.+ See `column.ui` for details.+ column.status:: Specify whether to output untracked files in `git status` in columns. See `column.ui` for details.
Show header, help, error messages, and prompt in colors for interactive
git-clean. Re-use config variables for other git commands, such as
git-add--interactive and git-stash:
* color.interactive: When set to always, always use colors for
interactive prompts and displays. When false (or never),
never. When set to true or auto, use colors only when the
output is to the terminal.
* color.interactive.<slot>: Use customized color for interactive
git-clean output (like git add --interactive). <slot> may be
prompt, header, help or error.
Signed-off-by: Jiang Xin <redacted>
Comments-by: Matthieu Moy [off-list ref]
---
builtin/clean.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 71 insertions(+), 1 deletion(-)
@@ -31,16 +32,81 @@ static const char *msg_skip_git_dir = N_("Skipping repository %s\n");staticconstchar*msg_would_skip_git_dir=N_("Would skip repository %s\n");staticconstchar*msg_warn_remove_failed=N_("failed to remove %s");+staticintclean_use_color=-1;+staticcharclean_colors[][COLOR_MAXLEN]={+GIT_COLOR_RESET,+GIT_COLOR_NORMAL,/* PLAIN */+GIT_COLOR_BOLD_BLUE,/* PROMPT */+GIT_COLOR_BOLD,/* HEADER */+GIT_COLOR_BOLD_RED,/* HELP */+GIT_COLOR_BOLD_RED,/* ERROR */+};+enumcolor_clean{+CLEAN_COLOR_RESET=0,+CLEAN_COLOR_PLAIN=1,+CLEAN_COLOR_PROMPT=2,+CLEAN_COLOR_HEADER=3,+CLEAN_COLOR_HELP=4,+CLEAN_COLOR_ERROR=5,+};++staticintparse_clean_color_slot(constchar*var,intofs)+{+if(!strcasecmp(var+ofs,"reset"))+returnCLEAN_COLOR_RESET;+if(!strcasecmp(var+ofs,"plain"))+returnCLEAN_COLOR_PLAIN;+if(!strcasecmp(var+ofs,"prompt"))+returnCLEAN_COLOR_PROMPT;+if(!strcasecmp(var+ofs,"header"))+returnCLEAN_COLOR_HEADER;+if(!strcasecmp(var+ofs,"help"))+returnCLEAN_COLOR_HELP;+if(!strcasecmp(var+ofs,"error"))+returnCLEAN_COLOR_ERROR;+return-1;+}+staticintgit_clean_config(constchar*var,constchar*value,void*cb){if(!prefixcmp(var,"column."))returngit_column_config(var,value,"clean",&colopts);+/* honors the color.interactive* config variables which also+appliedingit-add--interactiveandgit-stash*/+if(!strcmp(var,"color.interactive")){+clean_use_color=git_config_colorbool(var,value);+return0;+}+if(!prefixcmp(var,"color.interactive.")){+intslot=parse_clean_color_slot(var,18);+if(slot<0)+return0;+if(!value)+returnconfig_error_nonbool(var);+color_parse(value,var,clean_colors[slot]);+return0;+}+if(!strcmp(var,"clean.requireforce")){force=!git_config_bool(var,value);return0;}-returngit_default_config(var,value,cb);++/* inspect the color.ui config variable and others */+returngit_color_default_config(var,value,cb);+}++staticconstchar*clean_get_color(enumcolor_cleanix)+{+if(want_color(clean_use_color))+returnclean_colors[ix];+return"";+}++staticvoidclean_print_color(enumcolor_cleanix)+{+printf("%s",clean_get_color(ix));}staticintexclude_cb(conststructoption*opt,constchar*arg,intunset)
@@ -226,14 +292,18 @@ static void interactive_main_loop(void)while(del_list.nr){putchar('\n');+clean_print_color(CLEAN_COLOR_HEADER);printf_ln(Q_("Would remove the following item:","Would remove the following items:",del_list.nr));+clean_print_color(CLEAN_COLOR_RESET);putchar('\n');pretty_print_dels();+clean_print_color(CLEAN_COLOR_PROMPT);printf(_("Remove (y/n) ? "));+clean_print_color(CLEAN_COLOR_RESET);if(strbuf_getline(&confirm,stdin,'\n')!=EOF){strbuf_trim(&confirm);}else{
Rewrite menu using a new method `list_and_choose`, which is borrowed
from `git-add--interactive.perl`. We will use this framework to add
new actions for interactive git-clean later.
Please NOTE:
* Method `list_and_choose` return an array of integers, and
* it is up to you to free the allocated memory of the array.
* The array ends with EOF.
* If user pressed CTRL-D (i.e. EOF), no selection returned.
Signed-off-by: Jiang Xin <redacted>
---
builtin/clean.c | 447 ++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 418 insertions(+), 29 deletions(-)
@@ -281,54 +311,413 @@ static void pretty_print_dels(void)copts.indent=" ";copts.padding=2;print_columns(&list,colopts,&copts);-putchar('\n');strbuf_release(&buf);string_list_clear(&list,0);}-staticvoidinteractive_main_loop(void)+staticvoidpretty_print_menus(structstring_list*menu_list)+{+unsignedintlocal_colopts=0;+structcolumn_optionscopts;++local_colopts=COL_ENABLED|COL_ROW;+memset(&copts,0,sizeof(copts));+copts.indent=" ";+copts.padding=2;+print_columns(menu_list,local_colopts,&copts);+}++staticvoidprompt_help_cmd(intsingleton)+{+clean_print_color(CLEAN_COLOR_HELP);+printf_ln(singleton?+_("Prompt help:\n"+"1 - select a numbered item\n"+"foo - select item based on unique prefix\n"+" - (empty) select nothing"):+_("Prompt help:\n"+"1 - select a single item\n"+"3-5 - select a range of items\n"+"2-3,6-9 - select multiple ranges\n"+"foo - select item based on unique prefix\n"+"-... - unselect specified items\n"+"* - choose all items\n"+" - (empty) finish selecting"));+clean_print_color(CLEAN_COLOR_RESET);+}++/*+*displaymenustuffwithnumberprefixandhotkeyhighlight+*/+staticvoidprint_highlight_menu_stuff(structmenu_stuff*stuff,int**chosen)+{+staticstructstring_listmenu_list=STRING_LIST_INIT_DUP;+structstrbufmenu=STRBUF_INIT;+inti;++/* highlight hotkey in menu */+if(MENU_STUFF_TYPE_MENU_ITEM==stuff->type){+structmenu_item*item;++item=(structmenu_item*)stuff->stuff;+for(i=0;i<stuff->nr;i++,item++){+char*p;+inthighlighted=0;++p=item->title;+if((*chosen)[i]<0)+(*chosen)[i]=item->selected?1:0;+strbuf_addf(&menu,"%s%2d: ",(*chosen)[i]?"*":" ",i+1);+for(;*p;p++){+if(!highlighted&&*p==item->hotkey){+strbuf_addstr(&menu,clean_get_color(CLEAN_COLOR_PROMPT));+strbuf_addch(&menu,*p);+strbuf_addstr(&menu,clean_get_color(CLEAN_COLOR_RESET));+highlighted=1;+}else{+strbuf_addch(&menu,*p);+}+}+string_list_append(&menu_list,menu.buf);+strbuf_reset(&menu);+}+}elseif(MENU_STUFF_TYPE_STRING_LIST==stuff->type){+structstring_list_item*item;+structstrbufbuf=STRBUF_INIT;+i=0;++for_each_string_list_item(item,(structstring_list*)stuff->stuff){+if((*chosen)[i]<0)+(*chosen)[i]=0;+strbuf_addf(&menu,"%s%2d: %s",(*chosen)[i]?"*":" ",++i,item->string);+string_list_append(&menu_list,menu.buf);+strbuf_reset(&menu);+}+strbuf_release(&buf);+}++pretty_print_menus(&menu_list);++strbuf_release(&menu);+string_list_clear(&menu_list,0);+}++/*+*Parseuserinput,andreturnchoice(s)formenu(menu_stuff).+*+*Input+*(forsinglechoice)+*1-selectanumbereditem+*foo-selectitembasedonmenutitle+*-(empty)selectnothing+*+*(formultiplechoice)+*1-selectasingleitem+*3-5-selectarangeofitems+*2-3,6-9-selectmultipleranges+*foo-selectitembasedonmenutitle+*-...-unselectspecifieditems+**-chooseallitems+*-(empty)finishselecting+*+*Theparseresultwillbesavedinarray**chosen,and+*returnnumberoftotalselections.+*/+staticintparse_choice(structmenu_stuff*menu_stuff,+intis_single,+structstrbufinput,+int**chosen)+{+structstrbuf**choice_list,**choice_p;+intnr=0;+inti;++if(is_single){+choice_list=strbuf_split_max(&input,'\n',0);+}else{+char*p=input.buf;+do{+if(*p==',')+*p=' ';+}while(*p++);+choice_list=strbuf_split_max(&input,' ',0);+}++for(choice_p=choice_list;*choice_p;choice_p++){+char*p;+intchoose=1;+intbottom=0,top=0;+intis_range,is_number;++strbuf_trim(*choice_p);+if(!(*choice_p)->len)+continue;++/* Input that begins with '-'; unchoose */+if(*(*choice_p)->buf=='-'){+choose=0;+strbuf_remove((*choice_p),0,1);+}++is_range=0;+is_number=1;+for(p=(*choice_p)->buf;*p;p++){+if('-'==*p){+if(!is_range){+is_range=1;+is_number=0;+}else{+is_number=0;+is_range=0;+break;+}+}elseif(!isdigit(*p)){+is_number=0;+is_range=0;+break;+}+}++if(is_number){+bottom=atoi((*choice_p)->buf);+top=bottom;+}elseif(is_range){+bottom=atoi((*choice_p)->buf);+if(!*(strchr((*choice_p)->buf,'-')+1)){+top=menu_stuff->nr-1;+}else{+top=atoi(strchr((*choice_p)->buf,'-')+1);+}+}elseif(!strcmp((*choice_p)->buf,"*")){+bottom=1;+top=menu_stuff->nr;+}else{+if(MENU_STUFF_TYPE_MENU_ITEM==menu_stuff->type){+structmenu_item*item;++item=(structmenu_item*)menu_stuff->stuff;+for(i=0;i<menu_stuff->nr;i++,item++){+if(((*choice_p)->len==1&&+*(*choice_p)->buf==item->hotkey)||+!strcasecmp((*choice_p)->buf,item->title)){+bottom=i+1;+top=bottom;+break;+}+}+}elseif(MENU_STUFF_TYPE_STRING_LIST==menu_stuff->type){+structstring_list_item*item;++item=((structstring_list*)menu_stuff->stuff)->items;+for(i=0;i<menu_stuff->nr;i++,item++){+if(!strcasecmp((*choice_p)->buf,item->string)){+bottom=i+1;+top=bottom;+break;+}+}+}+}++if(top<=0||bottom<=0||top>menu_stuff->nr||bottom>top||+(is_single&&bottom!=top)){+clean_print_color(CLEAN_COLOR_ERROR);+printf_ln(_("Huh (%s)?"),(*choice_p)->buf);+clean_print_color(CLEAN_COLOR_RESET);+continue;+}++/* A range can be specified like 5-7 or 5-. */+for(i=bottom;i<=top;i++)+(*chosen)[i-1]=choose;+}++strbuf_list_free(choice_list);++for(i=0;i<menu_stuff->nr;i++)+nr+=(*chosen)[i];+returnnr;+}++/*+*Implementagit-add-interactivecompatibleUI,whichisborrowed+*fromgit-add--interactive.perl.+*+*Returnvalue:+*+*-Returnanarrayofintegers+*-,anditisuptoyoutofreetheallocatedmemory.+*-ThearrayendswithEOF.+*-IfuserpressedCTRL-D(i.e.EOF),noselectionreturned.+*/+staticint*list_and_choose(structmenu_opts*opts,structmenu_stuff*stuff){-structstrbufconfirm=STRBUF_INIT;+structstrbufchoice=STRBUF_INIT;+int*chosen,*result;+intnr=0;+inteof=0;+inti;++chosen=xmalloc(sizeof(int)*stuff->nr);+/* set chosen as uninitialized */+for(i=0;i<stuff->nr;i++)+chosen[i]=-1;++for(;;){+if(opts->header){+printf_ln("%s%s%s",+clean_get_color(CLEAN_COLOR_HEADER),+opts->header,+clean_get_color(CLEAN_COLOR_RESET));+}++/* the chosen array can be initialized by menu_item.selected */+print_highlight_menu_stuff(stuff,&chosen);++if(opts->flag&MENU_OPTS_LIST_ONLY)+break;++if(opts->prompt){+printf("%s%s%s%s",+clean_get_color(CLEAN_COLOR_PROMPT),+opts->prompt,+opts->flag&MENU_OPTS_SINGLETON?"> ":">> ",+clean_get_color(CLEAN_COLOR_RESET));+}++if(strbuf_getline(&choice,stdin,'\n')!=EOF){+strbuf_trim(&choice);+}else{+eof=1;+break;+}+/* help for prompt */+if(!strcmp(choice.buf,"?")){+prompt_help_cmd(opts->flag&MENU_OPTS_SINGLETON);+continue;+}++/* for a multiple-choice menu, press ENTER (empty) will return back */+if(!(opts->flag&MENU_OPTS_SINGLETON)&&!choice.len)+break;++nr=parse_choice(stuff,+opts->flag&MENU_OPTS_SINGLETON,+choice,+&chosen);++if(opts->flag&MENU_OPTS_SINGLETON){+if(nr)+break;+}elseif(opts->flag&MENU_OPTS_IMMEDIATE){+break;+}+}++if(eof){+result=xmalloc(sizeof(int));+*result=EOF;+}else{+intj=0;++/* recalculate nr, for a multiple-choice menu with initial selections */+if(!nr){+for(i=0;i<stuff->nr;i++)+nr+=chosen[i];+}++result=xmalloc(sizeof(int)*(nr+1));+memset(result,0,sizeof(int)*(nr+1));+for(i=0;i<stuff->nr&&j<nr;i++){+if(chosen[i])+result[j++]=i;+}+result[j]=EOF;+}++free(chosen);+strbuf_release(&choice);+returnresult;+}++staticintclean_cmd(void)+{+returnMENU_RETURN_NO_LOOP;+}++staticintquit_cmd(void)+{+string_list_clear(&del_list,0);+printf_ln(_("Bye."));+returnMENU_RETURN_NO_LOOP;+}++staticinthelp_cmd(void)+{+clean_print_color(CLEAN_COLOR_HELP);+printf_ln(_(+"clean - start cleaning\n"+"quit - stop cleaning\n"+"help - this screen\n"+"? - help for prompt selection"+));+clean_print_color(CLEAN_COLOR_RESET);+return0;+}++staticvoidinteractive_main_loop(void)+{while(del_list.nr){-putchar('\n');+structmenu_optsmenu_opts;+structmenu_stuffmenu_stuff;+structmenu_itemmenus[]={+{'c',"clean",0,clean_cmd},+{'q',"quit",0,quit_cmd},+{'h',"help",0,help_cmd},+};+int*chosen;++menu_opts.header=_("*** Commands ***");+menu_opts.prompt="What now";+menu_opts.flag=MENU_OPTS_SINGLETON;++menu_stuff.type=MENU_STUFF_TYPE_MENU_ITEM;+menu_stuff.stuff=menus;+menu_stuff.nr=sizeof(menus)/sizeof(structmenu_item);+clean_print_color(CLEAN_COLOR_HEADER);printf_ln(Q_("Would remove the following item:","Would remove the following items:",del_list.nr));clean_print_color(CLEAN_COLOR_RESET);-putchar('\n');pretty_print_dels();-clean_print_color(CLEAN_COLOR_PROMPT);-printf(_("Remove (y/n) ? "));-clean_print_color(CLEAN_COLOR_RESET);-if(strbuf_getline(&confirm,stdin,'\n')!=EOF){-strbuf_trim(&confirm);-}else{-/* Ctrl-D is the same as "quit" */-string_list_clear(&del_list,0);-putchar('\n');-printf_ln("Bye.");-break;-}--if(confirm.len){-if(!strncasecmp(confirm.buf,"yes",confirm.len)){-break;-}elseif(!strncasecmp(confirm.buf,"no",confirm.len)||-!strncasecmp(confirm.buf,"quit",confirm.len)){-string_list_clear(&del_list,0);-printf_ln("Bye.");-break;-}else{+chosen=list_and_choose(&menu_opts,&menu_stuff);++if(*chosen!=EOF){+intret;+ret=menus[*chosen].fn();+if(ret!=MENU_RETURN_NO_LOOP){+free(chosen);+chosen=NULL;+if(!del_list.nr){+clean_print_color(CLEAN_COLOR_ERROR);+printf_ln(_("No more files to clean, exiting."));+clean_print_color(CLEAN_COLOR_RESET);+break;+}continue;}+}else{+quit_cmd();}-}-strbuf_release(&confirm);+free(chosen);+chosen=NULL;+break;+}}intcmd_clean(intargc,constchar**argv,constchar*prefix)
Add a new action for interactive git-clean: filter by pattern. When
the user chooses this action, user can input space-separated
patterns (the same syntax as gitignore), and each clean candidate
that matches with one of the patterns will be excluded from cleaning.
When the user feels it's OK, presses ENTER and back to the confirmation
dialog.
Signed-off-by: Jiang Xin <redacted>
Suggested-by: Junio C Hamano <redacted>
---
builtin/clean.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 68 insertions(+)
@@ -646,6 +646,72 @@ static int clean_cmd(void)returnMENU_RETURN_NO_LOOP;}+staticintfilter_by_patterns_cmd(void)+{+structdir_structdir;+structstrbufconfirm=STRBUF_INIT;+structstrbuf**ignore_list;+structstring_list_item*item;+structexclude_list*el;+intchanged=-1,i;++for(;;){+if(!del_list.nr)+break;++if(changed)+pretty_print_dels();++clean_print_color(CLEAN_COLOR_PROMPT);+printf(_("Input ignore patterns>> "));+clean_print_color(CLEAN_COLOR_RESET);+if(strbuf_getline(&confirm,stdin,'\n')!=EOF)+strbuf_trim(&confirm);+else+putchar('\n');++/* Quit filter_by_pattern mode if press ENTER or Ctrl-D */+if(!confirm.len)+break;++memset(&dir,0,sizeof(dir));+el=add_exclude_list(&dir,EXC_CMDL,"manual exclude");+ignore_list=strbuf_split_max(&confirm,' ',0);++for(i=0;ignore_list[i];i++){+strbuf_trim(ignore_list[i]);+if(!ignore_list[i]->len)+continue;++add_exclude(ignore_list[i]->buf,"",0,el,-(i+1));+}++changed=0;+for_each_string_list_item(item,&del_list){+intdtype=DT_UNKNOWN;++if(is_excluded(&dir,item->string,&dtype)){+*item->string='\0';+changed++;+}+}++if(changed){+string_list_remove_empty_items(&del_list,0);+}else{+clean_print_color(CLEAN_COLOR_ERROR);+printf_ln(_("WARNING: Cannot find items matched by: %s"),confirm.buf);+clean_print_color(CLEAN_COLOR_RESET);+}++strbuf_list_free(ignore_list);+clear_directory(&dir);+}++strbuf_release(&confirm);+return0;+}+staticintquit_cmd(void){string_list_clear(&del_list,0);
@@ -658,6 +724,7 @@ static int help_cmd(void)clean_print_color(CLEAN_COLOR_HELP);printf_ln(_("clean - start cleaning\n"+"filter by pattern - exclude items from deletion\n""quit - stop cleaning\n""help - this screen\n""? - help for prompt selection"
@@ -673,6 +740,7 @@ static void interactive_main_loop(void)structmenu_stuffmenu_stuff;structmenu_itemmenus[]={{'c',"clean",0,clean_cmd},+{'f',"filter by pattern",0,filter_by_patterns_cmd},{'q',"quit",0,quit_cmd},{'h',"help",0,help_cmd},};
Add a new action for interactive git-clean: ask each. It's just like
the "rm -i" command, that the user must confirm one by one for each
file or directory to be cleaned.
Signed-off-by: Jiang Xin <redacted>
---
builtin/clean.c | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
@@ -749,6 +749,40 @@ static int select_by_numbers_cmd(void)return0;}+staticintrm_i_cmd(void)+{+structstrbufconfirm=STRBUF_INIT;+structstrbufbuf=STRBUF_INIT;+structstring_list_item*item;+constchar*qname;+intchanged=0,eof=0;++for_each_string_list_item(item,&del_list){+/* Ctrl-D should stop removing files */+if(!eof){+qname=quote_path_relative(item->string,-1,&buf,NULL);+printf(_("remove %s ? "),qname);+if(strbuf_getline(&confirm,stdin,'\n')!=EOF){+strbuf_trim(&confirm);+}else{+putchar('\n');+eof=1;+}+}+if(!confirm.len||!strncasecmp(confirm.buf,"no",confirm.len)){+*item->string='\0';+changed++;+}+}++if(changed)+string_list_remove_empty_items(&del_list,0);++strbuf_release(&buf);+strbuf_release(&confirm);+returnMENU_RETURN_NO_LOOP;+}+staticintquit_cmd(void){string_list_clear(&del_list,0);
@@ -763,6 +797,7 @@ static int help_cmd(void)"clean - start cleaning\n""filter by pattern - exclude items from deletion\n""select by numbers - select items to be deleted by numbers\n"+"ask each - confirm each deletion (like \"rm -i\")\n""quit - stop cleaning\n""help - this screen\n""? - help for prompt selection"
@@ -780,6 +815,7 @@ static void interactive_main_loop(void){'c',"clean",0,clean_cmd},{'f',"filter by pattern",0,filter_by_patterns_cmd},{'s',"select by numbers",0,select_by_numbers_cmd},+{'a',"ask each",0,rm_i_cmd},{'q',"quit",0,quit_cmd},{'h',"help",0,help_cmd},};
Add new wrapper `scan_clean_candidates`, which determines the del_list
(i.e. the cleaning candidates). This function will be reused later in
the interactive git-clean, so we can change flags of git-clean and
refresh the del_list.
Signed-off-by: Jiang Xin <redacted>
---
builtin/clean.c | 169 +++++++++++++++++++++++++++++++-------------------------
1 file changed, 93 insertions(+), 76 deletions(-)
@@ -295,6 +295,96 @@ static const char *path_relative(const char *in, const char *prefix)returnbuf;}+staticvoidscan_clean_candidates(constchar**pathspec,+structstring_listexclude_list,+constchar*prefix)+{+structdir_structdir;+structexclude_list*el;+char*seen=NULL;+constchar**pathspec_p=pathspec;+constchar*rel;+intpathspec_nr=0;+inti;++while(pathspec_p&&*(pathspec_p++))+pathspec_nr++;++memset(&dir,0,sizeof(dir));+if(clean_flags&CLEAN_OPTS_IGNORED_ONLY)+dir.flags|=DIR_SHOW_IGNORED;++if(clean_flags&CLEAN_OPTS_IGNORED_ONLY&&+clean_flags&CLEAN_OPTS_SHOW_IGNORED)+die(_("-x and -X cannot be used together"));++dir.flags|=DIR_SHOW_OTHER_DIRECTORIES;++if(!(clean_flags&CLEAN_OPTS_SHOW_IGNORED))+setup_standard_excludes(&dir);++el=add_exclude_list(&dir,EXC_CMDL,"--exclude option");+for(i=0;i<exclude_list.nr;i++)+add_exclude(exclude_list.items[i].string,"",0,el,-(i+1));++fill_directory(&dir,pathspec);++if(pathspec)+seen=xmalloc(pathspec_nr>0?pathspec_nr:1);++string_list_clear(&del_list,0);++for(i=0;i<dir.nr;i++){+structdir_entry*ent=dir.entries[i];+intlen,pos;+intmatches=0;+structcache_entry*ce;+structstatst;++/*+*Removethe'/'attheendthatdirectory+*walkingaddsfordirectoryentries.+*/+len=ent->len;+if(len&&ent->name[len-1]=='/')+len--;+pos=cache_name_pos(ent->name,len);+if(0<=pos)+continue;/* exact match */+pos=-pos-1;+if(pos<active_nr){+ce=active_cache[pos];+if(ce_namelen(ce)==len&&+!memcmp(ce->name,ent->name,len))+continue;/* Yup, this one exists unmerged */+}++if(lstat(ent->name,&st))+continue;++if(pathspec){+memset(seen,0,pathspec_nr>0?pathspec_nr:1);+matches=match_pathspec(pathspec,ent->name,len,+0,seen);+}++if(S_ISDIR(st.st_mode)){+if((clean_flags&CLEAN_OPTS_REMOVE_DIRECTORIES)||+(matches==MATCHED_EXACTLY)){+rel=path_relative(ent->name,prefix);+string_list_append(&del_list,rel);+}+}else{+if(pathspec&&!matches)+continue;+rel=path_relative(ent->name,prefix);+string_list_append(&del_list,rel);+}+}++free(seen);+}+staticvoidpretty_print_dels(void){structstring_listlist=STRING_LIST_INIT_DUP;
@@ -871,18 +961,15 @@ static void interactive_main_loop(void)intcmd_clean(intargc,constchar**argv,constchar*prefix){-inti,res;+intres;intdry_run=0,remove_directories=0,quiet=0,ignored=0;intignored_only=0,config_set=0,errors=0,gone=1;structstrbufabs_path=STRBUF_INIT;-structdir_structdir;staticconstchar**pathspec;structstrbufbuf=STRBUF_INIT;-structstring_listexclude_list=STRING_LIST_INIT_NODUP;-structexclude_list*el;structstring_list_item*item;+structstring_listexclude_list=STRING_LIST_INIT_NODUP;constchar*qname;-char*seen=NULL;structoptionoptions[]={OPT__QUIET(&quiet,N_("do not print names of files removed")),OPT__DRY_RUN(&dry_run,N_("dry run")),
@@ -932,78 +1019,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)if(remove_directories)clean_flags|=CLEAN_OPTS_REMOVE_DIRECTORIES;-memset(&dir,0,sizeof(dir));-if(clean_flags&CLEAN_OPTS_IGNORED_ONLY)-dir.flags|=DIR_SHOW_IGNORED;--if(clean_flags&CLEAN_OPTS_IGNORED_ONLY&&-clean_flags&CLEAN_OPTS_SHOW_IGNORED)-die(_("-x and -X cannot be used together"));--dir.flags|=DIR_SHOW_OTHER_DIRECTORIES;--if(!(clean_flags&CLEAN_OPTS_SHOW_IGNORED))-setup_standard_excludes(&dir);--el=add_exclude_list(&dir,EXC_CMDL,"--exclude option");-for(i=0;i<exclude_list.nr;i++)-add_exclude(exclude_list.items[i].string,"",0,el,-(i+1));-pathspec=get_pathspec(prefix,argv);-fill_directory(&dir,pathspec);--if(pathspec)-seen=xmalloc(argc>0?argc:1);--for(i=0;i<dir.nr;i++){-structdir_entry*ent=dir.entries[i];-intlen,pos;-intmatches=0;-structcache_entry*ce;-structstatst;-constchar*rel;--/*-*Removethe'/'attheendthatdirectory-*walkingaddsfordirectoryentries.-*/-len=ent->len;-if(len&&ent->name[len-1]=='/')-len--;-pos=cache_name_pos(ent->name,len);-if(0<=pos)-continue;/* exact match */-pos=-pos-1;-if(pos<active_nr){-ce=active_cache[pos];-if(ce_namelen(ce)==len&&-!memcmp(ce->name,ent->name,len))-continue;/* Yup, this one exists unmerged */-}--if(lstat(ent->name,&st))-continue;--if(pathspec){-memset(seen,0,argc>0?argc:1);-matches=match_pathspec(pathspec,ent->name,len,-0,seen);-}--if(S_ISDIR(st.st_mode)){-if((clean_flags&CLEAN_OPTS_REMOVE_DIRECTORIES)||-(matches==MATCHED_EXACTLY)){-rel=path_relative(ent->name,prefix);-string_list_append(&del_list,rel);-}-}else{-if(pathspec&&!matches)-continue;-rel=path_relative(ent->name,prefix);-string_list_append(&del_list,rel);-}-}+scan_clean_candidates(pathspec,exclude_list,prefix);if(interactive&&!dry_run&&del_list.nr>0)interactive_main_loop();
Draw a multiple choice menu using `list_and_choose` to select items
to be deleted by numbers.
User can input:
* 1,5-7 : select 1,5,6,7 items to be deleted
* * : select all items to be deleted
* -* : unselect all, nothing will be deleted
* : (empty) finish selecting, and return back to main menu
Signed-off-by: Jiang Xin <redacted>
---
builtin/clean.c | 39 +++++++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
@@ -712,6 +712,43 @@ static int filter_by_patterns_cmd(void)return0;}+staticintselect_by_numbers_cmd(void)+{+structmenu_optsmenu_opts;+structmenu_stuffmenu_stuff;+structstring_list_item*items;+int*chosen;+inti,j;++menu_opts.header=NULL;+menu_opts.prompt="Select items to delete";+menu_opts.flag=0;++menu_stuff.type=MENU_STUFF_TYPE_STRING_LIST;+menu_stuff.stuff=&del_list;+menu_stuff.nr=del_list.nr;++chosen=list_and_choose(&menu_opts,&menu_stuff);+items=del_list.items;+for(i=0,j=0;i<del_list.nr;i++){+if(i<chosen[j]){+*(items[i].string)='\0';+}elseif(i==chosen[j]){+/* delete selected item */+j++;+continue;+}else{+/* end of chosen (EOF), won't delete */+*(items[i].string)='\0';+}+}++string_list_remove_empty_items(&del_list,0);++free(chosen);+return0;+}+staticintquit_cmd(void){string_list_clear(&del_list,0);
@@ -725,6 +762,7 @@ static int help_cmd(void)printf_ln(_("clean - start cleaning\n""filter by pattern - exclude items from deletion\n"+"select by numbers - select items to be deleted by numbers\n""quit - stop cleaning\n""help - this screen\n""? - help for prompt selection"
@@ -741,6 +779,7 @@ static void interactive_main_loop(void)structmenu_itemmenus[]={{'c',"clean",0,clean_cmd},{'f',"filter by pattern",0,filter_by_patterns_cmd},+{'s',"select by numbers",0,select_by_numbers_cmd},{'q',"quit",0,quit_cmd},{'h',"help",0,help_cmd},};
Save some options in variable clean_flags, such as -ff (force > 1),
-x (ignored), -X (ignored_only), and -d (remove_directories). We may
change clean_flags later in the interactive git-clean.
Signed-off-by: Jiang Xin <redacted>
---
builtin/clean.c | 46 +++++++++++++++++++++++++++++++---------------
1 file changed, 31 insertions(+), 15 deletions(-)
@@ -902,13 +907,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)argc=parse_options(argc,argv,prefix,options,builtin_clean_usage,0);-memset(&dir,0,sizeof(dir));-if(ignored_only)-dir.flags|=DIR_SHOW_IGNORED;--if(ignored&&ignored_only)-die(_("-x and -X cannot be used together"));-if(interactive){if(!isatty(0)||!isatty(1))die(_("interactive clean can not run without a valid tty; "
@@ -922,15 +920,29 @@ int cmd_clean(int argc, const char **argv, const char *prefix)"refusing to clean"));}+if(read_cache()<0)+die(_("index file corrupt"));+if(force>1)-rm_flags=0;+clean_flags|=CLEAN_OPTS_REMOVE_NESTED_GIT;+if(ignored)+clean_flags|=CLEAN_OPTS_SHOW_IGNORED;+if(ignored_only)+clean_flags|=CLEAN_OPTS_IGNORED_ONLY;+if(remove_directories)+clean_flags|=CLEAN_OPTS_REMOVE_DIRECTORIES;-dir.flags|=DIR_SHOW_OTHER_DIRECTORIES;+memset(&dir,0,sizeof(dir));+if(clean_flags&CLEAN_OPTS_IGNORED_ONLY)+dir.flags|=DIR_SHOW_IGNORED;-if(read_cache()<0)-die(_("index file corrupt"));+if(clean_flags&CLEAN_OPTS_IGNORED_ONLY&&+clean_flags&CLEAN_OPTS_SHOW_IGNORED)+die(_("-x and -X cannot be used together"));++dir.flags|=DIR_SHOW_OTHER_DIRECTORIES;-if(!ignored)+if(!(clean_flags&CLEAN_OPTS_SHOW_IGNORED))setup_standard_excludes(&dir);el=add_exclude_list(&dir,EXC_CMDL,"--exclude option");
Add new action in the interactive mode, so that the user can change
git-clean flags, such as -x/-X/-d/-ff, and refresh the cleaning
candidates list.
Signed-off-by: Jiang Xin <redacted>
---
builtin/clean.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 101 insertions(+), 16 deletions(-)
@@ -879,6 +879,66 @@ static int rm_i_cmd(void)returnMENU_RETURN_NO_LOOP;}+staticinttoggle_flags_cmd(void)+{+structmenu_optsmenu_opts;+structmenu_stuffmenu_stuff;+structmenu_itemmenus[]={+{'d',"(d) remove directories",+clean_flags&CLEAN_OPTS_REMOVE_DIRECTORIES,NULL},+{'x',"(x) show ignored",+clean_flags&CLEAN_OPTS_SHOW_IGNORED,NULL},+{'X',"(X) ignored only",+clean_flags&CLEAN_OPTS_IGNORED_ONLY,NULL},+{'f',"(ff) remove nested.git",+clean_flags&CLEAN_OPTS_REMOVE_NESTED_GIT,NULL},+};+intnew_flags=0;+int*chosen;+inti;++menu_opts.header=NULL;+menu_opts.prompt="Change flags";+menu_opts.flag=0;++menu_stuff.type=MENU_STUFF_TYPE_MENU_ITEM;+menu_stuff.stuff=menus;+menu_stuff.nr=sizeof(menus)/sizeof(structmenu_item);++chosen=list_and_choose(&menu_opts,&menu_stuff);++for(i=0;chosen[i]!=EOF;i++){+switch(chosen[i]){+case0:+new_flags|=CLEAN_OPTS_REMOVE_DIRECTORIES;+break;+case1:+new_flags|=CLEAN_OPTS_SHOW_IGNORED;+break;+case2:+new_flags|=CLEAN_OPTS_IGNORED_ONLY;+break;+case3:+new_flags|=CLEAN_OPTS_REMOVE_NESTED_GIT;+break;+default:+break;+}+}++if(new_flags&CLEAN_OPTS_IGNORED_ONLY&&+new_flags&CLEAN_OPTS_SHOW_IGNORED){+clean_print_color(CLEAN_COLOR_ERROR);+printf_ln(_("-x and -X cannot be used together"));+clean_print_color(CLEAN_COLOR_RESET);+}else{+clean_flags=new_flags;+}++free(chosen);+return0;+}+staticintquit_cmd(void){string_list_clear(&del_list,0);
@@ -894,6 +954,7 @@ static int help_cmd(void)"filter by pattern - exclude items from deletion\n""select by numbers - select items to be deleted by numbers\n""ask each - confirm each deletion (like \"rm -i\")\n"+"toggle flags - toggle git-clean flags and update the list\n""quit - stop cleaning\n""help - this screen\n""? - help for prompt selection"
@@ -902,9 +963,14 @@ static int help_cmd(void)return0;}-staticvoidinteractive_main_loop(void)+staticvoidinteractive_main_loop(constchar**pathspec,+structstring_listexclude_list,+constchar*prefix){-while(del_list.nr){+intcached_clean_flags=clean_flags;+charflags_title[40];++for(;;){structmenu_optsmenu_opts;structmenu_stuffmenu_stuff;structmenu_itemmenus[]={
@@ -912,11 +978,24 @@ static void interactive_main_loop(void){'f',"filter by pattern",0,filter_by_patterns_cmd},{'s',"select by numbers",0,select_by_numbers_cmd},{'a',"ask each",0,rm_i_cmd},+{'t',flags_title,0,toggle_flags_cmd},{'q',"quit",0,quit_cmd},{'h',"help",0,help_cmd},};int*chosen;+if(!clean_flags){+strncpy(flags_title,"toggle flags: none",sizeof(flags_title)/sizeof(char));+}else{+snprintf(flags_title,sizeof(flags_title)/sizeof(char),+"toggle flags: -%s%s%s%s",+clean_flags&CLEAN_OPTS_REMOVE_DIRECTORIES?"d":"",+clean_flags&CLEAN_OPTS_SHOW_IGNORED?"x":"",+clean_flags&CLEAN_OPTS_IGNORED_ONLY?"X":"",+clean_flags&CLEAN_OPTS_REMOVE_NESTED_GIT?"ff":""+);+}+menu_opts.header=_("*** Commands ***");menu_opts.prompt="What now";menu_opts.flag=MENU_OPTS_SINGLETON;
@@ -925,13 +1004,25 @@ static void interactive_main_loop(void)menu_stuff.stuff=menus;menu_stuff.nr=sizeof(menus)/sizeof(structmenu_item);-clean_print_color(CLEAN_COLOR_HEADER);-printf_ln(Q_("Would remove the following item:",-"Would remove the following items:",-del_list.nr));-clean_print_color(CLEAN_COLOR_RESET);+if(cached_clean_flags!=clean_flags){+scan_clean_candidates(pathspec,exclude_list,prefix);+cached_clean_flags=clean_flags;+}-pretty_print_dels();+if(del_list.nr){+clean_print_color(CLEAN_COLOR_HEADER);+printf_ln(Q_("Would remove the following item:",+"Would remove the following items:",+del_list.nr));+clean_print_color(CLEAN_COLOR_RESET);++pretty_print_dels();+}else{+clean_print_color(CLEAN_COLOR_HEADER);+printf_ln(_("NOTE: no more files to clean; press \"t\" to toggle flags of git-clean."));+putchar('\n');+clean_print_color(CLEAN_COLOR_RESET);+}chosen=list_and_choose(&menu_opts,&menu_stuff);
@@ -941,12 +1032,6 @@ static void interactive_main_loop(void)if(ret!=MENU_RETURN_NO_LOOP){free(chosen);chosen=NULL;-if(!del_list.nr){-clean_print_color(CLEAN_COLOR_ERROR);-printf_ln(_("No more files to clean, exiting."));-clean_print_color(CLEAN_COLOR_RESET);-break;-}continue;}}else{
@@ -39,8 +39,8 @@ OPTIONS -i:: --interactive::- Show what would be done and the user must confirm before actually- cleaning.+ Show what would be done and clean files interactively. See+ ``Interactive mode'' for details. -n:: --dry-run::
@@ -69,6 +69,73 @@ OPTIONS Remove only files ignored by Git. This may be useful to rebuild everything from scratch, but keep manually created files.+Interactive mode+----------------+When the command enters the interactive mode, it shows the+files and directories to be cleaned, and goes into its+interactive command loop.++The command loop shows the list of subcommands available, and+gives a prompt "What now> ". In general, when the prompt ends+with a single '>', you can pick only one of the choices given+and type return, like this:++------------+ *** Commands ***+ 1: clean 2: filter by pattern 3: select by numbers+ 4. ask each 5. toggle flags: none 6. quit+ 7: help+ What now> 2+------------++You also could say `c` or `clean` above as long as the choice is unique.++The main command loop has 7 subcommands.++clean::++ Start cleaning files and directories, and then quit.++filter by pattern::++ This shows the files and directories to be deleted and issues an+ "Input ignore patterns>>" prompt. You can input space-seperated+ patterns to exclude files and directories from deletion.+ E.g. "*.c *.h" will excludes files end with ".c" and ".h" from+ deletion. When you are satisfied with the filtered result, press+ ENTER (empty) back to the main menu.++select by numbers::++ This shows the files and directories to be deleted and issues an+ "Select items to delete>>" prompt. When the prompt ends with double+ '>>' like this, you can make more than one selection, concatenated+ with whitespace or comma. Also you can say ranges. E.g. "2-5 7,9"+ to choose 2,3,4,5,7,9 from the list. If the second number in a+ range is omitted, all remaining patches are taken. E.g. "7-" to+ choose 7,8,9 from the list. You can say '*' to choose everything.+ Also when you are satisfied with the filtered result, press ENTER+ (empty) back to the main menu.++ask each::++ This will start to clean, and you must confirm one by one in order+ to delete items. Please note that this action is not as efficient+ as the above two actions.++toggle flags::++ This lets you change the flags for git-clean, such as -x/-X/-d/-ff,+ and refresh the cleaning candidates list automatically.++quit::++ This lets you quit without do cleaning.++help::++ Show brief usage of interactive git-clean.+ SEE ALSO -------- linkgit:gitignore[5]