The interactive git clean combines `git clean -n` and `git clean -f`
together to do safe cleaning, and has more features.
First it displays what would be removed in columns (so that you can
see them all in one screen). The user must confirm before actually
cleaning.
WARNING: The following items will be removed permanently. Press "y"
WARNING: to start cleaning, and press "n" to abort the cleaning.
WARNING: You can also enter the "edit" mode, and select items
WARNING: to be excluded from the cleaning.
What would be removed... What would be removed...
What would be removed... What would be removed...
Remove (yes/no/Edit) ?
In this confirmation dialog, the user has three choices:
* Yes: Start to do cleaning.
* No: Nothing will be deleted.
* Edit (default for the first time): Enter the edit mode.
When the user chooses the edit mode, it would look like this:
NOTE: Will remove the following items. You can input space-seperated
NOTE: patterns (just like .gitignore) to exclude items from deletion,
NOTE: or press ENTER to continue.
What would be removed... What would be removed...
What would be removed... What would be removed...
Input ignore patterns>
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.
WARNING: The following items will be removed permanently. Press "y"
WARNING: to start cleaning, and press "n" to abort the cleaning.
WARNING: You can also enter the "edit" mode, and select items
WARNING: to be excluded from the cleaning.
What would be removed...
Remove (Yes/no/edit) ?
This time the default choice of the confirmation dialog is "YES".
So when user press ENTER, start cleaning.
Jiang Xin (3):
Add support for -i/--interactive to git-clean
Show items of interactive git-clean in columns
Add colors to interactive git-clean
Documentation/git-clean.txt | 15 ++-
builtin/clean.c | 295 +++++++++++++++++++++++++++++++++++++++++---
2 files changed, 291 insertions(+), 19 deletions(-)
--
1.8.3.rc0.364.gc6aefbf
Show what would be done and the user must confirm before actually
cleaning. In the confirmation dialog, the user has three choices:
* Yes: Start to do cleaning.
* No: Nothing will be deleted.
* Edit (default for the first time): Enter the edit mode.
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-check-by: Eric Sunshine [off-list ref]
Comments-by: Matthieu Moy [off-list ref]
---
Documentation/git-clean.txt | 15 +++-
builtin/clean.c | 183 ++++++++++++++++++++++++++++++++++++++++----
2 files changed, 181 insertions(+), 17 deletions(-)
@@ -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 +143,138 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,returnret;}+voidinteractive_clean_edit(structstring_list*dels,constchar*prefix)+{+structdir_structdir;+structstrbufconfirm=STRBUF_INIT;+structstrbufbuf=STRBUF_INIT;+structstrbuf**ignore_list;+structstring_list_item*item;+structexclude_list*el;+constchar*qname;+intchanged=-1,i;++putchar('\n');+printf_ln(_(+"NOTE: Will remove the following items. You can input space-seperated\n"+"NOTE: patterns (just like .gitignore) to exclude items from deletion,\n"+"NOTE: or press ENTER to continue."+));++while(1){+/* dels list may become empty when we run string_list_remove_empty_items later */+if(!dels->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,dels){+qname=quote_path_relative(item->string,-1,&buf,prefix);+printf(_(msg_would_remove),qname);+}+putchar('\n');+}++printf(_("Input ignore patterns> "));+strbuf_getline(&confirm,stdin,'\n');+strbuf_trim(&confirm);++/* Quit edit mode */+if(!confirm.len)+break;++memset(&dir,0,sizeof(dir));+el=add_exclude_list(&dir,EXC_CMDL,"manual exclude");+ignore_list=strbuf_split_buf(confirm.buf,confirm.len,' ',0);++for(i=0;ignore_list[i];i++){+strbuf_trim(*ignore_list);+if(!(*ignore_list)->len)+continue;++add_exclude(ignore_list[i]->buf,"",0,el,-(i+1));+}++changed=0;+for_each_string_list_item(item,dels){+intdtype=DT_UNKNOWN;+constchar*qname;++qname=quote_path_relative(item->string,-1,&buf,prefix);++if(is_excluded(&dir,qname,&dtype)){+*item->string='\0';+changed++;+}+}++if(changed){+string_list_remove_empty_items(dels,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);+}++voidinteractive_clean(structstring_list*dels,constchar*prefix)+{+structstrbufconfirm=STRBUF_INIT;+structstrbufbuf=STRBUF_INIT;+structstring_list_item*item;+constchar*qname;+intcount=0;++/* dels list may become empty after return back from edit mode */+while(dels->nr){+/* Display dels in "Would remove ..." format */+putchar('\n');+for_each_string_list_item(item,dels){+qname=quote_path_relative(item->string,-1,&buf,prefix);+printf(_(msg_would_remove),qname);+}+putchar('\n');++/* Confirmation dialog */+printf(count>0?_("Remove (Yes/no/edit) ? "):_("Remove (yes/no/Edit) ? "));+strbuf_getline(&confirm,stdin,'\n');+strbuf_trim(&confirm);++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(dels,0);+break;+}elseif(!strncasecmp(confirm.buf,"edit",confirm.len)){+interactive_clean_edit(dels,prefix);+}else{+continue;+}+}elseif(count>0){+/* If back from edit_mode, confirmation dialog defaults to "yes" */+break;+}else{+/* For the first time, confirmation dialog defaults to "edit" */+interactive_clean_edit(dels,prefix);+}+count++;+}++strbuf_release(&buf);+strbuf_release(&confirm);+}+intcmd_clean(intargc,constchar**argv,constchar*prefix){inti,res;
@@ -154,12 +287,15 @@ int cmd_clean(int argc, const char **argv, const char *prefix)structstrbufbuf=STRBUF_INIT;structstring_listexclude_list=STRING_LIST_INIT_NODUP;structexclude_list*el;+structstring_listdels=STRING_LIST_INIT_DUP;+structstring_list_item*item;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")),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"),
@@ -186,12 +322,12 @@ 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(!dry_run&&!force&&!interactive){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"));}
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 | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 79 insertions(+), 1 deletion(-)
@@ -30,17 +31,82 @@ 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){/* honors the column.ui config variable only */if(!prefixcmp(var,"column."))returngit_column_config(var,value,NULL,&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)
@@ -189,16 +255,20 @@ void interactive_clean_edit(struct string_list *dels, const char *prefix)intchanged=-1,i;putchar('\n');+clean_print_color(CLEAN_COLOR_HELP);printf_ln(_("NOTE: Will remove the following items. You can input space-seperated\n""NOTE: patterns (just like .gitignore) to exclude items from deletion,\n""NOTE: or press ENTER to continue."));+clean_print_color(CLEAN_COLOR_RESET);while(1){/* dels list may become empty when we run string_list_remove_empty_items later */if(!dels->nr){+clean_print_color(CLEAN_COLOR_ERROR);printf_ln(_("No more files to clean, exiting."));+clean_print_color(CLEAN_COLOR_RESET);break;}
@@ -264,19 +338,23 @@ void interactive_clean(struct string_list *dels, const char *prefix)/* dels list may become empty after return back from edit mode */while(dels->nr){putchar('\n');+clean_print_color(CLEAN_COLOR_HEADER);printf_ln(_("WARNING: The following items will be removed permanently. Press \"y\"\n""WARNING: to start cleaning, and press \"n\" to abort the cleaning.\n""WARNING: You can also enter the \"edit\" mode, and select items\n""WARNING: to be excluded from the cleaning."));+clean_print_color(CLEAN_COLOR_RESET);putchar('\n');/* Display dels in columns */pretty_print_dels(dels,prefix);/* Confirmation dialog */+clean_print_color(CLEAN_COLOR_PROMPT);printf(count>0?_("Remove (Yes/no/edit) ? "):_("Remove (yes/no/Edit) ? "));+clean_print_color(CLEAN_COLOR_RESET);strbuf_getline(&confirm,stdin,'\n');strbuf_trim(&confirm);
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.
Since no longer show items to be cleaned using the "Would remove ..."
format (only plain filenames) in interactive mode, we add instructions
and warnings as header before them.
Signed-off-by: Jiang Xin <redacted>
Comments-by: Matthieu Moy [off-list ref]
---
builtin/clean.c | 64 ++++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 47 insertions(+), 17 deletions(-)
@@ -171,12 +205,8 @@ void interactive_clean_edit(struct string_list *dels, const char *prefix)if(changed){putchar('\n');-/* Display dels in "Would remove ..." format */-for_each_string_list_item(item,dels){-qname=quote_path_relative(item->string,-1,&buf,prefix);-printf(_(msg_would_remove),qname);-}-putchar('\n');+/* Display dels in columns */+pretty_print_dels(dels,prefix);}printf(_("Input ignore patterns> "));
@@ -229,21 +259,22 @@ void interactive_clean_edit(struct string_list *dels, const char *prefix)voidinteractive_clean(structstring_list*dels,constchar*prefix){structstrbufconfirm=STRBUF_INIT;-structstrbufbuf=STRBUF_INIT;-structstring_list_item*item;-constchar*qname;intcount=0;/* dels list may become empty after return back from edit mode */while(dels->nr){-/* Display dels in "Would remove ..." format */putchar('\n');-for_each_string_list_item(item,dels){-qname=quote_path_relative(item->string,-1,&buf,prefix);-printf(_(msg_would_remove),qname);-}+printf_ln(_(+"WARNING: The following items will be removed permanently. Press \"y\"\n"+"WARNING: to start cleaning, and press \"n\" to abort the cleaning.\n"+"WARNING: You can also enter the \"edit\" mode, and select items\n"+"WARNING: to be excluded from the cleaning."+));putchar('\n');+/* Display dels in columns */+pretty_print_dels(dels,prefix);+/* Confirmation dialog */printf(count>0?_("Remove (Yes/no/edit) ? "):_("Remove (yes/no/Edit) ? "));strbuf_getline(&confirm,stdin,'\n');
From: Eric Sunshine <hidden> Date: 2016-06-15 22:57:07
Usability observations below...
On Thu, May 2, 2013 at 11:49 PM, Jiang Xin [off-list ref] wrote:
The interactive git clean combines `git clean -n` and `git clean -f`
together to do safe cleaning, and has more features.
First it displays what would be removed in columns (so that you can
see them all in one screen). The user must confirm before actually
cleaning.
WARNING: The following items will be removed permanently. Press "y"
WARNING: to start cleaning, and press "n" to abort the cleaning.
WARNING: You can also enter the "edit" mode, and select items
WARNING: to be excluded from the cleaning.
The user intended for files to be removed when invoking git-clean,
therefore WARNING that git-clean will do what was requested explicitly
seems overkill. Along the same lines, the user asked explicitly for an
interactive session (via --interactive), hence the above paragraph is
effectively redundant since it does little more than tell the user (in
a lengthy fashion) what he already knows (that the session is
interactive). The short prompt printed after the listed files says the
same thing (more succinctly), thus this warning paragraph is
essentially superfluous.
What would be removed... What would be removed...
What would be removed... What would be removed...
Remove (yes/no/Edit) ?
For convenience, implementations traditionally allow single letter
responses (y/n/e), but this one does not. Should it?
In this confirmation dialog, the user has three choices:
* Yes: Start to do cleaning.
* No: Nothing will be deleted.
* Edit (default for the first time): Enter the edit mode.
What about the user who desires more traditional "rm -i" behavior in
which he is prompted for each file? Should that be supported with a
"Prompt [each]" option in the above menu?
When the user chooses the edit mode, it would look like this:
NOTE: Will remove the following items. You can input space-seperated
NOTE: patterns (just like .gitignore) to exclude items from deletion,
NOTE: or press ENTER to continue.
As earlier, this (lengthy) paragraph says little more than what could
be said in a more succinct prompt printed after the file list, thus is
probably superfluous.
What would be removed... What would be removed...
What would be removed... What would be removed...
Input ignore patterns>
The list of files to be removed was already shown directly above.
Dumping the entire list to the user's screen a second time upon
entering edit mode seems unnecessary. If you drop the WARNING and NOTE
paragraphs as suggested, then the session becomes much less verbose,
thus there is little reason to re-display the file list upon entering
edit mode. For instance:
% git clean -i
file1 file2 file3
file4 file5 file6
Remove (yes/no/edit)? e
Exclude (space-separated gitignore patterns): file[4-6]
file1 file2 file3
Exclude (space-separated gitignore patterns): [enter]
Remove (yes/no/edit)?
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.
WARNING: The following items will be removed permanently. Press "y"
WARNING: to start cleaning, and press "n" to abort the cleaning.
WARNING: You can also enter the "edit" mode, and select items
WARNING: to be excluded from the cleaning.
What would be removed...
Remove (Yes/no/edit) ?
This time the default choice of the confirmation dialog is "YES".
So when user press ENTER, start cleaning.
Is there precedent for this sort of self-mutating default action in
other utilities? Wouldn't this lead to high "surprise factor" for
users and potential for lost files? For instance:
% git clean -i
file1 file2 file3
file4 file5 file6
Remove (yes/no/Edit)? [enter]
{user presses ENTER for edit mode}
Exclude (space-separated gitignore patterns): file[3-6]
{user mistakenly types "3" rather than "4"}
file1 file2
Exclude (space-separated gitignore patterns): [enter]
Remove (Yes/no/edit)? [enter]
{user notices mistake and presses ENTER expecting edit mode}
Removing file3
Removing file4
Removing file5
Removing file6
Oh no! The user didn't notice the subtle change of default from
"yes/no/Edit" to "Yes/no/edit", thus he pressed ENTER thinking it
would take him to edit mode as it did initially, but instead git-clean
proceeded with the removals and file3 is lost.
Other considerations:
Is it necessary to force the user to escape from edit mode by pressing
ENTER (i.e. empty input)? Wouldn't you achieve the same level of
functionality by exiting back to the (yes/no/edit) prompt
automatically after the user enters his gitignore pattern(s)? For
instance:
% git clean -i
file1 file2 file3
file4 file5 file6
Remove (yes/no/edit)? e
Exclude (space-separated gitignore patterns): file[4-6]
file1 file2 file3
Remove (yes/no/edit)?
More generally, is this sort of modal edit mode desirable and
convenient? Can the edit operation be combined with the top-level
prompt? For example:
% git clean -i
file1 file2 file3
file4 file5 file6
Remove ([y]es, [n]o, [p]rompt, exclusion-list)? file[4-6]
file1 file2 file3
Remove ([y]es, [n]o, [p]rompt, exclusion-list)? p
file1 (y/n/q/!)? y
file2 (y/n/q/!)? n
file3 (y/n/q/!)? y
-- ES
WARNING: The following items will be removed permanently. Press "y"
WARNING: to start cleaning, and press "n" to abort the cleaning.
WARNING: You can also enter the "edit" mode, and select items
WARNING: to be excluded from the cleaning.
The user intended for files to be removed when invoking git-clean,
therefore WARNING that git-clean will do what was requested explicitly
seems overkill. Along the same lines, the user asked explicitly for an
interactive session (via --interactive), hence the above paragraph is
effectively redundant since it does little more than tell the user (in
a lengthy fashion) what he already knows (that the session is
interactive). The short prompt printed after the listed files says the
same thing (more succinctly), thus this warning paragraph is
essentially superfluous.
I will try to make the header short, and not too scary.
quoted
In this confirmation dialog, the user has three choices:
* Yes: Start to do cleaning.
* No: Nothing will be deleted.
* Edit (default for the first time): Enter the edit mode.
What about the user who desires more traditional "rm -i" behavior in
which he is prompted for each file? Should that be supported with a
"Prompt [each]" option in the above menu?
I'd like to have a try. Maybe I can borrow code/interface from
git-add--interactive.perl to support both (batch exclude and confirm
one by one). For example:
*** Would remove the following item(s) ***
files to be removed... files to be removed...
files to be removed... files to be removed...
files to be removed... files to be removed...
*** Commands ***
1. [y]es, clean 2. [n]o, quit 3. batch [e]xclude
4.[c]onfirm one by one
What now> e
input ignore patterns>> * ![a-c]*
files to be removed... files to be removed...
files to be removed... files to be removed...
Input ignore patterns>> ENTER
*** Would remove the following item(s) ***
files to be removed... files to be removed...
files to be removed... files to be removed...
*** Commands ***
1. [y]es, clean 2. [n]o, quit 3. batch [e]xclude
4.[c]onfirm one by one
What now> y
Removing ...
Removing ...
More generally, is this sort of modal edit mode desirable and
convenient? Can the edit operation be combined with the top-level
prompt? For example:
% git clean -i
file1 file2 file3
file4 file5 file6
Remove ([y]es, [n]o, [p]rompt, exclusion-list)? file[4-6]
file1 file2 file3
Remove ([y]es, [n]o, [p]rompt, exclusion-list)? p
file1 (y/n/q/!)? y
file2 (y/n/q/!)? n
file3 (y/n/q/!)? y
What If there is a file named 'y', and the user want to exclude it,
and press 'y' as a pattern.
--
Jiang Xin
From: Eric Sunshine <hidden> Date: 2016-06-15 22:57:08
On Fri, May 3, 2013 at 9:06 PM, Jiang Xin [off-list ref] wrote:
2013/5/3 Eric Sunshine [off-list ref]:
quoted
More generally, is this sort of modal edit mode desirable and
convenient? Can the edit operation be combined with the top-level
prompt? For example:
% git clean -i
file1 file2 file3
file4 file5 file6
Remove ([y]es, [n]o, [p]rompt, exclusion-list)? file[4-6]
file1 file2 file3
Remove ([y]es, [n]o, [p]rompt, exclusion-list)? p
file1 (y/n/q/!)? y
file2 (y/n/q/!)? n
file3 (y/n/q/!)? y
What If there is a file named 'y', and the user want to exclude it,
and press 'y' as a pattern.
The pattern [y] will match file named 'y'. It probably is unusual for
files named 'y', 'n', etc. to exist in the top-level directory, but
the gitignore patterns already provide an escape hatch for these
unusual cases. (That is not to say that this is the perfect example or
solution, but only that it may be worth considering such options when
designing the user-interface for convenience.)
Implement a 'git add --interactive' style of interactive git-clean.
It will show what would be done before start to clean. See
``Interactive mode`` for details.
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: edit by patterns 3: edit by numbers
4. rm -i 5. quit 6. help
What now> 2
------------
You also could say `c` or `clean` above as long as the choice is unique.
The main command loop has 6 subcommands.
clean::
Start cleaning files and directories, and then quit.
edit by patterns::
This shows the files and directories to be deleted and issues an
"Input ignore patterns>>" prompt. You can input a 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.
edit 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.
rm -i::
This will show a "rm -i" style cleaning, that you must confirm one
by one in order to delete items. This action is not as efficient
as the above two actions.
quit::
This lets you quit without do cleaning.
help::
Show brief usage of interactive git-clean.
Jiang Xin (7):
Add support for -i/--interactive to git-clean
Show items of interactive git-clean in columns
Add colors to interactive git-clean
git-clean: use a git-add-interactive compatible UI
git-clean: interactive cleaning by select numbers
git-clean: rm -i style interactive cleaning
git-clean: update document for interactive git-clean
Documentation/config.txt | 4 +
Documentation/git-clean.txt | 71 ++++-
builtin/clean.c | 700 ++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 752 insertions(+), 23 deletions(-)
--
1.8.3.rc1.338.gb35aa5d
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>
---
Documentation/git-clean.txt | 15 +++-
builtin/clean.c | 195 ++++++++++++++++++++++++++++++++++++++++----
2 files changed, 191 insertions(+), 19 deletions(-)
@@ -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,returnret;}+voidedit_by_patterns_cmd()+{+structdir_structdir;+structstrbufconfirm=STRBUF_INIT;+structstrbufbuf=STRBUF_INIT;+structstrbuf**ignore_list;+structstring_list_item*item;+structexclude_list*el;+constchar*qname;+intchanged=-1,i;++while(1){+/* dels list may become empty when we run string_list_remove_empty_items later */+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;+}++/* Quit edit mode */+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;+constchar*qname;++qname=quote_path_relative(item->string,-1,&buf,*the_prefix);++if(is_excluded(&dir,qname,&dtype)){+*item->string='\0';+changed++;+}+}++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);+}++voidinteractive_main_loop()+{+structstrbufconfirm=STRBUF_INIT;+structstrbufbuf=STRBUF_INIT;+structstring_list_item*item;+constchar*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;+}elseif(!strncasecmp(confirm.buf,"no",confirm.len)||+!strncasecmp(confirm.buf,"quit",confirm.len)){+string_list_clear(&del_list,0);+printf_ln("Bye.");+break;+}elseif(!strncasecmp(confirm.buf,"edit",confirm.len)){+edit_by_patterns_cmd();+}else{+continue;+}+}+}++strbuf_release(&buf);+strbuf_release(&confirm);+}+intcmd_clean(intargc,constchar**argv,constchar*prefix){inti,res;
@@ -154,12 +290,14 @@ int cmd_clean(int argc, const char **argv, const char *prefix)structstrbufbuf=STRBUF_INIT;structstring_listexclude_list=STRING_LIST_INIT_NODUP;structexclude_list*el;+structstring_list_item*item;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")),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"),
@@ -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"));+}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"));}
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.
Since no longer show items to be cleaned using the "Would remove ..."
format (only plain filenames) in interactive mode, we add instructions
and warnings as header before them.
Signed-off-by: Jiang Xin <redacted>
Comments-by: Matthieu Moy [off-list ref]
---
Documentation/config.txt | 4 ++++
builtin/clean.c | 58 +++++++++++++++++++++++++++++++++---------------
2 files changed, 44 insertions(+), 18 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.
@@ -166,12 +199,8 @@ void edit_by_patterns_cmd()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');+/* Display dels in columns */+pretty_print_dels();}printf(_("Input ignore patterns>> "));
@@ -228,23 +257,17 @@ void edit_by_patterns_cmd()voidinteractive_main_loop(){structstrbufconfirm=STRBUF_INIT;-structstrbufbuf=STRBUF_INIT;-structstring_list_item*item;-constchar*qname;/* dels list may become empty after return back from edit mode */while(del_list.nr){+putchar('\n');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');+/* Display dels in columns */+pretty_print_dels();/* Confirmation dialog */printf(_("Remove ([y]es/[n]o/[e]dit) ? "));
Rewrite menu using a new method `list_and_choose`, which is borrowed
from `git-add--interactive.perl`. We can reused this method later for
more actions.
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 | 410 ++++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 367 insertions(+), 43 deletions(-)
@@ -240,12 +269,284 @@ void pretty_print_dels()copts.indent=" ";copts.padding=2;print_columns(&list,colopts,&copts);-putchar('\n');strbuf_release(&buf);string_list_clear(&list,0);}-voidedit_by_patterns_cmd()+voidpretty_print_menus(structstring_list*menu_list)+{+structstrbufbuf=STRBUF_INIT;+unsignedintlocal_colopts=0;+structcolumn_optionscopts;++/*+*alwaysenablecolumndisplay,weonlyconsultcolumn.*+*aboutlayoutstrategyandstuff+*/+local_colopts=COL_ENABLED|COL_ROW;+memset(&copts,0,sizeof(copts));+copts.indent=" ";+copts.padding=2;+print_columns(menu_list,local_colopts,&copts);+strbuf_release(&buf);+}++voidprompt_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);+}++/*+*Implementagit-add-interactivecompatibleUI,whichisborrowed+*fromgit-add--interactive.perl.+*+*Returnvalue:+*+*-Returnanarrayofintegers+*-,anditisuptoyoutofreetheallocatedmemory.+*-ThearrayendswithEOF.+*-IfuserpressedCTRL-D(i.e.EOF),noselectionreturned.+*/+int*list_and_choose(structmenu_opts*opts,structmenu_stuff*stuff)+{+staticstructstring_listmenu_list=STRING_LIST_INIT_DUP;+structstrbufmenu=STRBUF_INIT;+structstrbufchoice=STRBUF_INIT;+structstrbuf**choice_list;+int*chosen,*result;+char*p;+intnr=0;+inti,j;+inteof=0;++chosen=xmalloc(sizeof(int)*stuff->nr);+memset(chosen,0,sizeof(int)*stuff->nr);++while(1){+inti=0,j=0;+string_list_clear(&menu_list,0);++if(opts->header){+printf_ln("%s%s%s",+clean_get_color(CLEAN_COLOR_HEADER),+opts->header,+clean_get_color(CLEAN_COLOR_RESET));+}++/* 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++){+p=item->title;+strbuf_addf(&menu,"%s%2d: ",chosen[i]?"*":" ",i+1);+for(;*p;p++){+if(*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));+}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){+constchar*qname;++qname=quote_path_relative(item->string,-1,&buf,*the_prefix);+strbuf_addf(&menu,"%s%2d: %s",chosen[i]?"*":" ",++i,qname);+string_list_append(&menu_list,menu.buf);+strbuf_reset(&menu);+}+}++pretty_print_menus(&menu_list);++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){+if(!(opts->flag&MENU_OPTS_SINGLETON)){+char*p=choice.buf;+do{+if(*p==',')+*p=' ';+}while(*p++);+}+strbuf_trim(&choice);+}else{+eof=1;+break;+}++/* help for prompt */+if(!strcmp(choice.buf,"?")){+prompt_help_cmd(opts->flag&MENU_OPTS_SINGLETON);+continue;+}++if(!(opts->flag&MENU_OPTS_SINGLETON)&&!choice.len)+break;++choice_list=strbuf_split_max(&choice,' ',0);+for(i=0;choice_list[i];i++){+intchoose=1;+intbottom=0,top=0;+char*p;+intis_range=0;+intis_number=1;++strbuf_trim(choice_list[i]);+if(!choice_list[i]->len)+continue;++/* Input that begins with '-'; unchoose */+if(*choice_list[i]->buf=='-'){+choose=0;+strbuf_remove(choice_list[i],0,1);+}++p=choice_list[i]->buf;+for(;*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_list[i]->buf);+top=bottom;+}elseif(is_range){+bottom=atoi(choice_list[i]->buf);+if(!*(strchr(choice_list[i]->buf,'-')+1)){+top=stuff->nr-1;+}else{+top=atoi(strchr(choice_list[i]->buf,'-')+1);+}+}elseif(!strcmp(choice_list[i]->buf,"*")){+bottom=1;+top=stuff->nr;+}else{+if(MENU_STUFF_TYPE_MENU_ITEM==stuff->type){+structmenu_item*item;++item=(structmenu_item*)stuff->stuff;+for(j=0;j<stuff->nr;j++,item++){+if((choice_list[i]->len==1&&+*choice_list[i]->buf==item->hotkey)||+!strcasecmp(choice_list[i]->buf,item->title)){+bottom=j+1;+top=bottom;+break;+}+}+}elseif(MENU_STUFF_TYPE_STRING_LIST==stuff->type){+structstring_list_item*item;++item=((structstring_list*)stuff->stuff)->items;+for(j=0;j<stuff->nr;j++,item++){+if(!strcasecmp(choice_list[i]->buf,item->string)){+bottom=j+1;+top=bottom;+break;+}+}+}+}++if(top<=0||bottom<=0||top>stuff->nr||bottom>top||+(opts->flag&MENU_OPTS_SINGLETON&&bottom!=top)){+printf_ln("%sHuh (%s)?%s",+clean_get_color(CLEAN_COLOR_ERROR),+choice_list[i]->buf,+clean_get_color(CLEAN_COLOR_RESET));+continue;+}++/* A range can be specified like 5-7 or 5-. */+for(j=bottom;j<=top;j++){+chosen[j-1]=choose;+nr++;+}+}++if(opts->flag&MENU_OPTS_SINGLETON){+if(nr)+break;+}elseif(opts->flag&MENU_OPTS_IMMEDIATE){+break;+}+}+++if(eof){+result=xmalloc(sizeof(int)*2);+result[0]=EOF;+result[1]=0;+}else{+result=xmalloc(sizeof(int)*(nr+1));+memset(result,0,sizeof(int)*(nr+1));+for(i=0,j=0;i<stuff->nr&&j<nr;i++){+if(chosen[i])+result[j++]=i;+}+result[j]=EOF;+}++free(chosen);+string_list_clear(&menu_list,0);+strbuf_release(&menu);+strbuf_release(&choice);+returnresult;+}++intclean_cmd()+{+returnMENU_RETURN_NO_LOOP;+}++intedit_by_patterns_cmd(){structdir_structdir;structstrbufconfirm=STRBUF_INIT;
@@ -257,16 +558,10 @@ void edit_by_patterns_cmd()while(1){/* dels list may become empty when we run string_list_remove_empty_items later */-if(!del_list.nr){-clean_print_color(CLEAN_COLOR_ERROR);-printf_ln(_("No more files to clean, exiting."));-clean_print_color(CLEAN_COLOR_RESET);+if(!del_list.nr)break;-}if(changed){-putchar('\n');-/* Display dels in columns */pretty_print_dels();}
@@ -324,56 +619,86 @@ void edit_by_patterns_cmd()strbuf_release(&buf);strbuf_release(&confirm);+return0;}-voidinteractive_main_loop()+intquit_cmd(){-structstrbufconfirm=STRBUF_INIT;+string_list_clear(&del_list,0);+printf_ln(_("Bye."));+returnMENU_RETURN_NO_LOOP;+}+inthelp_cmd(intx)+{+clean_print_color(CLEAN_COLOR_HELP);+printf_ln(_(+"clean - start cleaning\n"+"edit by patterns - exclude items from deletion\n"+"quit - stop cleaning\n"+"help - this screen\n"+"? - help for prompt selection"+));+clean_print_color(CLEAN_COLOR_RESET);+return0;+}++voidinteractive_main_loop()+{/* dels list may become empty after return back from edit mode */while(del_list.nr){-putchar('\n');+structmenu_optsmenu_opts;+structmenu_stuffmenu_stuff;+structmenu_itemmenus[]={+{'c',"clean",clean_cmd},+{'p',"edit by patterns",edit_by_patterns_cmd},+{'q',"quit",quit_cmd},+{'h',"help",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');-/* Display dels in columns */+/* display dels in columns */pretty_print_dels();-/* Confirmation dialog */-clean_print_color(CLEAN_COLOR_PROMPT);-printf(_("Remove ([y]es/[n]o/[e]dit) ? "));-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;-}elseif(!strncasecmp(confirm.buf,"edit",confirm.len)){-edit_by_patterns_cmd();-}else{+/* main menu */+chosen=list_and_choose(&menu_opts,&menu_stuff);++if(*chosen!=EOF){+intret;+ret=menus[*chosen].fn(1);+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)
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 | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 77 insertions(+), 1 deletion(-)
@@ -32,16 +33,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)
@@ -192,7 +258,9 @@ void edit_by_patterns_cmd()while(1){/* dels list may become empty when we run string_list_remove_empty_items later */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;}
@@ -261,16 +333,20 @@ void interactive_main_loop()/* dels list may become empty after return back from edit mode */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');/* Display dels in columns */pretty_print_dels();/* Confirmation dialog */+clean_print_color(CLEAN_COLOR_PROMPT);printf(_("Remove ([y]es/[n]o/[e]dit) ? "));+clean_print_color(CLEAN_COLOR_RESET);if(strbuf_getline(&confirm,stdin,'\n')!=EOF){strbuf_trim(&confirm);}else{
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(+)
@@ -622,6 +622,43 @@ int edit_by_patterns_cmd()return0;}+intedit_by_numbers_cmd()+{+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;+}+intquit_cmd(){string_list_clear(&del_list,0);
@@ -635,6 +672,7 @@ int help_cmd(int x)printf_ln(_("clean - start cleaning\n""edit by patterns - exclude items from deletion\n"+"edit by numbers - select items to be deleted by numbers\n""quit - stop cleaning\n""help - this screen\n""? - help for prompt selection"
@@ -652,6 +690,7 @@ void interactive_main_loop()structmenu_itemmenus[]={{'c',"clean",clean_cmd},{'p',"edit by patterns",edit_by_patterns_cmd},+{'n',"edit by numbers",edit_by_numbers_cmd},{'q',"quit",quit_cmd},{'h',"help",help_cmd},};
Add a "rm -i" style interactive cleaning method. User must confirm one
by one before starting to delete.
Signed-off-by: Jiang Xin <redacted>
---
builtin/clean.c | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
@@ -659,6 +659,40 @@ int edit_by_numbers_cmd()return0;}+intrm_i_cmd()+{+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,*the_prefix);+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;+}+intquit_cmd(){string_list_clear(&del_list,0);
@@ -673,6 +707,7 @@ int help_cmd(int x)"clean - start cleaning\n""edit by patterns - exclude items from deletion\n""edit by numbers - select items to be deleted by numbers\n"+"rm -i - delete items one by one, like \"rm -i\"\n""quit - stop cleaning\n""help - this screen\n""? - help for prompt selection"
@@ -691,6 +726,7 @@ void interactive_main_loop(){'c',"clean",clean_cmd},{'p',"edit by patterns",edit_by_patterns_cmd},{'n',"edit by numbers",edit_by_numbers_cmd},+{'i',"rm -i",rm_i_cmd},{'q',"quit",quit_cmd},{'h',"help",help_cmd},};
@@ -39,13 +39,8 @@ OPTIONS -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.+ Show what would be done and clean files interactively. See+ ``Interactive mode`` for details. -n:: --dry-run::
@@ -74,6 +69,67 @@ 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: edit by patterns 3: edit by numbers+ 4. rm -i 5. quit 6. help+ What now> 2+------------++You also could say `c` or `clean` above as long as the choice is unique.++The main command loop has 6 subcommands.++clean::++ Start cleaning files and directories, and then quit.++edit by patterns::++ This shows the files and directories to be deleted and issues an+ "Input ignore patterns>>" prompt. You can input a 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.++edit 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.++rm -i::++ This will show a "rm -i" style cleaning, that you must confirm one+ by one in order to delete items. This action is not as efficient+ as the above two actions.++quit::++ This lets you quit without do cleaning.++help::++ Show brief usage of interactive git-clean.+ SEE ALSO -------- linkgit:gitignore[5]
Rewrite menu using a new method `list_and_choose`, which is borrowed
from `git-add--interactive.perl`. We can reused this method later for
more actions.
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 | 410 ++++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 367 insertions(+), 43 deletions(-)
@@ -39,13 +39,8 @@ OPTIONS -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.+ Show what would be done and clean files interactively. See+ ``Interactive mode`` for details.
^^^^^^^^^^^^^^^^^^^^ should be ``Interactive mode''
--
Jiang Xin