{"thread":{"id":"33751","subject":"[PATCH v7 00/10] interactive git clean","startedAt":"2013-05-08T11:38:45Z","lastAt":"2013-05-13T14:46:00Z","messageCount":41,"participants":["Jiang Xin","Eric Sunshine","Junio C Hamano","Matthieu Moy"],"isPatch":true,"patchVersion":7,"patchTotal":10},"messages":[{"id":"216697","messageId":"cover.1368011946.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":null,"subject":"[PATCH v7 00/10] interactive git clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-08T11:38:45Z","receivedAt":"2013-05-08T11:38:45Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Significant updates since patch v6 series:\n\n * Refactor on patch 4/10: split `list_and_choose` into 3 functions,\n   to make it easy to read.\n\n * Mark no public functions as static for patch 1-10.\n\n * If set 'pager.clean' to true (i.e. isatty(1) is false), die\n   instead of do cleaning.\n\n * New action: flags. The user can update flags for git-clean,\n   such as -x/-d/-X/-ff.\n\n * Alway show interactive menu, even there are no files to clean.\n   Because the use can update flags for git-clean.\n \nUsage:\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    *** Commands ***\n      1: clean               2: edit by patterns    3: edit by numbers     4: rm -i\n      5: flags: none         6: quit                7: help\n    What now> 2\n\nYou also could say `c` or `clean` above as long as the choice is unique.\n\nThe main command loop has 7 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\nflags::\n\n  This lets you change the flags for git-clean, such as -x/-X/-d/-ff,\n  and refresh the cleaning candidates list automatically.\n\nquit::\n\n  This lets you quit without do cleaning.\n\nhelp::\n\n  Show brief usage of interactive git-clean.\n\n\nJiang Xin (10):\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  git-clean refactor: save some options in clean_flags\n  git-clean refactor: wrap in scan_clean_candidates\n  git-clean: change clean flags in interactive mode\n\n Documentation/config.txt    |   4 +\n Documentation/git-clean.txt |  76 +++-\n builtin/clean.c             | 972 ++++++++++++++++++++++++++++++++++++++++----\n 3 files changed, 981 insertions(+), 71 deletions(-)\n\n-- \n1.8.3.rc1.341.g1c24ab7\n"},{"id":"216700","messageId":"ad26375dece854339d64dcb82c17f19f8edccf48.1368011946.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"cover.1368011946.git.worldhello.net@gmail.com","subject":"[PATCH v7 01/10] Add support for -i/--interactive to git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-08T11:38:46Z","receivedAt":"2013-05-08T11:38:46Z","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             | 198 +++++++++++++++++++++++++++++++++++++++-----\n 2 files changed, 192 insertions(+), 21 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..49aab 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+static 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+static 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,41 @@ 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}\n-\t\t\tstrbuf_reset(&directory);\n+\t\t\tif (remove_directories || (matches == MATCHED_EXACTLY))\n+\t\t\t\tstring_list_append(&del_list, ent->name);\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 && !dry_run && del_list.nr > 0)\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 +444,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.341.g1c24ab7\n"},{"id":"216698","messageId":"6a7931a0bb14f8c71479e16175812bedb6b826cb.1368011946.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"ad26375dece854339d64dcb82c17f19f8edccf48.1368011946.git.worldhello.net@gmail.com","subject":"[PATCH v7 02/10] Show items of interactive git-clean in columns","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-08T11:38:47Z","receivedAt":"2013-05-08T11:38:47Z","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 49aab..38ed0 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+static 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 static void edit_by_patterns_cmd()\n {\n \tstruct dir_struct dir;\n@@ -153,7 +187,6 @@ static 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 @@ static 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 @@ static void edit_by_patterns_cmd()\n static 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 @@ static 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.341.g1c24ab7\n"},{"id":"216699","messageId":"d7302adabb4269dd94698090fb20c739f723f00a.1368011946.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"6a7931a0bb14f8c71479e16175812bedb6b826cb.1368011946.git.worldhello.net@gmail.com","subject":"[PATCH v7 03/10] Add colors to interactive git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-08T11:38:48Z","receivedAt":"2013-05-08T11:38:48Z","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 38ed0..9b029 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 @@ static 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 @@ static 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 @@ static 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 @@ static 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.341.g1c24ab7\n"},{"id":"216701","messageId":"e7cf5d7ffd83e238078b4585ac4bef4ec9a48e21.1368011946.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"d7302adabb4269dd94698090fb20c739f723f00a.1368011946.git.worldhello.net@gmail.com","subject":"[PATCH v7 04/10] git-clean: use a git-add-interactive compatible UI","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-08T11:38:49Z","receivedAt":"2013-05-08T11:38:49Z","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 | 469 +++++++++++++++++++++++++++++++++++++++++++++++++++-----\n 1 file changed, 428 insertions(+), 41 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 9b029..10f3 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -51,6 +51,36 @@ enum color_clean {\n \tCLEAN_COLOR_ERROR = 5,\n };\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+struct menu_opts {\n+\tconst char *header;\n+\tconst char *prompt;\n+\tint flag;\n+};\n+\n+#define MENU_RETURN_NO_LOOP\t\t10\n+\n+struct menu_item {\n+\tchar hotkey;\n+\tchar *title;\n+\tint selected;\n+\tint (*fn)();\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 static int parse_clean_color_slot(const char *var, int ofs)\n {\n \tif (!strcasecmp(var+ofs, \"reset\"))\n@@ -240,12 +270,345 @@ static 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-static void edit_by_patterns_cmd()\n+static void pretty_print_menus(struct string_list *menu_list)\n+{\n+\tunsigned int local_colopts = 0;\n+\tstruct column_options copts;\n+\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+}\n+\n+static 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+ * display menu stuff with number prefix and hotkey highlight\n+ */\n+static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)\n+{\n+\tstatic struct string_list menu_list = STRING_LIST_INIT_DUP;\n+\tstruct strbuf menu = STRBUF_INIT;\n+\tint i;\n+\n+\t/* highlight hotkey in menu */\n+\tif (MENU_STUFF_TYPE_MENU_ITEM == stuff->type) {\n+\t\tstruct menu_item *item;\n+\n+\t\titem = (struct menu_item *)stuff->stuff;\n+\t\tfor (i = 0; i < stuff->nr; i++, item++) {\n+\t\t\tchar *p;\n+\t\t\tint highlighted = 0;\n+\n+\t\t\tp = item->title;\n+\t\t\tif ((*chosen)[i] < 0)\n+\t\t\t\t(*chosen)[i] = item->selected ? 1 : 0;\n+\t\t\tstrbuf_addf(&menu, \"%s%2d: \", (*chosen)[i] ? \"*\" : \" \", i+1);\n+\t\t\tfor (; *p; p++) {\n+\t\t\t\tif (!highlighted && *p == item->hotkey) {\n+\t\t\t\t\tstrbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_PROMPT));\n+\t\t\t\t\tstrbuf_addch(&menu, *p);\n+\t\t\t\t\tstrbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_RESET));\n+\t\t\t\t\thighlighted = 1;\n+\t\t\t\t} else {\n+\t\t\t\t\tstrbuf_addch(&menu, *p);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tstring_list_append(&menu_list, menu.buf);\n+\t\t\tstrbuf_reset(&menu);\n+\t\t}\n+\t} else if (MENU_STUFF_TYPE_STRING_LIST == stuff->type) {\n+\t\tstruct string_list_item *item;\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\ti = 0;\n+\n+\t\tfor_each_string_list_item(item, (struct string_list *)stuff->stuff) {\n+\t\t\tconst char *qname;\n+\n+\t\t\tif ((*chosen)[i] < 0)\n+\t\t\t\t(*chosen)[i] = 0;\n+\t\t\tqname = quote_path_relative(item->string, -1, &buf, *the_prefix);\n+\t\t\tstrbuf_addf(&menu, \"%s%2d: %s\", (*chosen)[i] ? \"*\" : \" \", ++i, qname);\n+\t\t\tstring_list_append(&menu_list, menu.buf);\n+\t\t\tstrbuf_reset(&menu);\n+\t\t}\n+\t\tstrbuf_release(&buf);\n+\t}\n+\n+\tpretty_print_menus(&menu_list);\n+\n+\tstrbuf_release(&menu);\n+\tstring_list_clear(&menu_list, 0);\n+}\n+\n+/*\n+ * Parse user input, and return choice(s) for menu (menu_stuff).\n+ *\n+ * Input\n+ *     (for single choice)\n+ *         1          - select a numbered item\n+ *         foo        - select item based on menu title\n+ *                    - (empty) select nothing\n+ *\n+ *     (for multiple choice)\n+ *         1          - select a single item\n+ *         3-5        - select a range of items\n+ *         2-3,6-9    - select multiple ranges\n+ *         foo        - select item based on menu title\n+ *         -...       - unselect specified items\n+ *         *          - choose all items\n+ *                    - (empty) finish selecting\n+ *\n+ * The parse result will be saved in array **chosen, and\n+ * return number of total selections.\n+ */\n+static int parse_choice(struct menu_stuff *menu_stuff,\n+\t\t\tint is_single,\n+\t\t\tstruct strbuf input,\n+\t\t\tint **chosen)\n+{\n+\tstruct strbuf **choice_list, **choice_p;\n+\tint nr = 0;\n+\tint i;\n+\n+\tif (is_single) {\n+\t\tchoice_list = strbuf_split_max(&input, '\\n', 0);\n+\t} else {\n+\t\tchar *p = input.buf;\n+\t\tdo {\n+\t\t\tif (*p == ',')\n+\t\t\t\t*p = ' ';\n+\t\t} while (*p++);\n+\t\tchoice_list = strbuf_split_max(&input, ' ', 0);\n+\t}\n+\n+\tfor (choice_p = choice_list; *choice_p; choice_p++) {\n+\t\tchar *p;\n+\t\tint choose = 1;\n+\t\tint bottom = 0, top = 0;\n+\t\tint is_range, is_number;\n+\n+\t\tstrbuf_trim(*choice_p);\n+\t\tif (!(*choice_p)->len)\n+\t\t\tcontinue;\n+\n+\t\t/* Input that begins with '-'; unchoose */\n+\t\tif (*(*choice_p)->buf == '-') {\n+\t\t\tchoose = 0;\n+\t\t\tstrbuf_remove((*choice_p), 0, 1);\n+\t\t}\n+\n+\t\tis_range = 0;\n+\t\tis_number = 1;\n+\t\tfor(p = (*choice_p)->buf; *p; p++) {\n+\t\t\tif ('-' == *p) {\n+\t\t\t\tif (!is_range) {\n+\t\t\t\t\tis_range = 1;\n+\t\t\t\t\tis_number = 0;\n+\t\t\t\t} else {\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} else if (!isdigit(*p)) {\n+\t\t\t\tis_number = 0;\n+\t\t\t\tis_range = 0;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (is_number) {\n+\t\t\tbottom = atoi((*choice_p)->buf);\n+\t\t\ttop = bottom;\n+\t\t} else if (is_range) {\n+\t\t\tbottom = atoi((*choice_p)->buf);\n+\t\t\tif (!*(strchr((*choice_p)->buf, '-') + 1)) {\n+\t\t\t\ttop = menu_stuff->nr - 1;\n+\t\t\t} else {\n+\t\t\t\ttop = atoi(strchr((*choice_p)->buf, '-') + 1);\n+\t\t\t}\n+\t\t} else if (!strcmp((*choice_p)->buf, \"*\")) {\n+\t\t\tbottom = 1;\n+\t\t\ttop = menu_stuff->nr;\n+\t\t} else {\n+\t\t\tif (MENU_STUFF_TYPE_MENU_ITEM == menu_stuff->type) {\n+\t\t\t\tstruct menu_item *item;\n+\n+\t\t\t\titem = (struct menu_item *)menu_stuff->stuff;\n+\t\t\t\tfor (i = 0; i < menu_stuff->nr; i++, item++) {\n+\t\t\t\t\tif (((*choice_p)->len == 1 &&\n+\t\t\t\t\t     *(*choice_p)->buf == item->hotkey) ||\n+\t\t\t\t\t    !strcasecmp((*choice_p)->buf, item->title)) {\n+\t\t\t\t\t\tbottom = i + 1;\n+\t\t\t\t\t\ttop = bottom;\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t} else if (MENU_STUFF_TYPE_STRING_LIST == menu_stuff->type) {\n+\t\t\t\tstruct string_list_item *item;\n+\n+\t\t\t\titem = ((struct string_list *)menu_stuff->stuff)->items;\n+\t\t\t\tfor (i = 0; i < menu_stuff->nr; i++, item++) {\n+\t\t\t\t\tif (!strcasecmp((*choice_p)->buf, item->string)) {\n+\t\t\t\t\t\tbottom = i + 1;\n+\t\t\t\t\t\ttop = bottom;\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top ||\n+\t\t    (is_single && bottom != top)) {\n+\t\t\tclean_print_color(CLEAN_COLOR_ERROR);\n+\t\t\tprintf_ln(_(\"Huh (%s)?\"), (*choice_p)->buf);\n+\t\t\tclean_print_color(CLEAN_COLOR_RESET);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* A range can be specified like 5-7 or 5-. */\n+\t\tfor (i = bottom; i <= top; i++)\n+\t\t\t(*chosen)[i-1] = choose;\n+\t}\n+\n+\tstrbuf_list_free(choice_list);\n+\n+\tfor (i = 0; i < menu_stuff->nr; i++)\n+\t\tnr += (*chosen)[i];\n+\treturn nr;\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+static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)\n+{\n+\tstruct strbuf choice = STRBUF_INIT;\n+\tint *chosen, *result;\n+\tint nr = 0;\n+\tint eof = 0;\n+\tint i;\n+\n+\tchosen = xmalloc(sizeof(int) * stuff->nr);\n+\t/* set chosen as uninitialized */\n+\tfor (i = 0; i < stuff->nr; i++)\n+\t\tchosen[i] = -1;\n+\n+\twhile (1) {\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/* the chosen array can be initialized by menu_item.selected  */\n+\t\tprint_highlight_menu_stuff(stuff, &chosen);\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\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\t/* for a multiple-choice menu, press ENTER (empty) will return back */\n+\t\tif (!(opts->flag & MENU_OPTS_SINGLETON) && !choice.len)\n+\t\t\tbreak;\n+\n+\t\tnr = parse_choice(stuff,\n+\t\t\t\t  opts->flag & MENU_OPTS_SINGLETON,\n+\t\t\t\t  choice,\n+\t\t\t\t  &chosen);\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+\tif (eof) {\n+\t\tresult = xmalloc(sizeof(int));\n+\t\t*result = EOF;\n+\t} else {\n+\t\tint j = 0;\n+\n+\t\t/* recalculate nr, for a multiple-choice menu with initial selections */\n+\t\tif (!nr) {\n+\t\t\tfor (i = 0; i < stuff->nr; i++)\n+\t\t\t\tnr += chosen[i];\n+\t\t}\n+\n+\t\tresult = xmalloc(sizeof(int) * (nr + 1));\n+\t\tmemset(result, 0, sizeof(int) * (nr + 1));\n+\t\tfor (i = 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+\tstrbuf_release(&choice);\n+\treturn result;\n+}\n+\n+static int clean_cmd()\n+{\n+\treturn MENU_RETURN_NO_LOOP;\n+}\n+\n+static int edit_by_patterns_cmd()\n {\n \tstruct dir_struct dir;\n \tstruct strbuf confirm = STRBUF_INIT;\n@@ -257,16 +620,10 @@ static 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 +681,86 @@ static void edit_by_patterns_cmd()\n \n \tstrbuf_release(&buf);\n \tstrbuf_release(&confirm);\n+\treturn 0;\n }\n \n-static void interactive_main_loop()\n+static 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+static 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+static 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\t0, clean_cmd},\n+\t\t\t{'p', \"edit by patterns\",\t0, edit_by_patterns_cmd},\n+\t\t\t{'q', \"quit\",\t\t\t0, quit_cmd},\n+\t\t\t{'h', \"help\",\t\t\t0, help_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-- \n1.8.3.rc1.341.g1c24ab7\n"},{"id":"216702","messageId":"ae69a75fa1bbad9827ef3891a33c3bfa6685d914.1368011946.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"e7cf5d7ffd83e238078b4585ac4bef4ec9a48e21.1368011946.git.worldhello.net@gmail.com","subject":"[PATCH v7 05/10] git-clean: interactive cleaning by select numbers","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-08T11:38:50Z","receivedAt":"2013-05-08T11:38:50Z","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 10f3..75e37 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -684,6 +684,43 @@ static int edit_by_patterns_cmd()\n \treturn 0;\n }\n \n+static 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 static int quit_cmd()\n {\n \tstring_list_clear(&del_list, 0);\n@@ -697,6 +734,7 @@ static 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@@ -714,6 +752,7 @@ static void interactive_main_loop()\n \t\tstruct menu_item menus[] = {\n \t\t\t{'c', \"clean\",\t\t\t0, clean_cmd},\n \t\t\t{'p', \"edit by patterns\",\t0, edit_by_patterns_cmd},\n+\t\t\t{'n', \"edit by numbers\",\t0, edit_by_numbers_cmd},\n \t\t\t{'q', \"quit\",\t\t\t0, quit_cmd},\n \t\t\t{'h', \"help\",\t\t\t0, help_cmd},\n \t\t};\n-- \n1.8.3.rc1.341.g1c24ab7\n"},{"id":"216705","messageId":"95c019e293fcea899e5e227c35338f696f99525f.1368011946.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"ae69a75fa1bbad9827ef3891a33c3bfa6685d914.1368011946.git.worldhello.net@gmail.com","subject":"[PATCH v7 06/10] git-clean: rm -i style interactive cleaning","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-08T11:38:51Z","receivedAt":"2013-05-08T11:38:51Z","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 75e37..5bb36 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -721,6 +721,40 @@ static int edit_by_numbers_cmd()\n \treturn 0;\n }\n \n+static 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 static int quit_cmd()\n {\n \tstring_list_clear(&del_list, 0);\n@@ -735,6 +769,7 @@ static 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@@ -753,6 +788,7 @@ static void interactive_main_loop()\n \t\t\t{'c', \"clean\",\t\t\t0, clean_cmd},\n \t\t\t{'p', \"edit by patterns\",\t0, edit_by_patterns_cmd},\n \t\t\t{'n', \"edit by numbers\",\t0, edit_by_numbers_cmd},\n+\t\t\t{'i', \"rm -i\",\t\t\t0, rm_i_cmd},\n \t\t\t{'q', \"quit\",\t\t\t0, quit_cmd},\n \t\t\t{'h', \"help\",\t\t\t0, help_cmd},\n \t\t};\n-- \n1.8.3.rc1.341.g1c24ab7\n"},{"id":"216704","messageId":"187bf672e3795601f929f15b102746e5083471c5.1368011946.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"95c019e293fcea899e5e227c35338f696f99525f.1368011946.git.worldhello.net@gmail.com","subject":"[PATCH v7 07/10] git-clean: update document for interactive git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-08T11:38:52Z","receivedAt":"2013-05-08T11:38:52Z","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..47e8e 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.341.g1c24ab7\n"},{"id":"216706","messageId":"e9af9f5886217f99dedbb7e830bcebece892c891.1368011946.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"187bf672e3795601f929f15b102746e5083471c5.1368011946.git.worldhello.net@gmail.com","subject":"[PATCH v7 08/10] git-clean refactor: save some options in clean_flags","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-08T11:38:53Z","receivedAt":"2013-05-08T11:38:53Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Save some options in variable clean_flags, such as -ff (force > 1),\n-x (ignored), -X (ignored_only), and -d (remove_directories). We may\nchange clean_flags later in the interactive git-clean.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n builtin/clean.c | 46 +++++++++++++++++++++++++++++++---------------\n 1 file changed, 31 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 5bb36..d46f3 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -16,7 +16,13 @@\n #include \"column.h\"\n #include \"color.h\"\n \n+#define CLEAN_OPTS_SHOW_IGNORED\t\t01\n+#define CLEAN_OPTS_IGNORED_ONLY\t\t02\n+#define CLEAN_OPTS_REMOVE_DIRECTORIES\t04\n+#define CLEAN_OPTS_REMOVE_NESTED_GIT\t010\n+\n static int force = -1; /* unset */\n+static int clean_flags = 0;\n static int interactive;\n static struct string_list del_list = STRING_LIST_INIT_DUP;\n static const char **the_prefix;\n@@ -843,7 +849,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \tint i, res;\n \tint dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;\n \tint ignored_only = 0, config_set = 0, errors = 0, gone = 1;\n-\tint rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;\n \tstruct strbuf directory = STRBUF_INIT;\n \tstruct dir_struct dir;\n \tstatic const char **pathspec;\n@@ -879,13 +884,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\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-\tif (ignored_only)\n-\t\tdir.flags |= DIR_SHOW_IGNORED;\n-\n-\tif (ignored && ignored_only)\n-\t\tdie(_(\"-x and -X cannot be used together\"));\n-\n \tif (interactive) {\n \t\tif (!isatty(0) || !isatty(1))\n \t\t\tdie(_(\"interactive clean can not run without a valid tty; \"\n@@ -899,15 +897,29 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \t\t\t\t  \"refusing to clean\"));\n \t}\n \n+\tif (read_cache() < 0)\n+\t\tdie(_(\"index file corrupt\"));\n+\n \tif (force > 1)\n-\t\trm_flags = 0;\n+\t\tclean_flags |= CLEAN_OPTS_REMOVE_NESTED_GIT;\n+\tif (ignored)\n+\t\tclean_flags |= CLEAN_OPTS_SHOW_IGNORED;\n+\tif (ignored_only)\n+\t\tclean_flags |= CLEAN_OPTS_IGNORED_ONLY;\n+\tif (remove_directories)\n+\t\tclean_flags |= CLEAN_OPTS_REMOVE_DIRECTORIES;\n \n-\tdir.flags |= DIR_SHOW_OTHER_DIRECTORIES;\n+\tmemset(&dir, 0, sizeof(dir));\n+\tif (clean_flags & CLEAN_OPTS_IGNORED_ONLY)\n+\t\tdir.flags |= DIR_SHOW_IGNORED;\n \n-\tif (read_cache() < 0)\n-\t\tdie(_(\"index file corrupt\"));\n+\tif (clean_flags & CLEAN_OPTS_IGNORED_ONLY &&\n+\t    clean_flags & CLEAN_OPTS_SHOW_IGNORED)\n+\t\tdie(_(\"-x and -X cannot be used together\"));\n+\n+\tdir.flags |= DIR_SHOW_OTHER_DIRECTORIES;\n \n-\tif (!ignored)\n+\tif (!(clean_flags & CLEAN_OPTS_SHOW_IGNORED))\n \t\tsetup_standard_excludes(&dir);\n \n \tel = add_exclude_list(&dir, EXC_CMDL, \"--exclude option\");\n@@ -961,7 +973,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 ((clean_flags & CLEAN_OPTS_REMOVE_DIRECTORIES) ||\n+\t\t\t    (matches == MATCHED_EXACTLY))\n \t\t\t\tstring_list_append(&del_list, ent->name);\n \t\t} else {\n \t\t\tif (pathspec && !matches)\n@@ -981,7 +994,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\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\tif (remove_dirs(&directory, *the_prefix,\n+\t\t\t\t\tclean_flags & CLEAN_OPTS_REMOVE_NESTED_GIT ?\n+\t\t\t\t\t\t0 : REMOVE_DIR_KEEP_NESTED_GIT,\n+\t\t\t\t\tdry_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-- \n1.8.3.rc1.341.g1c24ab7\n"},{"id":"216703","messageId":"ebfb3ccf679c26734af655878cf84fff170c96c5.1368011946.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"e9af9f5886217f99dedbb7e830bcebece892c891.1368011946.git.worldhello.net@gmail.com","subject":"[PATCH v7 09/10] git-clean refactor: wrap in scan_clean_candidates","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-08T11:38:54Z","receivedAt":"2013-05-08T11:38:54Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add new function `scan_clean_candidates`, which determines the del_list\n(i.e. the cleaning candidates). This function will be reused later in\nthe interactive git-clean, so we can change flags of git-clean and\nrefresh the del_list.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n builtin/clean.c | 169 ++++++++++++++++++++++++++++++--------------------------\n 1 file changed, 92 insertions(+), 77 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex d46f3..0d8561 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -254,6 +254,95 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \treturn ret;\n }\n \n+static void scan_clean_candidates(const char **pathspec, struct string_list exclude_list)\n+{\n+\tstruct dir_struct dir;\n+\tstruct exclude_list *el;\n+\tchar *seen = NULL;\n+\tconst char **pathspec_p = pathspec;\n+\tint pathspec_nr = 0;\n+\tint i;\n+\n+\twhile (pathspec_p && *(pathspec_p++))\n+\t\tpathspec_nr++;\n+\n+\tmemset(&dir, 0, sizeof(dir));\n+\tif (clean_flags & CLEAN_OPTS_IGNORED_ONLY)\n+\t\tdir.flags |= DIR_SHOW_IGNORED;\n+\n+\tif (clean_flags & CLEAN_OPTS_IGNORED_ONLY &&\n+\t    clean_flags & CLEAN_OPTS_SHOW_IGNORED)\n+\t\tdie(_(\"-x and -X cannot be used together\"));\n+\n+\tdir.flags |= DIR_SHOW_OTHER_DIRECTORIES;\n+\n+\tif (!(clean_flags & CLEAN_OPTS_SHOW_IGNORED))\n+\t\tsetup_standard_excludes(&dir);\n+\n+\tel = add_exclude_list(&dir, EXC_CMDL, \"--exclude option\");\n+\tfor (i = 0; i < exclude_list.nr; i++)\n+\t\tadd_exclude(exclude_list.items[i].string, \"\", 0, el, -(i+1));\n+\n+\tfill_directory(&dir, pathspec);\n+\n+\tif (pathspec)\n+\t\tseen = xmalloc(pathspec_nr > 0 ? pathspec_nr : 1);\n+\n+\tstring_list_clear(&del_list, 0);\n+\n+\tfor (i = 0; i < dir.nr; i++) {\n+\t\tstruct dir_entry *ent = dir.entries[i];\n+\t\tint len, pos;\n+\t\tint matches = 0;\n+\t\tstruct cache_entry *ce;\n+\t\tstruct stat st;\n+\n+\t\t/*\n+\t\t * Remove the '/' at the end that directory\n+\t\t * walking adds for directory entries.\n+\t\t */\n+\t\tlen = ent->len;\n+\t\tif (len && ent->name[len-1] == '/')\n+\t\t\tlen--;\n+\t\tpos = cache_name_pos(ent->name, len);\n+\t\tif (0 <= pos)\n+\t\t\tcontinue;\t/* exact match */\n+\t\tpos = -pos - 1;\n+\t\tif (pos < active_nr) {\n+\t\t\tce = active_cache[pos];\n+\t\t\tif (ce_namelen(ce) == len &&\n+\t\t\t    !memcmp(ce->name, ent->name, len))\n+\t\t\t\tcontinue; /* Yup, this one exists unmerged */\n+\t\t}\n+\n+\t\t/*\n+\t\t * we might have removed this as part of earlier\n+\t\t * recursive directory removal, so lstat() here could\n+\t\t * fail with ENOENT.\n+\t\t */\n+\t\tif (lstat(ent->name, &st))\n+\t\t\tcontinue;\n+\n+\t\tif (pathspec) {\n+\t\t\tmemset(seen, 0, pathspec_nr > 0 ? pathspec_nr : 1);\n+\t\t\tmatches = match_pathspec(pathspec, ent->name, len,\n+\t\t\t\t\t\t 0, seen);\n+\t\t}\n+\n+\t\tif (S_ISDIR(st.st_mode)) {\n+\t\t\tif ((clean_flags & CLEAN_OPTS_REMOVE_DIRECTORIES) ||\n+\t\t\t    (matches == MATCHED_EXACTLY))\n+\t\t\t\tstring_list_append(&del_list, ent->name);\n+\t\t} else {\n+\t\t\tif (pathspec && !matches)\n+\t\t\t\tcontinue;\n+\t\t\tstring_list_append(&del_list, ent->name);\n+\t\t}\n+\t}\n+\n+\tfree(seen);\n+}\n+\n static void pretty_print_dels()\n {\n \tstruct string_list list = STRING_LIST_INIT_DUP;\n@@ -846,18 +935,15 @@ static void interactive_main_loop()\n \n int cmd_clean(int argc, const char **argv, const char *prefix)\n {\n-\tint i, res;\n+\tint res;\n \tint dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;\n \tint ignored_only = 0, config_set = 0, errors = 0, gone = 1;\n \tstruct strbuf directory = STRBUF_INIT;\n-\tstruct dir_struct dir;\n \tstatic const char **pathspec;\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+\tstruct string_list exclude_list = STRING_LIST_INIT_NODUP;\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@@ -909,79 +995,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \tif (remove_directories)\n \t\tclean_flags |= CLEAN_OPTS_REMOVE_DIRECTORIES;\n \n-\tmemset(&dir, 0, sizeof(dir));\n-\tif (clean_flags & CLEAN_OPTS_IGNORED_ONLY)\n-\t\tdir.flags |= DIR_SHOW_IGNORED;\n-\n-\tif (clean_flags & CLEAN_OPTS_IGNORED_ONLY &&\n-\t    clean_flags & CLEAN_OPTS_SHOW_IGNORED)\n-\t\tdie(_(\"-x and -X cannot be used together\"));\n-\n-\tdir.flags |= DIR_SHOW_OTHER_DIRECTORIES;\n-\n-\tif (!(clean_flags & CLEAN_OPTS_SHOW_IGNORED))\n-\t\tsetup_standard_excludes(&dir);\n-\n-\tel = add_exclude_list(&dir, EXC_CMDL, \"--exclude option\");\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(*the_prefix, argv);\n \n-\tfill_directory(&dir, pathspec);\n-\n-\tif (pathspec)\n-\t\tseen = xmalloc(argc > 0 ? argc : 1);\n-\n-\tfor (i = 0; i < dir.nr; i++) {\n-\t\tstruct dir_entry *ent = dir.entries[i];\n-\t\tint len, pos;\n-\t\tint matches = 0;\n-\t\tstruct cache_entry *ce;\n-\t\tstruct stat st;\n-\n-\t\t/*\n-\t\t * Remove the '/' at the end that directory\n-\t\t * walking adds for directory entries.\n-\t\t */\n-\t\tlen = ent->len;\n-\t\tif (len && ent->name[len-1] == '/')\n-\t\t\tlen--;\n-\t\tpos = cache_name_pos(ent->name, len);\n-\t\tif (0 <= pos)\n-\t\t\tcontinue;\t/* exact match */\n-\t\tpos = -pos - 1;\n-\t\tif (pos < active_nr) {\n-\t\t\tce = active_cache[pos];\n-\t\t\tif (ce_namelen(ce) == len &&\n-\t\t\t    !memcmp(ce->name, ent->name, len))\n-\t\t\t\tcontinue; /* Yup, this one exists unmerged */\n-\t\t}\n-\n-\t\t/*\n-\t\t * we might have removed this as part of earlier\n-\t\t * recursive directory removal, so lstat() here could\n-\t\t * fail with ENOENT.\n-\t\t */\n-\t\tif (lstat(ent->name, &st))\n-\t\t\tcontinue;\n-\n-\t\tif (pathspec) {\n-\t\t\tmemset(seen, 0, argc > 0 ? argc : 1);\n-\t\t\tmatches = match_pathspec(pathspec, ent->name, len,\n-\t\t\t\t\t\t 0, seen);\n-\t\t}\n-\n-\t\tif (S_ISDIR(st.st_mode)) {\n-\t\t\tif ((clean_flags & CLEAN_OPTS_REMOVE_DIRECTORIES) ||\n-\t\t\t    (matches == MATCHED_EXACTLY))\n-\t\t\t\tstring_list_append(&del_list, ent->name);\n-\t\t} else {\n-\t\t\tif (pathspec && !matches)\n-\t\t\t\tcontinue;\n-\t\t\tstring_list_append(&del_list, ent->name);\n-\t\t}\n-\t}\n+\tscan_clean_candidates(pathspec, exclude_list);\n \n \tif (interactive && !dry_run && del_list.nr > 0)\n \t\tinteractive_main_loop();\n@@ -1016,7 +1032,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \t\t\t}\n \t\t}\n \t}\n-\tfree(seen);\n \n \tstrbuf_release(&directory);\n \tstring_list_clear(&exclude_list, 0);\n-- \n1.8.3.rc1.341.g1c24ab7\n"},{"id":"216707","messageId":"24a8a0f11acdf881b4ab9057e91273b9de306016.1368011946.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"ebfb3ccf679c26734af655878cf84fff170c96c5.1368011946.git.worldhello.net@gmail.com","subject":"[PATCH v7 10/10] git-clean: change clean flags in interactive mode","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-08T11:38:55Z","receivedAt":"2013-05-08T11:38:55Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add new action in the interactive mode, so that the user can change\ngit-clean flags, such as -x/-X/-d/-ff, and refresh the cleaning\ncandidates list.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n Documentation/git-clean.txt |  11 +++--\n builtin/clean.c             | 117 +++++++++++++++++++++++++++++++++++++-------\n 2 files changed, 108 insertions(+), 20 deletions(-)\n\ndiff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt\nindex 47e8e..c7885 100644\n--- a/Documentation/git-clean.txt\n+++ b/Documentation/git-clean.txt\n@@ -82,14 +82,14 @@ 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+      1: clean               2: edit by patterns    3: edit by numbers     4: rm -i\n+      5: flags: none         6: quit                7: 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+The main command loop has 7 subcommands.\n \n clean::\n \n@@ -122,6 +122,11 @@ rm -i::\n   by one in order to delete items. This action is not as efficient\n   as the above two actions.\n \n+flags::\n+\n+  This lets you change the flags for git-clean, such as -x/-X/-d/-ff,\n+  and refresh the cleaning candidates list automatically.\n+\n quit::\n \n   This lets you quit without do cleaning.\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 0d8561..b2941 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -850,6 +850,66 @@ static int rm_i_cmd()\n \treturn MENU_RETURN_NO_LOOP;\n }\n \n+static int flags_cmd()\n+{\n+\tstruct menu_opts menu_opts;\n+\tstruct menu_stuff menu_stuff;\n+\tstruct menu_item menus[] = {\n+\t\t{'d', \"(-d) remove directories\",\n+\t\t\tclean_flags & CLEAN_OPTS_REMOVE_DIRECTORIES,\tNULL},\n+\t\t{'x', \"(-x) show ignored\",\n+\t\t\tclean_flags & CLEAN_OPTS_SHOW_IGNORED,\t\tNULL},\n+\t\t{'X', \"(-X) ignored only\",\n+\t\t\tclean_flags & CLEAN_OPTS_IGNORED_ONLY,\t\tNULL},\n+\t\t{'f', \"(-ff) remove nested.git\",\n+\t\t\tclean_flags & CLEAN_OPTS_REMOVE_NESTED_GIT,\tNULL},\n+\t};\n+\tint new_flags = 0;\n+\tint *chosen;\n+\tint i;\n+\n+\tmenu_opts.header = NULL;\n+\tmenu_opts.prompt = \"Change flags\";\n+\tmenu_opts.flag = 0;\n+\n+\tmenu_stuff.type = MENU_STUFF_TYPE_MENU_ITEM;\n+\tmenu_stuff.stuff = menus;\n+\tmenu_stuff.nr = sizeof(menus) / sizeof(struct menu_item);\n+\n+\tchosen = list_and_choose(&menu_opts, &menu_stuff);\n+\n+\tfor (i = 0; chosen[i] != EOF; i++) {\n+\t\tswitch (chosen[i]) {\n+\t\tcase 0:\n+\t\t\tnew_flags |= CLEAN_OPTS_REMOVE_DIRECTORIES;\n+\t\t\tbreak;\n+\t\tcase 1:\n+\t\t\tnew_flags |= CLEAN_OPTS_SHOW_IGNORED;\n+\t\t\tbreak;\n+\t\tcase 2:\n+\t\t\tnew_flags |= CLEAN_OPTS_IGNORED_ONLY;\n+\t\t\tbreak;\n+\t\tcase 3:\n+\t\t\tnew_flags |= CLEAN_OPTS_REMOVE_NESTED_GIT;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (new_flags & CLEAN_OPTS_IGNORED_ONLY &&\n+\t    new_flags & CLEAN_OPTS_SHOW_IGNORED) {\n+\t\tclean_print_color(CLEAN_COLOR_ERROR);\n+\t\tprintf_ln(_(\"-x and -X cannot be used together\"));\n+\t\tclean_print_color(CLEAN_COLOR_RESET);\n+\t} else {\n+\t\tclean_flags = new_flags;\n+\t}\n+\n+\tfree(chosen);\n+\treturn 0;\n+}\n+\n static int quit_cmd()\n {\n \tstring_list_clear(&del_list, 0);\n@@ -865,6 +925,7 @@ static int help_cmd(int x)\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    \"flags            - change git-clean flags and update the cleaning candidates\\n\"\n \t\t    \"quit             - stop cleaning\\n\"\n \t\t    \"help             - this screen\\n\"\n \t\t    \"?                - help for prompt selection\"\n@@ -873,10 +934,13 @@ static int help_cmd(int x)\n \treturn 0;\n }\n \n-static void interactive_main_loop()\n+static void interactive_main_loop(const char **pathspec, struct string_list exclude_list)\n {\n+\tint cached_clean_flags = clean_flags;\n+\tchar flags_title[20];\n+\n \t/* dels list may become empty after return back from edit mode */\n-\twhile (del_list.nr) {\n+\twhile (1) {\n \t\tstruct menu_opts menu_opts;\n \t\tstruct menu_stuff menu_stuff;\n \t\tstruct menu_item menus[] = {\n@@ -884,11 +948,24 @@ static void interactive_main_loop()\n \t\t\t{'p', \"edit by patterns\",\t0, edit_by_patterns_cmd},\n \t\t\t{'n', \"edit by numbers\",\t0, edit_by_numbers_cmd},\n \t\t\t{'i', \"rm -i\",\t\t\t0, rm_i_cmd},\n+\t\t\t{'f', flags_title,\t\t0, flags_cmd},\n \t\t\t{'q', \"quit\",\t\t\t0, quit_cmd},\n \t\t\t{'h', \"help\",\t\t\t0, help_cmd},\n \t\t};\n \t\tint *chosen;\n \n+\t\tif (!clean_flags) {\n+\t\t\tstrncpy(flags_title, \"flags: none\", sizeof(flags_title)/sizeof(char));\n+\t\t} else {\n+\t\t\tsnprintf(flags_title, sizeof(flags_title)/sizeof(char),\n+\t\t\t\t \"flags: -%s%s%s%s\",\n+\t\t\t\t clean_flags & CLEAN_OPTS_REMOVE_DIRECTORIES ? \"d\" : \"\",\n+\t\t\t\t clean_flags & CLEAN_OPTS_SHOW_IGNORED ? \"x\" : \"\",\n+\t\t\t\t clean_flags & CLEAN_OPTS_IGNORED_ONLY ? \"X\" : \"\",\n+\t\t\t\t clean_flags & CLEAN_OPTS_REMOVE_NESTED_GIT ? \"ff\" : \"\"\n+\t\t\t\t);\n+\t\t}\n+\n \t\tmenu_opts.header = _(\"*** Commands ***\");\n \t\tmenu_opts.prompt = \"What now\";\n \t\tmenu_opts.flag = MENU_OPTS_SINGLETON;\n@@ -897,14 +974,26 @@ static void interactive_main_loop()\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\tif (cached_clean_flags != clean_flags) {\n+\t\t\tscan_clean_candidates(pathspec, exclude_list);\n+\t\t\tcached_clean_flags = clean_flags;\n+\t\t}\n \n-\t\t/* display dels in columns */\n-\t\tpretty_print_dels();\n+\t\tif (del_list.nr) {\n+\t\t\tclean_print_color(CLEAN_COLOR_HEADER);\n+\t\t\tprintf_ln(Q_(\"Would remove the following item:\",\n+\t\t\t\t     \"Would remove the following items:\",\n+\t\t\t\t     del_list.nr));\n+\t\t\tclean_print_color(CLEAN_COLOR_RESET);\n+\n+\t\t\t/* display dels in columns */\n+\t\t\tpretty_print_dels();\n+\t\t} else {\n+\t\t\tclean_print_color(CLEAN_COLOR_HEADER);\n+\t\t\tprintf_ln(_(\"NOTE: No more files to clean. Press \\\"f\\\" can change the flags.\"));\n+\t\t\tputchar('\\n');\n+\t\t\tclean_print_color(CLEAN_COLOR_RESET);\n+\t\t}\n \n \t\t/* main menu */\n \t\tchosen = list_and_choose(&menu_opts, &menu_stuff);\n@@ -915,12 +1004,6 @@ static void interactive_main_loop()\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@@ -999,8 +1082,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \n \tscan_clean_candidates(pathspec, exclude_list);\n \n-\tif (interactive && !dry_run && del_list.nr > 0)\n-\t\tinteractive_main_loop();\n+\tif (interactive && !dry_run)\n+\t\tinteractive_main_loop(pathspec, exclude_list);\n \n \tfor_each_string_list_item(item, &del_list) {\n \t\tstruct stat st;\n-- \n1.8.3.rc1.341.g1c24ab7\n"},{"id":"216712","messageId":"CAPig+cR6aa+xODi=8DoiBpqQZM1eBHkGBvGzMGB2PSiT3osMEw@mail.gmail.com","threadId":"33751","inReplyTo":"cover.1368011946.git.worldhello.net@gmail.com","subject":"Re: [PATCH v7 00/10] interactive git clean","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-05-08T15:15:03Z","receivedAt":"2013-05-08T15:15:03Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, May 8, 2013 at 7:38 AM, Jiang Xin <worldhello.net@gmail.com> wrote:\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\nYour current implementation only allows excluding items from the list\nof files to delete. If you accidentally exclude some file which you\nactually want in the deletion list, there is no way to re-add it.\nWould it make sense to change the behavior so that it lists all files\nbut stars those which are to be deleted. This is similar to how the\n\"edit by numbers\" mode operates, but would apply to the deletion list\nprinted for the top-level menu as well. For example:\n\n  Will remove starred items:\n    file1  *file2  *file3\n    *file4  file5  file6\n  *** Commands ***\n  ...\n  What now> clean\n  Removing file2\n  Removing file3\n  Removing file4\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>     *** Commands ***\n>       1: clean               2: edit by patterns    3: edit by numbers     4: rm -i\n>       5: flags: none         6: quit                7: help\n>     What now> 2\n>\n> You also could say `c` or `clean` above as long as the choice is unique.\n\nIt is not obvious by reading the menu that \"edit by patterns\" can be\nabbreviated as 'p', and \"edit by numbers\" by 'n'. If you change the\nnames a bit, then the abbreviations become more obvious. For instance,\none possibility:\n\n  2. filter by pattern\n  3. select by number\n  5. toggle flags: none\n\nAlso, the abbreviation 'i' for \"rm -i\" is not obvious.\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\nIt seems odd that \"edit by patterns\" _excludes_ files from deletion,\nbut \"edit by patterns\" _includes_ them in the deletion list. These\nopposing behaviors force the user to invert his mode of thought when\nediting by patterns vs. numbers. While playing with \"edit by numbers\",\nI repeatedly found myself incorrectly inputting numbers of items I\nwanted to exclude rather than those I wanted to keep in the list,\nsince my brain had not made the 180 degree flip from \"edit by\npatterns\".\n\nMore generally, there are cases when it is more convenient to filter\nthe list by exclusion, and other cases when inclusion is more\nconvenient. For example, in a list of 20 files, I may want to delete\n18 but keep 2. In this case, it typically is easier to specify the two\nI want to keep. On the other hand, if I want to delete 2 files but\nkeep 18, it may be easier to specify the two I want to delete. Would\nit makes sense to support pattern negation (via '!' perhaps) in order\nto make this possible?\n\nOne thing not mentioned here is that \"edit by numbers\" (as with\ngit-add-interactive) also accepts input \"foo\" to match item \"foo\" in\nthe list. This suggests that it might make sense to accept patterns\nalso, so that \"*oo\" can match \"foo\". This, together with negation and\nthe idea mentioned above of always listing all files and only deleting\nstarred ones, would allow you to combine \"edit by patterns\" and \"edit\nby numbers\" into a single mode of operation.\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\nThe name \"rm -i\" is rather Unixy, and Windows users might not\nunderstand it, nor people who don't use rm's -i option. Other\npotentially better names might be: \"ask\", \"ask each\", \"confirm\",\n\"confirm each\", \"prompt\", or \"prompt each\".\n\nFunctionally, the current implementation of this mode makes it an\noddball. \"edit by patterns\" and \"edit by numbers\" both return to the\ntop-level menu, allowing the user to invoke \"clean\" or \"quit\" (or some\nother option), but \"rm -i\" always finishes by running \"clean\"\nunconditionally. This is strangely inconsistent. Shouldn't it also\nreturn to the top-level menu to give the user an opportunity to review\nthe final selection before invoking \"clean\" or \"quit\"?\n\n> flags::\n>\n>   This lets you change the flags for git-clean, such as -x/-X/-d/-ff,\n>   and refresh the cleaning candidates list automatically.\n\nIs your \"edit by patterns\" and \"edit by numbers\" input remembered\nacross flags changes? Should it be? In other words, if you spent some\ntime fine-tuning the deletion list but then realized that you forgot\nthe -d flag on the command line, you might want to toggle the -d flag\nhere without losing the fine-tuning you already did.\n\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\nHave you considered the idea of an additional menu item which launches\nan editor with a list of files to be removed so the user can edit it\n[*1*]? This might also be a convenient way to fine-tune the deletion\nlist, though it certainly doesn't need to be part of this series.\n\n[*1*] http://thread.gmane.org/gmane.comp.version-control.git/223271/focus=223431\n\n> Jiang Xin (10):\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>   git-clean refactor: save some options in clean_flags\n>   git-clean refactor: wrap in scan_clean_candidates\n>   git-clean: change clean flags in interactive mode\n\nThe way this series is built up seems a bit odd for a couple reasons.\n\nFirst, \"edit by patterns\" is just one of several selection modes\nsupported by --interactive. Why is it lumped in the first patch\nwhereas \"edit by numbers\" and \"rm -i\" are each added separately?\nShouldn't \"edit by patterns\", therefore, also be introduced in a\nseparate patch? Doing so might make review simpler.\n\nSecond, if you know that your intention is to emulate\ngit-add-interactive UI style, then implementing a \"dumb\" UI in patch 1\nonly to replace it entirely in patch 4 seems superfluous and\nburdensome to reviewers. Wouldn't it make more sense to \"do the right\nthing\" from the start by adding the desired UI early?\n\nGiven the above considerations, perhaps the series could be composed like this:\n\n  ui: add a git-add-interactive-style UI for general use\n  git-clean: add support for -i/--interactive to git-clean\n    (with menu items \"clean\", \"quit\", \"help\")\n  git-clean: interactive: show items in columns\n  git-clean: interactive: add colors\n  git-clean: interactive: add pattern-based fine-tuning\n  git-clean: interactive: add number-based fine-turning\n  git-clean: interactive: add \"rm -i\"-style fine-tuning\n  git-clean: refactor: save some options in clean_flags\n  git-clean: refactor: wrap in scan_clean_candidates\n  git-clean: interactive: allow changing clean-flags\n  doc: document interactive git-clean\n"},{"id":"216713","messageId":"CAPig+cQ0wC8STfzZTNT=Thu3HuiORnoDDisYD7Bp5W4zwek48A@mail.gmail.com","threadId":"33751","inReplyTo":"CAPig+cR6aa+xODi=8DoiBpqQZM1eBHkGBvGzMGB2PSiT3osMEw@mail.gmail.com","subject":"Re: [PATCH v7 00/10] interactive git clean","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-05-08T15:18:36Z","receivedAt":"2013-05-08T15:18:36Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, May 8, 2013 at 11:15 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Wed, May 8, 2013 at 7:38 AM, Jiang Xin <worldhello.net@gmail.com> wrote:\n>>     *** Commands ***\n>>       1: clean               2: edit by patterns    3: edit by numbers     4: rm -i\n>>       5: flags: none         6: quit                7: help\n>>     What now> 2\n>>\n>> You also could say `c` or `clean` above as long as the choice is unique.\n>\n> It is not obvious by reading the menu that \"edit by patterns\" can be\n> abbreviated as 'p', and \"edit by numbers\" by 'n'. If you change the\n> names a bit, then the abbreviations become more obvious. For instance,\n> one possibility:\n>\n>   2. filter by pattern\n>   3. select by number\n>   5. toggle flags: none\n\nBy \"more obvious\", I meant that the initial letter of each option is\nnow unique across the entire menu:\n\n  2. [f]ilter by pattern\n  3. [s]elect by number\n  4. [t]oggle flags: none\n"},{"id":"216715","messageId":"7vwqr9fo9b.fsf@alter.siamese.dyndns.org","threadId":"33751","inReplyTo":"CAPig+cR6aa+xODi=8DoiBpqQZM1eBHkGBvGzMGB2PSiT3osMEw@mail.gmail.com","subject":"Re: [PATCH v7 00/10] interactive git clean","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-05-08T16:08:16Z","receivedAt":"2013-05-08T16:08:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> On Wed, May 8, 2013 at 7:38 AM, Jiang Xin <worldhello.net@gmail.com> wrote:\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> Your current implementation only allows excluding items from the list\n> of files to delete. If you accidentally exclude some file which you\n> actually want in the deletion list, there is no way to re-add it.\n\nIf you accidentally dropped items from the list, is it such a big\ndeal?  I would imagine, if I were in that situation, that I would\nsimply continue and concentrate on making sure the remaining list\ndoes not include anything I want to keep, clean them and then run\nthe command again, at which point the list of untracked paths in the\nlist will be much smaller.\n\nWe cannot make the same argument against the approach to first\npresent a list and _remove_ items that should not be dropped. That\nis a genuine improvement, without which you cannot do a similar\nincremental removal, whose first invocation removes only a subset\n(but still a subset with no precious files you need to keep) of\nfiles to allow the list subsequent invocations presents to *shrink*.\n\nCleaning all unneeded files inside a single interactive session\nshould *never* be the goal---that will lead to an over-engineered\ndesign (e.g. switching \"clean -x\" flags in the middle? why?).  I\nthink Jiang's latest series is already way over-engineered, and I\nsuspect your suggestion leads it more into that direction.\n"},{"id":"216717","messageId":"7vr4hhflym.fsf@alter.siamese.dyndns.org","threadId":"33751","inReplyTo":"ad26375dece854339d64dcb82c17f19f8edccf48.1368011946.git.worldhello.net@gmail.com","subject":"Re: [PATCH v7 01/10] Add support for -i/--interactive to git-clean","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-05-08T16:57:53Z","receivedAt":"2013-05-08T16:57:53Z","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> Show what would be done and the user must confirm before actually\n> cleaning. 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>\n> When the user chooses the edit mode, the user can input space-\n> separated patterns (the same syntax as gitignore), and each clean\n> candidate that matches with one of the patterns will be excluded\n> from cleaning. When the user feels it's OK, presses ENTER and back\n> to the confirmation dialog.\n>\n> Signed-off-by: Jiang Xin <worldhello.net@gmail.com>\n> Suggested-by: Junio C Hamano <gitster@pobox.com>\n> Spelling-checked-by: Eric Sunshine <sunshine@sunshineco.com>\n> Comments-by: Matthieu Moy <Matthieu.Moy@imag.fr>\n> Suggested-by: Eric Sunshine <sunshine@sunshineco.com>\n\nListing everybody who has ever said anything in the review thread?\nI can understand that you may want to give credit to those who\nsignificantly helped, but please do not overdo it.\n\nIn any case, with the help of their inputs, you brought the patch\ninto its final shape.  Please sign-off at the _end_.\n\n> ---\n>  Documentation/git-clean.txt |  15 +++-\n>  builtin/clean.c             | 198 +++++++++++++++++++++++++++++++++++++++-----\n>  2 files changed, 192 insertions(+), 21 deletions(-)\n>\n> diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt\n> index 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::\n> diff --git a/builtin/clean.c b/builtin/clean.c\n> index 04e39..49aab 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\nEhh, why?\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> +static void edit_by_patterns_cmd()\n\nstatic void edit_by_patterns_cmd(void)\n\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\nAn unnecessary and overlong comment.  The message shown already\ntells the reader what is going on anyway, no?\n\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\nWhy break here?  If we got nothing, wouldn't confirm.len be zero?\nIf we did get something but the input got flushed without line-end,\nsending '\\n' to the terminal may be justified, but in that case you\nwould may have something useful, and asking confirm.len if it is\nempty would be the consistent way to check between two cases, no?\n\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\nA few points:\n\n * Pass prefix as a parameter to this function, just like how\n   remove_dirs() gets called, and get rid of the_prefix.\n\n * The result of quote_* is designed to avoid ambiguities, by\n   applying C-style quotes like HT => \\t and adding \"\" pair around\n   it as necessary.  I doubt feeding it to is_excluded() makes any\n   sense.  You probably meant path_relative(), but I am not sure.\n\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> +static 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> +\targc = parse_options(argc, argv, *the_prefix, options, builtin_clean_usage,\n>  \t\t\t     0);\n\nNone of these changes s/prefix/*the_prefix/ looks remotely\njustifiable.  Does anybody change *the_prefix after it is set and if\nso how?\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,41 @@ 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}\n> -\t\t\tstrbuf_reset(&directory);\n> +\t\t\tif (remove_directories || (matches == MATCHED_EXACTLY))\n> +\t\t\t\tstring_list_append(&del_list, ent->name);\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 && !dry_run && del_list.nr > 0)\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\nIgnoring errors silently?\n\nWith the \"interactive\" stuff, can you get into a situation where you\noriginally propose to remove D and D/F but the user tells you to\nremove D (editing D/F away), or vice versa?\n\nI think this patch should be in at least two parts:\n\n - Introduce the two-phase \"collect in del_list, remove in a\n   separate loop at the end\" restructuring.\n\n - (optional, if you are feeling ambitious) Change the path that is\n   stored in del_list relative to the prefix, so that all functions\n   that operate on the string in the del_list do not have to do\n   *_relative() thing.  Some functions may instead have to prepend\n   prefix but if they are minority compared to the users of\n   *_relative(), it may be an overall win from the readability's\n   point of view.\n\n - Add the \"interactively allow you to reduce the del_list\" bit\n   between the two phases.\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 +444,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"},{"id":"216718","messageId":"7vmws5flr3.fsf@alter.siamese.dyndns.org","threadId":"33751","inReplyTo":"6a7931a0bb14f8c71479e16175812bedb6b826cb.1368011946.git.worldhello.net@gmail.com","subject":"Re: [PATCH v7 02/10] Show items of interactive git-clean in columns","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-05-08T17:02:24Z","receivedAt":"2013-05-08T17:02:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nice (I didn't read the code, though).\n"},{"id":"216719","messageId":"7vip2tflqq.fsf@alter.siamese.dyndns.org","threadId":"33751","inReplyTo":"e7cf5d7ffd83e238078b4585ac4bef4ec9a48e21.1368011946.git.worldhello.net@gmail.com","subject":"Re: [PATCH v7 04/10] git-clean: use a git-add-interactive compatible UI","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-05-08T17:02:37Z","receivedAt":"2013-05-08T17:02:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nice (I didn't read the code, though).\n"},{"id":"216782","messageId":"CANYiYbEvYXiRE0OeR5vqBAaOFFpJTPBetQbawZD5EcMCj8veFw@mail.gmail.com","threadId":"33751","inReplyTo":"7vr4hhflym.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v7 01/10] Add support for -i/--interactive to git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-09T16:35:37Z","receivedAt":"2013-05-09T16:35:37Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"2013/5/9 Junio C Hamano <gitster@pobox.com>:\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> Ehh, why?\n\nNext reroll will save relative paths in del_list, and eliminate \"**the_prefix\".\n\n\n>> +\n>> +             printf(_(\"Input ignore patterns>> \"));\n>> +             if (strbuf_getline(&confirm, stdin, '\\n') != EOF) {\n>> +                     strbuf_trim(&confirm);\n>> +             } else {\n>> +                     putchar('\\n');\n>> +                     break;\n>\n> Why break here?  If we got nothing, wouldn't confirm.len be zero?\n> If we did get something but the input got flushed without line-end,\n> sending '\\n' to the terminal may be justified, but in that case you\n> would may have something useful, and asking confirm.len if it is\n> empty would be the consistent way to check between two cases, no?\n\nYes, this break is unnecessary, it left from pervious revision.\n\n\n>\n> A few points:\n>\n>  * Pass prefix as a parameter to this function, just like how\n>    remove_dirs() gets called, and get rid of the_prefix.\n>\n>  * The result of quote_* is designed to avoid ambiguities, by\n>    applying C-style quotes like HT => \\t and adding \"\" pair around\n>    it as necessary.  I doubt feeding it to is_excluded() makes any\n>    sense.  You probably meant path_relative(), but I am not sure.\n\nAppreciated, that is what I need. I write a local version of path_relative,\na combination of path_relative (in quote.c) and relative_path (in path.c),\nlike this:\n\n        static const char *path_relative(const char *in, const char *prefix)\n\n>> +     for_each_string_list_item(item, &del_list) {\n>> +             struct stat st;\n>> +\n>> +             if (lstat(item->string, &st))\n>> +                     continue;\n>\n> Ignoring errors silently?\n>\n> With the \"interactive\" stuff, can you get into a situation where you\n> originally propose to remove D and D/F but the user tells you to\n> remove D (editing D/F away), or vice versa?\n\nI can not find out such a case, that remove parent directory D,\nwhile left file in it, such as D/F.\n\n> I think this patch should be in at least two parts:\n>\n>  - Introduce the two-phase \"collect in del_list, remove in a\n>    separate loop at the end\" restructuring.\n>\n>  - (optional, if you are feeling ambitious) Change the path that is\n>    stored in del_list relative to the prefix, so that all functions\n>    that operate on the string in the del_list do not have to do\n>    *_relative() thing.  Some functions may instead have to prepend\n>    prefix but if they are minority compared to the users of\n>    *_relative(), it may be an overall win from the readability's\n>    point of view.\n>\n>  - Add the \"interactively allow you to reduce the del_list\" bit\n>    between the two phases.\n>\n\nWill send new reroll soon.\n\n\n-- \nJiang Xin\n"},{"id":"216784","messageId":"cover.1368118918.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"CANYiYbEvYXiRE0OeR5vqBAaOFFpJTPBetQbawZD5EcMCj8veFw@mail.gmail.com","subject":"[PATCH v8 00/12] Interactive git clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-09T17:14:04Z","receivedAt":"2013-05-09T17:14:04Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Updates since v7 series:\n\n * Eliminate global variable \"**the_prefix\".\n * Save relative paths in del_list.\n * Split 1/10 of v7 into 3 patches for readability.\n * Change orders of patches, thanks to Eric.\n * Update menu and hotkeys with the help of Eric.\n\nUsage:\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    *** Commands ***\n      1: clean         2: filter by pattern     3: select by numbers\n      4. ask each      5. toggle flags: none    6. quit\n      7: help\n    What now> 2\n\nYou also could say `c` or `clean` above as long as the choice is unique.\n\nThe main command loop has 7 subcommands.\n\nclean::\n\n   Start cleaning files and directories, and then quit.\n\nfilter by pattern::\n\n   This shows the files and directories to be deleted and issues an\n   \"Input ignore patterns>>\" prompt. You can input 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\nselect 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\nask each::\n\n  This will start to clean, and you must confirm one by one in order\n  to delete items. Please note that this action is not as efficient\n  as the above two actions.\n\ntoggle flags::\n\n  This lets you change the flags for git-clean, such as -x/-X/-d/-ff,\n  and refresh the cleaning candidates list automatically.\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 (12):\n  git-clean refactor: hold cleaning items in del_list\n  git-clean: add support for -i/--interactive\n  git-clean: show items of del_list in columns\n  git-clean: add colors to interactive git-clean\n  git-clean: use a git-add-interactive compatible UI\n  git-clean: add filter by pattern interactive action\n  git-clean: add select by numbers interactive action\n  git-clean: add ask each interactive action\n  git-clean refactor: save some options in clean_flags\n  git-clean refactor: add wrapper scan_clean_candidates\n  git-clean: add toggle flags interactive action\n  git-clean: update document for interactive git-clean\n\n Documentation/config.txt    |    4 +\n Documentation/git-clean.txt |   77 +++-\n builtin/clean.c             | 1019 +++++++++++++++++++++++++++++++++++++++----\n 3 files changed, 1023 insertions(+), 77 deletions(-)\n\n-- \n1.8.3.rc1.341.g24a8a0f\n"},{"id":"216785","messageId":"29d47a6c3711b558d0370b1cbc52079da11fc0fc.1368118918.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"cover.1368118918.git.worldhello.net@gmail.com","subject":"[PATCH v8 01/12] git-clean refactor: hold cleaning items in del_list","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-09T17:14:05Z","receivedAt":"2013-05-09T17:14:05Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Refactor git-clean operations into two phases:\n\n * collect cleaning candidates in del_list,\n * and remove them in a separate loop at the end.\n\nWe will introduce an interactive git-clean between the two phases.\nThe interactive git-clean will show what would be done and confirm\nbefore do real cleaning.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n builtin/clean.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++----------\n 1 file changed, 85 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 04e39..ccd4 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -15,6 +15,7 @@\n #include \"quote.h\"\n \n static int force = -1; /* unset */\n+static struct string_list del_list = STRING_LIST_INIT_DUP;\n \n static const char *const builtin_clean_usage[] = {\n \tN_(\"git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...\"),\n@@ -142,18 +143,61 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \treturn ret;\n }\n \n+/*\n+ * Give path as relative to prefix.\n+ *\n+ * This function is a combination of path_relative (in quote.c) and\n+ * relative_path (in path.c)\n+ */\n+static const char *path_relative(const char *in, const char *prefix)\n+{\n+\tstatic char buf[PATH_MAX + 1];\n+\tint off, i;\n+\tint len, prefix_len;\n+\n+\tlen = strlen(in);\n+\tif (prefix)\n+\t\tprefix_len = strlen(prefix);\n+\telse\n+\t\tprefix_len = 0;\n+\n+\toff = 0;\n+\ti = 0;\n+\twhile (i < prefix_len && i < len && prefix[i] == in[i]) {\n+\t\tif (prefix[i] == '/')\n+\t\t\toff = i + 1;\n+\t\ti++;\n+\t}\n+\tin += off;\n+\tlen -= off;\n+\n+\tif (i >= prefix_len)\n+\t\treturn in;\n+\n+\tbuf[0] = '\\0';\n+\twhile (i < prefix_len) {\n+\t\tif (prefix[i] == '/')\n+\t\t\tstrcat(buf, \"../\");\n+\t\ti++;\n+\t}\n+\tstrcat(buf, in);\n+\n+\treturn buf;\n+}\n+\n int cmd_clean(int argc, const char **argv, const char *prefix)\n {\n \tint i, res;\n \tint dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;\n \tint ignored_only = 0, config_set = 0, errors = 0, gone = 1;\n \tint rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;\n-\tstruct strbuf directory = STRBUF_INIT;\n+\tstruct strbuf abs_path = STRBUF_INIT;\n \tstruct dir_struct dir;\n \tstatic const char **pathspec;\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@@ -223,6 +267,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \t\tint matches = 0;\n \t\tstruct cache_entry *ce;\n \t\tstruct stat st;\n+\t\tconst char *rel;\n \n \t\t/*\n \t\t * Remove the '/' at the end that directory\n@@ -242,11 +287,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \t\t\t\tcontinue; /* Yup, this one exists unmerged */\n \t\t}\n \n-\t\t/*\n-\t\t * we might have removed this as part of earlier\n-\t\t * recursive directory removal, so lstat() here could\n-\t\t * fail with ENOENT.\n-\t\t */\n \t\tif (lstat(ent->name, &st))\n \t\t\tcontinue;\n \n@@ -257,33 +297,60 @@ 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\trel = path_relative(ent->name, prefix);\n+\t\t\t\tstring_list_append(&del_list, rel);\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\trel = path_relative(ent->name, prefix);\n+\t\t\tstring_list_append(&del_list, rel);\n+\t\t}\n+\t}\n+\n+\t/* TODO: do interactive git-clean here, which will modify del_list */\n+\n+\tfor_each_string_list_item(item, &del_list) {\n+\t\tstruct stat st;\n+\n+\t\tif (prefix) {\n+\t\t\tstrbuf_addstr(&abs_path, prefix);\n+\t\t}\n+\t\tstrbuf_addstr(&abs_path, item->string);\n+\n+\t\t/*\n+\t\t * we might have removed this as part of earlier\n+\t\t * recursive directory removal, so lstat() here could\n+\t\t * fail with ENOENT.\n+\t\t */\n+\t\tif (lstat(abs_path.buf, &st))\n+\t\t\tcontinue;\n+\n+\t\tif (S_ISDIR(st.st_mode)) {\n+\t\t\tif (remove_dirs(&abs_path, 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(item->string, -1, &buf, NULL);\n+\t\t\t\tprintf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);\n+\t\t\t}\n+\t\t} else {\n+\t\t\tres = dry_run ? 0 : unlink(abs_path.buf);\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, NULL);\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, NULL);\n \t\t\t\tprintf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);\n \t\t\t}\n \t\t}\n+\t\tstrbuf_reset(&abs_path);\n \t}\n \tfree(seen);\n \n-\tstrbuf_release(&directory);\n+\tstrbuf_release(&abs_path);\n+\tstring_list_clear(&del_list, 0);\n \tstring_list_clear(&exclude_list, 0);\n \treturn (errors != 0);\n }\n-- \n1.8.3.rc1.341.g24a8a0f\n"},{"id":"216786","messageId":"708200939d6360b9d0161670fd2449715f9b4fbf.1368118918.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"cover.1368118918.git.worldhello.net@gmail.com","subject":"[PATCH v8 02/12] git-clean: add support for -i/--interactive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-09T17:14:06Z","receivedAt":"2013-05-09T17:14:06Z","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.\n\n    Would remove ...\n    Would remove ...\n    Would remove ...\n\n    Remove (y/n) ?\n\nPress \"y\" to start cleaning, and press \"n\" if you want to abort.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n Documentation/git-clean.txt | 10 ++++++--\n builtin/clean.c             | 61 +++++++++++++++++++++++++++++++++++++++++----\n 2 files changed, 64 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt\nindex bdc3a..186e34 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,13 @@ 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.\n \n -n::\n --dry-run::\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex ccd4..2f9b9 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -15,10 +15,11 @@\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 \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@@ -185,6 +186,50 @@ static const char *path_relative(const char *in, const char *prefix)\n \treturn buf;\n }\n \n+static void interactive_main_loop(void)\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+\twhile (del_list.nr) {\n+\t\tputchar('\\n');\n+\t\tfor_each_string_list_item(item, &del_list) {\n+\t\t\tqname = quote_path_relative(item->string, -1, &buf, NULL);\n+\t\t\tprintf(_(msg_would_remove), qname);\n+\t\t}\n+\t\tputchar('\\n');\n+\n+\t\tprintf(_(\"Remove (y/n) ? \"));\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 {\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@@ -204,6 +249,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\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@@ -230,12 +276,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@@ -309,7 +359,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n-\t/* TODO: do interactive git-clean here, which will modify del_list */\n+\tif (interactive && !dry_run && del_list.nr > 0)\n+\t\tinteractive_main_loop();\n \n \tfor_each_string_list_item(item, &del_list) {\n \t\tstruct stat st;\n-- \n1.8.3.rc1.341.g24a8a0f\n"},{"id":"216787","messageId":"cb530e2eb30aa09bf293d60d04e15e0309059f51.1368118918.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"cover.1368118918.git.worldhello.net@gmail.com","subject":"[PATCH v8 03/12] git-clean: show items of del_list in columns","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-09T17:14:07Z","receivedAt":"2013-05-09T17:14:07Z","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\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          | 49 +++++++++++++++++++++++++++++++++++++++---------\n 2 files changed, 44 insertions(+), 9 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 2f9b9..a16988 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -13,10 +13,12 @@\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 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@@ -31,8 +33,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@@ -186,21 +193,46 @@ static const char *path_relative(const char *in, const char *prefix)\n \treturn buf;\n }\n \n-static void interactive_main_loop(void)\n+static void pretty_print_dels(void)\n {\n-\tstruct strbuf confirm = STRBUF_INIT;\n-\tstruct strbuf buf = STRBUF_INIT;\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, NULL);\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+static void interactive_main_loop(void)\n+{\n+\tstruct strbuf confirm = STRBUF_INIT;\n \n \twhile (del_list.nr) {\n \t\tputchar('\\n');\n-\t\tfor_each_string_list_item(item, &del_list) {\n-\t\t\tqname = quote_path_relative(item->string, -1, &buf, NULL);\n-\t\t\tprintf(_(msg_would_remove), qname);\n-\t\t}\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\tpretty_print_dels();\n+\n \t\tprintf(_(\"Remove (y/n) ? \"));\n \t\tif (strbuf_getline(&confirm, stdin, '\\n') != EOF) {\n \t\t\tstrbuf_trim(&confirm);\n@@ -226,7 +258,6 @@ static void interactive_main_loop(void)\n \t\t}\n \t}\n \n-\tstrbuf_release(&buf);\n \tstrbuf_release(&confirm);\n }\n \n-- \n1.8.3.rc1.341.g24a8a0f\n"},{"id":"216788","messageId":"69d358f762fab15528910d1429b1b8eb58308560.1368118918.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"cover.1368118918.git.worldhello.net@gmail.com","subject":"[PATCH v8 04/12] git-clean: add colors to interactive git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-09T17:14:08Z","receivedAt":"2013-05-09T17:14:08Z","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 | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 71 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex a16988..126fc 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@@ -31,16 +32,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@@ -226,14 +292,18 @@ static void interactive_main_loop(void)\n \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\tpretty_print_dels();\n \n+\t\tclean_print_color(CLEAN_COLOR_PROMPT);\n \t\tprintf(_(\"Remove (y/n) ? \"));\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.341.g24a8a0f\n"},{"id":"216789","messageId":"bd529a1dcd65cf0b982e32b16c5e3d67052dcdd5.1368118918.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"cover.1368118918.git.worldhello.net@gmail.com","subject":"[PATCH v8 05/12] git-clean: use a git-add-interactive compatible UI","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-09T17:14:09Z","receivedAt":"2013-05-09T17:14:09Z","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 will use this framework to add\nnew actions for interactive git-clean later.\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 | 447 ++++++++++++++++++++++++++++++++++++++++++++++++++++----\n 1 file changed, 418 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 126fc..a9a1ee 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -50,6 +50,36 @@ enum color_clean {\n \tCLEAN_COLOR_ERROR = 5,\n };\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+struct menu_opts {\n+\tconst char *header;\n+\tconst char *prompt;\n+\tint flag;\n+};\n+\n+#define MENU_RETURN_NO_LOOP\t\t10\n+\n+struct menu_item {\n+\tchar hotkey;\n+\tchar *title;\n+\tint selected;\n+\tint (*fn)();\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 static int parse_clean_color_slot(const char *var, int ofs)\n {\n \tif (!strcasecmp(var+ofs, \"reset\"))\n@@ -281,54 +311,413 @@ static void pretty_print_dels(void)\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-static void interactive_main_loop(void)\n+static void pretty_print_menus(struct string_list *menu_list)\n+{\n+\tunsigned int local_colopts = 0;\n+\tstruct column_options copts;\n+\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+}\n+\n+static 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+ * display menu stuff with number prefix and hotkey highlight\n+ */\n+static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)\n+{\n+\tstatic struct string_list menu_list = STRING_LIST_INIT_DUP;\n+\tstruct strbuf menu = STRBUF_INIT;\n+\tint i;\n+\n+\t/* highlight hotkey in menu */\n+\tif (MENU_STUFF_TYPE_MENU_ITEM == stuff->type) {\n+\t\tstruct menu_item *item;\n+\n+\t\titem = (struct menu_item *)stuff->stuff;\n+\t\tfor (i = 0; i < stuff->nr; i++, item++) {\n+\t\t\tchar *p;\n+\t\t\tint highlighted = 0;\n+\n+\t\t\tp = item->title;\n+\t\t\tif ((*chosen)[i] < 0)\n+\t\t\t\t(*chosen)[i] = item->selected ? 1 : 0;\n+\t\t\tstrbuf_addf(&menu, \"%s%2d: \", (*chosen)[i] ? \"*\" : \" \", i+1);\n+\t\t\tfor (; *p; p++) {\n+\t\t\t\tif (!highlighted && *p == item->hotkey) {\n+\t\t\t\t\tstrbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_PROMPT));\n+\t\t\t\t\tstrbuf_addch(&menu, *p);\n+\t\t\t\t\tstrbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_RESET));\n+\t\t\t\t\thighlighted = 1;\n+\t\t\t\t} else {\n+\t\t\t\t\tstrbuf_addch(&menu, *p);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tstring_list_append(&menu_list, menu.buf);\n+\t\t\tstrbuf_reset(&menu);\n+\t\t}\n+\t} else if (MENU_STUFF_TYPE_STRING_LIST == stuff->type) {\n+\t\tstruct string_list_item *item;\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\ti = 0;\n+\n+\t\tfor_each_string_list_item(item, (struct string_list *)stuff->stuff) {\n+\t\t\tif ((*chosen)[i] < 0)\n+\t\t\t\t(*chosen)[i] = 0;\n+\t\t\tstrbuf_addf(&menu, \"%s%2d: %s\", (*chosen)[i] ? \"*\" : \" \", ++i, item->string);\n+\t\t\tstring_list_append(&menu_list, menu.buf);\n+\t\t\tstrbuf_reset(&menu);\n+\t\t}\n+\t\tstrbuf_release(&buf);\n+\t}\n+\n+\tpretty_print_menus(&menu_list);\n+\n+\tstrbuf_release(&menu);\n+\tstring_list_clear(&menu_list, 0);\n+}\n+\n+/*\n+ * Parse user input, and return choice(s) for menu (menu_stuff).\n+ *\n+ * Input\n+ *     (for single choice)\n+ *         1          - select a numbered item\n+ *         foo        - select item based on menu title\n+ *                    - (empty) select nothing\n+ *\n+ *     (for multiple choice)\n+ *         1          - select a single item\n+ *         3-5        - select a range of items\n+ *         2-3,6-9    - select multiple ranges\n+ *         foo        - select item based on menu title\n+ *         -...       - unselect specified items\n+ *         *          - choose all items\n+ *                    - (empty) finish selecting\n+ *\n+ * The parse result will be saved in array **chosen, and\n+ * return number of total selections.\n+ */\n+static int parse_choice(struct menu_stuff *menu_stuff,\n+\t\t\tint is_single,\n+\t\t\tstruct strbuf input,\n+\t\t\tint **chosen)\n+{\n+\tstruct strbuf **choice_list, **choice_p;\n+\tint nr = 0;\n+\tint i;\n+\n+\tif (is_single) {\n+\t\tchoice_list = strbuf_split_max(&input, '\\n', 0);\n+\t} else {\n+\t\tchar *p = input.buf;\n+\t\tdo {\n+\t\t\tif (*p == ',')\n+\t\t\t\t*p = ' ';\n+\t\t} while (*p++);\n+\t\tchoice_list = strbuf_split_max(&input, ' ', 0);\n+\t}\n+\n+\tfor (choice_p = choice_list; *choice_p; choice_p++) {\n+\t\tchar *p;\n+\t\tint choose = 1;\n+\t\tint bottom = 0, top = 0;\n+\t\tint is_range, is_number;\n+\n+\t\tstrbuf_trim(*choice_p);\n+\t\tif (!(*choice_p)->len)\n+\t\t\tcontinue;\n+\n+\t\t/* Input that begins with '-'; unchoose */\n+\t\tif (*(*choice_p)->buf == '-') {\n+\t\t\tchoose = 0;\n+\t\t\tstrbuf_remove((*choice_p), 0, 1);\n+\t\t}\n+\n+\t\tis_range = 0;\n+\t\tis_number = 1;\n+\t\tfor(p = (*choice_p)->buf; *p; p++) {\n+\t\t\tif ('-' == *p) {\n+\t\t\t\tif (!is_range) {\n+\t\t\t\t\tis_range = 1;\n+\t\t\t\t\tis_number = 0;\n+\t\t\t\t} else {\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} else if (!isdigit(*p)) {\n+\t\t\t\tis_number = 0;\n+\t\t\t\tis_range = 0;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (is_number) {\n+\t\t\tbottom = atoi((*choice_p)->buf);\n+\t\t\ttop = bottom;\n+\t\t} else if (is_range) {\n+\t\t\tbottom = atoi((*choice_p)->buf);\n+\t\t\tif (!*(strchr((*choice_p)->buf, '-') + 1)) {\n+\t\t\t\ttop = menu_stuff->nr - 1;\n+\t\t\t} else {\n+\t\t\t\ttop = atoi(strchr((*choice_p)->buf, '-') + 1);\n+\t\t\t}\n+\t\t} else if (!strcmp((*choice_p)->buf, \"*\")) {\n+\t\t\tbottom = 1;\n+\t\t\ttop = menu_stuff->nr;\n+\t\t} else {\n+\t\t\tif (MENU_STUFF_TYPE_MENU_ITEM == menu_stuff->type) {\n+\t\t\t\tstruct menu_item *item;\n+\n+\t\t\t\titem = (struct menu_item *)menu_stuff->stuff;\n+\t\t\t\tfor (i = 0; i < menu_stuff->nr; i++, item++) {\n+\t\t\t\t\tif (((*choice_p)->len == 1 &&\n+\t\t\t\t\t     *(*choice_p)->buf == item->hotkey) ||\n+\t\t\t\t\t    !strcasecmp((*choice_p)->buf, item->title)) {\n+\t\t\t\t\t\tbottom = i + 1;\n+\t\t\t\t\t\ttop = bottom;\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t} else if (MENU_STUFF_TYPE_STRING_LIST == menu_stuff->type) {\n+\t\t\t\tstruct string_list_item *item;\n+\n+\t\t\t\titem = ((struct string_list *)menu_stuff->stuff)->items;\n+\t\t\t\tfor (i = 0; i < menu_stuff->nr; i++, item++) {\n+\t\t\t\t\tif (!strcasecmp((*choice_p)->buf, item->string)) {\n+\t\t\t\t\t\tbottom = i + 1;\n+\t\t\t\t\t\ttop = bottom;\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top ||\n+\t\t    (is_single && bottom != top)) {\n+\t\t\tclean_print_color(CLEAN_COLOR_ERROR);\n+\t\t\tprintf_ln(_(\"Huh (%s)?\"), (*choice_p)->buf);\n+\t\t\tclean_print_color(CLEAN_COLOR_RESET);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* A range can be specified like 5-7 or 5-. */\n+\t\tfor (i = bottom; i <= top; i++)\n+\t\t\t(*chosen)[i-1] = choose;\n+\t}\n+\n+\tstrbuf_list_free(choice_list);\n+\n+\tfor (i = 0; i < menu_stuff->nr; i++)\n+\t\tnr += (*chosen)[i];\n+\treturn nr;\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+static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)\n {\n-\tstruct strbuf confirm = STRBUF_INIT;\n+\tstruct strbuf choice = STRBUF_INIT;\n+\tint *chosen, *result;\n+\tint nr = 0;\n+\tint eof = 0;\n+\tint i;\n+\n+\tchosen = xmalloc(sizeof(int) * stuff->nr);\n+\t/* set chosen as uninitialized */\n+\tfor (i = 0; i < stuff->nr; i++)\n+\t\tchosen[i] = -1;\n+\n+\tfor (;;) {\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/* the chosen array can be initialized by menu_item.selected  */\n+\t\tprint_highlight_menu_stuff(stuff, &chosen);\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\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\t/* for a multiple-choice menu, press ENTER (empty) will return back */\n+\t\tif (!(opts->flag & MENU_OPTS_SINGLETON) && !choice.len)\n+\t\t\tbreak;\n+\n+\t\tnr = parse_choice(stuff,\n+\t\t\t\t  opts->flag & MENU_OPTS_SINGLETON,\n+\t\t\t\t  choice,\n+\t\t\t\t  &chosen);\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+\tif (eof) {\n+\t\tresult = xmalloc(sizeof(int));\n+\t\t*result = EOF;\n+\t} else {\n+\t\tint j = 0;\n+\n+\t\t/* recalculate nr, for a multiple-choice menu with initial selections */\n+\t\tif (!nr) {\n+\t\t\tfor (i = 0; i < stuff->nr; i++)\n+\t\t\t\tnr += chosen[i];\n+\t\t}\n+\n+\t\tresult = xmalloc(sizeof(int) * (nr + 1));\n+\t\tmemset(result, 0, sizeof(int) * (nr + 1));\n+\t\tfor (i = 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+\tstrbuf_release(&choice);\n+\treturn result;\n+}\n+\n+static int clean_cmd(void)\n+{\n+\treturn MENU_RETURN_NO_LOOP;\n+}\n+\n+static int quit_cmd(void)\n+{\n+\tstring_list_clear(&del_list, 0);\n+\tprintf_ln(_(\"Bye.\"));\n+\treturn MENU_RETURN_NO_LOOP;\n+}\n+\n+static int help_cmd(void)\n+{\n+\tclean_print_color(CLEAN_COLOR_HELP);\n+\tprintf_ln(_(\n+\t\t    \"clean               - start cleaning\\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+static void interactive_main_loop(void)\n+{\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\t0, clean_cmd},\n+\t\t\t{'q', \"quit\",\t\t\t0, quit_cmd},\n+\t\t\t{'h', \"help\",\t\t\t0, help_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\tpretty_print_dels();\n \n-\t\tclean_print_color(CLEAN_COLOR_PROMPT);\n-\t\tprintf(_(\"Remove (y/n) ? \"));\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 {\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();\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-- \n1.8.3.rc1.341.g24a8a0f\n"},{"id":"216790","messageId":"1a78a5e0f03d320bbac165342e0cedd2726d44d3.1368118918.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"cover.1368118918.git.worldhello.net@gmail.com","subject":"[PATCH v8 06/12] git-clean: add filter by pattern interactive action","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-09T17:14:10Z","receivedAt":"2013-05-09T17:14:10Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add a new action for interactive git-clean: filter by pattern. When\nthe user chooses this action, user can input space-separated\npatterns (the same syntax as gitignore), and each clean candidate\nthat matches with one of the patterns will be excluded from cleaning.\nWhen the user feels it's OK, presses ENTER and back to the confirmation\ndialog.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nSuggested-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/clean.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 68 insertions(+)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex a9a1ee..48731 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -646,6 +646,72 @@ static int clean_cmd(void)\n \treturn MENU_RETURN_NO_LOOP;\n }\n \n+static int filter_by_patterns_cmd(void)\n+{\n+\tstruct dir_struct dir;\n+\tstruct strbuf confirm = STRBUF_INIT;\n+\tstruct strbuf **ignore_list;\n+\tstruct string_list_item *item;\n+\tstruct exclude_list *el;\n+\tint changed = -1, i;\n+\n+\tfor (;;) {\n+\t\tif (!del_list.nr)\n+\t\t\tbreak;\n+\n+\t\tif (changed)\n+\t\t\tpretty_print_dels();\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\telse\n+\t\t\tputchar('\\n');\n+\n+\t\t/* Quit filter_by_pattern mode if press ENTER or Ctrl-D */\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+\n+\t\t\tif (is_excluded(&dir, item->string, &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\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+\t\tclear_directory(&dir);\n+\t}\n+\n+\tstrbuf_release(&confirm);\n+\treturn 0;\n+}\n+\n static int quit_cmd(void)\n {\n \tstring_list_clear(&del_list, 0);\n@@ -658,6 +724,7 @@ static int help_cmd(void)\n \tclean_print_color(CLEAN_COLOR_HELP);\n \tprintf_ln(_(\n \t\t    \"clean               - start cleaning\\n\"\n+\t\t    \"filter by pattern   - 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@@ -673,6 +740,7 @@ static void interactive_main_loop(void)\n \t\tstruct menu_stuff menu_stuff;\n \t\tstruct menu_item menus[] = {\n \t\t\t{'c', \"clean\",\t\t\t0, clean_cmd},\n+\t\t\t{'f', \"filter by pattern\",\t0, filter_by_patterns_cmd},\n \t\t\t{'q', \"quit\",\t\t\t0, quit_cmd},\n \t\t\t{'h', \"help\",\t\t\t0, help_cmd},\n \t\t};\n-- \n1.8.3.rc1.341.g24a8a0f\n"},{"id":"216793","messageId":"890352e6b239cfb997a2864fa129c346f6c0c3b3.1368118918.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"cover.1368118918.git.worldhello.net@gmail.com","subject":"[PATCH v8 07/12] git-clean: add select by numbers interactive action","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-09T17:14:11Z","receivedAt":"2013-05-09T17:14:11Z","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 48731..17b65 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -712,6 +712,43 @@ static int filter_by_patterns_cmd(void)\n \treturn 0;\n }\n \n+static int select_by_numbers_cmd(void)\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 static int quit_cmd(void)\n {\n \tstring_list_clear(&del_list, 0);\n@@ -725,6 +762,7 @@ static int help_cmd(void)\n \tprintf_ln(_(\n \t\t    \"clean               - start cleaning\\n\"\n \t\t    \"filter by pattern   - exclude items from deletion\\n\"\n+\t\t    \"select 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@@ -741,6 +779,7 @@ static void interactive_main_loop(void)\n \t\tstruct menu_item menus[] = {\n \t\t\t{'c', \"clean\",\t\t\t0, clean_cmd},\n \t\t\t{'f', \"filter by pattern\",\t0, filter_by_patterns_cmd},\n+\t\t\t{'s', \"select by numbers\",\t0, select_by_numbers_cmd},\n \t\t\t{'q', \"quit\",\t\t\t0, quit_cmd},\n \t\t\t{'h', \"help\",\t\t\t0, help_cmd},\n \t\t};\n-- \n1.8.3.rc1.341.g24a8a0f\n"},{"id":"216791","messageId":"a62c4951bed18ade8b7bf8de9a7719ac18323db6.1368118918.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"cover.1368118918.git.worldhello.net@gmail.com","subject":"[PATCH v8 08/12] git-clean: add ask each interactive action","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-09T17:14:12Z","receivedAt":"2013-05-09T17:14:12Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add a new action for interactive git-clean: ask each. It's just like\nthe \"rm -i\" command, that the user must confirm one by one for each\nfile or directory to be cleaned.\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 17b65..85345 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -749,6 +749,40 @@ static int select_by_numbers_cmd(void)\n \treturn 0;\n }\n \n+static int rm_i_cmd(void)\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, NULL);\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 static int quit_cmd(void)\n {\n \tstring_list_clear(&del_list, 0);\n@@ -763,6 +797,7 @@ static int help_cmd(void)\n \t\t    \"clean               - start cleaning\\n\"\n \t\t    \"filter by pattern   - exclude items from deletion\\n\"\n \t\t    \"select by numbers   - select items to be deleted by numbers\\n\"\n+\t\t    \"ask each            - confirm each deletion (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@@ -780,6 +815,7 @@ static void interactive_main_loop(void)\n \t\t\t{'c', \"clean\",\t\t\t0, clean_cmd},\n \t\t\t{'f', \"filter by pattern\",\t0, filter_by_patterns_cmd},\n \t\t\t{'s', \"select by numbers\",\t0, select_by_numbers_cmd},\n+\t\t\t{'a', \"ask each\",\t\t0, rm_i_cmd},\n \t\t\t{'q', \"quit\",\t\t\t0, quit_cmd},\n \t\t\t{'h', \"help\",\t\t\t0, help_cmd},\n \t\t};\n-- \n1.8.3.rc1.341.g24a8a0f\n"},{"id":"216794","messageId":"004346e031386fb325433117bc7d1b282c1740ff.1368118918.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"cover.1368118918.git.worldhello.net@gmail.com","subject":"[PATCH v8 09/12] git-clean refactor: save some options in clean_flags","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-09T17:14:13Z","receivedAt":"2013-05-09T17:14:13Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Save some options in variable clean_flags, such as -ff (force > 1),\n-x (ignored), -X (ignored_only), and -d (remove_directories). We may\nchange clean_flags later in the interactive git-clean.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n builtin/clean.c | 46 +++++++++++++++++++++++++++++++---------------\n 1 file changed, 31 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 85345..26d00 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -16,7 +16,13 @@\n #include \"column.h\"\n #include \"color.h\"\n \n+#define CLEAN_OPTS_SHOW_IGNORED\t\t01\n+#define CLEAN_OPTS_IGNORED_ONLY\t\t02\n+#define CLEAN_OPTS_REMOVE_DIRECTORIES\t04\n+#define CLEAN_OPTS_REMOVE_NESTED_GIT\t010\n+\n static int force = -1; /* unset */\n+static int clean_flags = 0;\n static int interactive;\n static struct string_list del_list = STRING_LIST_INIT_DUP;\n static unsigned int colopts;\n@@ -868,7 +874,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \tint i, res;\n \tint dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;\n \tint ignored_only = 0, config_set = 0, errors = 0, gone = 1;\n-\tint rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;\n \tstruct strbuf abs_path = STRBUF_INIT;\n \tstruct dir_struct dir;\n \tstatic const char **pathspec;\n@@ -902,13 +907,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, options, builtin_clean_usage,\n \t\t\t     0);\n \n-\tmemset(&dir, 0, sizeof(dir));\n-\tif (ignored_only)\n-\t\tdir.flags |= DIR_SHOW_IGNORED;\n-\n-\tif (ignored && ignored_only)\n-\t\tdie(_(\"-x and -X cannot be used together\"));\n-\n \tif (interactive) {\n \t\tif (!isatty(0) || !isatty(1))\n \t\t\tdie(_(\"interactive clean can not run without a valid tty; \"\n@@ -922,15 +920,29 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \t\t\t\t  \"refusing to clean\"));\n \t}\n \n+\tif (read_cache() < 0)\n+\t\tdie(_(\"index file corrupt\"));\n+\n \tif (force > 1)\n-\t\trm_flags = 0;\n+\t\tclean_flags |= CLEAN_OPTS_REMOVE_NESTED_GIT;\n+\tif (ignored)\n+\t\tclean_flags |= CLEAN_OPTS_SHOW_IGNORED;\n+\tif (ignored_only)\n+\t\tclean_flags |= CLEAN_OPTS_IGNORED_ONLY;\n+\tif (remove_directories)\n+\t\tclean_flags |= CLEAN_OPTS_REMOVE_DIRECTORIES;\n \n-\tdir.flags |= DIR_SHOW_OTHER_DIRECTORIES;\n+\tmemset(&dir, 0, sizeof(dir));\n+\tif (clean_flags & CLEAN_OPTS_IGNORED_ONLY)\n+\t\tdir.flags |= DIR_SHOW_IGNORED;\n \n-\tif (read_cache() < 0)\n-\t\tdie(_(\"index file corrupt\"));\n+\tif (clean_flags & CLEAN_OPTS_IGNORED_ONLY &&\n+\t    clean_flags & CLEAN_OPTS_SHOW_IGNORED)\n+\t\tdie(_(\"-x and -X cannot be used together\"));\n+\n+\tdir.flags |= DIR_SHOW_OTHER_DIRECTORIES;\n \n-\tif (!ignored)\n+\tif (!(clean_flags & CLEAN_OPTS_SHOW_IGNORED))\n \t\tsetup_standard_excludes(&dir);\n \n \tel = add_exclude_list(&dir, EXC_CMDL, \"--exclude option\");\n@@ -980,7 +992,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 ((clean_flags & CLEAN_OPTS_REMOVE_DIRECTORIES) ||\n+\t\t\t    (matches == MATCHED_EXACTLY)) {\n \t\t\t\trel = path_relative(ent->name, prefix);\n \t\t\t\tstring_list_append(&del_list, rel);\n \t\t\t}\n@@ -1012,7 +1025,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \t\t\tcontinue;\n \n \t\tif (S_ISDIR(st.st_mode)) {\n-\t\t\tif (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone))\n+\t\t\tif (remove_dirs(&abs_path, prefix,\n+\t\t\t\t\tclean_flags & CLEAN_OPTS_REMOVE_NESTED_GIT ?\n+\t\t\t\t\t\t0 : REMOVE_DIR_KEEP_NESTED_GIT,\n+\t\t\t\t\tdry_run, quiet, &gone))\n \t\t\t\terrors++;\n \t\t\tif (gone && !quiet) {\n \t\t\t\tqname = quote_path_relative(item->string, -1, &buf, NULL);\n-- \n1.8.3.rc1.341.g24a8a0f\n"},{"id":"216792","messageId":"d09dbbd3fc3e9f46bcd9f0444fbdc78e8cd0cb96.1368118918.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"cover.1368118918.git.worldhello.net@gmail.com","subject":"[PATCH v8 10/12] git-clean refactor: add wrapper scan_clean_candidates","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-09T17:14:14Z","receivedAt":"2013-05-09T17:14:14Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add new wrapper `scan_clean_candidates`, which determines the del_list\n(i.e. the cleaning candidates). This function will be reused later in\nthe interactive git-clean, so we can change flags of git-clean and\nrefresh the del_list.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n builtin/clean.c | 169 +++++++++++++++++++++++++++++++-------------------------\n 1 file changed, 93 insertions(+), 76 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 26d00..232d48 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -295,6 +295,96 @@ static const char *path_relative(const char *in, const char *prefix)\n \treturn buf;\n }\n \n+static void scan_clean_candidates(const char **pathspec,\n+\t\t\t\t  struct string_list exclude_list,\n+\t\t\t\t  const char *prefix)\n+{\n+\tstruct dir_struct dir;\n+\tstruct exclude_list *el;\n+\tchar *seen = NULL;\n+\tconst char **pathspec_p = pathspec;\n+\tconst char *rel;\n+\tint pathspec_nr = 0;\n+\tint i;\n+\n+\twhile (pathspec_p && *(pathspec_p++))\n+\t\tpathspec_nr++;\n+\n+\tmemset(&dir, 0, sizeof(dir));\n+\tif (clean_flags & CLEAN_OPTS_IGNORED_ONLY)\n+\t\tdir.flags |= DIR_SHOW_IGNORED;\n+\n+\tif (clean_flags & CLEAN_OPTS_IGNORED_ONLY &&\n+\t    clean_flags & CLEAN_OPTS_SHOW_IGNORED)\n+\t\tdie(_(\"-x and -X cannot be used together\"));\n+\n+\tdir.flags |= DIR_SHOW_OTHER_DIRECTORIES;\n+\n+\tif (!(clean_flags & CLEAN_OPTS_SHOW_IGNORED))\n+\t\tsetup_standard_excludes(&dir);\n+\n+\tel = add_exclude_list(&dir, EXC_CMDL, \"--exclude option\");\n+\tfor (i = 0; i < exclude_list.nr; i++)\n+\t\tadd_exclude(exclude_list.items[i].string, \"\", 0, el, -(i+1));\n+\n+\tfill_directory(&dir, pathspec);\n+\n+\tif (pathspec)\n+\t\tseen = xmalloc(pathspec_nr > 0 ? pathspec_nr : 1);\n+\n+\tstring_list_clear(&del_list, 0);\n+\n+\tfor (i = 0; i < dir.nr; i++) {\n+\t\tstruct dir_entry *ent = dir.entries[i];\n+\t\tint len, pos;\n+\t\tint matches = 0;\n+\t\tstruct cache_entry *ce;\n+\t\tstruct stat st;\n+\n+\t\t/*\n+\t\t * Remove the '/' at the end that directory\n+\t\t * walking adds for directory entries.\n+\t\t */\n+\t\tlen = ent->len;\n+\t\tif (len && ent->name[len-1] == '/')\n+\t\t\tlen--;\n+\t\tpos = cache_name_pos(ent->name, len);\n+\t\tif (0 <= pos)\n+\t\t\tcontinue;\t/* exact match */\n+\t\tpos = -pos - 1;\n+\t\tif (pos < active_nr) {\n+\t\t\tce = active_cache[pos];\n+\t\t\tif (ce_namelen(ce) == len &&\n+\t\t\t    !memcmp(ce->name, ent->name, len))\n+\t\t\t\tcontinue; /* Yup, this one exists unmerged */\n+\t\t}\n+\n+\t\tif (lstat(ent->name, &st))\n+\t\t\tcontinue;\n+\n+\t\tif (pathspec) {\n+\t\t\tmemset(seen, 0, pathspec_nr > 0 ? pathspec_nr : 1);\n+\t\t\tmatches = match_pathspec(pathspec, ent->name, len,\n+\t\t\t\t\t\t 0, seen);\n+\t\t}\n+\n+\t\tif (S_ISDIR(st.st_mode)) {\n+\t\t\tif ((clean_flags & CLEAN_OPTS_REMOVE_DIRECTORIES) ||\n+\t\t\t    (matches == MATCHED_EXACTLY)) {\n+\t\t\t\trel = path_relative(ent->name, prefix);\n+\t\t\t\tstring_list_append(&del_list, rel);\n+\t\t\t}\n+\t\t} else {\n+\t\t\tif (pathspec && !matches)\n+\t\t\t\tcontinue;\n+\t\t\trel = path_relative(ent->name, prefix);\n+\t\t\tstring_list_append(&del_list, rel);\n+\t\t}\n+\t}\n+\n+\tfree(seen);\n+}\n+\n static void pretty_print_dels(void)\n {\n \tstruct string_list list = STRING_LIST_INIT_DUP;\n@@ -871,18 +961,15 @@ static void interactive_main_loop(void)\n \n int cmd_clean(int argc, const char **argv, const char *prefix)\n {\n-\tint i, res;\n+\tint res;\n \tint dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;\n \tint ignored_only = 0, config_set = 0, errors = 0, gone = 1;\n \tstruct strbuf abs_path = STRBUF_INIT;\n-\tstruct dir_struct dir;\n \tstatic const char **pathspec;\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+\tstruct string_list exclude_list = STRING_LIST_INIT_NODUP;\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@@ -932,78 +1019,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \tif (remove_directories)\n \t\tclean_flags |= CLEAN_OPTS_REMOVE_DIRECTORIES;\n \n-\tmemset(&dir, 0, sizeof(dir));\n-\tif (clean_flags & CLEAN_OPTS_IGNORED_ONLY)\n-\t\tdir.flags |= DIR_SHOW_IGNORED;\n-\n-\tif (clean_flags & CLEAN_OPTS_IGNORED_ONLY &&\n-\t    clean_flags & CLEAN_OPTS_SHOW_IGNORED)\n-\t\tdie(_(\"-x and -X cannot be used together\"));\n-\n-\tdir.flags |= DIR_SHOW_OTHER_DIRECTORIES;\n-\n-\tif (!(clean_flags & CLEAN_OPTS_SHOW_IGNORED))\n-\t\tsetup_standard_excludes(&dir);\n-\n-\tel = add_exclude_list(&dir, EXC_CMDL, \"--exclude option\");\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 \n-\tfill_directory(&dir, pathspec);\n-\n-\tif (pathspec)\n-\t\tseen = xmalloc(argc > 0 ? argc : 1);\n-\n-\tfor (i = 0; i < dir.nr; i++) {\n-\t\tstruct dir_entry *ent = dir.entries[i];\n-\t\tint len, pos;\n-\t\tint matches = 0;\n-\t\tstruct cache_entry *ce;\n-\t\tstruct stat st;\n-\t\tconst char *rel;\n-\n-\t\t/*\n-\t\t * Remove the '/' at the end that directory\n-\t\t * walking adds for directory entries.\n-\t\t */\n-\t\tlen = ent->len;\n-\t\tif (len && ent->name[len-1] == '/')\n-\t\t\tlen--;\n-\t\tpos = cache_name_pos(ent->name, len);\n-\t\tif (0 <= pos)\n-\t\t\tcontinue;\t/* exact match */\n-\t\tpos = -pos - 1;\n-\t\tif (pos < active_nr) {\n-\t\t\tce = active_cache[pos];\n-\t\t\tif (ce_namelen(ce) == len &&\n-\t\t\t    !memcmp(ce->name, ent->name, len))\n-\t\t\t\tcontinue; /* Yup, this one exists unmerged */\n-\t\t}\n-\n-\t\tif (lstat(ent->name, &st))\n-\t\t\tcontinue;\n-\n-\t\tif (pathspec) {\n-\t\t\tmemset(seen, 0, argc > 0 ? argc : 1);\n-\t\t\tmatches = match_pathspec(pathspec, ent->name, len,\n-\t\t\t\t\t\t 0, seen);\n-\t\t}\n-\n-\t\tif (S_ISDIR(st.st_mode)) {\n-\t\t\tif ((clean_flags & CLEAN_OPTS_REMOVE_DIRECTORIES) ||\n-\t\t\t    (matches == MATCHED_EXACTLY)) {\n-\t\t\t\trel = path_relative(ent->name, prefix);\n-\t\t\t\tstring_list_append(&del_list, rel);\n-\t\t\t}\n-\t\t} else {\n-\t\t\tif (pathspec && !matches)\n-\t\t\t\tcontinue;\n-\t\t\trel = path_relative(ent->name, prefix);\n-\t\t\tstring_list_append(&del_list, rel);\n-\t\t}\n-\t}\n+\tscan_clean_candidates(pathspec, exclude_list, prefix);\n \n \tif (interactive && !dry_run && del_list.nr > 0)\n \t\tinteractive_main_loop();\n@@ -1047,7 +1065,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \t\t}\n \t\tstrbuf_reset(&abs_path);\n \t}\n-\tfree(seen);\n \n \tstrbuf_release(&abs_path);\n \tstring_list_clear(&del_list, 0);\n-- \n1.8.3.rc1.341.g24a8a0f\n"},{"id":"216795","messageId":"3de32e2d73ae6e88ae968814b45bb421281e371b.1368118918.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"cover.1368118918.git.worldhello.net@gmail.com","subject":"[PATCH v8 11/12] git-clean: add toggle flags interactive action","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-09T17:14:15Z","receivedAt":"2013-05-09T17:14:15Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add new action in the interactive mode, so that the user can change\ngit-clean flags, such as -x/-X/-d/-ff, and refresh the cleaning\ncandidates list.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n builtin/clean.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++--------\n 1 file changed, 101 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 232d48..b4472 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -879,6 +879,66 @@ static int rm_i_cmd(void)\n \treturn MENU_RETURN_NO_LOOP;\n }\n \n+static int toggle_flags_cmd(void)\n+{\n+\tstruct menu_opts menu_opts;\n+\tstruct menu_stuff menu_stuff;\n+\tstruct menu_item menus[] = {\n+\t\t{'d', \"(d) remove directories\",\n+\t\t\tclean_flags & CLEAN_OPTS_REMOVE_DIRECTORIES,\tNULL},\n+\t\t{'x', \"(x) show ignored\",\n+\t\t\tclean_flags & CLEAN_OPTS_SHOW_IGNORED,\t\tNULL},\n+\t\t{'X', \"(X) ignored only\",\n+\t\t\tclean_flags & CLEAN_OPTS_IGNORED_ONLY,\t\tNULL},\n+\t\t{'f', \"(ff) remove nested.git\",\n+\t\t\tclean_flags & CLEAN_OPTS_REMOVE_NESTED_GIT,\tNULL},\n+\t};\n+\tint new_flags = 0;\n+\tint *chosen;\n+\tint i;\n+\n+\tmenu_opts.header = NULL;\n+\tmenu_opts.prompt = \"Change flags\";\n+\tmenu_opts.flag = 0;\n+\n+\tmenu_stuff.type = MENU_STUFF_TYPE_MENU_ITEM;\n+\tmenu_stuff.stuff = menus;\n+\tmenu_stuff.nr = sizeof(menus) / sizeof(struct menu_item);\n+\n+\tchosen = list_and_choose(&menu_opts, &menu_stuff);\n+\n+\tfor (i = 0; chosen[i] != EOF; i++) {\n+\t\tswitch (chosen[i]) {\n+\t\tcase 0:\n+\t\t\tnew_flags |= CLEAN_OPTS_REMOVE_DIRECTORIES;\n+\t\t\tbreak;\n+\t\tcase 1:\n+\t\t\tnew_flags |= CLEAN_OPTS_SHOW_IGNORED;\n+\t\t\tbreak;\n+\t\tcase 2:\n+\t\t\tnew_flags |= CLEAN_OPTS_IGNORED_ONLY;\n+\t\t\tbreak;\n+\t\tcase 3:\n+\t\t\tnew_flags |= CLEAN_OPTS_REMOVE_NESTED_GIT;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (new_flags & CLEAN_OPTS_IGNORED_ONLY &&\n+\t    new_flags & CLEAN_OPTS_SHOW_IGNORED) {\n+\t\tclean_print_color(CLEAN_COLOR_ERROR);\n+\t\tprintf_ln(_(\"-x and -X cannot be used together\"));\n+\t\tclean_print_color(CLEAN_COLOR_RESET);\n+\t} else {\n+\t\tclean_flags = new_flags;\n+\t}\n+\n+\tfree(chosen);\n+\treturn 0;\n+}\n+\n static int quit_cmd(void)\n {\n \tstring_list_clear(&del_list, 0);\n@@ -894,6 +954,7 @@ static int help_cmd(void)\n \t\t    \"filter by pattern   - exclude items from deletion\\n\"\n \t\t    \"select by numbers   - select items to be deleted by numbers\\n\"\n \t\t    \"ask each            - confirm each deletion (like \\\"rm -i\\\")\\n\"\n+\t\t    \"toggle flags        - toggle git-clean flags and update the list\\n\"\n \t\t    \"quit                - stop cleaning\\n\"\n \t\t    \"help                - this screen\\n\"\n \t\t    \"?                   - help for prompt selection\"\n@@ -902,9 +963,14 @@ static int help_cmd(void)\n \treturn 0;\n }\n \n-static void interactive_main_loop(void)\n+static void interactive_main_loop(const char **pathspec,\n+\t\t\t\t  struct string_list exclude_list,\n+\t\t\t\t  const char *prefix)\n {\n-\twhile (del_list.nr) {\n+\tint cached_clean_flags = clean_flags;\n+\tchar flags_title[40];\n+\n+\tfor (;;) {\n \t\tstruct menu_opts menu_opts;\n \t\tstruct menu_stuff menu_stuff;\n \t\tstruct menu_item menus[] = {\n@@ -912,11 +978,24 @@ static void interactive_main_loop(void)\n \t\t\t{'f', \"filter by pattern\",\t0, filter_by_patterns_cmd},\n \t\t\t{'s', \"select by numbers\",\t0, select_by_numbers_cmd},\n \t\t\t{'a', \"ask each\",\t\t0, rm_i_cmd},\n+\t\t\t{'t', flags_title,\t\t0, toggle_flags_cmd},\n \t\t\t{'q', \"quit\",\t\t\t0, quit_cmd},\n \t\t\t{'h', \"help\",\t\t\t0, help_cmd},\n \t\t};\n \t\tint *chosen;\n \n+\t\tif (!clean_flags) {\n+\t\t\tstrncpy(flags_title, \"toggle flags: none\", sizeof(flags_title)/sizeof(char));\n+\t\t} else {\n+\t\t\tsnprintf(flags_title, sizeof(flags_title)/sizeof(char),\n+\t\t\t\t \"toggle flags: -%s%s%s%s\",\n+\t\t\t\t clean_flags & CLEAN_OPTS_REMOVE_DIRECTORIES ? \"d\" : \"\",\n+\t\t\t\t clean_flags & CLEAN_OPTS_SHOW_IGNORED ? \"x\" : \"\",\n+\t\t\t\t clean_flags & CLEAN_OPTS_IGNORED_ONLY ? \"X\" : \"\",\n+\t\t\t\t clean_flags & CLEAN_OPTS_REMOVE_NESTED_GIT ? \"ff\" : \"\"\n+\t\t\t\t);\n+\t\t}\n+\n \t\tmenu_opts.header = _(\"*** Commands ***\");\n \t\tmenu_opts.prompt = \"What now\";\n \t\tmenu_opts.flag = MENU_OPTS_SINGLETON;\n@@ -925,13 +1004,25 @@ static void interactive_main_loop(void)\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\tif (cached_clean_flags != clean_flags) {\n+\t\t\tscan_clean_candidates(pathspec, exclude_list, prefix);\n+\t\t\tcached_clean_flags = clean_flags;\n+\t\t}\n \n-\t\tpretty_print_dels();\n+\t\tif (del_list.nr) {\n+\t\t\tclean_print_color(CLEAN_COLOR_HEADER);\n+\t\t\tprintf_ln(Q_(\"Would remove the following item:\",\n+\t\t\t\t     \"Would remove the following items:\",\n+\t\t\t\t     del_list.nr));\n+\t\t\tclean_print_color(CLEAN_COLOR_RESET);\n+\n+\t\t\tpretty_print_dels();\n+\t\t} else {\n+\t\t\tclean_print_color(CLEAN_COLOR_HEADER);\n+\t\t\tprintf_ln(_(\"NOTE: no more files to clean; press \\\"t\\\" to toggle flags of git-clean.\"));\n+\t\t\tputchar('\\n');\n+\t\t\tclean_print_color(CLEAN_COLOR_RESET);\n+\t\t}\n \n \t\tchosen = list_and_choose(&menu_opts, &menu_stuff);\n \n@@ -941,12 +1032,6 @@ static void interactive_main_loop(void)\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@@ -1023,8 +1108,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \n \tscan_clean_candidates(pathspec, exclude_list, prefix);\n \n-\tif (interactive && !dry_run && del_list.nr > 0)\n-\t\tinteractive_main_loop();\n+\tif (interactive && !dry_run)\n+\t\tinteractive_main_loop(pathspec, exclude_list, prefix);\n \n \tfor_each_string_list_item(item, &del_list) {\n \t\tstruct stat st;\n-- \n1.8.3.rc1.341.g24a8a0f\n"},{"id":"216796","messageId":"29373d346dd31a2c73b582fb3291667df6105b9c.1368118918.git.worldhello.net@gmail.com","threadId":"33751","inReplyTo":"cover.1368118918.git.worldhello.net@gmail.com","subject":"[PATCH v8 12/12] git-clean: update document for interactive git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-09T17:14:16Z","receivedAt":"2013-05-09T17:14:16Z","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>\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/git-clean.txt | 71 +++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 69 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt\nindex 186e34..4512f7 100644\n--- a/Documentation/git-clean.txt\n+++ b/Documentation/git-clean.txt\n@@ -39,8 +39,8 @@ OPTIONS\n \n -i::\n --interactive::\n-\tShow what would be done and the user must confirm before actually\n-\tcleaning.\n+\tShow what would be done and clean files interactively. See\n+\t``Interactive mode'' for details.\n \n -n::\n --dry-run::\n@@ -69,6 +69,73 @@ 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: filter by pattern     3: select by numbers\n+      4. ask each      5. toggle flags: none    6. quit\n+      7: 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 7 subcommands.\n+\n+clean::\n+\n+   Start cleaning files and directories, and then quit.\n+\n+filter by pattern::\n+\n+   This shows the files and directories to be deleted and issues an\n+   \"Input ignore patterns>>\" prompt. You can input 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+select 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+ask each::\n+\n+  This will start to clean, and you must confirm one by one in order\n+  to delete items. Please note that this action is not as efficient\n+  as the above two actions.\n+\n+toggle flags::\n+\n+  This lets you change the flags for git-clean, such as -x/-X/-d/-ff,\n+  and refresh the cleaning candidates list automatically.\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.341.g24a8a0f\n"},{"id":"217083","messageId":"vpqvc6oi1f6.fsf@grenoble-inp.fr","threadId":"33751","inReplyTo":"ad26375dece854339d64dcb82c17f19f8edccf48.1368011946.git.worldhello.net@gmail.com","subject":"Re: [PATCH v7 01/10] Add support for -i/--interactive to git-clean","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2013-05-12T16:54:37Z","receivedAt":"2013-05-12T16:54:37Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\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\n\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\nThese two pieces of code are surprisingly similar ... Shouldn't they be\nfactored into a small helper function?\n\n> +\t\t/* Confirmation dialog */\n> +\t\tprintf(_(\"Remove ([y]es/[n]o/[e]dit) ? \"));\n\nTo be more consistent with \"git add -p\", this should use [] instead of\n(), and have no space before \"?\".\n\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\nThat makes it a 85 characters message, and we usually break lines before\n80. Adding \\n after \";\" (instead of a space) would be better IMO.\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"217085","messageId":"vpqli7ki0q4.fsf@grenoble-inp.fr","threadId":"33751","inReplyTo":"6a7931a0bb14f8c71479e16175812bedb6b826cb.1368011946.git.worldhello.net@gmail.com","subject":"Re: [PATCH v7 02/10] Show items of interactive git-clean in columns","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2013-05-12T17:09:39Z","receivedAt":"2013-05-12T17:09:39Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\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\n\n> +static void pretty_print_dels()\n\nAh, OK. That's the helper function I was expecting in 01/10. The patches\nwould be easier to review if the helper was introduced in 01/10 and\nthen modified here to add column display, but the result is OK.\n\n> +\t/*\n> +\t * always enable column display, we only consult column.*\n> +\t * about layout strategy and stuff\n> +\t */\n\nIsn't this conflicting with the documentation above?\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"217087","messageId":"vpqfvxsi0gw.fsf@grenoble-inp.fr","threadId":"33751","inReplyTo":"d7302adabb4269dd94698090fb20c739f723f00a.1368011946.git.worldhello.net@gmail.com","subject":"Re: [PATCH v7 03/10] Add colors to interactive git-clean","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2013-05-12T17:15:11Z","receivedAt":"2013-05-12T17:15:11Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\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\nThis should go to the documentation (a short summary is welcome in the\ncommit messages in addition, but users won't read this...)\n\n> +\tif (!prefixcmp(var, \"color.interactive.\")) {\n> +\t\tint slot = parse_clean_color_slot(var, 18);\n\nFor readability and maintainability: please use\nstrlen(\"color.interactive.\"), not 18.\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"217089","messageId":"vpq8v3khzty.fsf@grenoble-inp.fr","threadId":"33751","inReplyTo":"7vwqr9fo9b.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v7 00/10] interactive git clean","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2013-05-12T17:28:57Z","receivedAt":"2013-05-12T17:28:57Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Cleaning all unneeded files inside a single interactive session\n> should *never* be the goal---that will lead to an over-engineered\n> design (e.g. switching \"clean -x\" flags in the middle? why?).  I\n> think Jiang's latest series is already way over-engineered,\n\nI didn't read the end of the series in detail, but I tend to agree with\nyou. Maybe it's time to remember the YAGNI principle, and keep the\nnumber of options small at least for now (e.g. drop 11/12 git-clean: add\ntoggle flags interactive action). When users (including us) will have\nused \"git clean -i\" for a while, we'll have a better idea of what is\nreally needed.\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"217105","messageId":"CANYiYbFo+_048_1r1vmPzuRjibnrCuG19pCQg-UtXsBE2Cpyug@mail.gmail.com","threadId":"33751","inReplyTo":"vpq8v3khzty.fsf@grenoble-inp.fr","subject":"Re: [PATCH v7 00/10] interactive git clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-13T02:34:47Z","receivedAt":"2013-05-13T02:34:47Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"2013/5/13 Matthieu Moy <Matthieu.Moy@grenoble-inp.fr>:\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> Cleaning all unneeded files inside a single interactive session\n>> should *never* be the goal---that will lead to an over-engineered\n>> design (e.g. switching \"clean -x\" flags in the middle? why?).  I\n>> think Jiang's latest series is already way over-engineered,\n>\n> I didn't read the end of the series in detail, but I tend to agree with\n> you. Maybe it's time to remember the YAGNI principle, and keep the\n> number of options small at least for now (e.g. drop 11/12 git-clean: add\n> toggle flags interactive action). When users (including us) will have\n> used \"git clean -i\" for a while, we'll have a better idea of what is\n> really needed.\n\nMy initial intention for flags toggle is for git newbies, who are not clear\nabout how to use -x/-d/-X/-ff options. I feel it may have values for these\npeople. But since most people feel it may bring trouble, I can withdraw\npatch 9/12, 10/12, and 11/12.\n\nAnother improvement I want to hear your opinions. When the user execute\n\"git clean --interactive --dry-run\", the current implement is ignore the\n\"--interactive\" option, and do dry-run only. How about we combine them\ntogether? First show the user the interactive interface, and when user\nchoose to clean, do cleaning in dry-run mode.\n\n-- \nJiang Xin\nhttp://www.worldhello.net/\n"},{"id":"217107","messageId":"CANYiYbGqZ6FiHTZgejrA3a-njffQNJyaefFmCWfycuE6PGjwbA@mail.gmail.com","threadId":"33751","inReplyTo":"vpqfvxsi0gw.fsf@grenoble-inp.fr","subject":"Re: [PATCH v7 03/10] Add colors to interactive git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-13T02:47:41Z","receivedAt":"2013-05-13T02:47:41Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"2013/5/13 Matthieu Moy <Matthieu.Moy@grenoble-inp.fr>:\n> Jiang Xin <worldhello.net@gmail.com> writes:\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>\n> This should go to the documentation (a short summary is welcome in the\n> commit messages in addition, but users won't read this...)\n>\n>> +     if (!prefixcmp(var, \"color.interactive.\")) {\n>> +             int slot = parse_clean_color_slot(var, 18);\n>\n> For readability and maintainability: please use\n> strlen(\"color.interactive.\"), not 18.\n\nFeel like conventional:\n\n    git grep -C2 prefixcmp builtin/apply.c builtin/archive.c\nbuiltin/branch.c builtin/checkout.c\n\nBut maybe 18 characters are too long. ;-)\n\n\n-- \nJiang Xin\nhttp://www.worldhello.net/\n"},{"id":"217121","messageId":"7vhai7qwhi.fsf@alter.siamese.dyndns.org","threadId":"33751","inReplyTo":"CANYiYbGqZ6FiHTZgejrA3a-njffQNJyaefFmCWfycuE6PGjwbA@mail.gmail.com","subject":"Re: [PATCH v7 03/10] Add colors to interactive git-clean","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-05-13T05:28:41Z","receivedAt":"2013-05-13T05:28:41Z","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/13 Matthieu Moy <Matthieu.Moy@grenoble-inp.fr>:\n>> Jiang Xin <worldhello.net@gmail.com> writes:\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>>\n>> This should go to the documentation (a short summary is welcome in the\n>> commit messages in addition, but users won't read this...)\n>>\n>>> +     if (!prefixcmp(var, \"color.interactive.\")) {\n>>> +             int slot = parse_clean_color_slot(var, 18);\n>>\n>> For readability and maintainability: please use\n>> strlen(\"color.interactive.\"), not 18.\n>\n> Feel like conventional:\n>\n>     git grep -C2 prefixcmp builtin/apply.c builtin/archive.c\n> builtin/branch.c builtin/checkout.c\n>\n> But maybe 18 characters are too long. ;-)\n\nWhy does it even have to know where the prefix ends or how long the\nprefix is?\n\nDoesn't it suggest that perhaps the parse_clean_color_slot()'s\nexternal interface is misdesigned?  In the other examples you\nshowed, e.g.\n\n    builtin/apply.c:\tif (!prefixcmp(buffer, \"delta \")) {\n    builtin/apply.c-\t\tpatch_method = BINARY_DELTA_DEFLATED;\n    builtin/apply.c-\t\toriglen = strtoul(buffer + 6, NULL, 10);\n\nwe are calling external libraries that are designed to take a\npointer to a character, but the parse-clean-color-slot knows that it\nis fed the name of a configuration variable and it knows the shape\nof its input far better than a generic function like strtoul(), no?\n"},{"id":"217128","messageId":"vpqhai7fjxh.fsf@grenoble-inp.fr","threadId":"33751","inReplyTo":"CANYiYbFo+_048_1r1vmPzuRjibnrCuG19pCQg-UtXsBE2Cpyug@mail.gmail.com","subject":"Re: [PATCH v7 00/10] interactive git clean","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2013-05-13T06:55:22Z","receivedAt":"2013-05-13T06:55:22Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> My initial intention for flags toggle is for git newbies, who are not clear\n> about how to use -x/-d/-X/-ff options. I feel it may have values for these\n> people.\n\nIt may have value for some of them, but throwing too many options in a\nmenu is usually counter-productive for a newbie. I never heard anyone\nsay \"Git is super newbie-friendly because it has so many commands\", but\nquite often the opposite.\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"217129","messageId":"vpqbo8ffjuf.fsf@grenoble-inp.fr","threadId":"33751","inReplyTo":"CANYiYbGqZ6FiHTZgejrA3a-njffQNJyaefFmCWfycuE6PGjwbA@mail.gmail.com","subject":"Re: [PATCH v7 03/10] Add colors to interactive git-clean","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2013-05-13T06:57:12Z","receivedAt":"2013-05-13T06:57:12Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> 2013/5/13 Matthieu Moy <Matthieu.Moy@grenoble-inp.fr>:\n>> Jiang Xin <worldhello.net@gmail.com> writes:\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>>\n>> This should go to the documentation (a short summary is welcome in the\n>> commit messages in addition, but users won't read this...)\n>>\n>>> +     if (!prefixcmp(var, \"color.interactive.\")) {\n>>> +             int slot = parse_clean_color_slot(var, 18);\n>>\n>> For readability and maintainability: please use\n>> strlen(\"color.interactive.\"), not 18.\n>\n> Feel like conventional:\n>\n>     git grep -C2 prefixcmp builtin/apply.c builtin/archive.c\n> builtin/branch.c builtin/checkout.c\n\nI know there are already many instances of this in the code, but I don't\nthink it's a good idea to add even more.\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"217186","messageId":"CANYiYbGX1w5YxxTJg4J0JrN1+3Kn-OK99ma+Qbi=VcRYR2o2TA@mail.gmail.com","threadId":"33751","inReplyTo":"vpqvc6oi1f6.fsf@grenoble-inp.fr","subject":"Re: [PATCH v7 01/10] Add support for -i/--interactive to git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-13T14:46:00Z","receivedAt":"2013-05-13T14:46:00Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"2013/5/13 Matthieu Moy <Matthieu.Moy@grenoble-inp.fr>:\n>> +             /* Confirmation dialog */\n>> +             printf(_(\"Remove ([y]es/[n]o/[e]dit) ? \"));\n>\n> To be more consistent with \"git add -p\", this should use [] instead of\n> (), and have no space before \"?\".\n\nWill be replaced with:\n\n             printf(_(\"Remove [y/n]? \"));\n\n>\n>> +                     die(_(\"clean.requireForce defaults to true and neither -i, -n nor -f given; \"\n>>                                 \"refusing to clean\"));\n>\n> That makes it a 85 characters message, and we usually break lines before\n> 80. Adding \\n after \";\" (instead of a space) would be better IMO.\n>\n\nIf die with multiple lines, it looks ugly. E.g.\n\n    % git clean\n    fatal: clean.requireForce defaults to true and neither -i, -n nor -f given;\n    refusing to clean\n\nI think because of this, some die messages are longer than 80 characters.\nSuch as:\n\n    # builtin/apply.c:1496\n\n    die(Q_(\"git diff header lacks filename information when removing \"\n                \"%d leading pathname component (line %d)\",\n                \"git diff header lacks filename information when removing \"\n                \"%d leading pathname components (line %d)\",\n\n-- \nJiang Xin\nhttp://www.worldhello.net/\n"}]}