{"thread":{"id":"33711","subject":"[PATCH v5 0/3] interactive git clean","startedAt":"2013-05-03T03:49:52Z","lastAt":"2013-05-08T00:28:37Z","messageCount":22,"participants":["Jiang Xin","Eric Sunshine","Junio C Hamano","Matthieu Moy"],"isPatch":true,"patchVersion":5,"patchTotal":3},"messages":[{"id":"216323","messageId":"cover.1367551846.git.worldhello.net@gmail.com","threadId":"33711","inReplyTo":null,"subject":"[PATCH v5 0/3] interactive git clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-03T03:49:52Z","receivedAt":"2013-05-03T03:49:52Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"The interactive git clean combines `git clean -n` and `git clean -f`\ntogether to do safe cleaning, and has more features.\n\nFirst it displays what would be removed in columns (so that you can\nsee them all in one screen). The user must confirm before actually\ncleaning.\n\n    WARNING: The following items will be removed permanently. Press \"y\"\n    WARNING: to start cleaning, and press \"n\" to abort the cleaning.\n    WARNING: You can also enter the \"edit\" mode, and select items\n    WARNING: to be excluded from the cleaning.\n\n      What would be removed...    What would be removed...\n      What would be removed...    What would be removed...\n    \n    Remove (yes/no/Edit) ? \n\nIn this confirmation dialog, the user has three choices:\n\n * Yes: Start to do cleaning.\n * No:  Nothing will be deleted.\n * Edit (default for the first time): Enter the edit mode.\n\nWhen the user chooses the edit mode, it would look like this:\n\n\n    NOTE: Will remove the following items. You can input space-seperated\n    NOTE: patterns (just like .gitignore) to exclude items from deletion,\n    NOTE: or press ENTER to continue.\n    \n      What would be removed...    What would be removed...\n      What would be removed...    What would be removed...\n    \n    Input ignore patterns> \n\n\nThe user can input space-separated patterns (the same syntax as gitignore),\nand each clean candidate that matches with one of the patterns will be\nexcluded from cleaning.\n\nWhen the user feels it's OK, presses ENTER and back to the confirmation dialog.\n\n    WARNING: The following items will be removed permanently. Press \"y\"\n    WARNING: to start cleaning, and press \"n\" to abort the cleaning.\n    WARNING: You can also enter the \"edit\" mode, and select items\n    WARNING: to be excluded from the cleaning.\n\n      What would be removed...\n    \n    Remove (Yes/no/edit) ? \n\nThis time the default choice of the confirmation dialog is \"YES\".\nSo when user press ENTER, start cleaning.\n\nJiang Xin (3):\n  Add support for -i/--interactive to git-clean\n  Show items of interactive git-clean in columns\n  Add colors to interactive git-clean\n\n Documentation/git-clean.txt |  15 ++-\n builtin/clean.c             | 295 +++++++++++++++++++++++++++++++++++++++++---\n 2 files changed, 291 insertions(+), 19 deletions(-)\n\n-- \n1.8.3.rc0.364.gc6aefbf\n"},{"id":"216324","messageId":"63984814a9679ca8eedecda308bd1131658e95d6.1367551846.git.worldhello.net@gmail.com","threadId":"33711","inReplyTo":"cover.1367551846.git.worldhello.net@gmail.com","subject":"[PATCH v5 1/3] Add support for -i/--interactive to git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-03T03:49:53Z","receivedAt":"2013-05-03T03:49:53Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Show what would be done and the user must confirm before actually\ncleaning. In the confirmation dialog, the user has three choices:\n\n * Yes: Start to do cleaning.\n * No:  Nothing will be deleted.\n * Edit (default for the first time): Enter the edit mode.\n\nWhen the user chooses the edit mode, the user can input space-\nseparated patterns (the same syntax as gitignore), and each clean\ncandidate that matches with one of the patterns will be excluded\nfrom cleaning. When the user feels it's OK, presses ENTER and back\nto the confirmation dialog.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSpelling-check-by: Eric Sunshine <sunshine@sunshineco.com>\nComments-by: Matthieu Moy <Matthieu.Moy@imag.fr>\n---\n Documentation/git-clean.txt |  15 +++-\n builtin/clean.c             | 183 ++++++++++++++++++++++++++++++++++++++++----\n 2 files changed, 181 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt\nindex bdc3a..f5572 100644\n--- a/Documentation/git-clean.txt\n+++ b/Documentation/git-clean.txt\n@@ -8,7 +8,7 @@ git-clean - Remove untracked files from the working tree\n SYNOPSIS\n --------\n [verse]\n-'git clean' [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>...\n+'git clean' [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>...\n \n DESCRIPTION\n -----------\n@@ -34,7 +34,18 @@ OPTIONS\n -f::\n --force::\n \tIf the Git configuration variable clean.requireForce is not set\n-\tto false, 'git clean' will refuse to run unless given -f or -n.\n+\tto false, 'git clean' will refuse to run unless given -f, -n or\n+\t-i.\n+\n+-i::\n+--interactive::\n+\tShow what would be done and the user must confirm before actually\n+\tcleaning. In the confirmation dialog, the user can choose to abort\n+\tthe cleaning, or enter into an edit mode. In the edit mode, the\n+\tuser can input space-separated patterns (the same syntax as\n+\tgitignore), and each clean candidate that matches with one of the\n+\tpatterns will be excluded from cleaning. When the user feels it's\n+\tOK, presses ENTER and back to the confirmation dialog.\n \n -n::\n --dry-run::\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 04e39..12489 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -15,9 +15,10 @@\n #include \"quote.h\"\n \n static int force = -1; /* unset */\n+static int interactive;\n \n static const char *const builtin_clean_usage[] = {\n-\tN_(\"git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...\"),\n+\tN_(\"git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...\"),\n \tNULL\n };\n \n@@ -142,6 +143,138 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \treturn ret;\n }\n \n+void interactive_clean_edit(struct string_list *dels, const char *prefix)\n+{\n+\tstruct dir_struct dir;\n+\tstruct strbuf confirm = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct strbuf **ignore_list;\n+\tstruct string_list_item *item;\n+\tstruct exclude_list *el;\n+\tconst char *qname;\n+\tint changed = -1, i;\n+\n+\tputchar('\\n');\n+\tprintf_ln(_(\n+\t\t    \"NOTE: Will remove the following items. You can input space-seperated\\n\"\n+\t\t    \"NOTE: patterns (just like .gitignore) to exclude items from deletion,\\n\"\n+\t\t    \"NOTE: or press ENTER to continue.\"\n+\t\t   ));\n+\n+\twhile (1) {\n+\t\t/* dels list may become empty when we run string_list_remove_empty_items later */\n+\t\tif (!dels->nr) {\n+\t\t\tprintf_ln(_(\"No more files to clean, exiting.\"));\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (changed) {\n+\t\t\tputchar('\\n');\n+\n+\t\t\t/* Display dels in \"Would remove ...\" format */\n+\t\t\tfor_each_string_list_item(item, dels) {\n+\t\t\t\tqname = quote_path_relative(item->string, -1, &buf, prefix);\n+\t\t\t\tprintf(_(msg_would_remove), qname);\n+\t\t\t}\n+\t\t\tputchar('\\n');\n+\t\t}\n+\n+\t\tprintf(_(\"Input ignore patterns> \"));\n+\t\tstrbuf_getline(&confirm, stdin, '\\n');\n+\t\tstrbuf_trim(&confirm);\n+\n+\t\t/* Quit edit mode */\n+\t\tif (!confirm.len)\n+\t\t\tbreak;\n+\n+\t\tmemset(&dir, 0, sizeof(dir));\n+\t\tel = add_exclude_list(&dir, EXC_CMDL, \"manual exclude\");\n+\t\tignore_list = strbuf_split_buf(confirm.buf, confirm.len, ' ', 0);\n+\n+\t\tfor (i = 0; ignore_list[i]; i++) {\n+\t\t\tstrbuf_trim(*ignore_list);\n+\t\t\tif (!(*ignore_list)->len)\n+\t\t\t\tcontinue;\n+\n+\t\t\tadd_exclude(ignore_list[i]->buf, \"\", 0, el, -(i+1));\n+\t\t}\n+\n+\t\tchanged = 0;\n+\t\tfor_each_string_list_item(item, dels) {\n+\t\t\tint dtype = DT_UNKNOWN;\n+\t\t\tconst char *qname;\n+\n+\t\t\tqname = quote_path_relative(item->string, -1, &buf, prefix);\n+\n+\t\t\tif (is_excluded(&dir, qname, &dtype)) {\n+\t\t\t\t*item->string = '\\0';\n+\t\t\t\tchanged++;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (changed) {\n+\t\t\tstring_list_remove_empty_items(dels, 0);\n+\t\t} else {\n+\t\t\tprintf_ln(_(\"WARNING: Cannot find items matched by: %s\"), confirm.buf);\n+\t\t}\n+\n+\t\tstrbuf_list_free(ignore_list);\n+\t\tclear_directory(&dir);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tstrbuf_release(&confirm);\n+}\n+\n+void interactive_clean(struct string_list *dels, const char *prefix)\n+{\n+\tstruct strbuf confirm = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct string_list_item *item;\n+\tconst char *qname;\n+\tint count = 0;\n+\n+\t/* dels list may become empty after return back from edit mode */\n+\twhile (dels->nr) {\n+\t\t/* Display dels in \"Would remove ...\" format */\n+\t\tputchar('\\n');\n+\t\tfor_each_string_list_item(item, dels) {\n+\t\t\tqname = quote_path_relative(item->string, -1, &buf, prefix);\n+\t\t\tprintf(_(msg_would_remove), qname);\n+\t\t}\n+\t\tputchar('\\n');\n+\n+\t\t/* Confirmation dialog */\n+\t\tprintf(count > 0 ? _(\"Remove (Yes/no/edit) ? \") : _(\"Remove (yes/no/Edit) ? \"));\n+\t\tstrbuf_getline(&confirm, stdin, '\\n');\n+\t\tstrbuf_trim(&confirm);\n+\n+\t\tif (confirm.len) {\n+\t\t\tif (!strncasecmp(confirm.buf, \"yes\", confirm.len)) {\n+\t\t\t\tbreak;\n+\t\t\t} else if (!strncasecmp(confirm.buf, \"no\", confirm.len) ||\n+\t\t\t\t   !strncasecmp(confirm.buf, \"quit\", confirm.len)) {\n+\t\t\t\tstring_list_clear(dels, 0);\n+\t\t\t\tbreak;\n+\t\t\t} else if (!strncasecmp(confirm.buf, \"edit\", confirm.len)) {\n+\t\t\t\tinteractive_clean_edit(dels, prefix);\n+\t\t\t} else {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t} else if (count > 0) {\n+\t\t\t/* If back from edit_mode, confirmation dialog defaults to \"yes\" */\n+\t\t\tbreak;\n+\t\t} else {\n+\t\t\t/* For the first time, confirmation dialog defaults to \"edit\" */\n+\t\t\tinteractive_clean_edit(dels, prefix);\n+\t\t}\n+\t\tcount++;\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tstrbuf_release(&confirm);\n+}\n+\n int cmd_clean(int argc, const char **argv, const char *prefix)\n {\n \tint i, res;\n@@ -154,12 +287,15 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct string_list exclude_list = STRING_LIST_INIT_NODUP;\n \tstruct exclude_list *el;\n+\tstruct string_list dels = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n \tconst char *qname;\n \tchar *seen = NULL;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"do not print names of files removed\")),\n \t\tOPT__DRY_RUN(&dry_run, N_(\"dry run\")),\n \t\tOPT__FORCE(&force, N_(\"force\")),\n+\t\tOPT_BOOL('i', \"interactive\", &interactive, N_(\"interactive cleaning\")),\n \t\tOPT_BOOLEAN('d', NULL, &remove_directories,\n \t\t\t\tN_(\"remove whole directories\")),\n \t\t{ OPTION_CALLBACK, 'e', \"exclude\", &exclude_list, N_(\"pattern\"),\n@@ -186,12 +322,12 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \tif (ignored && ignored_only)\n \t\tdie(_(\"-x and -X cannot be used together\"));\n \n-\tif (!dry_run && !force) {\n+\tif (!dry_run && !force && !interactive) {\n \t\tif (config_set)\n-\t\t\tdie(_(\"clean.requireForce set to true and neither -n nor -f given; \"\n+\t\t\tdie(_(\"clean.requireForce set to true and neither -i, -n nor -f given; \"\n \t\t\t\t  \"refusing to clean\"));\n \t\telse\n-\t\t\tdie(_(\"clean.requireForce defaults to true and neither -n nor -f given; \"\n+\t\t\tdie(_(\"clean.requireForce defaults to true and neither -i, -n nor -f given; \"\n \t\t\t\t  \"refusing to clean\"));\n \t}\n \n@@ -257,26 +393,42 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \t\t}\n \n \t\tif (S_ISDIR(st.st_mode)) {\n-\t\t\tstrbuf_addstr(&directory, ent->name);\n \t\t\tif (remove_directories || (matches == MATCHED_EXACTLY)) {\n-\t\t\t\tif (remove_dirs(&directory, prefix, rm_flags, dry_run, quiet, &gone))\n-\t\t\t\t\terrors++;\n-\t\t\t\tif (gone && !quiet) {\n-\t\t\t\t\tqname = quote_path_relative(directory.buf, directory.len, &buf, prefix);\n-\t\t\t\t\tprintf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);\n-\t\t\t\t}\n+\t\t\t\tstring_list_append(&dels, ent->name);\n \t\t\t}\n-\t\t\tstrbuf_reset(&directory);\n \t\t} else {\n \t\t\tif (pathspec && !matches)\n \t\t\t\tcontinue;\n-\t\t\tres = dry_run ? 0 : unlink(ent->name);\n+\t\t\tstring_list_append(&dels, ent->name);\n+\t\t}\n+\t}\n+\n+\tif (interactive && dels.nr > 0 && !dry_run && isatty(0) && isatty(1))\n+\t\tinteractive_clean(&dels, prefix);\n+\n+\tfor_each_string_list_item(item, &dels) {\n+\t\tstruct stat st;\n+\n+\t\tif (lstat(item->string, &st))\n+\t\t\tcontinue;\n+\n+\t\tif (S_ISDIR(st.st_mode)) {\n+\t\t\tstrbuf_addstr(&directory, item->string);\n+\t\t\tif (remove_dirs(&directory, prefix, rm_flags, dry_run, quiet, &gone))\n+\t\t\t\terrors++;\n+\t\t\tif (gone && !quiet) {\n+\t\t\t\tqname = quote_path_relative(directory.buf, directory.len, &buf, prefix);\n+\t\t\t\tprintf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);\n+\t\t\t}\n+\t\t\tstrbuf_reset(&directory);\n+\t\t} else {\n+\t\t\tres = dry_run ? 0 : unlink(item->string);\n \t\t\tif (res) {\n-\t\t\t\tqname = quote_path_relative(ent->name, -1, &buf, prefix);\n+\t\t\t\tqname = quote_path_relative(item->string, -1, &buf, prefix);\n \t\t\t\twarning(_(msg_warn_remove_failed), qname);\n \t\t\t\terrors++;\n \t\t\t} else if (!quiet) {\n-\t\t\t\tqname = quote_path_relative(ent->name, -1, &buf, prefix);\n+\t\t\t\tqname = quote_path_relative(item->string, -1, &buf, prefix);\n \t\t\t\tprintf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);\n \t\t\t}\n \t\t}\n@@ -285,5 +437,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \n \tstrbuf_release(&directory);\n \tstring_list_clear(&exclude_list, 0);\n+\tstring_list_clear(&dels, 0);\n \treturn (errors != 0);\n }\n-- \n1.8.3.rc0.364.gc6aefbf\n"},{"id":"216326","messageId":"4f3bc9f30c0f544d16bef5b2a5ea4f5412ca3b36.1367551846.git.worldhello.net@gmail.com","threadId":"33711","inReplyTo":"63984814a9679ca8eedecda308bd1131658e95d6.1367551846.git.worldhello.net@gmail.com","subject":"[PATCH v5 2/3] Show items of interactive git-clean in columns","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-03T03:49:54Z","receivedAt":"2013-05-03T03:49:54Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"When there are lots of items to be cleaned, it is hard to see them all\nin one screen. Show them in columns instead of in one column will solve\nthis problem.\n\nSince no longer show items to be cleaned using the \"Would remove ...\"\nformat (only plain filenames) in interactive mode, we add instructions\nand warnings as header before them.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nComments-by: Matthieu Moy <Matthieu.Moy@imag.fr>\n---\n builtin/clean.c | 64 ++++++++++++++++++++++++++++++++++++++++++---------------\n 1 file changed, 47 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 12489..6ee7 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -13,9 +13,11 @@\n #include \"refs.h\"\n #include \"string-list.h\"\n #include \"quote.h\"\n+#include \"column.h\"\n \n static int force = -1; /* unset */\n static int interactive;\n+static unsigned int colopts;\n \n static const char *const builtin_clean_usage[] = {\n \tN_(\"git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...\"),\n@@ -30,8 +32,14 @@ static const char *msg_warn_remove_failed = N_(\"failed to remove %s\");\n \n static int git_clean_config(const char *var, const char *value, void *cb)\n {\n-\tif (!strcmp(var, \"clean.requireforce\"))\n+\t/* honors the column.ui config variable only */\n+\tif (!prefixcmp(var, \"column.\"))\n+\t\treturn git_column_config(var, value, NULL, &colopts);\n+\n+\tif (!strcmp(var, \"clean.requireforce\")) {\n \t\tforce = !git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -143,6 +151,33 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \treturn ret;\n }\n \n+void pretty_print_dels(struct string_list *dels, const char *prefix)\n+{\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tconst char *qname;\n+\tstruct column_options copts;\n+\n+\tfor_each_string_list_item(item, dels) {\n+\t\tqname = quote_path_relative(item->string, -1, &buf, prefix);\n+\t\tstring_list_append(&list, qname);\n+\t}\n+\n+\t/*\n+\t * always enable column display, we only consult column.*\n+\t * about layout strategy and stuff\n+\t */\n+\tcolopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;\n+\tmemset(&copts, 0, sizeof(copts));\n+\tcopts.indent = \"  \";\n+\tcopts.padding = 2;\n+\tprint_columns(&list, colopts, &copts);\n+\tputchar('\\n');\n+\tstrbuf_release(&buf);\n+\tstring_list_clear(&list, 0);\n+}\n+\n void interactive_clean_edit(struct string_list *dels, const char *prefix)\n {\n \tstruct dir_struct dir;\n@@ -151,7 +186,6 @@ void interactive_clean_edit(struct string_list *dels, const char *prefix)\n \tstruct strbuf **ignore_list;\n \tstruct string_list_item *item;\n \tstruct exclude_list *el;\n-\tconst char *qname;\n \tint changed = -1, i;\n \n \tputchar('\\n');\n@@ -171,12 +205,8 @@ void interactive_clean_edit(struct string_list *dels, const char *prefix)\n \t\tif (changed) {\n \t\t\tputchar('\\n');\n \n-\t\t\t/* Display dels in \"Would remove ...\" format */\n-\t\t\tfor_each_string_list_item(item, dels) {\n-\t\t\t\tqname = quote_path_relative(item->string, -1, &buf, prefix);\n-\t\t\t\tprintf(_(msg_would_remove), qname);\n-\t\t\t}\n-\t\t\tputchar('\\n');\n+\t\t\t/* Display dels in columns */\n+\t\t\tpretty_print_dels(dels, prefix);\n \t\t}\n \n \t\tprintf(_(\"Input ignore patterns> \"));\n@@ -229,21 +259,22 @@ void interactive_clean_edit(struct string_list *dels, const char *prefix)\n void interactive_clean(struct string_list *dels, const char *prefix)\n {\n \tstruct strbuf confirm = STRBUF_INIT;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tstruct string_list_item *item;\n-\tconst char *qname;\n \tint count = 0;\n \n \t/* dels list may become empty after return back from edit mode */\n \twhile (dels->nr) {\n-\t\t/* Display dels in \"Would remove ...\" format */\n \t\tputchar('\\n');\n-\t\tfor_each_string_list_item(item, dels) {\n-\t\t\tqname = quote_path_relative(item->string, -1, &buf, prefix);\n-\t\t\tprintf(_(msg_would_remove), qname);\n-\t\t}\n+\t\tprintf_ln(_(\n+\t\t\t    \"WARNING: The following items will be removed permanently. Press \\\"y\\\"\\n\"\n+\t\t\t    \"WARNING: to start cleaning, and press \\\"n\\\" to abort the cleaning.\\n\"\n+\t\t\t    \"WARNING: You can also enter the \\\"edit\\\" mode, and select items\\n\"\n+\t\t\t    \"WARNING: to be excluded from the cleaning.\"\n+\t\t\t   ));\n \t\tputchar('\\n');\n \n+\t\t/* Display dels in columns */\n+\t\tpretty_print_dels(dels, prefix);\n+\n \t\t/* Confirmation dialog */\n \t\tprintf(count > 0 ? _(\"Remove (Yes/no/edit) ? \") : _(\"Remove (yes/no/Edit) ? \"));\n \t\tstrbuf_getline(&confirm, stdin, '\\n');\n@@ -271,7 +302,6 @@ void interactive_clean(struct string_list *dels, const char *prefix)\n \t\tcount++;\n \t}\n \n-\tstrbuf_release(&buf);\n \tstrbuf_release(&confirm);\n }\n \n-- \n1.8.3.rc0.364.gc6aefbf\n"},{"id":"216325","messageId":"6e5487033b0f2cef49bd19e35825e3b0ddd1f58e.1367551846.git.worldhello.net@gmail.com","threadId":"33711","inReplyTo":"4f3bc9f30c0f544d16bef5b2a5ea4f5412ca3b36.1367551846.git.worldhello.net@gmail.com","subject":"[PATCH v5 3/3] Add colors to interactive git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-03T03:49:55Z","receivedAt":"2013-05-03T03:49:55Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Show header, help, error messages, and prompt in colors for interactive\ngit-clean. Re-use config variables for other git commands, such as\ngit-add--interactive and git-stash:\n\n * color.interactive: When set to always, always use colors for\n   interactive prompts and displays. When false (or never),\n   never. When set to true or auto, use colors only when the\n   output is to the terminal.\n\n * color.interactive.<slot>: Use customized color for interactive\n   git-clean output (like git add --interactive). <slot> may be\n   prompt, header, help or error.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nComments-by: Matthieu Moy <Matthieu.Moy@imag.fr>\n---\n builtin/clean.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 79 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 6ee7..1692b 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -14,6 +14,7 @@\n #include \"string-list.h\"\n #include \"quote.h\"\n #include \"column.h\"\n+#include \"color.h\"\n \n static int force = -1; /* unset */\n static int interactive;\n@@ -30,17 +31,82 @@ static const char *msg_skip_git_dir = N_(\"Skipping repository %s\\n\");\n static const char *msg_would_skip_git_dir = N_(\"Would skip repository %s\\n\");\n static const char *msg_warn_remove_failed = N_(\"failed to remove %s\");\n \n+static int clean_use_color = -1;\n+static char clean_colors[][COLOR_MAXLEN] = {\n+\tGIT_COLOR_RESET,\n+\tGIT_COLOR_NORMAL,\t/* PLAIN */\n+\tGIT_COLOR_BOLD_BLUE,\t/* PROMPT */\n+\tGIT_COLOR_BOLD,\t\t/* HEADER */\n+\tGIT_COLOR_BOLD_RED,\t/* HELP */\n+\tGIT_COLOR_BOLD_RED,\t/* ERROR */\n+};\n+enum color_clean {\n+\tCLEAN_COLOR_RESET = 0,\n+\tCLEAN_COLOR_PLAIN = 1,\n+\tCLEAN_COLOR_PROMPT = 2,\n+\tCLEAN_COLOR_HEADER = 3,\n+\tCLEAN_COLOR_HELP = 4,\n+\tCLEAN_COLOR_ERROR = 5,\n+};\n+\n+static int parse_clean_color_slot(const char *var, int ofs)\n+{\n+\tif (!strcasecmp(var+ofs, \"reset\"))\n+\t\treturn CLEAN_COLOR_RESET;\n+\tif (!strcasecmp(var+ofs, \"plain\"))\n+\t\treturn CLEAN_COLOR_PLAIN;\n+\tif (!strcasecmp(var+ofs, \"prompt\"))\n+\t\treturn CLEAN_COLOR_PROMPT;\n+\tif (!strcasecmp(var+ofs, \"header\"))\n+\t\treturn CLEAN_COLOR_HEADER;\n+\tif (!strcasecmp(var+ofs, \"help\"))\n+\t\treturn CLEAN_COLOR_HELP;\n+\tif (!strcasecmp(var+ofs, \"error\"))\n+\t\treturn CLEAN_COLOR_ERROR;\n+\treturn -1;\n+}\n+\n static int git_clean_config(const char *var, const char *value, void *cb)\n {\n \t/* honors the column.ui config variable only */\n \tif (!prefixcmp(var, \"column.\"))\n \t\treturn git_column_config(var, value, NULL, &colopts);\n \n+\t/* honors the color.interactive* config variables which also\n+\t   applied in git-add--interactive and git-stash */\n+\tif (!strcmp(var, \"color.interactive\")) {\n+\t\tclean_use_color = git_config_colorbool(var, value);\n+\t\treturn 0;\n+\t}\n+\tif (!prefixcmp(var, \"color.interactive.\")) {\n+\t\tint slot = parse_clean_color_slot(var, 18);\n+\t\tif (slot < 0)\n+\t\t\treturn 0;\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tcolor_parse(value, var, clean_colors[slot]);\n+\t\treturn 0;\n+\t}\n+\n \tif (!strcmp(var, \"clean.requireforce\")) {\n \t\tforce = !git_config_bool(var, value);\n \t\treturn 0;\n \t}\n-\treturn git_default_config(var, value, cb);\n+\n+\t/* inspect the color.ui config variable and others */\n+\treturn git_color_default_config(var, value, cb);\n+}\n+\n+static const char *clean_get_color(enum color_clean ix)\n+{\n+\tif (want_color(clean_use_color))\n+\t\treturn clean_colors[ix];\n+\treturn \"\";\n+}\n+\n+static void clean_print_color(enum color_clean ix)\n+{\n+\tprintf(\"%s\", clean_get_color(ix));\n }\n \n static int exclude_cb(const struct option *opt, const char *arg, int unset)\n@@ -189,16 +255,20 @@ void interactive_clean_edit(struct string_list *dels, const char *prefix)\n \tint changed = -1, i;\n \n \tputchar('\\n');\n+\tclean_print_color(CLEAN_COLOR_HELP);\n \tprintf_ln(_(\n \t\t    \"NOTE: Will remove the following items. You can input space-seperated\\n\"\n \t\t    \"NOTE: patterns (just like .gitignore) to exclude items from deletion,\\n\"\n \t\t    \"NOTE: or press ENTER to continue.\"\n \t\t   ));\n+\tclean_print_color(CLEAN_COLOR_RESET);\n \n \twhile (1) {\n \t\t/* dels list may become empty when we run string_list_remove_empty_items later */\n \t\tif (!dels->nr) {\n+\t\t\tclean_print_color(CLEAN_COLOR_ERROR);\n \t\t\tprintf_ln(_(\"No more files to clean, exiting.\"));\n+\t\t\tclean_print_color(CLEAN_COLOR_RESET);\n \t\t\tbreak;\n \t\t}\n \n@@ -209,7 +279,9 @@ void interactive_clean_edit(struct string_list *dels, const char *prefix)\n \t\t\tpretty_print_dels(dels, prefix);\n \t\t}\n \n+\t\tclean_print_color(CLEAN_COLOR_PROMPT);\n \t\tprintf(_(\"Input ignore patterns> \"));\n+\t\tclean_print_color(CLEAN_COLOR_RESET);\n \t\tstrbuf_getline(&confirm, stdin, '\\n');\n \t\tstrbuf_trim(&confirm);\n \n@@ -245,7 +317,9 @@ void interactive_clean_edit(struct string_list *dels, const char *prefix)\n \t\tif (changed) {\n \t\t\tstring_list_remove_empty_items(dels, 0);\n \t\t} else {\n+\t\t\tclean_print_color(CLEAN_COLOR_ERROR);\n \t\t\tprintf_ln(_(\"WARNING: Cannot find items matched by: %s\"), confirm.buf);\n+\t\t\tclean_print_color(CLEAN_COLOR_RESET);\n \t\t}\n \n \t\tstrbuf_list_free(ignore_list);\n@@ -264,19 +338,23 @@ void interactive_clean(struct string_list *dels, const char *prefix)\n \t/* dels list may become empty after return back from edit mode */\n \twhile (dels->nr) {\n \t\tputchar('\\n');\n+\t\tclean_print_color(CLEAN_COLOR_HEADER);\n \t\tprintf_ln(_(\n \t\t\t    \"WARNING: The following items will be removed permanently. Press \\\"y\\\"\\n\"\n \t\t\t    \"WARNING: to start cleaning, and press \\\"n\\\" to abort the cleaning.\\n\"\n \t\t\t    \"WARNING: You can also enter the \\\"edit\\\" mode, and select items\\n\"\n \t\t\t    \"WARNING: to be excluded from the cleaning.\"\n \t\t\t   ));\n+\t\tclean_print_color(CLEAN_COLOR_RESET);\n \t\tputchar('\\n');\n \n \t\t/* Display dels in columns */\n \t\tpretty_print_dels(dels, prefix);\n \n \t\t/* Confirmation dialog */\n+\t\tclean_print_color(CLEAN_COLOR_PROMPT);\n \t\tprintf(count > 0 ? _(\"Remove (Yes/no/edit) ? \") : _(\"Remove (yes/no/Edit) ? \"));\n+\t\tclean_print_color(CLEAN_COLOR_RESET);\n \t\tstrbuf_getline(&confirm, stdin, '\\n');\n \t\tstrbuf_trim(&confirm);\n \n-- \n1.8.3.rc0.364.gc6aefbf\n"},{"id":"216350","messageId":"CAPig+cQALgr_VXwG5jBiFVTM627se8zQz7vsmF=A9OLcp_GT9A@mail.gmail.com","threadId":"33711","inReplyTo":"cover.1367551846.git.worldhello.net@gmail.com","subject":"Re: [PATCH v5 0/3] interactive git clean","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-05-03T10:37:34Z","receivedAt":"2013-05-03T10:37:34Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Usability observations below...\n\nOn Thu, May 2, 2013 at 11:49 PM, Jiang Xin <worldhello.net@gmail.com> wrote:\n> The interactive git clean combines `git clean -n` and `git clean -f`\n> together to do safe cleaning, and has more features.\n>\n> First it displays what would be removed in columns (so that you can\n> see them all in one screen). The user must confirm before actually\n> cleaning.\n>\n>     WARNING: The following items will be removed permanently. Press \"y\"\n>     WARNING: to start cleaning, and press \"n\" to abort the cleaning.\n>     WARNING: You can also enter the \"edit\" mode, and select items\n>     WARNING: to be excluded from the cleaning.\n\nThe user intended for files to be removed when invoking git-clean,\ntherefore WARNING that git-clean will do what was requested explicitly\nseems overkill. Along the same lines, the user asked explicitly for an\ninteractive session (via --interactive), hence the above paragraph is\neffectively redundant since it does little more than tell the user (in\na lengthy fashion) what he already knows (that the session is\ninteractive). The short prompt printed after the listed files says the\nsame thing (more succinctly), thus this warning paragraph is\nessentially superfluous.\n\n>       What would be removed...    What would be removed...\n>       What would be removed...    What would be removed...\n>\n>     Remove (yes/no/Edit) ?\n\nFor convenience, implementations traditionally allow single letter\nresponses (y/n/e), but this one does not. Should it?\n\n> In this confirmation dialog, the user has three choices:\n>\n>  * Yes: Start to do cleaning.\n>  * No:  Nothing will be deleted.\n>  * Edit (default for the first time): Enter the edit mode.\n\nWhat about the user who desires more traditional \"rm -i\" behavior in\nwhich he is prompted for each file? Should that be supported with a\n\"Prompt [each]\" option in the above menu?\n\n> When the user chooses the edit mode, it would look like this:\n>\n>     NOTE: Will remove the following items. You can input space-seperated\n>     NOTE: patterns (just like .gitignore) to exclude items from deletion,\n>     NOTE: or press ENTER to continue.\n\nAs earlier, this (lengthy) paragraph says little more than what could\nbe said in a more succinct prompt printed after the file list, thus is\nprobably superfluous.\n\n>       What would be removed...    What would be removed...\n>       What would be removed...    What would be removed...\n>\n>     Input ignore patterns>\n\nThe list of files to be removed was already shown directly above.\nDumping the entire list to the user's screen a second time upon\nentering edit mode seems unnecessary. If you drop the WARNING and NOTE\nparagraphs as suggested, then the session becomes much less verbose,\nthus there is little reason to re-display the file list upon entering\nedit mode. For instance:\n\n  % git clean -i\n  file1 file2 file3\n  file4 file5 file6\n  Remove (yes/no/edit)? e\n  Exclude (space-separated gitignore patterns): file[4-6]\n  file1 file2 file3\n  Exclude (space-separated gitignore patterns): [enter]\n  Remove (yes/no/edit)?\n\n> The user can input space-separated patterns (the same syntax as gitignore),\n> and each clean candidate that matches with one of the patterns will be\n> excluded from cleaning.\n>\n> When the user feels it's OK, presses ENTER and back to the confirmation dialog.\n>\n>     WARNING: The following items will be removed permanently. Press \"y\"\n>     WARNING: to start cleaning, and press \"n\" to abort the cleaning.\n>     WARNING: You can also enter the \"edit\" mode, and select items\n>     WARNING: to be excluded from the cleaning.\n>\n>       What would be removed...\n>\n>     Remove (Yes/no/edit) ?\n>\n> This time the default choice of the confirmation dialog is \"YES\".\n> So when user press ENTER, start cleaning.\n\nIs there precedent for this sort of self-mutating default action in\nother utilities? Wouldn't this lead to high \"surprise factor\" for\nusers and potential for lost files? For instance:\n\n  % git clean -i\n  file1 file2 file3\n  file4 file5 file6\n  Remove (yes/no/Edit)? [enter]\n    {user presses ENTER for edit mode}\n  Exclude (space-separated gitignore patterns): file[3-6]\n    {user mistakenly types \"3\" rather than \"4\"}\n  file1 file2\n  Exclude (space-separated gitignore patterns): [enter]\n  Remove (Yes/no/edit)? [enter]\n    {user notices mistake and presses ENTER expecting edit mode}\n  Removing file3\n  Removing file4\n  Removing file5\n  Removing file6\n\nOh no! The user didn't notice the subtle change of default from\n\"yes/no/Edit\" to \"Yes/no/edit\",  thus he pressed ENTER thinking it\nwould take him to edit mode as it did initially, but instead git-clean\nproceeded with the removals and file3 is lost.\n\nOther considerations:\n\nIs it necessary to force the user to escape from edit mode by pressing\nENTER (i.e. empty input)? Wouldn't you achieve the same level of\nfunctionality by exiting back to the (yes/no/edit) prompt\nautomatically after the user enters his gitignore pattern(s)? For\ninstance:\n\n  % git clean -i\n  file1 file2 file3\n  file4 file5 file6\n  Remove (yes/no/edit)? e\n  Exclude (space-separated gitignore patterns): file[4-6]\n  file1 file2 file3\n  Remove (yes/no/edit)?\n\nMore generally, is this sort of modal edit mode desirable and\nconvenient? Can the edit operation be combined with the top-level\nprompt? For example:\n\n  % git clean -i\n  file1 file2 file3\n  file4 file5 file6\n  Remove ([y]es, [n]o, [p]rompt, exclusion-list)? file[4-6]\n  file1 file2 file3\n  Remove ([y]es, [n]o, [p]rompt, exclusion-list)? p\n  file1 (y/n/q/!)? y\n  file2 (y/n/q/!)? n\n  file3 (y/n/q/!)? y\n\n-- ES\n"},{"id":"216360","messageId":"7v4nekyrmb.fsf@alter.siamese.dyndns.org","threadId":"33711","inReplyTo":"cover.1367551846.git.worldhello.net@gmail.com","subject":"Re: [PATCH v5 0/3] interactive git clean","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-05-03T16:07:08Z","receivedAt":"2013-05-03T16:07:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> The interactive git clean combines `git clean -n` and `git clean -f`\n> together to do safe cleaning, and has more features.\n>\n> First it displays what would be removed in columns (so that you can\n> see them all in one screen). The user must confirm before actually\n> cleaning.\n>\n>     WARNING: The following items will be removed permanently. Press \"y\"\n>     WARNING: to start cleaning, and press \"n\" to abort the cleaning.\n>     WARNING: You can also enter the \"edit\" mode, and select items\n>     WARNING: to be excluded from the cleaning.\n>\n>       What would be removed...    What would be removed...\n>       What would be removed...    What would be removed...\n\nTo a user who explicitly _asked_ to run the interactive mode, I find\nthese four warning lines that shout at the user in all caps way\noverkill.  I would have expected the output to begin with a line to\nexplain what it is listing (e.g. \"Cleaning the following files:\"),\nthe list and then\n\n    Remove (Yes/no/edit/?) ?\n\nThe existing \"add -p\" prompt uses this trick to hint the user that a\nfurther help is available by typing \"?\", and it is a good example to\nfollow.\n"},{"id":"216395","messageId":"CANYiYbG5q7g-Gn-EGtsgS4XYLbQJuY6Pr_6FgKknADTD5_KoTg@mail.gmail.com","threadId":"33711","inReplyTo":"CAPig+cQALgr_VXwG5jBiFVTM627se8zQz7vsmF=A9OLcp_GT9A@mail.gmail.com","subject":"Re: [PATCH v5 0/3] interactive git clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-04T01:06:39Z","receivedAt":"2013-05-04T01:06:39Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"2013/5/3 Eric Sunshine <sunshine@sunshineco.com>:\n>>     WARNING: The following items will be removed permanently. Press \"y\"\n>>     WARNING: to start cleaning, and press \"n\" to abort the cleaning.\n>>     WARNING: You can also enter the \"edit\" mode, and select items\n>>     WARNING: to be excluded from the cleaning.\n>\n> The user intended for files to be removed when invoking git-clean,\n> therefore WARNING that git-clean will do what was requested explicitly\n> seems overkill. Along the same lines, the user asked explicitly for an\n> interactive session (via --interactive), hence the above paragraph is\n> effectively redundant since it does little more than tell the user (in\n> a lengthy fashion) what he already knows (that the session is\n> interactive). The short prompt printed after the listed files says the\n> same thing (more succinctly), thus this warning paragraph is\n> essentially superfluous.\n\nI will try to make the header short, and not too scary.\n\n>> In this confirmation dialog, the user has three choices:\n>>\n>>  * Yes: Start to do cleaning.\n>>  * No:  Nothing will be deleted.\n>>  * Edit (default for the first time): Enter the edit mode.\n>\n> What about the user who desires more traditional \"rm -i\" behavior in\n> which he is prompted for each file? Should that be supported with a\n> \"Prompt [each]\" option in the above menu?\n\nI'd like to have a try. Maybe I can borrow code/interface from\ngit-add--interactive.perl to support both (batch exclude and confirm\none by one). For example:\n\n*** Would remove the following item(s) ***\n\n    files to be removed...     files to be removed...\n    files to be removed...     files to be removed...\n    files to be removed...     files to be removed...\n\n*** Commands ***\n    1. [y]es, clean    2. [n]o, quit    3. batch [e]xclude\n4.[c]onfirm one by one\nWhat now> e\n\ninput ignore patterns>> * ![a-c]*\n    files to be removed...     files to be removed...\n    files to be removed...     files to be removed...\nInput ignore patterns>> ENTER\n\n*** Would remove the following item(s) ***\n\n    files to be removed...     files to be removed...\n    files to be removed...     files to be removed...\n\n*** Commands ***\n    1. [y]es, clean    2. [n]o, quit    3. batch [e]xclude\n4.[c]onfirm one by one\nWhat now> y\n\nRemoving ...\nRemoving ...\n\n> More generally, is this sort of modal edit mode desirable and\n> convenient? Can the edit operation be combined with the top-level\n> prompt? For example:\n>\n>   % git clean -i\n>   file1 file2 file3\n>   file4 file5 file6\n>   Remove ([y]es, [n]o, [p]rompt, exclusion-list)? file[4-6]\n>   file1 file2 file3\n>   Remove ([y]es, [n]o, [p]rompt, exclusion-list)? p\n>   file1 (y/n/q/!)? y\n>   file2 (y/n/q/!)? n\n>   file3 (y/n/q/!)? y\n\nWhat If there is a file named 'y', and the user want to exclude it,\nand press 'y' as a pattern.\n\n--\nJiang Xin\n"},{"id":"216445","messageId":"CAPig+cR--LQSkxc27_tMRrEnFJFApymqK9268BQA0bab38W3OQ@mail.gmail.com","threadId":"33711","inReplyTo":"CANYiYbG5q7g-Gn-EGtsgS4XYLbQJuY6Pr_6FgKknADTD5_KoTg@mail.gmail.com","subject":"Re: [PATCH v5 0/3] interactive git clean","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-05-05T12:35:55Z","receivedAt":"2013-05-05T12:35:55Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, May 3, 2013 at 9:06 PM, Jiang Xin <worldhello.net@gmail.com> wrote:\n> 2013/5/3 Eric Sunshine <sunshine@sunshineco.com>:\n>> More generally, is this sort of modal edit mode desirable and\n>> convenient? Can the edit operation be combined with the top-level\n>> prompt? For example:\n>>\n>>   % git clean -i\n>>   file1 file2 file3\n>>   file4 file5 file6\n>>   Remove ([y]es, [n]o, [p]rompt, exclusion-list)? file[4-6]\n>>   file1 file2 file3\n>>   Remove ([y]es, [n]o, [p]rompt, exclusion-list)? p\n>>   file1 (y/n/q/!)? y\n>>   file2 (y/n/q/!)? n\n>>   file3 (y/n/q/!)? y\n>\n> What If there is a file named 'y', and the user want to exclude it,\n> and press 'y' as a pattern.\n\nThe pattern [y] will match file named 'y'. It probably is unusual for\nfiles named 'y', 'n', etc. to exist in the top-level directory, but\nthe gitignore patterns already provide an escape hatch for these\nunusual cases. (That is not to say that this is the perfect example or\nsolution, but only that it may be worth considering such options when\ndesigning the user-interface for convenience.)\n"},{"id":"216474","messageId":"vpqli7s8rqp.fsf@grenoble-inp.fr","threadId":"33711","inReplyTo":"CAPig+cR--LQSkxc27_tMRrEnFJFApymqK9268BQA0bab38W3OQ@mail.gmail.com","subject":"Re: [PATCH v5 0/3] interactive git clean","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2013-05-06T07:58:06Z","receivedAt":"2013-05-06T07:58:06Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> The pattern [y] will match file named 'y'. It probably is unusual for\n> files named 'y', 'n', etc. to exist in the top-level directory, but\n> the gitignore patterns already provide an escape hatch for these\n> unusual cases.\n\nBut how does the user know that?\n\nI'd rather stay away from dwim that works in 99% of cases but do\nsomething dangerous in the 1% remaining, and complex un-guessable escape\nscheme to solve these few cases. The two stages (yes/no/edit, and then\nescape patterns) is clear, and does not require so many additional\nkeystrokes.\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"216477","messageId":"CAPig+cTSER2ngpGoM-3pwPO3dAnqq+9qCHdVb1YbLg9UgkPF0g@mail.gmail.com","threadId":"33711","inReplyTo":"vpqli7s8rqp.fsf@grenoble-inp.fr","subject":"Re: [PATCH v5 0/3] interactive git clean","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-05-06T09:40:06Z","receivedAt":"2013-05-06T09:40:06Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Hi Matthieu,\n\nOn Mon, May 6, 2013 at 3:58 AM, Matthieu Moy\n<Matthieu.Moy@grenoble-inp.fr> wrote:\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n>\n>> The pattern [y] will match file named 'y'. It probably is unusual for\n>> files named 'y', 'n', etc. to exist in the top-level directory, but\n>> the gitignore patterns already provide an escape hatch for these\n>> unusual cases.\n>\n> But how does the user know that?\n\nDocumentation. Junio also mentioned having a '?' in each prompt\nexplaining the options at that point.\n\n> I'd rather stay away from dwim that works in 99% of cases but do\n> something dangerous in the 1% remaining, and complex un-guessable escape\n> scheme to solve these few cases. The two stages (yes/no/edit, and then\n> escape patterns) is clear, and does not require so many additional\n> keystrokes.\n\nThe scheme doesn't have to be unsafe. git-clean *knows* which files\nare up for deletion. If one of those filenames conflicts with one of\nthe prompt options, --interactive mode can warn about the ambiguity at\nthe point the user types that particular ambiguous response, and ask\nfor clarification (\"did you mean 'yes, delete everything' or 'exclude\nfilename y'?\"). You get DWIM for 99.9% of the cases, and an extra\nprompt for the other 0.1%. Nothing unsafe. Is such an implementation\nmore complicated or ugly than the separate 'edit' mode? Is the user\nexperience better or worse (more or less convenient)? Is it easier or\nharder to document and explain?\n\nAnother observation which came to mind after my original email:\n\nWould it make sense for --interactive mode to launch an editor with\nthe list of files scheduled for deletion and allow the user to remove\nlines he does not want deleted (similar to \"git-rebase\n--interactive\"). Depending upon case, this could be more or less\nconvenient than the proposed gitignore-patterned 'edit' mode. It could\nbe yet another option in the yes/no/prompt/edit menu or it could be\nthe default and only behavior of \"git-clean --interactive\" (again\nsimilar to \"git-rebase --interactive).\n\nI'm not advocating any particular user interface. The observations and\nquestions about convenience and user experience both here and in my\noriginal email were made with the hope of promoting discussion before\nthe interface gets locked in. How clunky or fluid should it be? How\nverbose or terse?\n\n-- ES\n"},{"id":"216536","messageId":"cover.1367867498.git.worldhello.net@gmail.com","threadId":"33711","inReplyTo":"CANYiYbG5q7g-Gn-EGtsgS4XYLbQJuY6Pr_6FgKknADTD5_KoTg@mail.gmail.com","subject":"[PATCH v6 0/7] interactive git clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-06T19:18:49Z","receivedAt":"2013-05-06T19:18:49Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Implement a 'git add --interactive' style of interactive git-clean.\nIt will show what would be done before start to clean.  See\n``Interactive mode`` for details.\n\nInteractive mode\n----------------\nWhen the command enters the interactive mode, it shows the\nfiles and directories to be cleaned, and goes into its\ninteractive command loop.\n\nThe command loop shows the list of subcommands available, and\ngives a prompt \"What now> \".  In general, when the prompt ends\nwith a single '>', you can pick only one of the choices given\nand type return, like this:\n\n------------\n    *** Commands ***\n      1: clean             2: edit by patterns   3: edit by numbers\n      4. rm -i             5. quit               6. help\n    What now> 2\n------------\n\nYou also could say `c` or `clean` above as long as the choice is unique.\n\nThe main command loop has 6 subcommands.\n\nclean::\n\n   Start cleaning files and directories, and then quit.\n\nedit by patterns::\n\n   This shows the files and directories to be deleted and issues an\n   \"Input ignore patterns>>\" prompt. You can input a space-seperated\n   patterns to exclude files and directories from deletion.\n   E.g. \"*.c *.h\" will excludes files end with \".c\" and \".h\" from\n   deletion. When you are satisfied with the filtered result, press\n   ENTER (empty) back to the main menu.\n\nedit by numbers::\n\n   This shows the files and directories to be deleted and issues an\n   \"Select items to delete>>\" prompt. When the prompt ends with double\n   '>>' like this, you can make more than one selection, concatenated\n   with whitespace or comma.  Also you can say ranges.  E.g. \"2-5 7,9\"\n   to choose 2,3,4,5,7,9 from the list.  If the second number in a\n   range is omitted, all remaining patches are taken.  E.g. \"7-\" to\n   choose 7,8,9 from the list.  You can say '*' to choose everything.\n   Also when you are satisfied with the filtered result, press ENTER\n   (empty) back to the main menu.\n\nrm -i::\n\n  This will show a \"rm -i\" style cleaning, that you must confirm one\n  by one in order to delete items. This action is not as efficient\n  as the above two actions.\n\nquit::\n\n  This lets you quit without do cleaning.\n\nhelp::\n\n  Show brief usage of interactive git-clean.\n\n\n\nJiang Xin (7):\n  Add support for -i/--interactive to git-clean\n  Show items of interactive git-clean in columns\n  Add colors to interactive git-clean\n  git-clean: use a git-add-interactive compatible UI\n  git-clean: interactive cleaning by select numbers\n  git-clean: rm -i style interactive cleaning\n  git-clean: update document for interactive git-clean\n\n Documentation/config.txt    |   4 +\n Documentation/git-clean.txt |  71 ++++-\n builtin/clean.c             | 700 ++++++++++++++++++++++++++++++++++++++++++--\n 3 files changed, 752 insertions(+), 23 deletions(-)\n\n-- \n1.8.3.rc1.338.gb35aa5d\n"},{"id":"216537","messageId":"4a32eb7cc4e277fcbf4b46c13524874334bea2a3.1367867498.git.worldhello.net@gmail.com","threadId":"33711","inReplyTo":"cover.1367867498.git.worldhello.net@gmail.com","subject":"[PATCH v6 1/7] Add support for -i/--interactive to git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-06T19:18:50Z","receivedAt":"2013-05-06T19:18:50Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Show what would be done and the user must confirm before actually\ncleaning. In the confirmation dialog, the user has three choices:\n\n * y/yes:  Start to do cleaning.\n * n/no:   Nothing will be deleted.\n * e/edit: Exclude items from deletion using ignore patterns.\n\nWhen the user chooses the edit mode, the user can input space-\nseparated patterns (the same syntax as gitignore), and each clean\ncandidate that matches with one of the patterns will be excluded\nfrom cleaning. When the user feels it's OK, presses ENTER and back\nto the confirmation dialog.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSpelling-checked-by: Eric Sunshine <sunshine@sunshineco.com>\nComments-by: Matthieu Moy <Matthieu.Moy@imag.fr>\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/git-clean.txt |  15 +++-\n builtin/clean.c             | 195 ++++++++++++++++++++++++++++++++++++++++----\n 2 files changed, 191 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt\nindex bdc3a..f5572 100644\n--- a/Documentation/git-clean.txt\n+++ b/Documentation/git-clean.txt\n@@ -8,7 +8,7 @@ git-clean - Remove untracked files from the working tree\n SYNOPSIS\n --------\n [verse]\n-'git clean' [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>...\n+'git clean' [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>...\n \n DESCRIPTION\n -----------\n@@ -34,7 +34,18 @@ OPTIONS\n -f::\n --force::\n \tIf the Git configuration variable clean.requireForce is not set\n-\tto false, 'git clean' will refuse to run unless given -f or -n.\n+\tto false, 'git clean' will refuse to run unless given -f, -n or\n+\t-i.\n+\n+-i::\n+--interactive::\n+\tShow what would be done and the user must confirm before actually\n+\tcleaning. In the confirmation dialog, the user can choose to abort\n+\tthe cleaning, or enter into an edit mode. In the edit mode, the\n+\tuser can input space-separated patterns (the same syntax as\n+\tgitignore), and each clean candidate that matches with one of the\n+\tpatterns will be excluded from cleaning. When the user feels it's\n+\tOK, presses ENTER and back to the confirmation dialog.\n \n -n::\n --dry-run::\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 04e39..29fbf 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -15,9 +15,12 @@\n #include \"quote.h\"\n \n static int force = -1; /* unset */\n+static int interactive;\n+static struct string_list del_list = STRING_LIST_INIT_DUP;\n+static const char **the_prefix;\n \n static const char *const builtin_clean_usage[] = {\n-\tN_(\"git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...\"),\n+\tN_(\"git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...\"),\n \tNULL\n };\n \n@@ -142,6 +145,139 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \treturn ret;\n }\n \n+void edit_by_patterns_cmd()\n+{\n+\tstruct dir_struct dir;\n+\tstruct strbuf confirm = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct strbuf **ignore_list;\n+\tstruct string_list_item *item;\n+\tstruct exclude_list *el;\n+\tconst char *qname;\n+\tint changed = -1, i;\n+\n+\twhile (1) {\n+\t\t/* dels list may become empty when we run string_list_remove_empty_items later */\n+\t\tif (!del_list.nr) {\n+\t\t\tprintf_ln(_(\"No more files to clean, exiting.\"));\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (changed) {\n+\t\t\tputchar('\\n');\n+\n+\t\t\t/* Display dels in \"Would remove ...\" format */\n+\t\t\tfor_each_string_list_item(item, &del_list) {\n+\t\t\t\tqname = quote_path_relative(item->string, -1, &buf, *the_prefix);\n+\t\t\t\tprintf(_(msg_would_remove), qname);\n+\t\t\t}\n+\t\t\tputchar('\\n');\n+\t\t}\n+\n+\t\tprintf(_(\"Input ignore patterns>> \"));\n+\t\tif (strbuf_getline(&confirm, stdin, '\\n') != EOF) {\n+\t\t\tstrbuf_trim(&confirm);\n+\t\t} else {\n+\t\t\tputchar('\\n');\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t/* Quit edit mode */\n+\t\tif (!confirm.len)\n+\t\t\tbreak;\n+\n+\t\tmemset(&dir, 0, sizeof(dir));\n+\t\tel = add_exclude_list(&dir, EXC_CMDL, \"manual exclude\");\n+\t\tignore_list = strbuf_split_max(&confirm, ' ', 0);\n+\n+\t\tfor (i = 0; ignore_list[i]; i++) {\n+\t\t\tstrbuf_trim(ignore_list[i]);\n+\t\t\tif (!ignore_list[i]->len)\n+\t\t\t\tcontinue;\n+\n+\t\t\tadd_exclude(ignore_list[i]->buf, \"\", 0, el, -(i+1));\n+\t\t}\n+\n+\t\tchanged = 0;\n+\t\tfor_each_string_list_item(item, &del_list) {\n+\t\t\tint dtype = DT_UNKNOWN;\n+\t\t\tconst char *qname;\n+\n+\t\t\tqname = quote_path_relative(item->string, -1, &buf, *the_prefix);\n+\n+\t\t\tif (is_excluded(&dir, qname, &dtype)) {\n+\t\t\t\t*item->string = '\\0';\n+\t\t\t\tchanged++;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (changed) {\n+\t\t\tstring_list_remove_empty_items(&del_list, 0);\n+\t\t} else {\n+\t\t\tprintf_ln(_(\"WARNING: Cannot find items matched by: %s\"), confirm.buf);\n+\t\t}\n+\n+\t\tstrbuf_list_free(ignore_list);\n+\t\tclear_directory(&dir);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tstrbuf_release(&confirm);\n+}\n+\n+void interactive_main_loop()\n+{\n+\tstruct strbuf confirm = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct string_list_item *item;\n+\tconst char *qname;\n+\n+\t/* dels list may become empty after return back from edit mode */\n+\twhile (del_list.nr) {\n+\t\tprintf_ln(Q_(\"Would remove the following item:\",\n+\t\t\t     \"Would remove the following items:\",\n+\t\t\t     del_list.nr));\n+\t\tputchar('\\n');\n+\n+\t\t/* Display dels in \"Would remove ...\" format */\n+\t\tfor_each_string_list_item(item, &del_list) {\n+\t\t\tqname = quote_path_relative(item->string, -1, &buf, *the_prefix);\n+\t\t\tprintf(_(msg_would_remove), qname);\n+\t\t}\n+\t\tputchar('\\n');\n+\n+\t\t/* Confirmation dialog */\n+\t\tprintf(_(\"Remove ([y]es/[n]o/[e]dit) ? \"));\n+\t\tif (strbuf_getline(&confirm, stdin, '\\n') != EOF) {\n+\t\t\tstrbuf_trim(&confirm);\n+\t\t} else {\n+\t\t\t/* Ctrl-D is the same as \"quit\" */\n+\t\t\tstring_list_clear(&del_list, 0);\n+\t\t\tputchar('\\n');\n+\t\t\tprintf_ln(\"Bye.\");\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (confirm.len) {\n+\t\t\tif (!strncasecmp(confirm.buf, \"yes\", confirm.len)) {\n+\t\t\t\tbreak;\n+\t\t\t} else if (!strncasecmp(confirm.buf, \"no\", confirm.len) ||\n+\t\t\t\t   !strncasecmp(confirm.buf, \"quit\", confirm.len)) {\n+\t\t\t\tstring_list_clear(&del_list, 0);\n+\t\t\t\tprintf_ln(\"Bye.\");\n+\t\t\t\tbreak;\n+\t\t\t} else if (!strncasecmp(confirm.buf, \"edit\", confirm.len)) {\n+\t\t\t\tedit_by_patterns_cmd();\n+\t\t\t} else {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\tstrbuf_release(&confirm);\n+}\n+\n int cmd_clean(int argc, const char **argv, const char *prefix)\n {\n \tint i, res;\n@@ -154,12 +290,14 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct string_list exclude_list = STRING_LIST_INIT_NODUP;\n \tstruct exclude_list *el;\n+\tstruct string_list_item *item;\n \tconst char *qname;\n \tchar *seen = NULL;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"do not print names of files removed\")),\n \t\tOPT__DRY_RUN(&dry_run, N_(\"dry run\")),\n \t\tOPT__FORCE(&force, N_(\"force\")),\n+\t\tOPT_BOOL('i', \"interactive\", &interactive, N_(\"interactive cleaning\")),\n \t\tOPT_BOOLEAN('d', NULL, &remove_directories,\n \t\t\t\tN_(\"remove whole directories\")),\n \t\t{ OPTION_CALLBACK, 'e', \"exclude\", &exclude_list, N_(\"pattern\"),\n@@ -176,7 +314,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \telse\n \t\tconfig_set = 1;\n \n-\targc = parse_options(argc, argv, prefix, options, builtin_clean_usage,\n+\tthe_prefix = &prefix;\n+\n+\targc = parse_options(argc, argv, *the_prefix, options, builtin_clean_usage,\n \t\t\t     0);\n \n \tmemset(&dir, 0, sizeof(dir));\n@@ -186,12 +326,16 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \tif (ignored && ignored_only)\n \t\tdie(_(\"-x and -X cannot be used together\"));\n \n-\tif (!dry_run && !force) {\n+\tif (interactive) {\n+\t\tif (!isatty(0) || !isatty(1))\n+\t\t\tdie(_(\"interactive clean can not run without a valid tty; \"\n+\t\t\t\t  \"refusing to clean\"));\n+\t} else if (!dry_run && !force) {\n \t\tif (config_set)\n-\t\t\tdie(_(\"clean.requireForce set to true and neither -n nor -f given; \"\n+\t\t\tdie(_(\"clean.requireForce set to true and neither -i, -n nor -f given; \"\n \t\t\t\t  \"refusing to clean\"));\n \t\telse\n-\t\t\tdie(_(\"clean.requireForce defaults to true and neither -n nor -f given; \"\n+\t\t\tdie(_(\"clean.requireForce defaults to true and neither -i, -n nor -f given; \"\n \t\t\t\t  \"refusing to clean\"));\n \t}\n \n@@ -210,7 +354,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \tfor (i = 0; i < exclude_list.nr; i++)\n \t\tadd_exclude(exclude_list.items[i].string, \"\", 0, el, -(i+1));\n \n-\tpathspec = get_pathspec(prefix, argv);\n+\tpathspec = get_pathspec(*the_prefix, argv);\n \n \tfill_directory(&dir, pathspec);\n \n@@ -257,26 +401,42 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \t\t}\n \n \t\tif (S_ISDIR(st.st_mode)) {\n-\t\t\tstrbuf_addstr(&directory, ent->name);\n \t\t\tif (remove_directories || (matches == MATCHED_EXACTLY)) {\n-\t\t\t\tif (remove_dirs(&directory, prefix, rm_flags, dry_run, quiet, &gone))\n-\t\t\t\t\terrors++;\n-\t\t\t\tif (gone && !quiet) {\n-\t\t\t\t\tqname = quote_path_relative(directory.buf, directory.len, &buf, prefix);\n-\t\t\t\t\tprintf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);\n-\t\t\t\t}\n+\t\t\t\tstring_list_append(&del_list, ent->name);\n \t\t\t}\n-\t\t\tstrbuf_reset(&directory);\n \t\t} else {\n \t\t\tif (pathspec && !matches)\n \t\t\t\tcontinue;\n-\t\t\tres = dry_run ? 0 : unlink(ent->name);\n+\t\t\tstring_list_append(&del_list, ent->name);\n+\t\t}\n+\t}\n+\n+\tif (interactive && del_list.nr > 0 && !dry_run && isatty(0) && isatty(1))\n+\t\tinteractive_main_loop();\n+\n+\tfor_each_string_list_item(item, &del_list) {\n+\t\tstruct stat st;\n+\n+\t\tif (lstat(item->string, &st))\n+\t\t\tcontinue;\n+\n+\t\tif (S_ISDIR(st.st_mode)) {\n+\t\t\tstrbuf_addstr(&directory, item->string);\n+\t\t\tif (remove_dirs(&directory, *the_prefix, rm_flags, dry_run, quiet, &gone))\n+\t\t\t\terrors++;\n+\t\t\tif (gone && !quiet) {\n+\t\t\t\tqname = quote_path_relative(directory.buf, directory.len, &buf, *the_prefix);\n+\t\t\t\tprintf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);\n+\t\t\t}\n+\t\t\tstrbuf_reset(&directory);\n+\t\t} else {\n+\t\t\tres = dry_run ? 0 : unlink(item->string);\n \t\t\tif (res) {\n-\t\t\t\tqname = quote_path_relative(ent->name, -1, &buf, prefix);\n+\t\t\t\tqname = quote_path_relative(item->string, -1, &buf, *the_prefix);\n \t\t\t\twarning(_(msg_warn_remove_failed), qname);\n \t\t\t\terrors++;\n \t\t\t} else if (!quiet) {\n-\t\t\t\tqname = quote_path_relative(ent->name, -1, &buf, prefix);\n+\t\t\t\tqname = quote_path_relative(item->string, -1, &buf, *the_prefix);\n \t\t\t\tprintf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);\n \t\t\t}\n \t\t}\n@@ -285,5 +445,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \n \tstrbuf_release(&directory);\n \tstring_list_clear(&exclude_list, 0);\n+\tstring_list_clear(&del_list, 0);\n \treturn (errors != 0);\n }\n-- \n1.8.3.rc1.338.gb35aa5d\n"},{"id":"216538","messageId":"6276dc8861b40e8377586f30b85c33e92c4e68fe.1367867498.git.worldhello.net@gmail.com","threadId":"33711","inReplyTo":"4a32eb7cc4e277fcbf4b46c13524874334bea2a3.1367867498.git.worldhello.net@gmail.com","subject":"[PATCH v6 2/7] Show items of interactive git-clean in columns","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-06T19:18:51Z","receivedAt":"2013-05-06T19:18:51Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"When there are lots of items to be cleaned, it is hard to see them all\nin one screen. Show them in columns instead of in one column will solve\nthis problem.\n\nSince no longer show items to be cleaned using the \"Would remove ...\"\nformat (only plain filenames) in interactive mode, we add instructions\nand warnings as header before them.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nComments-by: Matthieu Moy <Matthieu.Moy@imag.fr>\n---\n Documentation/config.txt |  4 ++++\n builtin/clean.c          | 58 +++++++++++++++++++++++++++++++++---------------\n 2 files changed, 44 insertions(+), 18 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 6e53f..98bfa 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -955,6 +955,10 @@ column.branch::\n \tSpecify whether to output branch listing in `git branch` in columns.\n \tSee `column.ui` for details.\n \n+column.clean::\n+\tSpecify whether to output cleaning files in `git clean -i` in columns.\n+\tSee `column.ui` for details.\n+\n column.status::\n \tSpecify whether to output untracked files in `git status` in columns.\n \tSee `column.ui` for details.\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 29fbf..43383 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -13,11 +13,13 @@\n #include \"refs.h\"\n #include \"string-list.h\"\n #include \"quote.h\"\n+#include \"column.h\"\n \n static int force = -1; /* unset */\n static int interactive;\n static struct string_list del_list = STRING_LIST_INIT_DUP;\n static const char **the_prefix;\n+static unsigned int colopts;\n \n static const char *const builtin_clean_usage[] = {\n \tN_(\"git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...\"),\n@@ -32,8 +34,13 @@ static const char *msg_warn_remove_failed = N_(\"failed to remove %s\");\n \n static int git_clean_config(const char *var, const char *value, void *cb)\n {\n-\tif (!strcmp(var, \"clean.requireforce\"))\n+\tif (!prefixcmp(var, \"column.\"))\n+\t\treturn git_column_config(var, value, \"clean\", &colopts);\n+\n+\tif (!strcmp(var, \"clean.requireforce\")) {\n \t\tforce = !git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -145,6 +152,33 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \treturn ret;\n }\n \n+void pretty_print_dels()\n+{\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tconst char *qname;\n+\tstruct column_options copts;\n+\n+\tfor_each_string_list_item(item, &del_list) {\n+\t\tqname = quote_path_relative(item->string, -1, &buf, *the_prefix);\n+\t\tstring_list_append(&list, qname);\n+\t}\n+\n+\t/*\n+\t * always enable column display, we only consult column.*\n+\t * about layout strategy and stuff\n+\t */\n+\tcolopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;\n+\tmemset(&copts, 0, sizeof(copts));\n+\tcopts.indent = \"  \";\n+\tcopts.padding = 2;\n+\tprint_columns(&list, colopts, &copts);\n+\tputchar('\\n');\n+\tstrbuf_release(&buf);\n+\tstring_list_clear(&list, 0);\n+}\n+\n void edit_by_patterns_cmd()\n {\n \tstruct dir_struct dir;\n@@ -153,7 +187,6 @@ void edit_by_patterns_cmd()\n \tstruct strbuf **ignore_list;\n \tstruct string_list_item *item;\n \tstruct exclude_list *el;\n-\tconst char *qname;\n \tint changed = -1, i;\n \n \twhile (1) {\n@@ -166,12 +199,8 @@ void edit_by_patterns_cmd()\n \t\tif (changed) {\n \t\t\tputchar('\\n');\n \n-\t\t\t/* Display dels in \"Would remove ...\" format */\n-\t\t\tfor_each_string_list_item(item, &del_list) {\n-\t\t\t\tqname = quote_path_relative(item->string, -1, &buf, *the_prefix);\n-\t\t\t\tprintf(_(msg_would_remove), qname);\n-\t\t\t}\n-\t\t\tputchar('\\n');\n+\t\t\t/* Display dels in columns */\n+\t\t\tpretty_print_dels();\n \t\t}\n \n \t\tprintf(_(\"Input ignore patterns>> \"));\n@@ -228,23 +257,17 @@ void edit_by_patterns_cmd()\n void interactive_main_loop()\n {\n \tstruct strbuf confirm = STRBUF_INIT;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tstruct string_list_item *item;\n-\tconst char *qname;\n \n \t/* dels list may become empty after return back from edit mode */\n \twhile (del_list.nr) {\n+\t\tputchar('\\n');\n \t\tprintf_ln(Q_(\"Would remove the following item:\",\n \t\t\t     \"Would remove the following items:\",\n \t\t\t     del_list.nr));\n \t\tputchar('\\n');\n \n-\t\t/* Display dels in \"Would remove ...\" format */\n-\t\tfor_each_string_list_item(item, &del_list) {\n-\t\t\tqname = quote_path_relative(item->string, -1, &buf, *the_prefix);\n-\t\t\tprintf(_(msg_would_remove), qname);\n-\t\t}\n-\t\tputchar('\\n');\n+\t\t/* Display dels in columns */\n+\t\tpretty_print_dels();\n \n \t\t/* Confirmation dialog */\n \t\tprintf(_(\"Remove ([y]es/[n]o/[e]dit) ? \"));\n@@ -274,7 +297,6 @@ void interactive_main_loop()\n \t\t}\n \t}\n \n-\tstrbuf_release(&buf);\n \tstrbuf_release(&confirm);\n }\n \n-- \n1.8.3.rc1.338.gb35aa5d\n"},{"id":"216540","messageId":"c77c33fe295bb8577336d5bb4fe0d8da32f69df3.1367867498.git.worldhello.net@gmail.com","threadId":"33711","inReplyTo":"6276dc8861b40e8377586f30b85c33e92c4e68fe.1367867498.git.worldhello.net@gmail.com","subject":"[PATCH v6 3/7] Add colors to interactive git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-06T19:18:52Z","receivedAt":"2013-05-06T19:18:52Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Show header, help, error messages, and prompt in colors for interactive\ngit-clean. Re-use config variables for other git commands, such as\ngit-add--interactive and git-stash:\n\n * color.interactive: When set to always, always use colors for\n   interactive prompts and displays. When false (or never),\n   never. When set to true or auto, use colors only when the\n   output is to the terminal.\n\n * color.interactive.<slot>: Use customized color for interactive\n   git-clean output (like git add --interactive). <slot> may be\n   prompt, header, help or error.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nComments-by: Matthieu Moy <Matthieu.Moy@imag.fr>\n---\n builtin/clean.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 77 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 43383..6bda3 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -14,6 +14,7 @@\n #include \"string-list.h\"\n #include \"quote.h\"\n #include \"column.h\"\n+#include \"color.h\"\n \n static int force = -1; /* unset */\n static int interactive;\n@@ -32,16 +33,81 @@ static const char *msg_skip_git_dir = N_(\"Skipping repository %s\\n\");\n static const char *msg_would_skip_git_dir = N_(\"Would skip repository %s\\n\");\n static const char *msg_warn_remove_failed = N_(\"failed to remove %s\");\n \n+static int clean_use_color = -1;\n+static char clean_colors[][COLOR_MAXLEN] = {\n+\tGIT_COLOR_RESET,\n+\tGIT_COLOR_NORMAL,\t/* PLAIN */\n+\tGIT_COLOR_BOLD_BLUE,\t/* PROMPT */\n+\tGIT_COLOR_BOLD,\t\t/* HEADER */\n+\tGIT_COLOR_BOLD_RED,\t/* HELP */\n+\tGIT_COLOR_BOLD_RED,\t/* ERROR */\n+};\n+enum color_clean {\n+\tCLEAN_COLOR_RESET = 0,\n+\tCLEAN_COLOR_PLAIN = 1,\n+\tCLEAN_COLOR_PROMPT = 2,\n+\tCLEAN_COLOR_HEADER = 3,\n+\tCLEAN_COLOR_HELP = 4,\n+\tCLEAN_COLOR_ERROR = 5,\n+};\n+\n+static int parse_clean_color_slot(const char *var, int ofs)\n+{\n+\tif (!strcasecmp(var+ofs, \"reset\"))\n+\t\treturn CLEAN_COLOR_RESET;\n+\tif (!strcasecmp(var+ofs, \"plain\"))\n+\t\treturn CLEAN_COLOR_PLAIN;\n+\tif (!strcasecmp(var+ofs, \"prompt\"))\n+\t\treturn CLEAN_COLOR_PROMPT;\n+\tif (!strcasecmp(var+ofs, \"header\"))\n+\t\treturn CLEAN_COLOR_HEADER;\n+\tif (!strcasecmp(var+ofs, \"help\"))\n+\t\treturn CLEAN_COLOR_HELP;\n+\tif (!strcasecmp(var+ofs, \"error\"))\n+\t\treturn CLEAN_COLOR_ERROR;\n+\treturn -1;\n+}\n+\n static int git_clean_config(const char *var, const char *value, void *cb)\n {\n \tif (!prefixcmp(var, \"column.\"))\n \t\treturn git_column_config(var, value, \"clean\", &colopts);\n \n+\t/* honors the color.interactive* config variables which also\n+\t   applied in git-add--interactive and git-stash */\n+\tif (!strcmp(var, \"color.interactive\")) {\n+\t\tclean_use_color = git_config_colorbool(var, value);\n+\t\treturn 0;\n+\t}\n+\tif (!prefixcmp(var, \"color.interactive.\")) {\n+\t\tint slot = parse_clean_color_slot(var, 18);\n+\t\tif (slot < 0)\n+\t\t\treturn 0;\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tcolor_parse(value, var, clean_colors[slot]);\n+\t\treturn 0;\n+\t}\n+\n \tif (!strcmp(var, \"clean.requireforce\")) {\n \t\tforce = !git_config_bool(var, value);\n \t\treturn 0;\n \t}\n-\treturn git_default_config(var, value, cb);\n+\n+\t/* inspect the color.ui config variable and others */\n+\treturn git_color_default_config(var, value, cb);\n+}\n+\n+static const char *clean_get_color(enum color_clean ix)\n+{\n+\tif (want_color(clean_use_color))\n+\t\treturn clean_colors[ix];\n+\treturn \"\";\n+}\n+\n+static void clean_print_color(enum color_clean ix)\n+{\n+\tprintf(\"%s\", clean_get_color(ix));\n }\n \n static int exclude_cb(const struct option *opt, const char *arg, int unset)\n@@ -192,7 +258,9 @@ void edit_by_patterns_cmd()\n \twhile (1) {\n \t\t/* dels list may become empty when we run string_list_remove_empty_items later */\n \t\tif (!del_list.nr) {\n+\t\t\tclean_print_color(CLEAN_COLOR_ERROR);\n \t\t\tprintf_ln(_(\"No more files to clean, exiting.\"));\n+\t\t\tclean_print_color(CLEAN_COLOR_RESET);\n \t\t\tbreak;\n \t\t}\n \n@@ -203,7 +271,9 @@ void edit_by_patterns_cmd()\n \t\t\tpretty_print_dels();\n \t\t}\n \n+\t\tclean_print_color(CLEAN_COLOR_PROMPT);\n \t\tprintf(_(\"Input ignore patterns>> \"));\n+\t\tclean_print_color(CLEAN_COLOR_RESET);\n \t\tif (strbuf_getline(&confirm, stdin, '\\n') != EOF) {\n \t\t\tstrbuf_trim(&confirm);\n \t\t} else {\n@@ -243,7 +313,9 @@ void edit_by_patterns_cmd()\n \t\tif (changed) {\n \t\t\tstring_list_remove_empty_items(&del_list, 0);\n \t\t} else {\n+\t\t\tclean_print_color(CLEAN_COLOR_ERROR);\n \t\t\tprintf_ln(_(\"WARNING: Cannot find items matched by: %s\"), confirm.buf);\n+\t\t\tclean_print_color(CLEAN_COLOR_RESET);\n \t\t}\n \n \t\tstrbuf_list_free(ignore_list);\n@@ -261,16 +333,20 @@ void interactive_main_loop()\n \t/* dels list may become empty after return back from edit mode */\n \twhile (del_list.nr) {\n \t\tputchar('\\n');\n+\t\tclean_print_color(CLEAN_COLOR_HEADER);\n \t\tprintf_ln(Q_(\"Would remove the following item:\",\n \t\t\t     \"Would remove the following items:\",\n \t\t\t     del_list.nr));\n+\t\tclean_print_color(CLEAN_COLOR_RESET);\n \t\tputchar('\\n');\n \n \t\t/* Display dels in columns */\n \t\tpretty_print_dels();\n \n \t\t/* Confirmation dialog */\n+\t\tclean_print_color(CLEAN_COLOR_PROMPT);\n \t\tprintf(_(\"Remove ([y]es/[n]o/[e]dit) ? \"));\n+\t\tclean_print_color(CLEAN_COLOR_RESET);\n \t\tif (strbuf_getline(&confirm, stdin, '\\n') != EOF) {\n \t\t\tstrbuf_trim(&confirm);\n \t\t} else {\n-- \n1.8.3.rc1.338.gb35aa5d\n"},{"id":"216539","messageId":"217e7618e9b9d50a50461959b38984ac8be88651.1367867498.git.worldhello.net@gmail.com","threadId":"33711","inReplyTo":"c77c33fe295bb8577336d5bb4fe0d8da32f69df3.1367867498.git.worldhello.net@gmail.com","subject":"[PATCH v6 4/7] git-clean: use a git-add-interactive compatible UI","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-06T19:18:53Z","receivedAt":"2013-05-06T19:18:53Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Rewrite menu using a new method `list_and_choose`, which is borrowed\nfrom `git-add--interactive.perl`. We can reused this method later for\nmore actions.\n\nPlease NOTE:\n\n * Method `list_and_choose` return an array of integers, and\n * it is up to you to free the allocated memory of the array.\n * The array ends with EOF.\n * If user pressed CTRL-D (i.e. EOF), no selection returned.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n builtin/clean.c | 410 ++++++++++++++++++++++++++++++++++++++++++++++++++------\n 1 file changed, 367 insertions(+), 43 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 6bda3..3b9f3 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -16,6 +16,35 @@\n #include \"column.h\"\n #include \"color.h\"\n \n+#define MENU_OPTS_SINGLETON\t\t01\n+#define MENU_OPTS_IMMEDIATE\t\t02\n+#define MENU_OPTS_LIST_ONLY\t\t04\n+\n+#define MENU_RETURN_NO_LOOP\t\t10\n+\n+struct menu_opts {\n+\tconst char *header;\n+\tconst char *prompt;\n+\tint flag;\n+};\n+\n+enum menu_stuff_type {\n+\tMENU_STUFF_TYPE_STRING_LIST = 1,\n+\tMENU_STUFF_TYPE_MENU_ITEM\n+};\n+\n+struct menu_stuff {\n+\tenum menu_stuff_type type;\n+\tint nr;\n+\tvoid *stuff;\n+};\n+\n+struct menu_item {\n+\tchar hotkey;\n+\tchar *title;\n+\tint (*fn)();\n+};\n+\n static int force = -1; /* unset */\n static int interactive;\n static struct string_list del_list = STRING_LIST_INIT_DUP;\n@@ -240,12 +269,284 @@ void pretty_print_dels()\n \tcopts.indent = \"  \";\n \tcopts.padding = 2;\n \tprint_columns(&list, colopts, &copts);\n-\tputchar('\\n');\n \tstrbuf_release(&buf);\n \tstring_list_clear(&list, 0);\n }\n \n-void edit_by_patterns_cmd()\n+void pretty_print_menus(struct string_list *menu_list)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tunsigned int local_colopts = 0;\n+\tstruct column_options copts;\n+\n+\t/*\n+\t * always enable column display, we only consult column.*\n+\t * about layout strategy and stuff\n+\t */\n+\tlocal_colopts = COL_ENABLED | COL_ROW;\n+\tmemset(&copts, 0, sizeof(copts));\n+\tcopts.indent = \"  \";\n+\tcopts.padding = 2;\n+\tprint_columns(menu_list, local_colopts, &copts);\n+\tstrbuf_release(&buf);\n+}\n+\n+void prompt_help_cmd(int singleton)\n+{\n+\tclean_print_color(CLEAN_COLOR_HELP);\n+\tprintf_ln(singleton ?\n+\t\t  _(\"Prompt help:\\n\"\n+\t\t    \"1          - select a numbered item\\n\"\n+\t\t    \"foo        - select item based on unique prefix\\n\"\n+\t\t    \"           - (empty) select nothing\") :\n+\t\t  _(\"Prompt help:\\n\"\n+\t\t    \"1          - select a single item\\n\"\n+\t\t    \"3-5        - select a range of items\\n\"\n+\t\t    \"2-3,6-9    - select multiple ranges\\n\"\n+\t\t    \"foo        - select item based on unique prefix\\n\"\n+\t\t    \"-...       - unselect specified items\\n\"\n+\t\t    \"*          - choose all items\\n\"\n+\t\t    \"           - (empty) finish selecting\"));\n+\tclean_print_color(CLEAN_COLOR_RESET);\n+}\n+\n+/*\n+ * Implement a git-add-interactive compatible UI, which is borrowed\n+ * from git-add--interactive.perl.\n+ *\n+ * Return value:\n+ *\n+ *   - Return an array of integers\n+ *   - , and it is up to you to free the allocated memory.\n+ *   - The array ends with EOF.\n+ *   - If user pressed CTRL-D (i.e. EOF), no selection returned.\n+ */\n+int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)\n+{\n+\tstatic struct string_list menu_list = STRING_LIST_INIT_DUP;\n+\tstruct strbuf menu = STRBUF_INIT;\n+\tstruct strbuf choice = STRBUF_INIT;\n+\tstruct strbuf **choice_list;\n+\tint *chosen, *result;\n+\tchar *p;\n+\tint nr = 0;\n+\tint i, j;\n+\tint eof = 0;\n+\n+\tchosen = xmalloc(sizeof(int) * stuff->nr);\n+\tmemset(chosen, 0, sizeof(int) * stuff->nr);\n+\n+\twhile (1) {\n+\t\tint i = 0, j = 0;\n+\t\tstring_list_clear(&menu_list, 0);\n+\n+\t\tif (opts->header) {\n+\t\t\tprintf_ln(\"%s%s%s\",\n+\t\t\t\t  clean_get_color(CLEAN_COLOR_HEADER),\n+\t\t\t\t  opts->header,\n+\t\t\t\t  clean_get_color(CLEAN_COLOR_RESET));\n+\t\t}\n+\n+\t\t/* highlight hotkey in menu */\n+\t\tif (MENU_STUFF_TYPE_MENU_ITEM == stuff->type) {\n+\t\t\tstruct menu_item *item;\n+\n+\t\t\titem = (struct menu_item *)stuff->stuff;\n+\t\t\tfor (i = 0; i < stuff->nr; i++, item++) {\n+\t\t\t\tp = item->title;\n+\t\t\t\tstrbuf_addf(&menu, \"%s%2d: \", chosen[i] ? \"*\" : \" \", i+1);\n+\t\t\t\tfor (; *p; p++) {\n+\t\t\t\t\tif (*p == item->hotkey) {\n+\t\t\t\t\t\tstrbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_PROMPT));\n+\t\t\t\t\t\tstrbuf_addch(&menu, *p);\n+\t\t\t\t\t\tstrbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_RESET));\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\tstrbuf_addch(&menu, *p);\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t\tstring_list_append(&menu_list, menu.buf);\n+\t\t\t\tstrbuf_reset(&menu);\n+\t\t\t}\n+\t\t} else if (MENU_STUFF_TYPE_STRING_LIST == stuff->type) {\n+\t\t\tstruct string_list_item *item;\n+\t\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\t\ti = 0;\n+\n+\t\t\tfor_each_string_list_item(item, (struct string_list *)stuff->stuff) {\n+\t\t\t\tconst char *qname;\n+\n+\t\t\t\tqname = quote_path_relative(item->string, -1, &buf, *the_prefix);\n+\t\t\t\tstrbuf_addf(&menu, \"%s%2d: %s\", chosen[i] ? \"*\" : \" \", ++i, qname);\n+\t\t\t\tstring_list_append(&menu_list, menu.buf);\n+\t\t\t\tstrbuf_reset(&menu);\n+\t\t\t}\n+\t\t}\n+\n+\t\tpretty_print_menus(&menu_list);\n+\n+\t\tif (opts->flag & MENU_OPTS_LIST_ONLY)\n+\t\t\tbreak;\n+\n+\t\tif (opts->prompt) {\n+\t\t\tprintf(\"%s%s%s%s\",\n+\t\t\t       clean_get_color(CLEAN_COLOR_PROMPT),\n+\t\t\t       opts->prompt,\n+\t\t\t       opts->flag & MENU_OPTS_SINGLETON ? \"> \" : \">> \",\n+\t\t\t       clean_get_color(CLEAN_COLOR_RESET));\n+\t\t}\n+\n+\t\tif (strbuf_getline(&choice, stdin, '\\n') != EOF) {\n+\t\t\tif (!(opts->flag & MENU_OPTS_SINGLETON)) {\n+\t\t\t\tchar *p = choice.buf;\n+\t\t\t\tdo {\n+\t\t\t\t\tif (*p == ',')\n+\t\t\t\t\t\t*p = ' ';\n+\t\t\t\t} while (*p++);\n+\t\t\t}\n+\t\t\tstrbuf_trim(&choice);\n+\t\t} else {\n+\t\t\teof = 1;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t/* help for prompt */\n+\t\tif (!strcmp(choice.buf, \"?\")) {\n+\t\t\tprompt_help_cmd(opts->flag & MENU_OPTS_SINGLETON);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!(opts->flag & MENU_OPTS_SINGLETON) && !choice.len)\n+\t\t\tbreak;\n+\n+\t\tchoice_list = strbuf_split_max(&choice, ' ', 0);\n+\t\tfor (i = 0; choice_list[i]; i++) {\n+\t\t\tint choose = 1;\n+\t\t\tint bottom = 0, top = 0;\n+\t\t\tchar *p;\n+\t\t\tint is_range = 0;\n+\t\t\tint is_number = 1;\n+\n+\t\t\tstrbuf_trim(choice_list[i]);\n+\t\t\tif (!choice_list[i]->len)\n+\t\t\t\tcontinue;\n+\n+\t\t\t/* Input that begins with '-'; unchoose */\n+\t\t\tif (*choice_list[i]->buf == '-') {\n+\t\t\t\tchoose = 0;\n+\t\t\t\tstrbuf_remove(choice_list[i], 0, 1);\n+\t\t\t}\n+\n+\t\t\tp = choice_list[i]->buf;\n+\t\t\tfor(; *p; p++) {\n+\t\t\t\tif ('-' == *p) {\n+\t\t\t\t\tif (!is_range) {\n+\t\t\t\t\t\tis_range = 1;\n+\t\t\t\t\t\tis_number = 0;\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\tis_number = 0;\n+\t\t\t\t\t\tis_range = 0;\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t}\n+\t\t\t\t} else if (!isdigit(*p)) {\n+\t\t\t\t\tis_number = 0;\n+\t\t\t\t\tis_range = 0;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tif (is_number) {\n+\t\t\t\tbottom = atoi(choice_list[i]->buf);\n+\t\t\t\ttop = bottom;\n+\t\t\t} else if (is_range) {\n+\t\t\t\tbottom = atoi(choice_list[i]->buf);\n+\t\t\t\tif (!*(strchr(choice_list[i]->buf, '-') + 1)) {\n+\t\t\t\t\ttop = stuff->nr - 1;\n+\t\t\t\t} else {\n+\t\t\t\t\ttop = atoi(strchr(choice_list[i]->buf, '-') + 1);\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(choice_list[i]->buf, \"*\")) {\n+\t\t\t\tbottom = 1;\n+\t\t\t\ttop = stuff->nr;\n+\t\t\t} else {\n+\t\t\t\tif (MENU_STUFF_TYPE_MENU_ITEM == stuff->type) {\n+\t\t\t\t\tstruct menu_item *item;\n+\n+\t\t\t\t\titem = (struct menu_item *)stuff->stuff;\n+\t\t\t\t\tfor (j = 0; j < stuff->nr; j++, item++) {\n+\t\t\t\t\t\tif ((choice_list[i]->len == 1 &&\n+\t\t\t\t\t\t     *choice_list[i]->buf == item->hotkey) ||\n+\t\t\t\t\t\t    !strcasecmp(choice_list[i]->buf, item->title)) {\n+\t\t\t\t\t\t\tbottom = j + 1;\n+\t\t\t\t\t\t\ttop = bottom;\n+\t\t\t\t\t\t\tbreak;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t} else if (MENU_STUFF_TYPE_STRING_LIST == stuff->type) {\n+\t\t\t\t\tstruct string_list_item *item;\n+\n+\t\t\t\t\titem = ((struct string_list *)stuff->stuff)->items;\n+\t\t\t\t\tfor (j = 0; j < stuff->nr; j++, item++) {\n+\t\t\t\t\t\tif (!strcasecmp(choice_list[i]->buf, item->string)) {\n+\t\t\t\t\t\t\tbottom = j + 1;\n+\t\t\t\t\t\t\ttop = bottom;\n+\t\t\t\t\t\t\tbreak;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tif (top <= 0 || bottom <= 0 || top > stuff-> nr || bottom > top ||\n+\t\t\t    (opts->flag & MENU_OPTS_SINGLETON && bottom != top)) {\n+\t\t\t\tprintf_ln(\"%sHuh (%s)?%s\",\n+\t\t\t\t\t  clean_get_color(CLEAN_COLOR_ERROR),\n+\t\t\t\t\t  choice_list[i]->buf,\n+\t\t\t\t\t  clean_get_color(CLEAN_COLOR_RESET));\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\t/* A range can be specified like 5-7 or 5-. */\n+\t\t\tfor (j = bottom; j <= top; j++) {\n+\t\t\t\tchosen[j-1] = choose;\n+\t\t\t\tnr++;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (opts->flag & MENU_OPTS_SINGLETON) {\n+\t\t\tif (nr)\n+\t\t\t\tbreak;\n+\t\t} else if (opts->flag & MENU_OPTS_IMMEDIATE) {\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\n+\tif (eof) {\n+\t\tresult = xmalloc(sizeof(int) * 2);\n+\t\tresult[0] = EOF;\n+\t\tresult[1] = 0;\n+\t} else {\n+\t\tresult = xmalloc(sizeof(int) * (nr + 1));\n+\t\tmemset(result, 0, sizeof(int) * (nr + 1));\n+\t\tfor (i = 0, j = 0; i < stuff->nr && j < nr; i++) {\n+\t\t\tif (chosen[i])\n+\t\t\t\tresult[j++] = i;\n+\t\t}\n+\t\tresult[j] = EOF;\n+\t}\n+\n+\tfree(chosen);\n+\tstring_list_clear(&menu_list, 0);\n+\tstrbuf_release(&menu);\n+\tstrbuf_release(&choice);\n+\treturn result;\n+}\n+\n+int clean_cmd()\n+{\n+\treturn MENU_RETURN_NO_LOOP;\n+}\n+\n+int edit_by_patterns_cmd()\n {\n \tstruct dir_struct dir;\n \tstruct strbuf confirm = STRBUF_INIT;\n@@ -257,16 +558,10 @@ void edit_by_patterns_cmd()\n \n \twhile (1) {\n \t\t/* dels list may become empty when we run string_list_remove_empty_items later */\n-\t\tif (!del_list.nr) {\n-\t\t\tclean_print_color(CLEAN_COLOR_ERROR);\n-\t\t\tprintf_ln(_(\"No more files to clean, exiting.\"));\n-\t\t\tclean_print_color(CLEAN_COLOR_RESET);\n+\t\tif (!del_list.nr)\n \t\t\tbreak;\n-\t\t}\n \n \t\tif (changed) {\n-\t\t\tputchar('\\n');\n-\n \t\t\t/* Display dels in columns */\n \t\t\tpretty_print_dels();\n \t\t}\n@@ -324,56 +619,86 @@ void edit_by_patterns_cmd()\n \n \tstrbuf_release(&buf);\n \tstrbuf_release(&confirm);\n+\treturn 0;\n }\n \n-void interactive_main_loop()\n+int quit_cmd()\n {\n-\tstruct strbuf confirm = STRBUF_INIT;\n+\tstring_list_clear(&del_list, 0);\n+\tprintf_ln(_(\"Bye.\"));\n+\treturn MENU_RETURN_NO_LOOP;\n+}\n \n+int help_cmd(int x)\n+{\n+\tclean_print_color(CLEAN_COLOR_HELP);\n+\tprintf_ln(_(\n+\t\t    \"clean            - start cleaning\\n\"\n+\t\t    \"edit by patterns - exclude items from deletion\\n\"\n+\t\t    \"quit             - stop cleaning\\n\"\n+\t\t    \"help             - this screen\\n\"\n+\t\t    \"?                - help for prompt selection\"\n+\t\t   ));\n+\tclean_print_color(CLEAN_COLOR_RESET);\n+\treturn 0;\n+}\n+\n+void interactive_main_loop()\n+{\n \t/* dels list may become empty after return back from edit mode */\n \twhile (del_list.nr) {\n-\t\tputchar('\\n');\n+\t\tstruct menu_opts menu_opts;\n+\t\tstruct menu_stuff menu_stuff;\n+\t\tstruct menu_item menus[] = {\n+\t\t\t{'c', \"clean\",\t\t\tclean_cmd},\n+\t\t\t{'p', \"edit by patterns\",\tedit_by_patterns_cmd},\n+\t\t\t{'q', \"quit\",\t\t\tquit_cmd},\n+\t\t\t{'h', \"help\",\t\t\thelp_cmd},\n+\t\t};\n+\t\tint *chosen;\n+\n+\t\tmenu_opts.header = _(\"*** Commands ***\");\n+\t\tmenu_opts.prompt = \"What now\";\n+\t\tmenu_opts.flag = MENU_OPTS_SINGLETON;\n+\n+\t\tmenu_stuff.type = MENU_STUFF_TYPE_MENU_ITEM;\n+\t\tmenu_stuff.stuff = menus;\n+\t\tmenu_stuff.nr = sizeof(menus) / sizeof(struct menu_item);\n+\n \t\tclean_print_color(CLEAN_COLOR_HEADER);\n \t\tprintf_ln(Q_(\"Would remove the following item:\",\n \t\t\t     \"Would remove the following items:\",\n \t\t\t     del_list.nr));\n \t\tclean_print_color(CLEAN_COLOR_RESET);\n-\t\tputchar('\\n');\n \n-\t\t/* Display dels in columns */\n+\t\t/* display dels in columns */\n \t\tpretty_print_dels();\n \n-\t\t/* Confirmation dialog */\n-\t\tclean_print_color(CLEAN_COLOR_PROMPT);\n-\t\tprintf(_(\"Remove ([y]es/[n]o/[e]dit) ? \"));\n-\t\tclean_print_color(CLEAN_COLOR_RESET);\n-\t\tif (strbuf_getline(&confirm, stdin, '\\n') != EOF) {\n-\t\t\tstrbuf_trim(&confirm);\n-\t\t} else {\n-\t\t\t/* Ctrl-D is the same as \"quit\" */\n-\t\t\tstring_list_clear(&del_list, 0);\n-\t\t\tputchar('\\n');\n-\t\t\tprintf_ln(\"Bye.\");\n-\t\t\tbreak;\n-\t\t}\n-\n-\t\tif (confirm.len) {\n-\t\t\tif (!strncasecmp(confirm.buf, \"yes\", confirm.len)) {\n-\t\t\t\tbreak;\n-\t\t\t} else if (!strncasecmp(confirm.buf, \"no\", confirm.len) ||\n-\t\t\t\t   !strncasecmp(confirm.buf, \"quit\", confirm.len)) {\n-\t\t\t\tstring_list_clear(&del_list, 0);\n-\t\t\t\tprintf_ln(\"Bye.\");\n-\t\t\t\tbreak;\n-\t\t\t} else if (!strncasecmp(confirm.buf, \"edit\", confirm.len)) {\n-\t\t\t\tedit_by_patterns_cmd();\n-\t\t\t} else {\n+\t\t/* main menu */\n+\t\tchosen = list_and_choose(&menu_opts, &menu_stuff);\n+\n+\t\tif (*chosen != EOF) {\n+\t\t\tint ret;\n+\t\t\tret = menus[*chosen].fn(1);\n+\t\t\tif (ret != MENU_RETURN_NO_LOOP) {\n+\t\t\t\tfree(chosen);\n+\t\t\t\tchosen = NULL;\n+\t\t\t\tif (!del_list.nr) {\n+\t\t\t\t\tclean_print_color(CLEAN_COLOR_ERROR);\n+\t\t\t\t\tprintf_ln(_(\"No more files to clean, exiting.\"));\n+\t\t\t\t\tclean_print_color(CLEAN_COLOR_RESET);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n \t\t\t\tcontinue;\n \t\t\t}\n+\t\t} else {\n+\t\t\tquit_cmd();\n \t\t}\n-\t}\n \n-\tstrbuf_release(&confirm);\n+\t\tfree(chosen);\n+\t\tchosen = NULL;\n+\t\tbreak;\n+\t}\n }\n \n int cmd_clean(int argc, const char **argv, const char *prefix)\n@@ -499,9 +824,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \t\t}\n \n \t\tif (S_ISDIR(st.st_mode)) {\n-\t\t\tif (remove_directories || (matches == MATCHED_EXACTLY)) {\n+\t\t\tif (remove_directories || (matches == MATCHED_EXACTLY))\n \t\t\t\tstring_list_append(&del_list, ent->name);\n-\t\t\t}\n \t\t} else {\n \t\t\tif (pathspec && !matches)\n \t\t\t\tcontinue;\n-- \n1.8.3.rc1.338.gb35aa5d\n"},{"id":"216542","messageId":"c81cef0983760c76c2a6b92c1fcc2c49a80a8747.1367867498.git.worldhello.net@gmail.com","threadId":"33711","inReplyTo":"217e7618e9b9d50a50461959b38984ac8be88651.1367867498.git.worldhello.net@gmail.com","subject":"[PATCH v6 5/7] git-clean: interactive cleaning by select numbers","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-06T19:18:54Z","receivedAt":"2013-05-06T19:18:54Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Draw a multiple choice menu using `list_and_choose` to select items\nto be deleted by numbers.\n\nUser can input:\n\n *  1,5-7 : select 1,5,6,7 items to be deleted\n *  *     : select all items to be deleted\n *  -*    : unselect all, nothing will be deleted\n *        : (empty) finish selecting, and return back to main menu\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n builtin/clean.c | 39 +++++++++++++++++++++++++++++++++++++++\n 1 file changed, 39 insertions(+)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 3b9f3..3b07f 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -622,6 +622,43 @@ int edit_by_patterns_cmd()\n \treturn 0;\n }\n \n+int edit_by_numbers_cmd()\n+{\n+\tstruct menu_opts menu_opts;\n+\tstruct menu_stuff menu_stuff;\n+\tstruct string_list_item *items;\n+\tint *chosen;\n+\tint i, j;\n+\n+\tmenu_opts.header = NULL;\n+\tmenu_opts.prompt = \"Select items to delete\";\n+\tmenu_opts.flag = 0;\n+\n+\tmenu_stuff.type = MENU_STUFF_TYPE_STRING_LIST;\n+\tmenu_stuff.stuff = &del_list;\n+\tmenu_stuff.nr = del_list.nr;\n+\n+\tchosen = list_and_choose(&menu_opts, &menu_stuff);\n+\titems = del_list.items;\n+\tfor(i = 0, j = 0; i < del_list.nr; i++) {\n+\t\tif (i < chosen[j]) {\n+\t\t\t*(items[i].string) = '\\0';\n+\t\t} else if (i == chosen[j]) {\n+\t\t\t/* delete selected item */\n+\t\t\tj++;\n+\t\t\tcontinue;\n+\t\t} else {\n+\t\t\t/* end of chosen (EOF), won't delete */\n+\t\t\t*(items[i].string) = '\\0';\n+\t\t}\n+\t}\n+\n+\tstring_list_remove_empty_items(&del_list, 0);\n+\n+\tfree(chosen);\n+\treturn 0;\n+}\n+\n int quit_cmd()\n {\n \tstring_list_clear(&del_list, 0);\n@@ -635,6 +672,7 @@ int help_cmd(int x)\n \tprintf_ln(_(\n \t\t    \"clean            - start cleaning\\n\"\n \t\t    \"edit by patterns - exclude items from deletion\\n\"\n+\t\t    \"edit by numbers  - select items to be deleted by numbers\\n\"\n \t\t    \"quit             - stop cleaning\\n\"\n \t\t    \"help             - this screen\\n\"\n \t\t    \"?                - help for prompt selection\"\n@@ -652,6 +690,7 @@ void interactive_main_loop()\n \t\tstruct menu_item menus[] = {\n \t\t\t{'c', \"clean\",\t\t\tclean_cmd},\n \t\t\t{'p', \"edit by patterns\",\tedit_by_patterns_cmd},\n+\t\t\t{'n', \"edit by numbers\",\tedit_by_numbers_cmd},\n \t\t\t{'q', \"quit\",\t\t\tquit_cmd},\n \t\t\t{'h', \"help\",\t\t\thelp_cmd},\n \t\t};\n-- \n1.8.3.rc1.338.gb35aa5d\n"},{"id":"216543","messageId":"5353bf3a49c83ca27ad4be221b1b4856d119f8c1.1367867498.git.worldhello.net@gmail.com","threadId":"33711","inReplyTo":"c81cef0983760c76c2a6b92c1fcc2c49a80a8747.1367867498.git.worldhello.net@gmail.com","subject":"[PATCH v6 6/7] git-clean: rm -i style interactive cleaning","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-06T19:18:55Z","receivedAt":"2013-05-06T19:18:55Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add a \"rm -i\" style interactive cleaning method. User must confirm one\nby one before starting to delete.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n builtin/clean.c | 36 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 36 insertions(+)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 3b07f..f36ad 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -659,6 +659,40 @@ int edit_by_numbers_cmd()\n \treturn 0;\n }\n \n+int rm_i_cmd()\n+{\n+\tstruct strbuf confirm = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct string_list_item *item;\n+\tconst char *qname;\n+\tint changed = 0, eof = 0;\n+\n+\tfor_each_string_list_item(item, &del_list) {\n+\t\t/* Ctrl-D should stop removing files */\n+\t\tif (!eof) {\n+\t\t\tqname = quote_path_relative(item->string, -1, &buf, *the_prefix);\n+\t\t\tprintf(_(\"remove %s ? \"), qname);\n+\t\t\tif (strbuf_getline(&confirm, stdin, '\\n') != EOF) {\n+\t\t\t\tstrbuf_trim(&confirm);\n+\t\t\t} else {\n+\t\t\t\tputchar('\\n');\n+\t\t\t\teof = 1;\n+\t\t\t}\n+\t\t}\n+\t\tif (!confirm.len || !strncasecmp(confirm.buf, \"no\", confirm.len)) {\n+\t\t\t*item->string = '\\0';\n+\t\t\tchanged++;\n+\t\t}\n+\t}\n+\n+\tif (changed)\n+\t\tstring_list_remove_empty_items(&del_list, 0);\n+\n+\tstrbuf_release(&buf);\n+\tstrbuf_release(&confirm);\n+\treturn MENU_RETURN_NO_LOOP;\n+}\n+\n int quit_cmd()\n {\n \tstring_list_clear(&del_list, 0);\n@@ -673,6 +707,7 @@ int help_cmd(int x)\n \t\t    \"clean            - start cleaning\\n\"\n \t\t    \"edit by patterns - exclude items from deletion\\n\"\n \t\t    \"edit by numbers  - select items to be deleted by numbers\\n\"\n+\t\t    \"rm -i            - delete items one by one, like \\\"rm -i\\\"\\n\"\n \t\t    \"quit             - stop cleaning\\n\"\n \t\t    \"help             - this screen\\n\"\n \t\t    \"?                - help for prompt selection\"\n@@ -691,6 +726,7 @@ void interactive_main_loop()\n \t\t\t{'c', \"clean\",\t\t\tclean_cmd},\n \t\t\t{'p', \"edit by patterns\",\tedit_by_patterns_cmd},\n \t\t\t{'n', \"edit by numbers\",\tedit_by_numbers_cmd},\n+\t\t\t{'i', \"rm -i\",\t\t\trm_i_cmd},\n \t\t\t{'q', \"quit\",\t\t\tquit_cmd},\n \t\t\t{'h', \"help\",\t\t\thelp_cmd},\n \t\t};\n-- \n1.8.3.rc1.338.gb35aa5d\n"},{"id":"216544","messageId":"b35aa5d0de820e5e13da32d23a70a82d3df83521.1367867498.git.worldhello.net@gmail.com","threadId":"33711","inReplyTo":"5353bf3a49c83ca27ad4be221b1b4856d119f8c1.1367867498.git.worldhello.net@gmail.com","subject":"[PATCH v6 7/7] git-clean: update document for interactive git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-06T19:18:56Z","receivedAt":"2013-05-06T19:18:56Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Signed-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n Documentation/git-clean.txt | 70 ++++++++++++++++++++++++++++++++++++++++-----\n 1 file changed, 63 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt\nindex f5572..56d60 100644\n--- a/Documentation/git-clean.txt\n+++ b/Documentation/git-clean.txt\n@@ -39,13 +39,8 @@ OPTIONS\n \n -i::\n --interactive::\n-\tShow what would be done and the user must confirm before actually\n-\tcleaning. In the confirmation dialog, the user can choose to abort\n-\tthe cleaning, or enter into an edit mode. In the edit mode, the\n-\tuser can input space-separated patterns (the same syntax as\n-\tgitignore), and each clean candidate that matches with one of the\n-\tpatterns will be excluded from cleaning. When the user feels it's\n-\tOK, presses ENTER and back to the confirmation dialog.\n+\tShow what would be done and clean files interactively. See\n+\t``Interactive mode`` for details.\n \n -n::\n --dry-run::\n@@ -74,6 +69,67 @@ OPTIONS\n \tRemove only files ignored by Git.  This may be useful to rebuild\n \teverything from scratch, but keep manually created files.\n \n+Interactive mode\n+----------------\n+When the command enters the interactive mode, it shows the\n+files and directories to be cleaned, and goes into its\n+interactive command loop.\n+\n+The command loop shows the list of subcommands available, and\n+gives a prompt \"What now> \".  In general, when the prompt ends\n+with a single '>', you can pick only one of the choices given\n+and type return, like this:\n+\n+------------\n+    *** Commands ***\n+      1: clean             2: edit by patterns   3: edit by numbers\n+      4. rm -i             5. quit               6. help\n+    What now> 2\n+------------\n+\n+You also could say `c` or `clean` above as long as the choice is unique.\n+\n+The main command loop has 6 subcommands.\n+\n+clean::\n+\n+   Start cleaning files and directories, and then quit.\n+\n+edit by patterns::\n+\n+   This shows the files and directories to be deleted and issues an\n+   \"Input ignore patterns>>\" prompt. You can input a space-seperated\n+   patterns to exclude files and directories from deletion.\n+   E.g. \"*.c *.h\" will excludes files end with \".c\" and \".h\" from\n+   deletion. When you are satisfied with the filtered result, press\n+   ENTER (empty) back to the main menu.\n+\n+edit by numbers::\n+\n+   This shows the files and directories to be deleted and issues an\n+   \"Select items to delete>>\" prompt. When the prompt ends with double\n+   '>>' like this, you can make more than one selection, concatenated\n+   with whitespace or comma.  Also you can say ranges.  E.g. \"2-5 7,9\"\n+   to choose 2,3,4,5,7,9 from the list.  If the second number in a\n+   range is omitted, all remaining patches are taken.  E.g. \"7-\" to\n+   choose 7,8,9 from the list.  You can say '*' to choose everything.\n+   Also when you are satisfied with the filtered result, press ENTER\n+   (empty) back to the main menu.\n+\n+rm -i::\n+\n+  This will show a \"rm -i\" style cleaning, that you must confirm one\n+  by one in order to delete items. This action is not as efficient\n+  as the above two actions.\n+\n+quit::\n+\n+  This lets you quit without do cleaning.\n+\n+help::\n+\n+  Show brief usage of interactive git-clean.\n+\n SEE ALSO\n --------\n linkgit:gitignore[5]\n-- \n1.8.3.rc1.338.gb35aa5d\n"},{"id":"216582","messageId":"CANYiYbEE90zvhoRp3utOzRWXnpVGTcaruWDuCCHE77FmGwB2_w@mail.gmail.com","threadId":"33711","inReplyTo":"217e7618e9b9d50a50461959b38984ac8be88651.1367867498.git.worldhello.net@gmail.com","subject":"Re: [PATCH v6 4/7] git-clean: use a git-add-interactive compatible UI","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-07T04:16:53Z","receivedAt":"2013-05-07T04:16:53Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"2013/5/7 Jiang Xin <worldhello.net@gmail.com>:\n> Rewrite menu using a new method `list_and_choose`, which is borrowed\n> from `git-add--interactive.perl`. We can reused this method later for\n> more actions.\n>\n> Please NOTE:\n>\n>  * Method `list_and_choose` return an array of integers, and\n>  * it is up to you to free the allocated memory of the array.\n>  * The array ends with EOF.\n>  * If user pressed CTRL-D (i.e. EOF), no selection returned.\n>\n> Signed-off-by: Jiang Xin <worldhello.net@gmail.com>\n> ---\n>  builtin/clean.c | 410 ++++++++++++++++++++++++++++++++++++++++++++++++++------\n>  1 file changed, 367 insertions(+), 43 deletions(-)\n>\n> diff --git a/builtin/clean.c b/builtin/clean.c\n> index 6bda3..3b9f3 100644\n> --- a/builtin/clean.c\n> +++ b/builtin/clean.c\n> @@ -16,6 +16,35 @@\n>  #include \"column.h\"\n>  #include \"color.h\"\n>\n> +#define MENU_OPTS_SINGLETON            01\n> +#define MENU_OPTS_IMMEDIATE            02\n> +#define MENU_OPTS_LIST_ONLY            04\n> +\n> +#define MENU_RETURN_NO_LOOP            10\n> +\n> +struct menu_opts {\n> +       const char *header;\n> +       const char *prompt;\n> +       int flag;\n> +};\n> +\n> +enum menu_stuff_type {\n> +       MENU_STUFF_TYPE_STRING_LIST = 1,\n> +       MENU_STUFF_TYPE_MENU_ITEM\n> +};\n> +\n> +struct menu_stuff {\n> +       enum menu_stuff_type type;\n> +       int nr;\n> +       void *stuff;\n> +};\n> +\n> +struct menu_item {\n> +       char hotkey;\n> +       char *title;\n> +       int (*fn)();\n> +};\n> +\n>  static int force = -1; /* unset */\n>  static int interactive;\n>  static struct string_list del_list = STRING_LIST_INIT_DUP;\n> @@ -240,12 +269,284 @@ void pretty_print_dels()\n>         copts.indent = \"  \";\n>         copts.padding = 2;\n>         print_columns(&list, colopts, &copts);\n> -       putchar('\\n');\n>         strbuf_release(&buf);\n>         string_list_clear(&list, 0);\n>  }\n>\n> -void edit_by_patterns_cmd()\n> +void pretty_print_menus(struct string_list *menu_list)\n> +{\n> +       struct strbuf buf = STRBUF_INIT;\nunused buf should be deleted.\n\n> +       unsigned int local_colopts = 0;\n> +       struct column_options copts;\n> +\n> +       /*\n> +        * always enable column display, we only consult column.*\n> +        * about layout strategy and stuff\n> +        */\nremove the above comments.\n\n> +       local_colopts = COL_ENABLED | COL_ROW;\n> +       memset(&copts, 0, sizeof(copts));\n> +       copts.indent = \"  \";\n> +       copts.padding = 2;\n> +       print_columns(menu_list, local_colopts, &copts);\n> +       strbuf_release(&buf);\nremove strbuf_release of unused variable : buf.\n\n> +}\n> +\n> +void prompt_help_cmd(int singleton)\n> +{\n> +       clean_print_color(CLEAN_COLOR_HELP);\n> +       printf_ln(singleton ?\n> +                 _(\"Prompt help:\\n\"\n> +                   \"1          - select a numbered item\\n\"\n> +                   \"foo        - select item based on unique prefix\\n\"\n> +                   \"           - (empty) select nothing\") :\n> +                 _(\"Prompt help:\\n\"\n> +                   \"1          - select a single item\\n\"\n> +                   \"3-5        - select a range of items\\n\"\n> +                   \"2-3,6-9    - select multiple ranges\\n\"\n> +                   \"foo        - select item based on unique prefix\\n\"\n> +                   \"-...       - unselect specified items\\n\"\n> +                   \"*          - choose all items\\n\"\n> +                   \"           - (empty) finish selecting\"));\n> +       clean_print_color(CLEAN_COLOR_RESET);\n> +}\n> +\n> +/*\n> + * Implement a git-add-interactive compatible UI, which is borrowed\n> + * from git-add--interactive.perl.\n> + *\n> + * Return value:\n> + *\n> + *   - Return an array of integers\n> + *   - , and it is up to you to free the allocated memory.\n> + *   - The array ends with EOF.\n> + *   - If user pressed CTRL-D (i.e. EOF), no selection returned.\n> + */\n> +int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)\n> +{\n> +       static struct string_list menu_list = STRING_LIST_INIT_DUP;\n> +       struct strbuf menu = STRBUF_INIT;\n> +       struct strbuf choice = STRBUF_INIT;\n> +       struct strbuf **choice_list;\n> +       int *chosen, *result;\n> +       char *p;\n> +       int nr = 0;\n> +       int i, j;\n> +       int eof = 0;\n> +\n> +       chosen = xmalloc(sizeof(int) * stuff->nr);\n> +       memset(chosen, 0, sizeof(int) * stuff->nr);\n> +\n> +       while (1) {\n> +               int i = 0, j = 0;\n> +               string_list_clear(&menu_list, 0);\n> +\n> +               if (opts->header) {\n> +                       printf_ln(\"%s%s%s\",\n> +                                 clean_get_color(CLEAN_COLOR_HEADER),\n> +                                 opts->header,\n> +                                 clean_get_color(CLEAN_COLOR_RESET));\n> +               }\n> +\n> +               /* highlight hotkey in menu */\n> +               if (MENU_STUFF_TYPE_MENU_ITEM == stuff->type) {\n> +                       struct menu_item *item;\n> +\n> +                       item = (struct menu_item *)stuff->stuff;\n> +                       for (i = 0; i < stuff->nr; i++, item++) {\n> +                               p = item->title;\n> +                               strbuf_addf(&menu, \"%s%2d: \", chosen[i] ? \"*\" : \" \", i+1);\n> +                               for (; *p; p++) {\n> +                                       if (*p == item->hotkey) {\n> +                                               strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_PROMPT));\n> +                                               strbuf_addch(&menu, *p);\n> +                                               strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_RESET));\n> +                                       } else {\n> +                                               strbuf_addch(&menu, *p);\n> +                                       }\n> +                               }\n> +                               string_list_append(&menu_list, menu.buf);\n> +                               strbuf_reset(&menu);\n> +                       }\n> +               } else if (MENU_STUFF_TYPE_STRING_LIST == stuff->type) {\n> +                       struct string_list_item *item;\n> +                       struct strbuf buf = STRBUF_INIT;\nshould call strbuf_release later\n\n> +                       i = 0;\n> +\n> +                       for_each_string_list_item(item, (struct string_list *)stuff->stuff) {\n> +                               const char *qname;\n> +\n> +                               qname = quote_path_relative(item->string, -1, &buf, *the_prefix);\n> +                               strbuf_addf(&menu, \"%s%2d: %s\", chosen[i] ? \"*\" : \" \", ++i, qname);\n> +                               string_list_append(&menu_list, menu.buf);\n> +                               strbuf_reset(&menu);\n> +                       }\n+                       strbuf_release(&buf);\n\n> +               }\n> +\n> +               pretty_print_menus(&menu_list);\n> +\n> +               if (opts->flag & MENU_OPTS_LIST_ONLY)\n> +                       break;\n> +\n> +               if (opts->prompt) {\n> +                       printf(\"%s%s%s%s\",\n> +                              clean_get_color(CLEAN_COLOR_PROMPT),\n> +                              opts->prompt,\n> +                              opts->flag & MENU_OPTS_SINGLETON ? \"> \" : \">> \",\n> +                              clean_get_color(CLEAN_COLOR_RESET));\n> +               }\n> +\n> +               if (strbuf_getline(&choice, stdin, '\\n') != EOF) {\n> +                       if (!(opts->flag & MENU_OPTS_SINGLETON)) {\n> +                               char *p = choice.buf;\n> +                               do {\n> +                                       if (*p == ',')\n> +                                               *p = ' ';\n> +                               } while (*p++);\n> +                       }\n> +                       strbuf_trim(&choice);\n> +               } else {\n> +                       eof = 1;\n> +                       break;\n> +               }\n> +\n> +               /* help for prompt */\n> +               if (!strcmp(choice.buf, \"?\")) {\n> +                       prompt_help_cmd(opts->flag & MENU_OPTS_SINGLETON);\n> +                       continue;\n> +               }\n> +\n> +               if (!(opts->flag & MENU_OPTS_SINGLETON) && !choice.len)\n> +                       break;\n> +\n> +               choice_list = strbuf_split_max(&choice, ' ', 0);\nShould be freed later\n\n> +               for (i = 0; choice_list[i]; i++) {\n> +                       int choose = 1;\n> +                       int bottom = 0, top = 0;\n> +                       char *p;\n> +                       int is_range = 0;\n> +                       int is_number = 1;\n> +\n> +                       strbuf_trim(choice_list[i]);\n> +                       if (!choice_list[i]->len)\n> +                               continue;\n> +\n> +                       /* Input that begins with '-'; unchoose */\n> +                       if (*choice_list[i]->buf == '-') {\n> +                               choose = 0;\n> +                               strbuf_remove(choice_list[i], 0, 1);\n> +                       }\n> +\n> +                       p = choice_list[i]->buf;\n> +                       for(; *p; p++) {\n> +                               if ('-' == *p) {\n> +                                       if (!is_range) {\n> +                                               is_range = 1;\n> +                                               is_number = 0;\n> +                                       } else {\n> +                                               is_number = 0;\n> +                                               is_range = 0;\n> +                                               break;\n> +                                       }\n> +                               } else if (!isdigit(*p)) {\n> +                                       is_number = 0;\n> +                                       is_range = 0;\n> +                                       break;\n> +                               }\n> +                       }\n> +\n> +                       if (is_number) {\n> +                               bottom = atoi(choice_list[i]->buf);\n> +                               top = bottom;\n> +                       } else if (is_range) {\n> +                               bottom = atoi(choice_list[i]->buf);\n> +                               if (!*(strchr(choice_list[i]->buf, '-') + 1)) {\n> +                                       top = stuff->nr - 1;\n> +                               } else {\n> +                                       top = atoi(strchr(choice_list[i]->buf, '-') + 1);\n> +                               }\n> +                       } else if (!strcmp(choice_list[i]->buf, \"*\")) {\n> +                               bottom = 1;\n> +                               top = stuff->nr;\n> +                       } else {\n> +                               if (MENU_STUFF_TYPE_MENU_ITEM == stuff->type) {\n> +                                       struct menu_item *item;\n> +\n> +                                       item = (struct menu_item *)stuff->stuff;\n> +                                       for (j = 0; j < stuff->nr; j++, item++) {\n> +                                               if ((choice_list[i]->len == 1 &&\n> +                                                    *choice_list[i]->buf == item->hotkey) ||\n> +                                                   !strcasecmp(choice_list[i]->buf, item->title)) {\n> +                                                       bottom = j + 1;\n> +                                                       top = bottom;\n> +                                                       break;\n> +                                               }\n> +                                       }\n> +                               } else if (MENU_STUFF_TYPE_STRING_LIST == stuff->type) {\n> +                                       struct string_list_item *item;\n> +\n> +                                       item = ((struct string_list *)stuff->stuff)->items;\n> +                                       for (j = 0; j < stuff->nr; j++, item++) {\n> +                                               if (!strcasecmp(choice_list[i]->buf, item->string)) {\n> +                                                       bottom = j + 1;\n> +                                                       top = bottom;\n> +                                                       break;\n> +                                               }\n> +                                       }\n> +                               }\n> +                       }\n> +\n> +                       if (top <= 0 || bottom <= 0 || top > stuff-> nr || bottom > top ||\n> +                           (opts->flag & MENU_OPTS_SINGLETON && bottom != top)) {\n> +                               printf_ln(\"%sHuh (%s)?%s\",\n> +                                         clean_get_color(CLEAN_COLOR_ERROR),\n> +                                         choice_list[i]->buf,\n> +                                         clean_get_color(CLEAN_COLOR_RESET));\n> +                               continue;\n> +                       }\n> +\n> +                       /* A range can be specified like 5-7 or 5-. */\n> +                       for (j = bottom; j <= top; j++) {\n> +                               chosen[j-1] = choose;\n> +                               nr++;\n> +                       }\n> +               }\n\n+               strbuf_list_free(choice_list);\n\n> +\n> +               if (opts->flag & MENU_OPTS_SINGLETON) {\n> +                       if (nr)\n> +                               break;\n> +               } else if (opts->flag & MENU_OPTS_IMMEDIATE) {\n> +                       break;\n> +               }\n> +       }\n> +\n> +\n> +       if (eof) {\n> +               result = xmalloc(sizeof(int) * 2);\n> +               result[0] = EOF;\n> +               result[1] = 0;\nAllocate one element is OK, like:\n\n+               result = xmalloc(sizeof(int));\n+               *result = EOF;\n\n> +       } else {\n> +               result = xmalloc(sizeof(int) * (nr + 1));\n> +               memset(result, 0, sizeof(int) * (nr + 1));\n\nAdd initial for j here:\n\n+               j = 0;\n\n> +               for (i = 0, j = 0; i < stuff->nr && j < nr; i++) {\n> +                       if (chosen[i])\n> +                               result[j++] = i;\n> +               }\n> +               result[j] = EOF;\n> +       }\n> +\n> +       free(chosen);\n> +       string_list_clear(&menu_list, 0);\n> +       strbuf_release(&menu);\n> +       strbuf_release(&choice);\n> +       return result;\n> +}\n\n\n-- \nJiang Xin\n"},{"id":"216583","messageId":"CANYiYbHUmRy+k5DRQnxaH-q2jMtHc1=xNDt4zfq3ZsAC6dJPJQ@mail.gmail.com","threadId":"33711","inReplyTo":"b35aa5d0de820e5e13da32d23a70a82d3df83521.1367867498.git.worldhello.net@gmail.com","subject":"Re: [PATCH v6 7/7] git-clean: update document for interactive git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-07T04:20:47Z","receivedAt":"2013-05-07T04:20:47Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"2013/5/7 Jiang Xin <worldhello.net@gmail.com>:\n> Signed-off-by: Jiang Xin <worldhello.net@gmail.com>\n> ---\n>  Documentation/git-clean.txt | 70 ++++++++++++++++++++++++++++++++++++++++-----\n>  1 file changed, 63 insertions(+), 7 deletions(-)\n>\n> diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt\n> index f5572..56d60 100644\n> --- a/Documentation/git-clean.txt\n> +++ b/Documentation/git-clean.txt\n> @@ -39,13 +39,8 @@ OPTIONS\n>\n>  -i::\n>  --interactive::\n> -       Show what would be done and the user must confirm before actually\n> -       cleaning. In the confirmation dialog, the user can choose to abort\n> -       the cleaning, or enter into an edit mode. In the edit mode, the\n> -       user can input space-separated patterns (the same syntax as\n> -       gitignore), and each clean candidate that matches with one of the\n> -       patterns will be excluded from cleaning. When the user feels it's\n> -       OK, presses ENTER and back to the confirmation dialog.\n> +       Show what would be done and clean files interactively. See\n> +       ``Interactive mode`` for details.\n          ^^^^^^^^^^^^^^^^^^^^ should be ``Interactive mode''\n\n\n-- \nJiang Xin\n"},{"id":"216620","messageId":"7vmws6lsto.fsf@alter.siamese.dyndns.org","threadId":"33711","inReplyTo":"CANYiYbEE90zvhoRp3utOzRWXnpVGTcaruWDuCCHE77FmGwB2_w@mail.gmail.com","subject":"Re: [PATCH v6 4/7] git-clean: use a git-add-interactive compatible UI","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-05-07T15:20:51Z","receivedAt":"2013-05-07T15:20:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> 2013/5/7 Jiang Xin <worldhello.net@gmail.com>:\n>> Rewrite menu using a new method `list_and_choose`, which is borrowed\n>> from `git-add--interactive.perl`. We can reused this method later for\n>> more actions.\n>>\n>> Please NOTE:\n>>\n>>  * Method `list_and_choose` return an array of integers, and\n>>  * it is up to you to free the allocated memory of the array.\n>>  * The array ends with EOF.\n>>  * If user pressed CTRL-D (i.e. EOF), no selection returned.\n>>\n>> Signed-off-by: Jiang Xin <worldhello.net@gmail.com>\n>> ---\n>>  builtin/clean.c | 410 ++++++++++++++++++++++++++++++++++++++++++++++++++------\n>>  1 file changed, 367 insertions(+), 43 deletions(-)\n>...\n>> -void edit_by_patterns_cmd()\n>> +void pretty_print_menus(struct string_list *menu_list)\n>> +{\n>> +       struct strbuf buf = STRBUF_INIT;\n> unused buf should be deleted.\n>\n>> +       unsigned int local_colopts = 0;\n>> +       struct column_options copts;\n>> +\n>> +       /*\n>> +        * always enable column display, we only consult column.*\n>> +        * about layout strategy and stuff\n>> +        */\n> remove the above comments.\n>\n>> +       local_colopts = COL_ENABLED | COL_ROW;\n>> +       memset(&copts, 0, sizeof(copts));\n>> +       copts.indent = \"  \";\n>> +       copts.padding = 2;\n>> +       print_columns(menu_list, local_colopts, &copts);\n>> +       strbuf_release(&buf);\n> remove strbuf_release of unused variable : buf.\n>\n>> +}\n>...\n>> +               } else if (MENU_STUFF_TYPE_STRING_LIST == stuff->type) {\n>> +                       struct string_list_item *item;\n>> +                       struct strbuf buf = STRBUF_INIT;\n> should call strbuf_release later\n> ...\n>> +       } else {\n>> +               result = xmalloc(sizeof(int) * (nr + 1));\n>> +               memset(result, 0, sizeof(int) * (nr + 1));\n>\n> Add initial for j here:\n\n\nWhat is this message trying to achieve?  \"self review\"???\n\nA bit puzzled....\n"},{"id":"216673","messageId":"CANYiYbFgWiLjF1kPKe_o=qH6YB91O_SA2=sT0RgQ9jctXqkdJw@mail.gmail.com","threadId":"33711","inReplyTo":"7vmws6lsto.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v6 4/7] git-clean: use a git-add-interactive compatible UI","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-08T00:28:37Z","receivedAt":"2013-05-08T00:28:37Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"2013/5/7 Junio C Hamano <gitster@pobox.com>:\n> What is this message trying to achieve?  \"self review\"???\n>\n> A bit puzzled....\n\nMaybe I should send a new rerolled patch series after this. Yesterday I\nwanted to wait for a while to see suggestions and reviews from others.\n\n-- \nJiang Xin\n"}]}