git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v9 0/9] interactive git-clean
@ 2013-05-14  8:45 Jiang Xin
  2013-05-14  8:45 ` [PATCH v9 1/9] git-clean: refactor git-clean into two phases Jiang Xin
                   ` (9 more replies)
  0 siblings, 10 replies; 19+ messages in thread
From: Jiang Xin @ 2013-05-14  8:45 UTC (permalink / raw)
  To: Junio C Hamano, Eric Sunshine, Matthieu Moy, Git List; +Cc: Jiang Xin

Updates since v8:

 * Run interactive git-clean even if in dry_run mode. 

       -	if (interactive && !dry_run && del_list.nr > 0)
       +	if (interactive && del_list.nr > 0)
        		interactive_main_loop();
 
 * Variable menu_list is not static.
   (copy from global del_list, and forget to remove static)

        static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)
        {
       -	static struct string_list menu_list = STRING_LIST_INIT_DUP;
       +	struct string_list menu_list = STRING_LIST_INIT_DUP;
        	struct strbuf menu = STRBUF_INIT;
        	int i;

 * i18n:

       @@ -567,21 +567,21 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
        		if (opts->header) {
        			printf_ln("%s%s%s",
        				  clean_get_color(CLEAN_COLOR_HEADER),
       -				  opts->header,
       +				  _(opts->header),
        				  clean_get_color(CLEAN_COLOR_RESET));
        		}
        
       ... 
       
        		if (opts->prompt) {
        			printf("%s%s%s%s",
        			       clean_get_color(CLEAN_COLOR_PROMPT),
       -			       opts->prompt,
       -			       opts->flag & MENU_OPTS_SINGLETON ? "> " : ">> ",
       +			       _(opts->prompt),
       +			       opts->flags & MENU_OPTS_SINGLETON ? "> " : ">> ",
        			       clean_get_color(CLEAN_COLOR_RESET));
        		}


       @@ -721,8 +724,8 @@ static int select_by_numbers_cmd(void)
        	int i, j;
        
       -	menu_opts.prompt = "Select items to delete";
       +	menu_opts.prompt = N_("Select items to delete");

       -		menu_opts.header = _("*** Commands ***");
       -		menu_opts.prompt = "What now";
       +		menu_opts.header = N_("*** Commands ***");
       +		menu_opts.prompt = N_("What now");
  

 * Update documentation for "color.interactive" and "color.interactive.<slot>",
   because `git clean --interactive` reuses these variables from
   `git-add--interactive`.

        color.interactive::
        	When set to `always`, always use colors for interactive prompts
       -	and displays (such as those used by "git-add --interactive").
       -	When false (or `never`), never.  When set to `true` or `auto`, use
       -	colors only when the output is to the terminal. Defaults to false.
       +	and displays (such as those used by "git-add --interactive" and
       +	"git-clean --interactive"). When false (or `never`), never.
       +	When set to `true` or `auto`, use colors only when the output is
       +	to the terminal. Defaults to false.
        
        color.interactive.<slot>::
       -	Use customized color for 'git add --interactive'
       -	output. `<slot>` may be `prompt`, `header`, `help` or `error`, for
       -	four distinct types of normal output from interactive
       -	commands.  The values of these variables may be specified as
       -	in color.branch.<slot>.
       +	Use customized color for 'git add --interactive' and 'git clean
       +	--interactive' output. `<slot>` may be `prompt`, `header`, `help`
       +	or `error`, for four distinct types of normal output from
       +	interactive commands.  The values of these variables may be
       +	specified as in color.branch.<slot>.
 
 * Update documentation for "column.clean", because we only use layout settings.

        column.clean::
       -	Specify whether to output cleaning files in `git clean -i` in columns.
       -	See `column.ui` for details.
       +	Specify the layout when list items in `git clean -i`, which always
       +	shows files and directories in columns. See `column.ui` for details.

 * Refactor: change variables' names, such as:

       @@ -57,7 +57,7 @@ enum color_clean {
        struct menu_opts {
        	const char *header;
        	const char *prompt;
       -	int flag;
       +	int flags;
        };
 

       @@ -428,7 +428,7 @@ static int parse_choice(struct menu_stuff *menu_stuff,
        			struct strbuf input,
        			int **chosen)
        {
       -	struct strbuf **choice_list, **choice_p;
       +	struct strbuf **choice_list, **ptr;
        	int nr = 0;
        	int i;

 * Refactor parse_clean_color_slot:

       @@ -80,19 +80,19 @@ struct menu_stuff {
        	void *stuff;
        };
        
       -static int parse_clean_color_slot(const char *var, int ofs)
       +static int parse_clean_color_slot(const char *var)
        {
       -	if (!strcasecmp(var+ofs, "reset"))
       +	if (!strcasecmp(var, "reset"))
        		return CLEAN_COLOR_RESET;
       -	if (!strcasecmp(var+ofs, "plain"))
       +	if (!strcasecmp(var, "plain"))
        		return CLEAN_COLOR_PLAIN;
       -	if (!strcasecmp(var+ofs, "prompt"))
       +	if (!strcasecmp(var, "prompt"))
        		return CLEAN_COLOR_PROMPT;
       -	if (!strcasecmp(var+ofs, "header"))
       +	if (!strcasecmp(var, "header"))
        		return CLEAN_COLOR_HEADER;
       -	if (!strcasecmp(var+ofs, "help"))
       +	if (!strcasecmp(var, "help"))
        		return CLEAN_COLOR_HELP;
       -	if (!strcasecmp(var+ofs, "error"))
       +	if (!strcasecmp(var, "error"))
        		return CLEAN_COLOR_ERROR;
        	return -1;
        }
       @@ -109,7 +109,8 @@ static int git_clean_config(const char *var, const char *value, void *cb)
        		return 0;
        	}
        	if (!prefixcmp(var, "color.interactive.")) {
       -		int slot = parse_clean_color_slot(var, 18);
       +		int slot = parse_clean_color_slot(var +
       +						  strlen("color.interactive."));
        		if (slot < 0)
        			return 0;
        		if (!value)

 * Withdraw PATCH v8 10/12, 11/12 and 12/12.

   - git-clean refactor: save some options in clean_flags
   - git-clean refactor: add wrapper scan_clean_candidates
   - git-clean: add toggle flags interactive action

Usage:

See: [PATCH v9 9/9] git-clean: add documentation for interactive git-clean


Jiang Xin (9):
  git-clean: refactor git-clean into two phases
  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: add documentation for interactive git-clean

 Documentation/config.txt    |  21 +-
 Documentation/git-clean.txt |  71 +++-
 builtin/clean.c             | 802 ++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 860 insertions(+), 34 deletions(-)

-- 
1.8.3.rc1.404.gb9fcf3e

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v9 1/9] git-clean: refactor git-clean into two phases
  2013-05-14  8:45 [PATCH v9 0/9] interactive git-clean Jiang Xin
@ 2013-05-14  8:45 ` Jiang Xin
  2013-05-14 23:27   ` Junio C Hamano
  2013-05-14  8:45 ` [PATCH v9 2/9] git-clean: add support for -i/--interactive Jiang Xin
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 19+ messages in thread
From: Jiang Xin @ 2013-05-14  8:45 UTC (permalink / raw)
  To: Junio C Hamano, Eric Sunshine, Matthieu Moy, Git List; +Cc: Jiang Xin

Before introducing interactive git-clean, refactor git-clean operations
into two phases:

 * hold cleaning items in del_list,
 * and remove them in a separate loop at the end.

We will introduce interactive git-clean between the two phases. The
interactive git-clean will show what would be done and must confirm
before do real cleaning.

Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
---
 builtin/clean.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 85 insertions(+), 18 deletions(-)

diff --git a/builtin/clean.c b/builtin/clean.c
index 04e39..ccd4 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -15,6 +15,7 @@
 #include "quote.h"
 
 static int force = -1; /* unset */
+static struct string_list del_list = STRING_LIST_INIT_DUP;
 
 static const char *const builtin_clean_usage[] = {
 	N_("git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
@@ -142,18 +143,61 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
 	return ret;
 }
 
+/*
+ * Give path as relative to prefix.
+ *
+ * This function is a combination of path_relative (in quote.c) and
+ * relative_path (in path.c)
+ */
+static const char *path_relative(const char *in, const char *prefix)
+{
+	static char buf[PATH_MAX + 1];
+	int off, i;
+	int len, prefix_len;
+
+	len = strlen(in);
+	if (prefix)
+		prefix_len = strlen(prefix);
+	else
+		prefix_len = 0;
+
+	off = 0;
+	i = 0;
+	while (i < prefix_len && i < len && prefix[i] == in[i]) {
+		if (prefix[i] == '/')
+			off = i + 1;
+		i++;
+	}
+	in += off;
+	len -= off;
+
+	if (i >= prefix_len)
+		return in;
+
+	buf[0] = '\0';
+	while (i < prefix_len) {
+		if (prefix[i] == '/')
+			strcat(buf, "../");
+		i++;
+	}
+	strcat(buf, in);
+
+	return buf;
+}
+
 int cmd_clean(int argc, const char **argv, const char *prefix)
 {
 	int i, res;
 	int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;
 	int ignored_only = 0, config_set = 0, errors = 0, gone = 1;
 	int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
-	struct strbuf directory = STRBUF_INIT;
+	struct strbuf abs_path = STRBUF_INIT;
 	struct dir_struct dir;
 	static const char **pathspec;
 	struct strbuf buf = STRBUF_INIT;
 	struct string_list exclude_list = STRING_LIST_INIT_NODUP;
 	struct exclude_list *el;
+	struct string_list_item *item;
 	const char *qname;
 	char *seen = NULL;
 	struct option options[] = {
@@ -223,6 +267,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 		int matches = 0;
 		struct cache_entry *ce;
 		struct stat st;
+		const char *rel;
 
 		/*
 		 * Remove the '/' at the end that directory
@@ -242,11 +287,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 				continue; /* Yup, this one exists unmerged */
 		}
 
-		/*
-		 * we might have removed this as part of earlier
-		 * recursive directory removal, so lstat() here could
-		 * fail with ENOENT.
-		 */
 		if (lstat(ent->name, &st))
 			continue;
 
@@ -257,33 +297,60 @@ 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);
-				}
+				rel = path_relative(ent->name, prefix);
+				string_list_append(&del_list, rel);
 			}
-			strbuf_reset(&directory);
 		} else {
 			if (pathspec && !matches)
 				continue;
-			res = dry_run ? 0 : unlink(ent->name);
+			rel = path_relative(ent->name, prefix);
+			string_list_append(&del_list, rel);
+		}
+	}
+
+	/* TODO: do interactive git-clean here, which will modify del_list */
+
+	for_each_string_list_item(item, &del_list) {
+		struct stat st;
+
+		if (prefix) {
+			strbuf_addstr(&abs_path, prefix);
+		}
+		strbuf_addstr(&abs_path, item->string);
+
+		/*
+		 * we might have removed this as part of earlier
+		 * recursive directory removal, so lstat() here could
+		 * fail with ENOENT.
+		 */
+		if (lstat(abs_path.buf, &st))
+			continue;
+
+		if (S_ISDIR(st.st_mode)) {
+			if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone))
+				errors++;
+			if (gone && !quiet) {
+				qname = quote_path_relative(item->string, -1, &buf, NULL);
+				printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
+			}
+		} else {
+			res = dry_run ? 0 : unlink(abs_path.buf);
 			if (res) {
-				qname = quote_path_relative(ent->name, -1, &buf, prefix);
+				qname = quote_path_relative(item->string, -1, &buf, NULL);
 				warning(_(msg_warn_remove_failed), qname);
 				errors++;
 			} else if (!quiet) {
-				qname = quote_path_relative(ent->name, -1, &buf, prefix);
+				qname = quote_path_relative(item->string, -1, &buf, NULL);
 				printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
 			}
 		}
+		strbuf_reset(&abs_path);
 	}
 	free(seen);
 
-	strbuf_release(&directory);
+	strbuf_release(&abs_path);
+	string_list_clear(&del_list, 0);
 	string_list_clear(&exclude_list, 0);
 	return (errors != 0);
 }
-- 
1.8.3.rc1.404.gb9fcf3e

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v9 2/9] git-clean: add support for -i/--interactive
  2013-05-14  8:45 [PATCH v9 0/9] interactive git-clean Jiang Xin
  2013-05-14  8:45 ` [PATCH v9 1/9] git-clean: refactor git-clean into two phases Jiang Xin
@ 2013-05-14  8:45 ` Jiang Xin
  2013-05-14  8:45 ` [PATCH v9 3/9] git-clean: show items of del_list in columns Jiang Xin
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 19+ messages in thread
From: Jiang Xin @ 2013-05-14  8:45 UTC (permalink / raw)
  To: Junio C Hamano, Eric Sunshine, Matthieu Moy, Git List; +Cc: Jiang Xin

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 <worldhello.net@gmail.com>
---
 Documentation/git-clean.txt | 10 ++++++--
 builtin/clean.c             | 61 +++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 64 insertions(+), 7 deletions(-)

diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt
index bdc3a..186e34 100644
--- a/Documentation/git-clean.txt
+++ b/Documentation/git-clean.txt
@@ -8,7 +8,7 @@ git-clean - Remove untracked files from the working tree
 SYNOPSIS
 --------
 [verse]
-'git clean' [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>...
+'git clean' [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>...
 
 DESCRIPTION
 -----------
@@ -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::
diff --git a/builtin/clean.c b/builtin/clean.c
index ccd4..127463 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -15,10 +15,11 @@
 #include "quote.h"
 
 static int force = -1; /* unset */
+static int interactive;
 static struct string_list del_list = STRING_LIST_INIT_DUP;
 
 static const char *const builtin_clean_usage[] = {
-	N_("git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
+	N_("git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
 	NULL
 };
 
@@ -185,6 +186,50 @@ static const char *path_relative(const char *in, const char *prefix)
 	return buf;
 }
 
+static void interactive_main_loop(void)
+{
+	struct strbuf confirm = STRBUF_INIT;
+	struct strbuf buf = STRBUF_INIT;
+	struct string_list_item *item;
+	const char *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;
+			} 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 {
+				continue;
+			}
+		}
+	}
+
+	strbuf_release(&buf);
+	strbuf_release(&confirm);
+}
+
 int cmd_clean(int argc, const char **argv, const char *prefix)
 {
 	int i, 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"));
+	} 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"));
 	}
 
@@ -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 && del_list.nr > 0)
+		interactive_main_loop();
 
 	for_each_string_list_item(item, &del_list) {
 		struct stat st;
-- 
1.8.3.rc1.404.gb9fcf3e

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v9 3/9] git-clean: show items of del_list in columns
  2013-05-14  8:45 [PATCH v9 0/9] interactive git-clean Jiang Xin
  2013-05-14  8:45 ` [PATCH v9 1/9] git-clean: refactor git-clean into two phases Jiang Xin
  2013-05-14  8:45 ` [PATCH v9 2/9] git-clean: add support for -i/--interactive Jiang Xin
@ 2013-05-14  8:45 ` Jiang Xin
  2013-05-14  8:45 ` [PATCH v9 4/9] git-clean: add colors to interactive git-clean Jiang Xin
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 19+ messages in thread
From: Jiang Xin @ 2013-05-14  8:45 UTC (permalink / raw)
  To: Junio C Hamano, Eric Sunshine, Matthieu Moy, Git List; +Cc: Jiang Xin

When there are lots of items to be cleaned, it is hard to see them all
in one screen. Show them in columns will solve this problem.

Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Comments-by: Matthieu Moy <Matthieu.Moy@imag.fr>
---
 Documentation/config.txt |  4 ++++
 builtin/clean.c          | 49 +++++++++++++++++++++++++++++++++++++++---------
 2 files changed, 44 insertions(+), 9 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 6e53f..e031b 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -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 the layout when list items in `git clean -i`, which always
+	shows files and directories 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.
diff --git a/builtin/clean.c b/builtin/clean.c
index 127463..d7c68 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -13,10 +13,12 @@
 #include "refs.h"
 #include "string-list.h"
 #include "quote.h"
+#include "column.h"
 
 static int force = -1; /* unset */
 static int interactive;
 static struct string_list del_list = STRING_LIST_INIT_DUP;
+static unsigned int colopts;
 
 static const char *const builtin_clean_usage[] = {
 	N_("git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
@@ -31,8 +33,13 @@ static const char *msg_warn_remove_failed = N_("failed to remove %s");
 
 static int git_clean_config(const char *var, const char *value, void *cb)
 {
-	if (!strcmp(var, "clean.requireforce"))
+	if (!prefixcmp(var, "column."))
+		return git_column_config(var, value, "clean", &colopts);
+
+	if (!strcmp(var, "clean.requireforce")) {
 		force = !git_config_bool(var, value);
+		return 0;
+	}
 	return git_default_config(var, value, cb);
 }
 
@@ -186,21 +193,46 @@ static const char *path_relative(const char *in, const char *prefix)
 	return buf;
 }
 
-static void interactive_main_loop(void)
+static void pretty_print_dels(void)
 {
-	struct strbuf confirm = STRBUF_INIT;
-	struct strbuf buf = STRBUF_INIT;
+	struct string_list list = STRING_LIST_INIT_DUP;
 	struct string_list_item *item;
+	struct strbuf buf = STRBUF_INIT;
 	const char *qname;
+	struct column_options copts;
+
+	for_each_string_list_item(item, &del_list) {
+		qname = quote_path_relative(item->string, -1, &buf, NULL);
+		string_list_append(&list, qname);
+	}
+
+	/*
+	 * always enable column display, we only consult column.*
+	 * about layout strategy and stuff
+	 */
+	colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
+	memset(&copts, 0, sizeof(copts));
+	copts.indent = "  ";
+	copts.padding = 2;
+	print_columns(&list, colopts, &copts);
+	putchar('\n');
+	strbuf_release(&buf);
+	string_list_clear(&list, 0);
+}
+
+static void interactive_main_loop(void)
+{
+	struct strbuf confirm = STRBUF_INIT;
 
 	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);
-		}
+		printf_ln(Q_("Would remove the following item:",
+			     "Would remove the following items:",
+			     del_list.nr));
 		putchar('\n');
 
+		pretty_print_dels();
+
 		printf(_("Remove [y/n]? "));
 		if (strbuf_getline(&confirm, stdin, '\n') != EOF) {
 			strbuf_trim(&confirm);
@@ -226,7 +258,6 @@ static void interactive_main_loop(void)
 		}
 	}
 
-	strbuf_release(&buf);
 	strbuf_release(&confirm);
 }
 
-- 
1.8.3.rc1.404.gb9fcf3e

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v9 4/9] git-clean: add colors to interactive git-clean
  2013-05-14  8:45 [PATCH v9 0/9] interactive git-clean Jiang Xin
                   ` (2 preceding siblings ...)
  2013-05-14  8:45 ` [PATCH v9 3/9] git-clean: show items of del_list in columns Jiang Xin
@ 2013-05-14  8:45 ` Jiang Xin
  2013-05-14  8:45 ` [PATCH v9 5/9] git-clean: use a git-add-interactive compatible UI Jiang Xin
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 19+ messages in thread
From: Jiang Xin @ 2013-05-14  8:45 UTC (permalink / raw)
  To: Junio C Hamano, Eric Sunshine, Matthieu Moy, Git List; +Cc: Jiang Xin

Show header, help, error messages, and prompt in colors for interactive
git-clean. Re-use config variables, such as "color.interactive" and
"color.interactive.<slot>" for command `git-add--interactive`.

Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Comments-by: Matthieu Moy <Matthieu.Moy@imag.fr>
---
 Documentation/config.txt | 17 +++++------
 builtin/clean.c          | 73 +++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 81 insertions(+), 9 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index e031b..83613 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -876,16 +876,17 @@ The values of these variables may be specified as in color.branch.<slot>.
 
 color.interactive::
 	When set to `always`, always use colors for interactive prompts
-	and displays (such as those used by "git-add --interactive").
-	When false (or `never`), never.  When set to `true` or `auto`, use
-	colors only when the output is to the terminal. Defaults to false.
+	and displays (such as those used by "git-add --interactive" and
+	"git-clean --interactive"). When false (or `never`), never.
+	When set to `true` or `auto`, use colors only when the output is
+	to the terminal. Defaults to false.
 
 color.interactive.<slot>::
-	Use customized color for 'git add --interactive'
-	output. `<slot>` may be `prompt`, `header`, `help` or `error`, for
-	four distinct types of normal output from interactive
-	commands.  The values of these variables may be specified as
-	in color.branch.<slot>.
+	Use customized color for 'git add --interactive' and 'git clean
+	--interactive' output. `<slot>` may be `prompt`, `header`, `help`
+	or `error`, for four distinct types of normal output from
+	interactive commands.  The values of these variables may be
+	specified as in color.branch.<slot>.
 
 color.pager::
 	A boolean to enable/disable colored output when the pager is in
diff --git a/builtin/clean.c b/builtin/clean.c
index d7c68..5c781 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -14,6 +14,7 @@
 #include "string-list.h"
 #include "quote.h"
 #include "column.h"
+#include "color.h"
 
 static int force = -1; /* unset */
 static int interactive;
@@ -31,16 +32,82 @@ static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
 static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
 static const char *msg_warn_remove_failed = N_("failed to remove %s");
 
+static int clean_use_color = -1;
+static char clean_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 */
+};
+enum color_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,
+};
+
+static int parse_clean_color_slot(const char *var)
+{
+	if (!strcasecmp(var, "reset"))
+		return CLEAN_COLOR_RESET;
+	if (!strcasecmp(var, "plain"))
+		return CLEAN_COLOR_PLAIN;
+	if (!strcasecmp(var, "prompt"))
+		return CLEAN_COLOR_PROMPT;
+	if (!strcasecmp(var, "header"))
+		return CLEAN_COLOR_HEADER;
+	if (!strcasecmp(var, "help"))
+		return CLEAN_COLOR_HELP;
+	if (!strcasecmp(var, "error"))
+		return CLEAN_COLOR_ERROR;
+	return -1;
+}
+
 static int git_clean_config(const char *var, const char *value, void *cb)
 {
 	if (!prefixcmp(var, "column."))
 		return git_column_config(var, value, "clean", &colopts);
 
+	/* honors the color.interactive* config variables which also
+	   applied in git-add--interactive and git-stash */
+	if (!strcmp(var, "color.interactive")) {
+		clean_use_color = git_config_colorbool(var, value);
+		return 0;
+	}
+	if (!prefixcmp(var, "color.interactive.")) {
+		int slot = parse_clean_color_slot(var +
+						  strlen("color.interactive."));
+		if (slot < 0)
+			return 0;
+		if (!value)
+			return config_error_nonbool(var);
+		color_parse(value, var, clean_colors[slot]);
+		return 0;
+	}
+
 	if (!strcmp(var, "clean.requireforce")) {
 		force = !git_config_bool(var, value);
 		return 0;
 	}
-	return git_default_config(var, value, cb);
+
+	/* inspect the color.ui config variable and others */
+	return git_color_default_config(var, value, cb);
+}
+
+static const char *clean_get_color(enum color_clean ix)
+{
+	if (want_color(clean_use_color))
+		return clean_colors[ix];
+	return "";
+}
+
+static void clean_print_color(enum color_clean ix)
+{
+	printf("%s", clean_get_color(ix));
 }
 
 static int exclude_cb(const struct option *opt, const char *arg, int unset)
@@ -226,14 +293,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 {
-- 
1.8.3.rc1.404.gb9fcf3e

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v9 5/9] git-clean: use a git-add-interactive compatible UI
  2013-05-14  8:45 [PATCH v9 0/9] interactive git-clean Jiang Xin
                   ` (3 preceding siblings ...)
  2013-05-14  8:45 ` [PATCH v9 4/9] git-clean: add colors to interactive git-clean Jiang Xin
@ 2013-05-14  8:45 ` Jiang Xin
  2013-05-14  8:45 ` [PATCH v9 6/9] git-clean: add filter by pattern interactive action Jiang Xin
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 19+ messages in thread
From: Jiang Xin @ 2013-05-14  8:45 UTC (permalink / raw)
  To: Junio C Hamano, Eric Sunshine, Matthieu Moy, Git List; +Cc: Jiang Xin

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 <worldhello.net@gmail.com>
---
 builtin/clean.c | 449 ++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 420 insertions(+), 29 deletions(-)

diff --git a/builtin/clean.c b/builtin/clean.c
index 5c781..7930f 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -50,6 +50,36 @@ enum color_clean {
 	CLEAN_COLOR_ERROR = 5,
 };
 
+#define MENU_OPTS_SINGLETON		01
+#define MENU_OPTS_IMMEDIATE		02
+#define MENU_OPTS_LIST_ONLY		04
+
+struct menu_opts {
+	const char *header;
+	const char *prompt;
+	int flags;
+};
+
+#define MENU_RETURN_NO_LOOP		10
+
+struct menu_item {
+	char hotkey;
+	char *title;
+	int selected;
+	int (*fn)();
+};
+
+enum menu_stuff_type {
+	MENU_STUFF_TYPE_STRING_LIST = 1,
+	MENU_STUFF_TYPE_MENU_ITEM
+};
+
+struct menu_stuff {
+	enum menu_stuff_type type;
+	int nr;
+	void *stuff;
+};
+
 static int parse_clean_color_slot(const char *var)
 {
 	if (!strcasecmp(var, "reset"))
@@ -282,54 +312,415 @@ 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);
 }
 
-static void interactive_main_loop(void)
+static void pretty_print_menus(struct string_list *menu_list)
+{
+	unsigned int local_colopts = 0;
+	struct column_options copts;
+
+	local_colopts = COL_ENABLED | COL_ROW;
+	memset(&copts, 0, sizeof(copts));
+	copts.indent = "  ";
+	copts.padding = 2;
+	print_columns(menu_list, local_colopts, &copts);
+}
+
+static void prompt_help_cmd(int singleton)
+{
+	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);
+}
+
+/*
+ * display menu stuff with number prefix and hotkey highlight
+ */
+static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)
+{
+	struct string_list menu_list = STRING_LIST_INIT_DUP;
+	struct strbuf menu = STRBUF_INIT;
+	int i;
+
+	if (MENU_STUFF_TYPE_MENU_ITEM == stuff->type) {
+		struct menu_item *item;
+
+		item = (struct menu_item *)stuff->stuff;
+		for (i = 0; i < stuff->nr; i++, item++) {
+			char *p;
+			int highlighted = 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);
+		}
+	} else if (MENU_STUFF_TYPE_STRING_LIST == stuff->type) {
+		struct string_list_item *item;
+		struct strbuf buf = STRBUF_INIT;
+		i = 0;
+
+		for_each_string_list_item(item, (struct string_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);
+}
+
+/*
+ * Parse user input, and return choice(s) for menu (menu_stuff).
+ *
+ * Input
+ *     (for single choice)
+ *         1          - select a numbered item
+ *         foo        - select item based on menu title
+ *                    - (empty) select nothing
+ *
+ *     (for multiple choice)
+ *         1          - select a single item
+ *         3-5        - select a range of items
+ *         2-3,6-9    - select multiple ranges
+ *         foo        - select item based on menu title
+ *         -...       - unselect specified items
+ *         *          - choose all items
+ *                    - (empty) finish selecting
+ *
+ * The parse result will be saved in array **chosen, and
+ * return number of total selections.
+ */
+static int parse_choice(struct menu_stuff *menu_stuff,
+			int is_single,
+			struct strbuf input,
+			int **chosen)
+{
+	struct strbuf **choice_list, **ptr;
+	int nr = 0;
+	int i;
+
+	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 (ptr = choice_list; *ptr; ptr++) {
+		char *p;
+		int choose = 1;
+		int bottom = 0, top = 0;
+		int is_range, is_number;
+
+		strbuf_trim(*ptr);
+		if (!(*ptr)->len)
+			continue;
+
+		/* Input that begins with '-'; unchoose */
+		if (*(*ptr)->buf == '-') {
+			choose = 0;
+			strbuf_remove((*ptr), 0, 1);
+		}
+
+		is_range = 0;
+		is_number = 1;
+		for(p = (*ptr)->buf; *p; p++) {
+			if ('-' == *p) {
+				if (!is_range) {
+					is_range = 1;
+					is_number = 0;
+				} else {
+					is_number = 0;
+					is_range = 0;
+					break;
+				}
+			} else if (!isdigit(*p)) {
+				is_number = 0;
+				is_range = 0;
+				break;
+			}
+		}
+
+		if (is_number) {
+			bottom = atoi((*ptr)->buf);
+			top = bottom;
+		} else if (is_range) {
+			bottom = atoi((*ptr)->buf);
+			/* a range can be specified like 5-7 or 5- */
+			if (!*(strchr((*ptr)->buf, '-') + 1)) {
+				top = menu_stuff->nr - 1;
+			} else {
+				top = atoi(strchr((*ptr)->buf, '-') + 1);
+			}
+		} else if (!strcmp((*ptr)->buf, "*")) {
+			bottom = 1;
+			top = menu_stuff->nr;
+		} else {
+			if (MENU_STUFF_TYPE_MENU_ITEM == menu_stuff->type) {
+				struct menu_item *item;
+
+				item = (struct menu_item *)menu_stuff->stuff;
+				for (i = 0; i < menu_stuff->nr; i++, item++) {
+					if (((*ptr)->len == 1 &&
+					     *(*ptr)->buf == item->hotkey) ||
+					    !strcasecmp((*ptr)->buf, item->title)) {
+						bottom = i + 1;
+						top = bottom;
+						break;
+					}
+				}
+			} else if (MENU_STUFF_TYPE_STRING_LIST == menu_stuff->type) {
+				struct string_list_item *item;
+
+				item = ((struct string_list *)menu_stuff->stuff)->items;
+				for (i = 0; i < menu_stuff->nr; i++, item++) {
+					if (!strcasecmp((*ptr)->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)?"), (*ptr)->buf);
+			clean_print_color(CLEAN_COLOR_RESET);
+			continue;
+		}
+
+		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];
+	return nr;
+}
+
+/*
+ * Implement a git-add-interactive compatible UI, which is borrowed
+ * from git-add--interactive.perl.
+ *
+ * Return value:
+ *
+ *   - Return an array of integers
+ *   - , and it is up to you to free the allocated memory.
+ *   - The array ends with EOF.
+ *   - If user pressed CTRL-D (i.e. EOF), no selection returned.
+ */
+static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
+{
+	struct strbuf choice = STRBUF_INIT;
+	int *chosen, *result;
+	int nr = 0;
+	int eof = 0;
+	int i;
+
+	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));
+		}
+
+		/* chosen will be initialized by print_highlight_menu_stuff */
+		print_highlight_menu_stuff(stuff, &chosen);
+
+		if (opts->flags & MENU_OPTS_LIST_ONLY)
+			break;
+
+		if (opts->prompt) {
+			printf("%s%s%s%s",
+			       clean_get_color(CLEAN_COLOR_PROMPT),
+			       _(opts->prompt),
+			       opts->flags & 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->flags & MENU_OPTS_SINGLETON);
+			continue;
+		}
+
+		/* for a multiple-choice menu, press ENTER (empty) will return back */
+		if (!(opts->flags & MENU_OPTS_SINGLETON) && !choice.len)
+			break;
+
+		nr = parse_choice(stuff,
+				  opts->flags & MENU_OPTS_SINGLETON,
+				  choice,
+				  &chosen);
+
+		if (opts->flags & MENU_OPTS_SINGLETON) {
+			if (nr)
+				break;
+		} else if (opts->flags & MENU_OPTS_IMMEDIATE) {
+			break;
+		}
+	}
+
+	if (eof) {
+		result = xmalloc(sizeof(int));
+		*result = EOF;
+	} else {
+		int j = 0;
+
+		/*
+		 * recalculate nr, if return back from menu directly with
+		 * default 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);
+	return result;
+}
+
+static int clean_cmd(void)
+{
+	return MENU_RETURN_NO_LOOP;
+}
+
+static int quit_cmd(void)
+{
+	string_list_clear(&del_list, 0);
+	printf_ln(_("Bye."));
+	return MENU_RETURN_NO_LOOP;
+}
+
+static int help_cmd(void)
 {
-	struct strbuf confirm = STRBUF_INIT;
+	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);
+	return 0;
+}
 
+static void interactive_main_loop(void)
+{
 	while (del_list.nr) {
-		putchar('\n');
+		struct menu_opts menu_opts;
+		struct menu_stuff menu_stuff;
+		struct menu_item menus[] = {
+			{'c', "clean",			0, clean_cmd},
+			{'q', "quit",			0, quit_cmd},
+			{'h', "help",			0, help_cmd},
+		};
+		int *chosen;
+
+		menu_opts.header = N_("*** Commands ***");
+		menu_opts.prompt = N_("What now");
+		menu_opts.flags = MENU_OPTS_SINGLETON;
+
+		menu_stuff.type = MENU_STUFF_TYPE_MENU_ITEM;
+		menu_stuff.stuff = menus;
+		menu_stuff.nr = sizeof(menus) / sizeof(struct menu_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;
-			} 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 {
+		chosen = list_and_choose(&menu_opts, &menu_stuff);
+
+		if (*chosen != EOF) {
+			int ret;
+			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;
+	}
 }
 
 int cmd_clean(int argc, const char **argv, const char *prefix)
-- 
1.8.3.rc1.404.gb9fcf3e

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v9 6/9] git-clean: add filter by pattern interactive action
  2013-05-14  8:45 [PATCH v9 0/9] interactive git-clean Jiang Xin
                   ` (4 preceding siblings ...)
  2013-05-14  8:45 ` [PATCH v9 5/9] git-clean: use a git-add-interactive compatible UI Jiang Xin
@ 2013-05-14  8:45 ` Jiang Xin
  2013-05-14  8:45 ` [PATCH v9 7/9] git-clean: add select by numbers " Jiang Xin
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 19+ messages in thread
From: Jiang Xin @ 2013-05-14  8:45 UTC (permalink / raw)
  To: Junio C Hamano, Eric Sunshine, Matthieu Moy, Git List; +Cc: Jiang Xin

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 <worldhello.net@gmail.com>
Suggested-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/clean.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 68 insertions(+)

diff --git a/builtin/clean.c b/builtin/clean.c
index 7930f..80cf1 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -649,6 +649,72 @@ static int clean_cmd(void)
 	return MENU_RETURN_NO_LOOP;
 }
 
+static int filter_by_patterns_cmd(void)
+{
+	struct dir_struct dir;
+	struct strbuf confirm = STRBUF_INIT;
+	struct strbuf **ignore_list;
+	struct string_list_item *item;
+	struct exclude_list *el;
+	int changed = -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) {
+			int dtype = 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);
+	return 0;
+}
+
 static int quit_cmd(void)
 {
 	string_list_clear(&del_list, 0);
@@ -661,6 +727,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"
@@ -676,6 +743,7 @@ static void interactive_main_loop(void)
 		struct menu_stuff menu_stuff;
 		struct menu_item menus[] = {
 			{'c', "clean",			0, clean_cmd},
+			{'f', "filter by pattern",	0, filter_by_patterns_cmd},
 			{'q', "quit",			0, quit_cmd},
 			{'h', "help",			0, help_cmd},
 		};
-- 
1.8.3.rc1.404.gb9fcf3e

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v9 7/9] git-clean: add select by numbers interactive action
  2013-05-14  8:45 [PATCH v9 0/9] interactive git-clean Jiang Xin
                   ` (5 preceding siblings ...)
  2013-05-14  8:45 ` [PATCH v9 6/9] git-clean: add filter by pattern interactive action Jiang Xin
@ 2013-05-14  8:45 ` Jiang Xin
  2013-05-14  8:45 ` [PATCH v9 8/9] git-clean: add ask each " Jiang Xin
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 19+ messages in thread
From: Jiang Xin @ 2013-05-14  8:45 UTC (permalink / raw)
  To: Junio C Hamano, Eric Sunshine, Matthieu Moy, Git List; +Cc: Jiang Xin

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 <worldhello.net@gmail.com>
---
 builtin/clean.c | 39 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/builtin/clean.c b/builtin/clean.c
index 80cf1..74fc9 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -715,6 +715,43 @@ static int filter_by_patterns_cmd(void)
 	return 0;
 }
 
+static int select_by_numbers_cmd(void)
+{
+	struct menu_opts menu_opts;
+	struct menu_stuff menu_stuff;
+	struct string_list_item *items;
+	int *chosen;
+	int i, j;
+
+	menu_opts.header = NULL;
+	menu_opts.prompt = N_("Select items to delete");
+	menu_opts.flags = 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';
+		} else if (i == chosen[j]) {
+			/* delete selected item */
+			j++;
+			continue;
+		} else {
+			/* end of chosen (chosen[j] == EOF), won't delete */
+			*(items[i].string) = '\0';
+		}
+	}
+
+	string_list_remove_empty_items(&del_list, 0);
+
+	free(chosen);
+	return 0;
+}
+
 static int quit_cmd(void)
 {
 	string_list_clear(&del_list, 0);
@@ -728,6 +765,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"
@@ -744,6 +782,7 @@ static void interactive_main_loop(void)
 		struct menu_item menus[] = {
 			{'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},
 		};
-- 
1.8.3.rc1.404.gb9fcf3e

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v9 8/9] git-clean: add ask each interactive action
  2013-05-14  8:45 [PATCH v9 0/9] interactive git-clean Jiang Xin
                   ` (6 preceding siblings ...)
  2013-05-14  8:45 ` [PATCH v9 7/9] git-clean: add select by numbers " Jiang Xin
@ 2013-05-14  8:45 ` Jiang Xin
  2013-05-14  8:45 ` [PATCH v9 9/9] git-clean: add documentation for interactive git-clean Jiang Xin
  2013-05-14 23:27 ` [PATCH v9 0/9] " Junio C Hamano
  9 siblings, 0 replies; 19+ messages in thread
From: Jiang Xin @ 2013-05-14  8:45 UTC (permalink / raw)
  To: Junio C Hamano, Eric Sunshine, Matthieu Moy, Git List; +Cc: Jiang Xin

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 <worldhello.net@gmail.com>
---
 builtin/clean.c | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/builtin/clean.c b/builtin/clean.c
index 74fc9..fc68b 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -752,6 +752,40 @@ static int select_by_numbers_cmd(void)
 	return 0;
 }
 
+static int ask_each_cmd(void)
+{
+	struct strbuf confirm = STRBUF_INIT;
+	struct strbuf buf = STRBUF_INIT;
+	struct string_list_item *item;
+	const char *qname;
+	int changed = 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);
+	return MENU_RETURN_NO_LOOP;
+}
+
 static int quit_cmd(void)
 {
 	string_list_clear(&del_list, 0);
@@ -766,6 +800,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"
@@ -783,6 +818,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, ask_each_cmd},
 			{'q', "quit",			0, quit_cmd},
 			{'h', "help",			0, help_cmd},
 		};
-- 
1.8.3.rc1.404.gb9fcf3e

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v9 9/9] git-clean: add documentation for interactive git-clean
  2013-05-14  8:45 [PATCH v9 0/9] interactive git-clean Jiang Xin
                   ` (7 preceding siblings ...)
  2013-05-14  8:45 ` [PATCH v9 8/9] git-clean: add ask each " Jiang Xin
@ 2013-05-14  8:45 ` Jiang Xin
  2013-05-14 23:27 ` [PATCH v9 0/9] " Junio C Hamano
  9 siblings, 0 replies; 19+ messages in thread
From: Jiang Xin @ 2013-05-14  8:45 UTC (permalink / raw)
  To: Junio C Hamano, Eric Sunshine, Matthieu Moy, Git List; +Cc: Jiang Xin

Add new section "Interactive mode" for documentation of interactive
git-clean.

Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
---
 Documentation/git-clean.txt | 65 +++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 63 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt
index 186e34..5bf76 100644
--- a/Documentation/git-clean.txt
+++ b/Documentation/git-clean.txt
@@ -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,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: filter by pattern    3: select by numbers
+        4: ask each             5: quit                 6: help
+    What now> 1
+------------
+
+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.
+
+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.
+
+quit::
+
+  This lets you quit without do cleaning.
+
+help::
+
+  Show brief usage of interactive git-clean.
+
 SEE ALSO
 --------
 linkgit:gitignore[5]
-- 
1.8.3.rc1.404.gb9fcf3e

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v9 1/9] git-clean: refactor git-clean into two phases
  2013-05-14  8:45 ` [PATCH v9 1/9] git-clean: refactor git-clean into two phases Jiang Xin
@ 2013-05-14 23:27   ` Junio C Hamano
  2013-05-15  0:40     ` Jiang Xin
                       ` (3 more replies)
  0 siblings, 4 replies; 19+ messages in thread
From: Junio C Hamano @ 2013-05-14 23:27 UTC (permalink / raw)
  To: Jiang Xin; +Cc: Eric Sunshine, Matthieu Moy, Git List

Jiang Xin <worldhello.net@gmail.com> writes:

> +/*
> + * Give path as relative to prefix.
> + *
> + * This function is a combination of path_relative (in quote.c) and
> + * relative_path (in path.c)
> + */
> +static const char *path_relative(const char *in, const char *prefix)
> +{
> +...

Hmph.  Is it possible to reuse the public one (in path.c) here and
in quote.c, perhaps after enhancing it a bit to serve needs of the
callers of two existing ones and the new callers of this one?

> @@ -242,11 +287,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
>  				continue; /* Yup, this one exists unmerged */
>  		}
>  
> -		/*
> -		 * we might have removed this as part of earlier
> -		 * recursive directory removal, so lstat() here could
> -		 * fail with ENOENT.
> -		 */
>  		if (lstat(ent->name, &st))
>  			continue;

I am guessing that the reason why you removed the comment is because
during this phase there is no way we "might have removed".  But if
that is the case, does it still make sense to run lstat() and ignore
errors from the call?

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v9 0/9] interactive git-clean
  2013-05-14  8:45 [PATCH v9 0/9] interactive git-clean Jiang Xin
                   ` (8 preceding siblings ...)
  2013-05-14  8:45 ` [PATCH v9 9/9] git-clean: add documentation for interactive git-clean Jiang Xin
@ 2013-05-14 23:27 ` Junio C Hamano
  9 siblings, 0 replies; 19+ messages in thread
From: Junio C Hamano @ 2013-05-14 23:27 UTC (permalink / raw)
  To: Jiang Xin; +Cc: Eric Sunshine, Matthieu Moy, Git List

Except for one nit in 1/9, the series seems to be nicely done.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v9 1/9] git-clean: refactor git-clean into two phases
  2013-05-14 23:27   ` Junio C Hamano
@ 2013-05-15  0:40     ` Jiang Xin
  2013-05-15 15:03       ` Junio C Hamano
  2013-05-15 15:18     ` [RFC 0/2] refactor relative_path in path.c Jiang Xin
                       ` (2 subsequent siblings)
  3 siblings, 1 reply; 19+ messages in thread
From: Jiang Xin @ 2013-05-15  0:40 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Eric Sunshine, Matthieu Moy, Git List

2013/5/15 Junio C Hamano <gitster@pobox.com>:
>> @@ -242,11 +287,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
>>                               continue; /* Yup, this one exists unmerged */
>>               }
>>
>> -             /*
>> -              * we might have removed this as part of earlier
>> -              * recursive directory removal, so lstat() here could
>> -              * fail with ENOENT.
>> -              */
>>               if (lstat(ent->name, &st))
>>                       continue;
>
> I am guessing that the reason why you removed the comment is because
> during this phase there is no way we "might have removed".  But if
> that is the case, does it still make sense to run lstat() and ignore
> errors from the call?
>

Run lstat() here is necessary, because we need to check whether
ent->name points to a file or a directory. If ent points to a directory,
only add to del_list when user provides '-x' option to git-clean.

                if (S_ISDIR(st.st_mode)) {
                        if (remove_directories || (matches ==
MATCHED_EXACTLY)) {
                                rel = path_relative(ent->name, prefix);
                                string_list_append(&del_list, rel);
                        }
                } ...


When start to do cleaning, there is a duplicate lstat() call, and the removed
comments above are moved here.

                /*
                 * we might have removed this as part of earlier
                 * recursive directory removal, so lstat() here could
                 * fail with ENOENT.
                 */
                if (lstat(abs_path.buf, &st))
                        continue;

                if (S_ISDIR(st.st_mode)) {
                        if (remove_dirs(&abs_path, prefix, rm_flags,
dry_run, quiet, &gone))
                                errors++;
                        if (gone && !quiet) {
                                qname =
quote_path_relative(item->string, -1, &buf, NULL);
                                printf(dry_run ? _(msg_would_remove) :
_(msg_remove), qname);
                        }
                }

But I am not clear how "earlier recursive directory removal" could
make lstat() failure here. If it is not the case, maybe we can test a
directory by ent->name (ends with '/' or '\\' ?).

-- 
Jiang Xin

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v9 1/9] git-clean: refactor git-clean into two phases
  2013-05-15  0:40     ` Jiang Xin
@ 2013-05-15 15:03       ` Junio C Hamano
  2013-05-15 15:07         ` Jiang Xin
  0 siblings, 1 reply; 19+ messages in thread
From: Junio C Hamano @ 2013-05-15 15:03 UTC (permalink / raw)
  To: Jiang Xin; +Cc: Eric Sunshine, Matthieu Moy, Git List

Jiang Xin <worldhello.net@gmail.com> writes:

> 2013/5/15 Junio C Hamano <gitster@pobox.com>:
>>> @@ -242,11 +287,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
>>>                               continue; /* Yup, this one exists unmerged */
>>>               }
>>>
>>> -             /*
>>> -              * we might have removed this as part of earlier
>>> -              * recursive directory removal, so lstat() here could
>>> -              * fail with ENOENT.
>>> -              */
>>>               if (lstat(ent->name, &st))
>>>                       continue;
>>
>> I am guessing that the reason why you removed the comment is because
>> during this phase there is no way we "might have removed".  But if
>> that is the case, does it still make sense to run lstat() and ignore
>> errors from the call?
>>
>
> Run lstat() here is necessary, because we need to check whether
> ent->name points to a file or a directory. If ent points to a directory,
> only add to del_list when user provides '-x' option to git-clean.

Sorry, but that was not the question; we can see st is used
immediately below so somebody needs to fill it.

I was pointing out that the "lstat() is expected to fail with ENOENT
but it is not an error worth reporting" justification the original
code had to silently ignore an error, because you no longer remove
anything immediately in this part of the code.  Is "if () continue"
still valid thing to do here, not "if () die()"?

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v9 1/9] git-clean: refactor git-clean into two phases
  2013-05-15 15:03       ` Junio C Hamano
@ 2013-05-15 15:07         ` Jiang Xin
  0 siblings, 0 replies; 19+ messages in thread
From: Jiang Xin @ 2013-05-15 15:07 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Eric Sunshine, Matthieu Moy, Git List

2013/5/15 Junio C Hamano <gitster@pobox.com>:
> Jiang Xin <worldhello.net@gmail.com> writes:
>
>> 2013/5/15 Junio C Hamano <gitster@pobox.com>:
>>>> @@ -242,11 +287,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
>>>>                               continue; /* Yup, this one exists unmerged */
>>>>               }
>>>>
>>>> -             /*
>>>> -              * we might have removed this as part of earlier
>>>> -              * recursive directory removal, so lstat() here could
>>>> -              * fail with ENOENT.
>>>> -              */
>>>>               if (lstat(ent->name, &st))
>>>>                       continue;
>>>
>>> I am guessing that the reason why you removed the comment is because
>>> during this phase there is no way we "might have removed".  But if
>>> that is the case, does it still make sense to run lstat() and ignore
>>> errors from the call?
>>>
>>
>> Run lstat() here is necessary, because we need to check whether
>> ent->name points to a file or a directory. If ent points to a directory,
>> only add to del_list when user provides '-x' option to git-clean.
>
> Sorry, but that was not the question; we can see st is used
> immediately below so somebody needs to fill it.
>
> I was pointing out that the "lstat() is expected to fail with ENOENT
> but it is not an error worth reporting" justification the original
> code had to silently ignore an error, because you no longer remove
> anything immediately in this part of the code.  Is "if () continue"
> still valid thing to do here, not "if () die()"?

I'm clear, it could be:

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


-- 
Jiang Xin

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [RFC 0/2] refactor relative_path in path.c
  2013-05-14 23:27   ` Junio C Hamano
  2013-05-15  0:40     ` Jiang Xin
@ 2013-05-15 15:18     ` Jiang Xin
  2013-05-15 18:24       ` Junio C Hamano
  2013-05-15 15:18     ` [RFC 1/2] path.c: refactor relative_path(), not only strip prefix Jiang Xin
  2013-05-15 15:18     ` [RFC 2/2] quote.c: remove path_relative, use relative_path instead Jiang Xin
  3 siblings, 1 reply; 19+ messages in thread
From: Jiang Xin @ 2013-05-15 15:18 UTC (permalink / raw)
  To: Junio C Hamano, Eric Sunshine, Matthieu Moy, Git List; +Cc: Jiang Xin

2013/5/15 Junio C Hamano <gitster@pobox.com>:
> Jiang Xin <worldhello.net@gmail.com> writes:
>
>> +/*
>> + * Give path as relative to prefix.
>> + *
>> + * This function is a combination of path_relative (in quote.c) and
>> + * relative_path (in path.c)
>> + */
>> +static const char *path_relative(const char *in, const char *prefix)
>> +{
>> +...
>
> Hmph.  Is it possible to reuse the public one (in path.c) here and
> in quote.c, perhaps after enhancing it a bit to serve needs of the
> callers of two existing ones and the new callers of this one?
>

These two patches enhance relative_path() in path.c, so that function
relative_path() will return real relative path, not a path strip off
the prefix.

The 2nd patch is a bit aggressive, it refactor all related functions,
remove unnecessary arguments: len and/or prefix_len.

Please review them. They will be prerequisites for the interactive
git-clean patch series.

Jiang Xin (2):
  path.c: refactor relative_path(), not only strip prefix
  quote.c: remove path_relative, use relative_path instead

 builtin/clean.c    | 18 +++++------
 builtin/grep.c     |  4 +--
 builtin/ls-files.c | 13 ++++----
 path.c             | 94 ++++++++++++++++++++++++++++++++++++++++++------------
 quote.c            | 71 +++--------------------------------------
 quote.h            |  7 ++--
 wt-status.c        | 17 +++++-----
 7 files changed, 107 insertions(+), 117 deletions(-)

-- 
1.8.3.rc1.404.ga32c147

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [RFC 1/2] path.c: refactor relative_path(), not only strip prefix
  2013-05-14 23:27   ` Junio C Hamano
  2013-05-15  0:40     ` Jiang Xin
  2013-05-15 15:18     ` [RFC 0/2] refactor relative_path in path.c Jiang Xin
@ 2013-05-15 15:18     ` Jiang Xin
  2013-05-15 15:18     ` [RFC 2/2] quote.c: remove path_relative, use relative_path instead Jiang Xin
  3 siblings, 0 replies; 19+ messages in thread
From: Jiang Xin @ 2013-05-15 15:18 UTC (permalink / raw)
  To: Junio C Hamano, Eric Sunshine, Matthieu Moy, Git List; +Cc: Jiang Xin

Original design of relative_path() is simple, just strip the prefix
(*base) from the abosolute path (*abs). In most cases, we need a real
relative path, such as: ../foo, ../../bar. That's why there is another
reimplementation (path_relative()) in quote.c.

Refactor relative_path() in path.c to return real relative path, so
that user can reuse this function without reimplement his/her own.
I will use this method for interactive git-clean later. Some of the
implementations are borrowed from path_relative() in quote.c.

Different results for relative_path() before and after this refactor:

    base path  abs path  relative (orignal)  relative (refactor)
    =========  ========  ==================  ===================
    /a/b       /a/b/c/   c/                  c/
    //a///b/   /a/b//c/  c/                  c/
    /a/b       /a/b/     .                   ./
    /a/b/      /a        /a                  ../
    /a/b/      /         /                   ../../
    /a/b/      /a/c      /a/c                ../c

Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
---
 path.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 74 insertions(+), 20 deletions(-)

diff --git a/path.c b/path.c
index 04ff..4dafa8 100644
--- a/path.c
+++ b/path.c
@@ -444,38 +444,92 @@ int adjust_shared_perm(const char *path)
 const char *relative_path(const char *abs, const char *base)
 {
 	static char buf[PATH_MAX + 1];
-	int i = 0, j = 0;
+	int abs_off, base_off, i, j;
+	int abs_len, base_len;
 
 	if (!base || !base[0])
 		return abs;
-	while (base[i]) {
+
+	abs_len = strlen(abs);
+	base_len = strlen(base);
+
+	abs_off = 0;
+	base_off = 0;
+	i = 0;
+	j = 0;
+	while (i < base_len && j < abs_len && base[i] == abs[j]) {
 		if (is_dir_sep(base[i])) {
-			if (!is_dir_sep(abs[j]))
-				return abs;
 			while (is_dir_sep(base[i]))
 				i++;
 			while (is_dir_sep(abs[j]))
 				j++;
-			continue;
-		} else if (abs[j] != base[i]) {
+			base_off = i;
+			abs_off = j;
+		} else {
+			i++;
+			j++;
+		}
+	}
+	if (
+	    /* base seems like prefix of abs */
+	    i >= base_len &&
+	    /*
+	     * but "/foo" is not a prefix of "/foobar"
+	     * (i.e. base not end with '/')
+	     */
+	    base_off < base_len) {
+		if (j >= abs_len) {
+			/* abs="/a/b", base="/a/b" */
+			abs_off = abs_len;
+		} else if (is_dir_sep(abs[j])) {
+			/* abs="/a/b/c", base="/a/b" */
+			while (is_dir_sep(abs[j]))
+				j++;
+			abs_off = j;
+		} else {
+			/* abs="/a/bbb/c", base="/a/b" */
+			i = base_off;
+		}
+	} else if (
+		   /* abs is short than base (prefix of base) */
+		   j >= abs_len &&
+		   /* abs not end with '/' */
+		   abs_off < abs_len) {
+		if (is_dir_sep(base[i])) {
+			/* abs="/a/b", base="/a/b/c/" */
+			while (is_dir_sep(base[i]))
+				i++;
+			abs_off = abs_len;
+		}
+	}
+	abs += abs_off;
+	abs_len -= abs_off;
+
+	/* base is prefix of abs */
+	if (i >= base_len) {
+		if (*abs == '\0') {
+			strcpy(buf, "./");
+			return buf;
+		} else {
 			return abs;
 		}
+	}
+
+	buf[0] = '\0';
+	while (i < base_len) {
+		if (is_dir_sep(base[i])) {
+			strcat(buf, "../");
+			while (is_dir_sep(base[i]))
+				i++;
+			continue;
+		}
 		i++;
-		j++;
 	}
-	if (
-	    /* "/foo" is a prefix of "/foo" */
-	    abs[j] &&
-	    /* "/foo" is not a prefix of "/foobar" */
-	    !is_dir_sep(base[i-1]) && !is_dir_sep(abs[j])
-	   )
-		return abs;
-	while (is_dir_sep(abs[j]))
-		j++;
-	if (!abs[j])
-		strcpy(buf, ".");
-	else
-		strcpy(buf, abs + j);
+	if (!is_dir_sep(base[base_len - 1]))
+		strcat(buf, "../");
+
+	strcat(buf, abs);
+
 	return buf;
 }
 
-- 
1.8.3.rc1.404.ga32c147

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [RFC 2/2] quote.c: remove path_relative, use relative_path instead
  2013-05-14 23:27   ` Junio C Hamano
                       ` (2 preceding siblings ...)
  2013-05-15 15:18     ` [RFC 1/2] path.c: refactor relative_path(), not only strip prefix Jiang Xin
@ 2013-05-15 15:18     ` Jiang Xin
  3 siblings, 0 replies; 19+ messages in thread
From: Jiang Xin @ 2013-05-15 15:18 UTC (permalink / raw)
  To: Junio C Hamano, Eric Sunshine, Matthieu Moy, Git List; +Cc: Jiang Xin

Since there is a enhanced version of relative_path() in path.c,
remove duplicate counterpart path_relative() in quote.c.

Also refactor related functions, remove unnecessary arguments:
len and/or prefix_len.

Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
---
 builtin/clean.c    | 18 +++++++-------
 builtin/grep.c     |  4 +--
 builtin/ls-files.c | 13 +++++-----
 quote.c            | 71 ++++--------------------------------------------------
 quote.h            |  7 +++---
 wt-status.c        | 17 ++++++-------
 6 files changed, 33 insertions(+), 97 deletions(-)

diff --git a/builtin/clean.c b/builtin/clean.c
index 04e39..a93c3 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -56,7 +56,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
 	if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
 			!resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
 		if (!quiet) {
-			quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+			quote_path_relative(path->buf, &quoted, prefix);
 			printf(dry_run ?  _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
 					quoted.buf);
 		}
@@ -70,7 +70,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
 		/* an empty dir could be removed even if it is unreadble */
 		res = dry_run ? 0 : rmdir(path->buf);
 		if (res) {
-			quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+			quote_path_relative(path->buf, &quoted, prefix);
 			warning(_(msg_warn_remove_failed), quoted.buf);
 			*dir_gone = 0;
 		}
@@ -94,7 +94,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
 			if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))
 				ret = 1;
 			if (gone) {
-				quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+				quote_path_relative(path->buf, &quoted, prefix);
 				string_list_append(&dels, quoted.buf);
 			} else
 				*dir_gone = 0;
@@ -102,10 +102,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
 		} else {
 			res = dry_run ? 0 : unlink(path->buf);
 			if (!res) {
-				quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+				quote_path_relative(path->buf, &quoted, prefix);
 				string_list_append(&dels, quoted.buf);
 			} else {
-				quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+				quote_path_relative(path->buf, &quoted, prefix);
 				warning(_(msg_warn_remove_failed), quoted.buf);
 				*dir_gone = 0;
 				ret = 1;
@@ -127,7 +127,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
 		if (!res)
 			*dir_gone = 1;
 		else {
-			quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+			quote_path_relative(path->buf, &quoted, prefix);
 			warning(_(msg_warn_remove_failed), quoted.buf);
 			*dir_gone = 0;
 			ret = 1;
@@ -262,7 +262,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 				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);
+					qname = quote_path_relative(directory.buf, &buf, prefix);
 					printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
 				}
 			}
@@ -272,11 +272,11 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 				continue;
 			res = dry_run ? 0 : unlink(ent->name);
 			if (res) {
-				qname = quote_path_relative(ent->name, -1, &buf, prefix);
+				qname = quote_path_relative(ent->name, &buf, prefix);
 				warning(_(msg_warn_remove_failed), qname);
 				errors++;
 			} else if (!quiet) {
-				qname = quote_path_relative(ent->name, -1, &buf, prefix);
+				qname = quote_path_relative(ent->name, &buf, prefix);
 				printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
 			}
 		}
diff --git a/builtin/grep.c b/builtin/grep.c
index 159e65..b5222 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -286,7 +286,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
 	struct strbuf pathbuf = STRBUF_INIT;
 
 	if (opt->relative && opt->prefix_length) {
-		quote_path_relative(filename + tree_name_len, -1, &pathbuf,
+		quote_path_relative(filename + tree_name_len, &pathbuf,
 				    opt->prefix);
 		strbuf_insert(&pathbuf, 0, filename, tree_name_len);
 	} else {
@@ -318,7 +318,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)
 	struct strbuf buf = STRBUF_INIT;
 
 	if (opt->relative && opt->prefix_length)
-		quote_path_relative(filename, -1, &buf, opt->prefix);
+		quote_path_relative(filename, &buf, opt->prefix);
 	else
 		strbuf_addstr(&buf, filename);
 
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 220207..bb563 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -46,10 +46,9 @@ static const char *tag_modified = "";
 static const char *tag_skip_worktree = "";
 static const char *tag_resolve_undo = "";
 
-static void write_name(const char* name, size_t len)
+static void write_name(const char* name)
 {
-	write_name_quoted_relative(name, len, prefix, prefix_len, stdout,
-			line_terminator);
+	write_name_quoted_relative(name, prefix, stdout, line_terminator);
 }
 
 static void show_dir_entry(const char *tag, struct dir_entry *ent)
@@ -63,7 +62,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)
 		return;
 
 	fputs(tag, stdout);
-	write_name(ent->name, ent->len);
+	write_name(ent->name);
 }
 
 static void show_other_files(struct dir_struct *dir)
@@ -163,7 +162,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
 		       find_unique_abbrev(ce->sha1,abbrev),
 		       ce_stage(ce));
 	}
-	write_name(ce->name, ce_namelen(ce));
+	write_name(ce->name);
 	if (debug_mode) {
 		printf("  ctime: %d:%d\n", ce->ce_ctime.sec, ce->ce_ctime.nsec);
 		printf("  mtime: %d:%d\n", ce->ce_mtime.sec, ce->ce_mtime.nsec);
@@ -196,7 +195,7 @@ static void show_ru_info(void)
 			printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
 			       find_unique_abbrev(ui->sha1[i], abbrev),
 			       i + 1);
-			write_name(path, len);
+			write_name(path);
 		}
 	}
 }
@@ -389,7 +388,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, const char
 		if (found_dup)
 			continue;
 
-		name = quote_path_relative(pathspec[num], -1, &sb, prefix);
+		name = quote_path_relative(pathspec[num], &sb, prefix);
 		error("pathspec '%s' did not match any file(s) known to git.",
 		      name);
 		errors++;
diff --git a/quote.c b/quote.c
index 91122..97c57 100644
--- a/quote.c
+++ b/quote.c
@@ -312,81 +312,20 @@ void write_name_quotedpfx(const char *pfx, size_t pfxlen,
 	fputc(terminator, fp);
 }
 
-static const char *path_relative(const char *in, int len,
-				 struct strbuf *sb, const char *prefix,
-				 int prefix_len);
-
-void write_name_quoted_relative(const char *name, size_t len,
-				const char *prefix, size_t prefix_len,
+void write_name_quoted_relative(const char *name, const char *prefix,
 				FILE *fp, int terminator)
 {
-	struct strbuf sb = STRBUF_INIT;
-
-	name = path_relative(name, len, &sb, prefix, prefix_len);
+	name = relative_path(name, prefix);
 	write_name_quoted(name, fp, terminator);
-
-	strbuf_release(&sb);
-}
-
-/*
- * Give path as relative to prefix.
- *
- * The strbuf may or may not be used, so do not assume it contains the
- * returned path.
- */
-static const char *path_relative(const char *in, int len,
-				 struct strbuf *sb, const char *prefix,
-				 int prefix_len)
-{
-	int off, i;
-
-	if (len < 0)
-		len = strlen(in);
-	if (prefix_len < 0) {
-		if (prefix)
-			prefix_len = strlen(prefix);
-		else
-			prefix_len = 0;
-	}
-
-	off = 0;
-	i = 0;
-	while (i < prefix_len && i < len && prefix[i] == in[i]) {
-		if (prefix[i] == '/')
-			off = i + 1;
-		i++;
-	}
-	in += off;
-	len -= off;
-
-	if (i >= prefix_len)
-		return in;
-
-	strbuf_reset(sb);
-	strbuf_grow(sb, len);
-
-	while (i < prefix_len) {
-		if (prefix[i] == '/')
-			strbuf_addstr(sb, "../");
-		i++;
-	}
-	strbuf_add(sb, in, len);
-
-	return sb->buf;
 }
 
 /* quote path as relative to the given prefix */
-char *quote_path_relative(const char *in, int len,
-			  struct strbuf *out, const char *prefix)
+char *quote_path_relative(const char *in, struct strbuf *out,
+			  const char *prefix)
 {
-	struct strbuf sb = STRBUF_INIT;
-	const char *rel = path_relative(in, len, &sb, prefix, -1);
+	const char *rel = relative_path(in, prefix);
 	strbuf_reset(out);
 	quote_c_style_counted(rel, strlen(rel), out, NULL, 0);
-	strbuf_release(&sb);
-
-	if (!out->len)
-		strbuf_addstr(out, "./");
 
 	return out->buf;
 }
diff --git a/quote.h b/quote.h
index 13315..27022 100644
--- a/quote.h
+++ b/quote.h
@@ -60,12 +60,11 @@ extern void quote_two_c_style(struct strbuf *, const char *, const char *, int);
 extern void write_name_quoted(const char *name, FILE *, int terminator);
 extern void write_name_quotedpfx(const char *pfx, size_t pfxlen,
                                  const char *name, FILE *, int terminator);
-extern void write_name_quoted_relative(const char *name, size_t len,
-		const char *prefix, size_t prefix_len,
-		FILE *fp, int terminator);
+extern void write_name_quoted_relative(const char *name, const char *prefix,
+				       FILE *fp, int terminator);
 
 /* quote path as relative to the given prefix */
-extern char *quote_path_relative(const char *in, int len,
+extern char *quote_path_relative(const char *in,
 			  struct strbuf *out, const char *prefix);
 
 /* quoting as a string literal for other languages */
diff --git a/wt-status.c b/wt-status.c
index bf84a..002ac 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -243,7 +243,7 @@ static void wt_status_print_unmerged_data(struct wt_status *s,
 	struct strbuf onebuf = STRBUF_INIT;
 	const char *one, *how = _("bug");
 
-	one = quote_path(it->string, -1, &onebuf, s->prefix);
+	one = quote_path(it->string, &onebuf, s->prefix);
 	status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 	switch (d->stagemask) {
 	case 1: how = _("both deleted:"); break;
@@ -297,8 +297,8 @@ static void wt_status_print_change_data(struct wt_status *s,
 		    change_type);
 	}
 
-	one = quote_path(one_name, -1, &onebuf, s->prefix);
-	two = quote_path(two_name, -1, &twobuf, s->prefix);
+	one = quote_path(one_name, &onebuf, s->prefix);
+	two = quote_path(two_name, &twobuf, s->prefix);
 
 	status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 	switch (status) {
@@ -706,8 +706,7 @@ static void wt_status_print_other(struct wt_status *s,
 		struct string_list_item *it;
 		const char *path;
 		it = &(l->items[i]);
-		path = quote_path(it->string, strlen(it->string),
-				  &buf, s->prefix);
+		path = quote_path(it->string, &buf, s->prefix);
 		if (column_active(s->colopts)) {
 			string_list_append(&output, path);
 			continue;
@@ -1289,7 +1288,7 @@ static void wt_shortstatus_unmerged(struct string_list_item *it,
 	} else {
 		struct strbuf onebuf = STRBUF_INIT;
 		const char *one;
-		one = quote_path(it->string, -1, &onebuf, s->prefix);
+		one = quote_path(it->string, &onebuf, s->prefix);
 		printf(" %s\n", one);
 		strbuf_release(&onebuf);
 	}
@@ -1317,7 +1316,7 @@ static void wt_shortstatus_status(struct string_list_item *it,
 		struct strbuf onebuf = STRBUF_INIT;
 		const char *one;
 		if (d->head_path) {
-			one = quote_path(d->head_path, -1, &onebuf, s->prefix);
+			one = quote_path(d->head_path, &onebuf, s->prefix);
 			if (*one != '"' && strchr(one, ' ') != NULL) {
 				putchar('"');
 				strbuf_addch(&onebuf, '"');
@@ -1326,7 +1325,7 @@ static void wt_shortstatus_status(struct string_list_item *it,
 			printf("%s -> ", one);
 			strbuf_release(&onebuf);
 		}
-		one = quote_path(it->string, -1, &onebuf, s->prefix);
+		one = quote_path(it->string, &onebuf, s->prefix);
 		if (*one != '"' && strchr(one, ' ') != NULL) {
 			putchar('"');
 			strbuf_addch(&onebuf, '"');
@@ -1345,7 +1344,7 @@ static void wt_shortstatus_other(struct string_list_item *it,
 	} else {
 		struct strbuf onebuf = STRBUF_INIT;
 		const char *one;
-		one = quote_path(it->string, -1, &onebuf, s->prefix);
+		one = quote_path(it->string, &onebuf, s->prefix);
 		color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign);
 		printf(" %s\n", one);
 		strbuf_release(&onebuf);
-- 
1.8.3.rc1.404.ga32c147

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [RFC 0/2] refactor relative_path in path.c
  2013-05-15 15:18     ` [RFC 0/2] refactor relative_path in path.c Jiang Xin
@ 2013-05-15 18:24       ` Junio C Hamano
  0 siblings, 0 replies; 19+ messages in thread
From: Junio C Hamano @ 2013-05-15 18:24 UTC (permalink / raw)
  To: Jiang Xin; +Cc: Eric Sunshine, Matthieu Moy, Git List

Jiang Xin <worldhello.net@gmail.com> writes:

> These two patches enhance relative_path() in path.c, so that function
> relative_path() will return real relative path, not a path strip off
> the prefix.
>
> The 2nd patch is a bit aggressive, it refactor all related functions,
> remove unnecessary arguments: len and/or prefix_len.

I did not particularly find the second one "aggressive"; it would
have been much more pleasant to review if the "drop unused 'len'"
part were made into a separate patch [3/2] as a follow-up, though.

It is a bit sad that relative_path() in [1/2] uses a single static
and fixed sized buffer.  How is the new implementation making sure
the expanded result does not overflow the buf[]?

^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2013-05-15 18:24 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-05-14  8:45 [PATCH v9 0/9] interactive git-clean Jiang Xin
2013-05-14  8:45 ` [PATCH v9 1/9] git-clean: refactor git-clean into two phases Jiang Xin
2013-05-14 23:27   ` Junio C Hamano
2013-05-15  0:40     ` Jiang Xin
2013-05-15 15:03       ` Junio C Hamano
2013-05-15 15:07         ` Jiang Xin
2013-05-15 15:18     ` [RFC 0/2] refactor relative_path in path.c Jiang Xin
2013-05-15 18:24       ` Junio C Hamano
2013-05-15 15:18     ` [RFC 1/2] path.c: refactor relative_path(), not only strip prefix Jiang Xin
2013-05-15 15:18     ` [RFC 2/2] quote.c: remove path_relative, use relative_path instead Jiang Xin
2013-05-14  8:45 ` [PATCH v9 2/9] git-clean: add support for -i/--interactive Jiang Xin
2013-05-14  8:45 ` [PATCH v9 3/9] git-clean: show items of del_list in columns Jiang Xin
2013-05-14  8:45 ` [PATCH v9 4/9] git-clean: add colors to interactive git-clean Jiang Xin
2013-05-14  8:45 ` [PATCH v9 5/9] git-clean: use a git-add-interactive compatible UI Jiang Xin
2013-05-14  8:45 ` [PATCH v9 6/9] git-clean: add filter by pattern interactive action Jiang Xin
2013-05-14  8:45 ` [PATCH v9 7/9] git-clean: add select by numbers " Jiang Xin
2013-05-14  8:45 ` [PATCH v9 8/9] git-clean: add ask each " Jiang Xin
2013-05-14  8:45 ` [PATCH v9 9/9] git-clean: add documentation for interactive git-clean Jiang Xin
2013-05-14 23:27 ` [PATCH v9 0/9] " Junio C Hamano

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).