* [PATCH v6 1/7] Add support for -i/--interactive to git-clean
2013-05-06 19:18 ` [PATCH v6 0/7] " Jiang Xin
@ 2013-05-06 19:18 ` Jiang Xin
2013-05-06 19:18 ` [PATCH v6 2/7] Show items of interactive git-clean in columns Jiang Xin
` (5 subsequent siblings)
6 siblings, 0 replies; 22+ messages in thread
From: Jiang Xin @ 2013-05-06 19:18 UTC (permalink / raw)
To: Junio C Hamano, Matthieu Moy, Eric Sunshine, Thomas Rast
Cc: Git List, Jiang Xin
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 <worldhello.net@gmail.com>
Suggested-by: Junio C Hamano <gitster@pobox.com>
Spelling-checked-by: Eric Sunshine <sunshine@sunshineco.com>
Comments-by: Matthieu Moy <Matthieu.Moy@imag.fr>
Suggested-by: Eric Sunshine <sunshine@sunshineco.com>
---
Documentation/git-clean.txt | 15 +++-
builtin/clean.c | 195 ++++++++++++++++++++++++++++++++++++++++----
2 files changed, 191 insertions(+), 19 deletions(-)
diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt
index bdc3a..f5572 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,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::
diff --git a/builtin/clean.c b/builtin/clean.c
index 04e39..29fbf 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -15,9 +15,12 @@
#include "quote.h"
static int force = -1; /* unset */
+static int interactive;
+static struct string_list del_list = STRING_LIST_INIT_DUP;
+static const char **the_prefix;
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
};
@@ -142,6 +145,139 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
return ret;
}
+void edit_by_patterns_cmd()
+{
+ struct dir_struct dir;
+ struct strbuf confirm = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf **ignore_list;
+ struct string_list_item *item;
+ struct exclude_list *el;
+ const char *qname;
+ int changed = -1, i;
+
+ while (1) {
+ /* dels list may become empty when we run string_list_remove_empty_items later */
+ 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) {
+ int dtype = DT_UNKNOWN;
+ const char *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);
+}
+
+void interactive_main_loop()
+{
+ struct strbuf confirm = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT;
+ struct string_list_item *item;
+ const char *qname;
+
+ /* dels list may become empty after return back from edit mode */
+ while (del_list.nr) {
+ printf_ln(Q_("Would remove the following item:",
+ "Would remove the following items:",
+ del_list.nr));
+ putchar('\n');
+
+ /* Display dels in "Would remove ..." format */
+ for_each_string_list_item(item, &del_list) {
+ qname = quote_path_relative(item->string, -1, &buf, *the_prefix);
+ printf(_(msg_would_remove), qname);
+ }
+ putchar('\n');
+
+ /* Confirmation dialog */
+ printf(_("Remove ([y]es/[n]o/[e]dit) ? "));
+ if (strbuf_getline(&confirm, stdin, '\n') != EOF) {
+ strbuf_trim(&confirm);
+ } else {
+ /* Ctrl-D is the same as "quit" */
+ string_list_clear(&del_list, 0);
+ putchar('\n');
+ printf_ln("Bye.");
+ break;
+ }
+
+ if (confirm.len) {
+ if (!strncasecmp(confirm.buf, "yes", confirm.len)) {
+ break;
+ } else if (!strncasecmp(confirm.buf, "no", confirm.len) ||
+ !strncasecmp(confirm.buf, "quit", confirm.len)) {
+ string_list_clear(&del_list, 0);
+ printf_ln("Bye.");
+ break;
+ } else if (!strncasecmp(confirm.buf, "edit", confirm.len)) {
+ edit_by_patterns_cmd();
+ } else {
+ continue;
+ }
+ }
+ }
+
+ strbuf_release(&buf);
+ strbuf_release(&confirm);
+}
+
int cmd_clean(int argc, const char **argv, const char *prefix)
{
int i, res;
@@ -154,12 +290,14 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
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[] = {
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"),
@@ -176,7 +314,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
else
config_set = 1;
- argc = parse_options(argc, argv, prefix, options, builtin_clean_usage,
+ the_prefix = &prefix;
+
+ argc = parse_options(argc, argv, *the_prefix, options, builtin_clean_usage,
0);
memset(&dir, 0, sizeof(dir));
@@ -186,12 +326,16 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (ignored && ignored_only)
die(_("-x and -X cannot be used together"));
- if (!dry_run && !force) {
+ if (interactive) {
+ if (!isatty(0) || !isatty(1))
+ die(_("interactive clean can not run without a valid tty; "
+ "refusing to clean"));
+ } else if (!dry_run && !force) {
if (config_set)
- die(_("clean.requireForce set to true and neither -n nor -f given; "
+ die(_("clean.requireForce set to true and neither -i, -n nor -f given; "
"refusing to clean"));
else
- die(_("clean.requireForce defaults to true and neither -n nor -f given; "
+ die(_("clean.requireForce defaults to true and neither -i, -n nor -f given; "
"refusing to clean"));
}
@@ -210,7 +354,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
for (i = 0; i < exclude_list.nr; i++)
add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1));
- pathspec = get_pathspec(prefix, argv);
+ pathspec = get_pathspec(*the_prefix, argv);
fill_directory(&dir, pathspec);
@@ -257,26 +401,42 @@ 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);
- }
+ string_list_append(&del_list, ent->name);
}
- strbuf_reset(&directory);
} else {
if (pathspec && !matches)
continue;
- res = dry_run ? 0 : unlink(ent->name);
+ string_list_append(&del_list, ent->name);
+ }
+ }
+
+ if (interactive && del_list.nr > 0 && !dry_run && isatty(0) && isatty(1))
+ interactive_main_loop();
+
+ for_each_string_list_item(item, &del_list) {
+ struct stat st;
+
+ if (lstat(item->string, &st))
+ continue;
+
+ if (S_ISDIR(st.st_mode)) {
+ strbuf_addstr(&directory, item->string);
+ if (remove_dirs(&directory, *the_prefix, rm_flags, dry_run, quiet, &gone))
+ errors++;
+ if (gone && !quiet) {
+ qname = quote_path_relative(directory.buf, directory.len, &buf, *the_prefix);
+ printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
+ }
+ strbuf_reset(&directory);
+ } else {
+ res = dry_run ? 0 : unlink(item->string);
if (res) {
- qname = quote_path_relative(ent->name, -1, &buf, prefix);
+ qname = quote_path_relative(item->string, -1, &buf, *the_prefix);
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, *the_prefix);
printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
}
}
@@ -285,5 +445,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
strbuf_release(&directory);
string_list_clear(&exclude_list, 0);
+ string_list_clear(&del_list, 0);
return (errors != 0);
}
--
1.8.3.rc1.338.gb35aa5d
^ permalink raw reply [flat|nested] 22+ messages in thread* [PATCH v6 2/7] Show items of interactive git-clean in columns
2013-05-06 19:18 ` [PATCH v6 0/7] " Jiang Xin
2013-05-06 19:18 ` [PATCH v6 1/7] Add support for -i/--interactive to git-clean Jiang Xin
@ 2013-05-06 19:18 ` Jiang Xin
2013-05-06 19:18 ` [PATCH v6 3/7] Add colors to interactive git-clean Jiang Xin
` (4 subsequent siblings)
6 siblings, 0 replies; 22+ messages in thread
From: Jiang Xin @ 2013-05-06 19:18 UTC (permalink / raw)
To: Junio C Hamano, Matthieu Moy, Eric Sunshine, Thomas Rast
Cc: Git List, 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 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 <worldhello.net@gmail.com>
Comments-by: Matthieu Moy <Matthieu.Moy@imag.fr>
---
Documentation/config.txt | 4 ++++
builtin/clean.c | 58 +++++++++++++++++++++++++++++++++---------------
2 files changed, 44 insertions(+), 18 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 6e53f..98bfa 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 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.
diff --git a/builtin/clean.c b/builtin/clean.c
index 29fbf..43383 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -13,11 +13,13 @@
#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 const char **the_prefix;
+static unsigned int colopts;
static const char *const builtin_clean_usage[] = {
N_("git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
@@ -32,8 +34,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);
}
@@ -145,6 +152,33 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
return ret;
}
+void pretty_print_dels()
+{
+ 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, *the_prefix);
+ 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);
+}
+
void edit_by_patterns_cmd()
{
struct dir_struct dir;
@@ -153,7 +187,6 @@ void edit_by_patterns_cmd()
struct strbuf **ignore_list;
struct string_list_item *item;
struct exclude_list *el;
- const char *qname;
int changed = -1, i;
while (1) {
@@ -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()
void interactive_main_loop()
{
struct strbuf confirm = STRBUF_INIT;
- struct strbuf buf = STRBUF_INIT;
- struct string_list_item *item;
- const char *qname;
/* dels list may become empty after return back from edit mode */
while (del_list.nr) {
+ 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) ? "));
@@ -274,7 +297,6 @@ void interactive_main_loop()
}
}
- strbuf_release(&buf);
strbuf_release(&confirm);
}
--
1.8.3.rc1.338.gb35aa5d
^ permalink raw reply [flat|nested] 22+ messages in thread* [PATCH v6 3/7] Add colors to interactive git-clean
2013-05-06 19:18 ` [PATCH v6 0/7] " Jiang Xin
2013-05-06 19:18 ` [PATCH v6 1/7] Add support for -i/--interactive to git-clean Jiang Xin
2013-05-06 19:18 ` [PATCH v6 2/7] Show items of interactive git-clean in columns Jiang Xin
@ 2013-05-06 19:18 ` Jiang Xin
2013-05-06 19:18 ` [PATCH v6 4/7] git-clean: use a git-add-interactive compatible UI Jiang Xin
` (3 subsequent siblings)
6 siblings, 0 replies; 22+ messages in thread
From: Jiang Xin @ 2013-05-06 19:18 UTC (permalink / raw)
To: Junio C Hamano, Matthieu Moy, Eric Sunshine, Thomas Rast
Cc: Git List, Jiang Xin
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 <worldhello.net@gmail.com>
Comments-by: Matthieu Moy <Matthieu.Moy@imag.fr>
---
builtin/clean.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 77 insertions(+), 1 deletion(-)
diff --git a/builtin/clean.c b/builtin/clean.c
index 43383..6bda3 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;
@@ -32,16 +33,81 @@ 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, int ofs)
+{
+ if (!strcasecmp(var+ofs, "reset"))
+ return CLEAN_COLOR_RESET;
+ if (!strcasecmp(var+ofs, "plain"))
+ return CLEAN_COLOR_PLAIN;
+ if (!strcasecmp(var+ofs, "prompt"))
+ return CLEAN_COLOR_PROMPT;
+ if (!strcasecmp(var+ofs, "header"))
+ return CLEAN_COLOR_HEADER;
+ if (!strcasecmp(var+ofs, "help"))
+ return CLEAN_COLOR_HELP;
+ if (!strcasecmp(var+ofs, "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, 18);
+ 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)
@@ -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;
}
@@ -203,7 +271,9 @@ void edit_by_patterns_cmd()
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 {
@@ -243,7 +313,9 @@ void edit_by_patterns_cmd()
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);
@@ -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 {
--
1.8.3.rc1.338.gb35aa5d
^ permalink raw reply [flat|nested] 22+ messages in thread* [PATCH v6 4/7] git-clean: use a git-add-interactive compatible UI
2013-05-06 19:18 ` [PATCH v6 0/7] " Jiang Xin
` (2 preceding siblings ...)
2013-05-06 19:18 ` [PATCH v6 3/7] Add colors to interactive git-clean Jiang Xin
@ 2013-05-06 19:18 ` Jiang Xin
2013-05-07 4:16 ` Jiang Xin
2013-05-06 19:18 ` [PATCH v6 5/7] git-clean: interactive cleaning by select numbers Jiang Xin
` (2 subsequent siblings)
6 siblings, 1 reply; 22+ messages in thread
From: Jiang Xin @ 2013-05-06 19:18 UTC (permalink / raw)
To: Junio C Hamano, Matthieu Moy, Eric Sunshine, Thomas Rast
Cc: Git List, Jiang Xin
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 <worldhello.net@gmail.com>
---
builtin/clean.c | 410 ++++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 367 insertions(+), 43 deletions(-)
diff --git a/builtin/clean.c b/builtin/clean.c
index 6bda3..3b9f3 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -16,6 +16,35 @@
#include "column.h"
#include "color.h"
+#define MENU_OPTS_SINGLETON 01
+#define MENU_OPTS_IMMEDIATE 02
+#define MENU_OPTS_LIST_ONLY 04
+
+#define MENU_RETURN_NO_LOOP 10
+
+struct menu_opts {
+ const char *header;
+ const char *prompt;
+ int flag;
+};
+
+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;
+};
+
+struct menu_item {
+ char hotkey;
+ char *title;
+ int (*fn)();
+};
+
static int force = -1; /* unset */
static int interactive;
static struct string_list del_list = STRING_LIST_INIT_DUP;
@@ -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);
}
-void edit_by_patterns_cmd()
+void pretty_print_menus(struct string_list *menu_list)
+{
+ struct strbuf buf = STRBUF_INIT;
+ unsigned int local_colopts = 0;
+ struct column_options copts;
+
+ /*
+ * always enable column display, we only consult column.*
+ * about layout strategy and stuff
+ */
+ 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);
+}
+
+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);
+}
+
+/*
+ * 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.
+ */
+int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
+{
+ static struct string_list menu_list = STRING_LIST_INIT_DUP;
+ struct strbuf menu = STRBUF_INIT;
+ struct strbuf choice = STRBUF_INIT;
+ struct strbuf **choice_list;
+ int *chosen, *result;
+ char *p;
+ int nr = 0;
+ int i, j;
+ int eof = 0;
+
+ chosen = xmalloc(sizeof(int) * stuff->nr);
+ memset(chosen, 0, sizeof(int) * stuff->nr);
+
+ while (1) {
+ int i = 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) {
+ struct menu_item *item;
+
+ item = (struct menu_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);
+ }
+ } 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) {
+ const char *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++) {
+ int choose = 1;
+ int bottom = 0, top = 0;
+ char *p;
+ int is_range = 0;
+ int is_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;
+ }
+ } else if (!isdigit(*p)) {
+ is_number = 0;
+ is_range = 0;
+ break;
+ }
+ }
+
+ if (is_number) {
+ bottom = atoi(choice_list[i]->buf);
+ top = bottom;
+ } else if (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);
+ }
+ } else if (!strcmp(choice_list[i]->buf, "*")) {
+ bottom = 1;
+ top = stuff->nr;
+ } else {
+ if (MENU_STUFF_TYPE_MENU_ITEM == stuff->type) {
+ struct menu_item *item;
+
+ item = (struct menu_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;
+ }
+ }
+ } else if (MENU_STUFF_TYPE_STRING_LIST == stuff->type) {
+ struct string_list_item *item;
+
+ item = ((struct string_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;
+ } else if (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);
+ return result;
+}
+
+int clean_cmd()
+{
+ return MENU_RETURN_NO_LOOP;
+}
+
+int edit_by_patterns_cmd()
{
struct dir_struct dir;
struct strbuf confirm = 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);
+ return 0;
}
-void interactive_main_loop()
+int quit_cmd()
{
- struct strbuf confirm = STRBUF_INIT;
+ string_list_clear(&del_list, 0);
+ printf_ln(_("Bye."));
+ return MENU_RETURN_NO_LOOP;
+}
+int help_cmd(int x)
+{
+ 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);
+ return 0;
+}
+
+void interactive_main_loop()
+{
/* dels list may become empty after return back from edit mode */
while (del_list.nr) {
- putchar('\n');
+ struct menu_opts menu_opts;
+ struct menu_stuff menu_stuff;
+ struct menu_item menus[] = {
+ {'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(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');
- /* 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;
- } else if (!strncasecmp(confirm.buf, "no", confirm.len) ||
- !strncasecmp(confirm.buf, "quit", confirm.len)) {
- string_list_clear(&del_list, 0);
- printf_ln("Bye.");
- break;
- } else if (!strncasecmp(confirm.buf, "edit", confirm.len)) {
- edit_by_patterns_cmd();
- } else {
+ /* main menu */
+ chosen = list_and_choose(&menu_opts, &menu_stuff);
+
+ if (*chosen != EOF) {
+ int ret;
+ 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;
+ }
}
int cmd_clean(int argc, const char **argv, const char *prefix)
@@ -499,9 +824,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
}
if (S_ISDIR(st.st_mode)) {
- if (remove_directories || (matches == MATCHED_EXACTLY)) {
+ if (remove_directories || (matches == MATCHED_EXACTLY))
string_list_append(&del_list, ent->name);
- }
} else {
if (pathspec && !matches)
continue;
--
1.8.3.rc1.338.gb35aa5d
^ permalink raw reply [flat|nested] 22+ messages in thread* Re: [PATCH v6 4/7] git-clean: use a git-add-interactive compatible UI
2013-05-06 19:18 ` [PATCH v6 4/7] git-clean: use a git-add-interactive compatible UI Jiang Xin
@ 2013-05-07 4:16 ` Jiang Xin
2013-05-07 15:20 ` Junio C Hamano
0 siblings, 1 reply; 22+ messages in thread
From: Jiang Xin @ 2013-05-07 4:16 UTC (permalink / raw)
To: Junio C Hamano, Matthieu Moy, Eric Sunshine, Thomas Rast
Cc: Git List, Jiang Xin
2013/5/7 Jiang Xin <worldhello.net@gmail.com>:
> 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 <worldhello.net@gmail.com>
> ---
> builtin/clean.c | 410 ++++++++++++++++++++++++++++++++++++++++++++++++++------
> 1 file changed, 367 insertions(+), 43 deletions(-)
>
> diff --git a/builtin/clean.c b/builtin/clean.c
> index 6bda3..3b9f3 100644
> --- a/builtin/clean.c
> +++ b/builtin/clean.c
> @@ -16,6 +16,35 @@
> #include "column.h"
> #include "color.h"
>
> +#define MENU_OPTS_SINGLETON 01
> +#define MENU_OPTS_IMMEDIATE 02
> +#define MENU_OPTS_LIST_ONLY 04
> +
> +#define MENU_RETURN_NO_LOOP 10
> +
> +struct menu_opts {
> + const char *header;
> + const char *prompt;
> + int flag;
> +};
> +
> +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;
> +};
> +
> +struct menu_item {
> + char hotkey;
> + char *title;
> + int (*fn)();
> +};
> +
> static int force = -1; /* unset */
> static int interactive;
> static struct string_list del_list = STRING_LIST_INIT_DUP;
> @@ -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);
> }
>
> -void edit_by_patterns_cmd()
> +void pretty_print_menus(struct string_list *menu_list)
> +{
> + struct strbuf buf = STRBUF_INIT;
unused buf should be deleted.
> + unsigned int local_colopts = 0;
> + struct column_options copts;
> +
> + /*
> + * always enable column display, we only consult column.*
> + * about layout strategy and stuff
> + */
remove the above comments.
> + 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);
remove strbuf_release of unused variable : buf.
> +}
> +
> +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);
> +}
> +
> +/*
> + * 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.
> + */
> +int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
> +{
> + static struct string_list menu_list = STRING_LIST_INIT_DUP;
> + struct strbuf menu = STRBUF_INIT;
> + struct strbuf choice = STRBUF_INIT;
> + struct strbuf **choice_list;
> + int *chosen, *result;
> + char *p;
> + int nr = 0;
> + int i, j;
> + int eof = 0;
> +
> + chosen = xmalloc(sizeof(int) * stuff->nr);
> + memset(chosen, 0, sizeof(int) * stuff->nr);
> +
> + while (1) {
> + int i = 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) {
> + struct menu_item *item;
> +
> + item = (struct menu_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);
> + }
> + } else if (MENU_STUFF_TYPE_STRING_LIST == stuff->type) {
> + struct string_list_item *item;
> + struct strbuf buf = STRBUF_INIT;
should call strbuf_release later
> + i = 0;
> +
> + for_each_string_list_item(item, (struct string_list *)stuff->stuff) {
> + const char *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);
> + }
+ strbuf_release(&buf);
> + }
> +
> + 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);
Should be freed later
> + for (i = 0; choice_list[i]; i++) {
> + int choose = 1;
> + int bottom = 0, top = 0;
> + char *p;
> + int is_range = 0;
> + int is_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;
> + }
> + } else if (!isdigit(*p)) {
> + is_number = 0;
> + is_range = 0;
> + break;
> + }
> + }
> +
> + if (is_number) {
> + bottom = atoi(choice_list[i]->buf);
> + top = bottom;
> + } else if (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);
> + }
> + } else if (!strcmp(choice_list[i]->buf, "*")) {
> + bottom = 1;
> + top = stuff->nr;
> + } else {
> + if (MENU_STUFF_TYPE_MENU_ITEM == stuff->type) {
> + struct menu_item *item;
> +
> + item = (struct menu_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;
> + }
> + }
> + } else if (MENU_STUFF_TYPE_STRING_LIST == stuff->type) {
> + struct string_list_item *item;
> +
> + item = ((struct string_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++;
> + }
> + }
+ strbuf_list_free(choice_list);
> +
> + if (opts->flag & MENU_OPTS_SINGLETON) {
> + if (nr)
> + break;
> + } else if (opts->flag & MENU_OPTS_IMMEDIATE) {
> + break;
> + }
> + }
> +
> +
> + if (eof) {
> + result = xmalloc(sizeof(int) * 2);
> + result[0] = EOF;
> + result[1] = 0;
Allocate one element is OK, like:
+ result = xmalloc(sizeof(int));
+ *result = EOF;
> + } else {
> + result = xmalloc(sizeof(int) * (nr + 1));
> + memset(result, 0, sizeof(int) * (nr + 1));
Add initial for j here:
+ j = 0;
> + 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);
> + return result;
> +}
--
Jiang Xin
^ permalink raw reply [flat|nested] 22+ messages in thread* Re: [PATCH v6 4/7] git-clean: use a git-add-interactive compatible UI
2013-05-07 4:16 ` Jiang Xin
@ 2013-05-07 15:20 ` Junio C Hamano
2013-05-08 0:28 ` Jiang Xin
0 siblings, 1 reply; 22+ messages in thread
From: Junio C Hamano @ 2013-05-07 15:20 UTC (permalink / raw)
To: Jiang Xin; +Cc: Matthieu Moy, Eric Sunshine, Thomas Rast, Git List
Jiang Xin <worldhello.net@gmail.com> writes:
> 2013/5/7 Jiang Xin <worldhello.net@gmail.com>:
>> 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 <worldhello.net@gmail.com>
>> ---
>> builtin/clean.c | 410 ++++++++++++++++++++++++++++++++++++++++++++++++++------
>> 1 file changed, 367 insertions(+), 43 deletions(-)
>...
>> -void edit_by_patterns_cmd()
>> +void pretty_print_menus(struct string_list *menu_list)
>> +{
>> + struct strbuf buf = STRBUF_INIT;
> unused buf should be deleted.
>
>> + unsigned int local_colopts = 0;
>> + struct column_options copts;
>> +
>> + /*
>> + * always enable column display, we only consult column.*
>> + * about layout strategy and stuff
>> + */
> remove the above comments.
>
>> + 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);
> remove strbuf_release of unused variable : buf.
>
>> +}
>...
>> + } else if (MENU_STUFF_TYPE_STRING_LIST == stuff->type) {
>> + struct string_list_item *item;
>> + struct strbuf buf = STRBUF_INIT;
> should call strbuf_release later
> ...
>> + } else {
>> + result = xmalloc(sizeof(int) * (nr + 1));
>> + memset(result, 0, sizeof(int) * (nr + 1));
>
> Add initial for j here:
What is this message trying to achieve? "self review"???
A bit puzzled....
^ permalink raw reply [flat|nested] 22+ messages in thread* Re: [PATCH v6 4/7] git-clean: use a git-add-interactive compatible UI
2013-05-07 15:20 ` Junio C Hamano
@ 2013-05-08 0:28 ` Jiang Xin
0 siblings, 0 replies; 22+ messages in thread
From: Jiang Xin @ 2013-05-08 0:28 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Matthieu Moy, Eric Sunshine, Thomas Rast, Git List
2013/5/7 Junio C Hamano <gitster@pobox.com>:
> What is this message trying to achieve? "self review"???
>
> A bit puzzled....
Maybe I should send a new rerolled patch series after this. Yesterday I
wanted to wait for a while to see suggestions and reviews from others.
--
Jiang Xin
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v6 5/7] git-clean: interactive cleaning by select numbers
2013-05-06 19:18 ` [PATCH v6 0/7] " Jiang Xin
` (3 preceding siblings ...)
2013-05-06 19:18 ` [PATCH v6 4/7] git-clean: use a git-add-interactive compatible UI Jiang Xin
@ 2013-05-06 19:18 ` Jiang Xin
2013-05-06 19:18 ` [PATCH v6 6/7] git-clean: rm -i style interactive cleaning Jiang Xin
2013-05-06 19:18 ` [PATCH v6 7/7] git-clean: update document for interactive git-clean Jiang Xin
6 siblings, 0 replies; 22+ messages in thread
From: Jiang Xin @ 2013-05-06 19:18 UTC (permalink / raw)
To: Junio C Hamano, Matthieu Moy, Eric Sunshine, Thomas Rast
Cc: Git List, 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 3b9f3..3b07f 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -622,6 +622,43 @@ int edit_by_patterns_cmd()
return 0;
}
+int edit_by_numbers_cmd()
+{
+ 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 = "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';
+ } else if (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);
+ return 0;
+}
+
int quit_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()
struct menu_item menus[] = {
{'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},
};
--
1.8.3.rc1.338.gb35aa5d
^ permalink raw reply [flat|nested] 22+ messages in thread* [PATCH v6 6/7] git-clean: rm -i style interactive cleaning
2013-05-06 19:18 ` [PATCH v6 0/7] " Jiang Xin
` (4 preceding siblings ...)
2013-05-06 19:18 ` [PATCH v6 5/7] git-clean: interactive cleaning by select numbers Jiang Xin
@ 2013-05-06 19:18 ` Jiang Xin
2013-05-06 19:18 ` [PATCH v6 7/7] git-clean: update document for interactive git-clean Jiang Xin
6 siblings, 0 replies; 22+ messages in thread
From: Jiang Xin @ 2013-05-06 19:18 UTC (permalink / raw)
To: Junio C Hamano, Matthieu Moy, Eric Sunshine, Thomas Rast
Cc: Git List, Jiang Xin
Add a "rm -i" style interactive cleaning method. User must confirm one
by one before starting to delete.
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 3b07f..f36ad 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -659,6 +659,40 @@ int edit_by_numbers_cmd()
return 0;
}
+int rm_i_cmd()
+{
+ 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, *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);
+ return MENU_RETURN_NO_LOOP;
+}
+
int quit_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},
};
--
1.8.3.rc1.338.gb35aa5d
^ permalink raw reply [flat|nested] 22+ messages in thread* [PATCH v6 7/7] git-clean: update document for interactive git-clean
2013-05-06 19:18 ` [PATCH v6 0/7] " Jiang Xin
` (5 preceding siblings ...)
2013-05-06 19:18 ` [PATCH v6 6/7] git-clean: rm -i style interactive cleaning Jiang Xin
@ 2013-05-06 19:18 ` Jiang Xin
2013-05-07 4:20 ` Jiang Xin
6 siblings, 1 reply; 22+ messages in thread
From: Jiang Xin @ 2013-05-06 19:18 UTC (permalink / raw)
To: Junio C Hamano, Matthieu Moy, Eric Sunshine, Thomas Rast
Cc: Git List, Jiang Xin
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
---
Documentation/git-clean.txt | 70 ++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 63 insertions(+), 7 deletions(-)
diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt
index f5572..56d60 100644
--- a/Documentation/git-clean.txt
+++ b/Documentation/git-clean.txt
@@ -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]
--
1.8.3.rc1.338.gb35aa5d
^ permalink raw reply [flat|nested] 22+ messages in thread* Re: [PATCH v6 7/7] git-clean: update document for interactive git-clean
2013-05-06 19:18 ` [PATCH v6 7/7] git-clean: update document for interactive git-clean Jiang Xin
@ 2013-05-07 4:20 ` Jiang Xin
0 siblings, 0 replies; 22+ messages in thread
From: Jiang Xin @ 2013-05-07 4:20 UTC (permalink / raw)
To: Junio C Hamano, Matthieu Moy, Eric Sunshine, Thomas Rast
Cc: Git List, Jiang Xin
2013/5/7 Jiang Xin <worldhello.net@gmail.com>:
> Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
> ---
> Documentation/git-clean.txt | 70 ++++++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 63 insertions(+), 7 deletions(-)
>
> diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt
> index f5572..56d60 100644
> --- a/Documentation/git-clean.txt
> +++ b/Documentation/git-clean.txt
> @@ -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
^ permalink raw reply [flat|nested] 22+ messages in thread