{"thread":{"id":"33862","subject":"[PATCH v12 03/15] quote.c: remove path_relative, use relative_path instead","startedAt":"2013-05-18T03:18:52Z","lastAt":"2013-05-30T06:56:36Z","messageCount":38,"participants":["Jiang Xin","Junio C Hamano","Michael Haggerty"],"isPatch":true,"patchVersion":12,"patchTotal":15},"messages":[{"id":"217781","messageId":"cover.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368782129.git.worldhello.net@gmail.com","subject":"[PATCH v12 00/15] Interactive git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:18:52Z","receivedAt":"2013-05-18T03:18:52Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"I feel change the order of patch 01/15 and patch 02/15 is better.\nI.E. Add test cases for relative_path first, then refactor\nrelative_path and fix the test cases.\n\nAlso fix wrong indent in t/7301.\n\nDifferences with gitster/jx/clean-interactive:\n\ndiff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt\nindex 75fb54..5bf76 100644\n--- a/Documentation/git-clean.txt\n+++ b/Documentation/git-clean.txt\n@@ -82,8 +82,8 @@ and type return, like this:\n \n ------------\n     *** Commands ***\n-\t1: clean                2: filter by pattern    3: select by numbers\n-\t4: ask each             5: quit                 6: help\n+        1: clean                2: filter by pattern    3: select by numbers\n+        4: ask each             5: quit                 6: help\n     What now> 1\n ------------\n \ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 73ba2..7d34b 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -64,7 +64,7 @@ struct menu_opts {\n \n struct menu_item {\n \tchar hotkey;\n-\tchar *title;\n+\tconst char *title;\n \tint selected;\n \tint (*fn)();\n };\n@@ -312,22 +312,26 @@ static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)\n {\n \tstruct string_list menu_list = STRING_LIST_INIT_DUP;\n \tstruct strbuf menu = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct menu_item *menu_item;\n+\tstruct string_list_item *string_list_item;\n \tint i;\n \n-\tif (stuff->type == MENU_STUFF_TYPE_MENU_ITEM) {\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+\tswitch (stuff->type) {\n+\tdefault:\n+\t\tdie(\"Bad type of menu_staff when print menu\");\n+\tcase MENU_STUFF_TYPE_MENU_ITEM:\n+\t\tmenu_item = (struct menu_item *)stuff->stuff;\n+\t\tfor (i = 0; i < stuff->nr; i++, menu_item++) {\n+\t\t\tconst char *p;\n \t\t\tint highlighted = 0;\n \n-\t\t\tp = item->title;\n+\t\t\tp = menu_item->title;\n \t\t\tif ((*chosen)[i] < 0)\n-\t\t\t\t(*chosen)[i] = item->selected ? 1 : 0;\n+\t\t\t\t(*chosen)[i] = menu_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\tif (!highlighted && *p == menu_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@@ -339,27 +343,25 @@ static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)\n \t\t\tstring_list_append(&menu_list, menu.buf);\n \t\t\tstrbuf_reset(&menu);\n \t\t}\n-\n-\t} else if (stuff->type == MENU_STUFF_TYPE_STRING_LIST) {\n-\t\tstruct string_list_item *item;\n-\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tbreak;\n+\tcase MENU_STUFF_TYPE_STRING_LIST:\n \t\ti = 0;\n-\n-\t\tfor_each_string_list_item(item, (struct string_list *)stuff->stuff) {\n+\t\tfor_each_string_list_item(string_list_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\",\n-\t\t\t\t    (*chosen)[i] ? \"*\" : \" \", i + 1, item->string);\n+\t\t\t\t    (*chosen)[i] ? \"*\" : \" \", i+1, string_list_item->string);\n \t\t\tstring_list_append(&menu_list, menu.buf);\n \t\t\tstrbuf_reset(&menu);\n \t\t\ti++;\n \t\t}\n-\t\tstrbuf_release(&buf);\n+\t\tbreak;\n \t}\n \n \tpretty_print_menus(&menu_list);\n \n \tstrbuf_release(&menu);\n+\tstrbuf_release(&buf);\n \tstring_list_clear(&menu_list, 0);\n }\n \n@@ -390,6 +392,8 @@ static int parse_choice(struct menu_stuff *menu_stuff,\n \t\t\tint **chosen)\n {\n \tstruct strbuf **choice_list, **ptr;\n+\tstruct menu_item *menu_item;\n+\tstruct string_list_item *string_list_item;\n \tint nr = 0;\n \tint i;\n \n@@ -454,30 +458,31 @@ static int parse_choice(struct menu_stuff *menu_stuff,\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\tswitch (menu_stuff->type) {\n+\t\t\tdefault:\n+\t\t\t\tdie(\"Bad type of menu_stuff when parse choice\");\n+\t\t\tcase MENU_STUFF_TYPE_MENU_ITEM:\n+\t\t\t\tmenu_item = (struct menu_item *)menu_stuff->stuff;\n+\t\t\t\tfor (i = 0; i < menu_stuff->nr; i++, menu_item++) {\n \t\t\t\t\tif (((*ptr)->len == 1 &&\n-\t\t\t\t\t     *(*ptr)->buf == item->hotkey) ||\n-\t\t\t\t\t    !strcasecmp((*ptr)->buf, item->title)) {\n+\t\t\t\t\t     *(*ptr)->buf == menu_item->hotkey) ||\n+\t\t\t\t\t    !strcasecmp((*ptr)->buf, menu_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((*ptr)->buf, item->string)) {\n+\t\t\t\tbreak;\n+\t\t\tcase MENU_STUFF_TYPE_STRING_LIST:\n+\t\t\t\tstring_list_item = ((struct string_list *)menu_stuff->stuff)->items;\n+\t\t\t\tfor (i = 0; i < menu_stuff->nr; i++, string_list_item++) {\n+\t\t\t\t\tif (!strcasecmp((*ptr)->buf, string_list_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\tbreak;\n \t\t\t}\n \t\t}\n \ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex d3b33..6acff4 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -46,11 +46,11 @@ static const char *tag_modified = \"\";\n static const char *tag_skip_worktree = \"\";\n static const char *tag_resolve_undo = \"\";\n \n-static void write_name(const char* name)\n+static void write_name(const char *name)\n {\n \n \t/* turn off prefix, if run with \"--full-name\" */\n-\twrite_name_quoted_relative(name, prefix_len > 0 ? prefix : NULL,\n+\twrite_name_quoted_relative(name, prefix_len ? prefix : NULL,\n \t\t\t\t   stdout, line_terminator);\n }\n \ndiff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh\nindex 09a42..dfe47 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -12,6 +12,11 @@ norm_path() {\n \t\"test \\\"\\$(test-path-utils normalize_path_copy '$1')\\\" = '$2'\"\n }\n \n+relative_path() {\n+\ttest_expect_success $4 \"relative path: $1 $2 => $3\" \\\n+\t\"test \\\"\\$(test-path-utils relative_path '$1' '$2')\\\" = '$3'\"\n+}\n+\n # On Windows, we are using MSYS's bash, which mangles the paths.\n # Absolute paths are anchored at the MSYS installation directory,\n # which means that the path / accounts for this many characters:\n@@ -183,4 +188,22 @@ test_expect_success SYMLINKS 'real path works on symlinks' '\n \ttest \"$sym\" = \"$(test-path-utils real_path \"$dir2/syml\")\"\n '\n \n+relative_path /a/b/c/\t/a/b/\t\tc/\n+relative_path /a/b/c/\t/a/b\t\tc/\n+relative_path /a//b//c/\t//a/b//\t\tc/\n+relative_path /a/b\t/a/b\t\t./\n+relative_path /a/b/\t/a/b\t\t./\n+relative_path /a\t/a/b\t\t../\n+relative_path /\t\t/a/b/\t\t../../\n+relative_path /a/c\t/a/b/\t\t../c\n+relative_path /a/c\t/a/b\t\t../c\n+relative_path /a/b\t\"<empty>\"\t/a/b\n+relative_path /a/b \t\"<null>\"\t/a/b\n+relative_path \"<empty>\"\t/a/b\t\t./\n+relative_path \"<empty>\"\t\"<empty>\"\t./\n+relative_path \"<empty>\"\t\"<null>\"\t./\n+relative_path \"<null>\"\t\"<empty>\"\t./\n+relative_path \"<null>\"\t\"<null>\"\t./\n+relative_path \"<null>\"\t/a/b\t\t./\n+\n test_done\ndiff --git a/test-path-utils.c b/test-path-utils.c\nindex 0092cb..bc4ad 100644\n--- a/test-path-utils.c\n+++ b/test-path-utils.c\n@@ -1,6 +1,19 @@\n #include \"cache.h\"\n #include \"string-list.h\"\n \n+#define PARSE_ARGV_STRING(var, input) do { \\\n+\tif (!strcmp(input, \"<null>\")) { \\\n+\t\tvar = NULL; \\\n+\t} else if (!strcmp(input, \"<empty>\")) { \\\n+\t\tvar = \"\"; \\\n+\t} else if (*input == '<' || *input == '(') { \\\n+\t\tfprintf(stderr, \"Bad value: %s\\n\", input); \\\n+\t\treturn 1; \\\n+\t} else { \\\n+\t\tvar = input; \\\n+\t} \\\n+} while (0)\n+\n /*\n  * A \"string_list_each_func_t\" function that normalizes an entry from\n  * GIT_CEILING_DIRECTORIES.  If the path is unusable for some reason,\n@@ -103,6 +116,20 @@ int main(int argc, char **argv)\n \t\treturn 0;\n \t}\n \n+\tif (argc == 4 && !strcmp(argv[1], \"relative_path\")) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tconst char *abs, *base, *rel;\n+\t\tPARSE_ARGV_STRING(abs, argv[2]);\n+\t\tPARSE_ARGV_STRING(base, argv[3]);\n+\t\trel = relative_path(abs, base, &sb);\n+\t\tif (!rel)\n+\t\t\tputs(\"(null)\");\n+\t\telse\n+\t\t\tputs(strlen(rel) > 0 ? rel : \"(empty)\");\n+\t\tstrbuf_release(&sb);\n+\t\treturn 0;\n+\t}\n+\n \tfprintf(stderr, \"%s: unknown function name: %s\\n\", argv[0],\n \t\targv[1] ? argv[1] : \"(there was none)\");\n \treturn 1;\n\nJiang Xin (15):\n  test: add test cases for relative_path\n  path.c: refactor relative_path(), not only strip prefix\n  quote.c: remove path_relative, use relative_path instead\n  Refactor quote_path_relative, remove unused params\n  Refactor write_name_quoted_relative, remove unused params\n  git-clean: refactor git-clean into two phases\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: add documentation for interactive git-clean\n  test: add t7301 for git-clean--interactive\n\n Documentation/config.txt     |  21 +-\n Documentation/git-clean.txt  |  71 +++-\n builtin/clean.c              | 779 +++++++++++++++++++++++++++++++++++++++++--\n builtin/grep.c               |   5 +-\n builtin/ls-files.c           |  16 +-\n cache.h                      |   2 +-\n path.c                       | 112 +++++--\n quote.c                      |  65 +---\n quote.h                      |   7 +-\n setup.c                      |   5 +-\n t/t0060-path-utils.sh        |  23 ++\n t/t7301-clean-interactive.sh | 439 ++++++++++++++++++++++++\n test-path-utils.c            |  27 ++\n wt-status.c                  |  17 +-\n 14 files changed, 1438 insertions(+), 151 deletions(-)\n create mode 100755 t/t7301-clean-interactive.sh\n\n-- \n1.8.3.rc2.26.g7472058\n"},{"id":"217782","messageId":"ea0e3b1148ba1614916a8b1cca8d2d046acd047c.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"[PATCH v12 01/15] test: add test cases for relative_path","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:18:53Z","receivedAt":"2013-05-18T03:18:53Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add subcommand \"relative_path\" in test-path-utils, and add test cases\nin t0060.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n t/t0060-path-utils.sh | 26 ++++++++++++++++++++++++++\n test-path-utils.c     | 25 +++++++++++++++++++++++++\n 2 files changed, 51 insertions(+)\n\ndiff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh\nindex 09a42..2199b 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -12,6 +12,11 @@ norm_path() {\n \t\"test \\\"\\$(test-path-utils normalize_path_copy '$1')\\\" = '$2'\"\n }\n \n+relative_path() {\n+\ttest_expect_success $4 \"relative path: $1 $2 => $3\" \\\n+\t\"test \\\"\\$(test-path-utils relative_path '$1' '$2')\\\" = '$3'\"\n+}\n+\n # On Windows, we are using MSYS's bash, which mangles the paths.\n # Absolute paths are anchored at the MSYS installation directory,\n # which means that the path / accounts for this many characters:\n@@ -183,4 +188,25 @@ test_expect_success SYMLINKS 'real path works on symlinks' '\n \ttest \"$sym\" = \"$(test-path-utils real_path \"$dir2/syml\")\"\n '\n \n+relative_path /a/b/c/\t/a/b/\t\tc/\n+relative_path /a/b/c/\t/a/b\t\tc/\n+relative_path /a//b//c/\t//a/b//\t\tc/\n+relative_path /a/b\t/a/b\t\t.\n+relative_path /a/b/\t/a/b\t\t.\n+relative_path /a\t/a/b\t\t/a\n+relative_path /\t\t/a/b/\t\t/\n+relative_path /a/c\t/a/b/\t\t/a/c\n+relative_path /a/c\t/a/b\t\t/a/c\n+relative_path /a/b\t\"<empty>\"\t/a/b\n+relative_path /a/b \t\"<null>\"\t/a/b\n+relative_path \"<empty>\"\t/a/b\t\t\"(empty)\"\n+relative_path \"<empty>\"\t\"<empty>\"\t\"(empty)\"\n+relative_path \"<empty>\"\t\"<null>\"\t\"(empty)\"\n+relative_path \"<null>\"\t\"<empty>\"\t\"(null)\"\n+relative_path \"<null>\"\t\"<null>\"\t\"(null)\"\n+\n+test_expect_failure 'relative path: <null> /a/b => segfault' '\n+\ttest-path-utils relative_path \"<null>\" \"/a/b\"\n+'\n+\n test_done\ndiff --git a/test-path-utils.c b/test-path-utils.c\nindex 0092cb..18267 100644\n--- a/test-path-utils.c\n+++ b/test-path-utils.c\n@@ -1,6 +1,19 @@\n #include \"cache.h\"\n #include \"string-list.h\"\n \n+#define PARSE_ARGV_STRING(var, input) do { \\\n+\tif (!strcmp(input, \"<null>\")) { \\\n+\t\tvar = NULL; \\\n+\t} else if (!strcmp(input, \"<empty>\")) { \\\n+\t\tvar = \"\"; \\\n+\t} else if (*input == '<' || *input == '(') { \\\n+\t\tfprintf(stderr, \"Bad value: %s\\n\", input); \\\n+\t\treturn 1; \\\n+\t} else { \\\n+\t\tvar = input; \\\n+\t} \\\n+} while (0)\n+\n /*\n  * A \"string_list_each_func_t\" function that normalizes an entry from\n  * GIT_CEILING_DIRECTORIES.  If the path is unusable for some reason,\n@@ -103,6 +116,18 @@ int main(int argc, char **argv)\n \t\treturn 0;\n \t}\n \n+\tif (argc == 4 && !strcmp(argv[1], \"relative_path\")) {\n+\t\tconst char *abs, *base, *rel;\n+\t\tPARSE_ARGV_STRING(abs, argv[2]);\n+\t\tPARSE_ARGV_STRING(base, argv[3]);\n+\t\trel = relative_path(abs, base);\n+\t\tif (!rel)\n+\t\t\tputs(\"(null)\");\n+\t\telse\n+\t\t\tputs(strlen(rel) > 0 ? rel : \"(empty)\");\n+\t\treturn 0;\n+\t}\n+\n \tfprintf(stderr, \"%s: unknown function name: %s\\n\", argv[0],\n \t\targv[1] ? argv[1] : \"(there was none)\");\n \treturn 1;\n-- \n1.8.3.rc2.26.g7472058\n"},{"id":"217780","messageId":"1d8f513cd0d9e739bdd54e0c3f14637c7f0a05de.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"[PATCH v12 02/15] path.c: refactor relative_path(), not only strip prefix","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:18:54Z","receivedAt":"2013-05-18T03:18:54Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Original design of relative_path() is simple, just strip the prefix\n(*base) from the absolute path (*abs). In most cases, we need a real\nrelative path, such as: ../foo, ../../bar. That's why there is another\nreimplementation (path_relative()) in quote.c.\n\nRefactor relative_path() in path.c to return real relative path, so\nthat user can reuse this function without reimplement his/her own.\nI will use this method for interactive git-clean later. Some of the\nimplementations are borrowed from path_relative() in quote.c.\n\nDifferent results for relative_path() before and after this refactor:\n\n    abs path  base path  relative (original)  relative (refactor)\n    ========  =========  ===================  ===================\n    /a/b/c/   /a/b       c/                   c/\n    /a/b//c/  //a///b/   c/                   c/\n    /a/b      /a/b       .                    ./\n    /a/b/     /a/b       .                    ./\n    /a        /a/b/      /a                   ../\n    /         /a/b/      /                    ../../\n    /a/c      /a/b/      /a/c                 ../c\n    /a/b      (empty)    /a/b                 /a/b\n    /a/b      (null)     /a/b                 /a/b\n    (empty)   /a/b       (empty)              ./\n    (null)    (empty)    (null)               ./\n    (null)    /a/b       (segfault)           ./\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n cache.h               |   2 +-\n path.c                | 112 +++++++++++++++++++++++++++++++++++++++-----------\n setup.c               |   5 ++-\n t/t0060-path-utils.sh |  27 ++++++------\n test-path-utils.c     |   4 +-\n 5 files changed, 107 insertions(+), 43 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 94ca1..4016c 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -737,7 +737,7 @@ int is_directory(const char *);\n const char *real_path(const char *path);\n const char *real_path_if_valid(const char *path);\n const char *absolute_path(const char *path);\n-const char *relative_path(const char *abs, const char *base);\n+const char *relative_path(const char *abs, const char *base, struct strbuf *sb);\n int normalize_path_copy(char *dst, const char *src);\n int longest_ancestor_length(const char *path, struct string_list *prefixes);\n char *strip_path_suffix(const char *path, const char *suffix);\ndiff --git a/path.c b/path.c\nindex 04ff..e24ea 100644\n--- a/path.c\n+++ b/path.c\n@@ -441,42 +441,104 @@ int adjust_shared_perm(const char *path)\n \treturn 0;\n }\n \n-const char *relative_path(const char *abs, const char *base)\n+/*\n+ * Give path as relative to prefix.\n+ *\n+ * The strbuf may or may not be used, so do not assume it contains the\n+ * returned path.\n+ */\n+const char *relative_path(const char *abs, const char *base,\n+\t\t\t  struct strbuf *sb)\n {\n-\tstatic char buf[PATH_MAX + 1];\n-\tint i = 0, j = 0;\n-\n-\tif (!base || !base[0])\n+\tint abs_off, base_off, i, j;\n+\tint abs_len, base_len;\n+\n+\tabs_len = abs? strlen(abs): 0;\n+\tbase_len = base ? strlen(base) : 0;\n+\tabs_off = 0;\n+\tbase_off = 0;\n+\ti = 0;\n+\tj = 0;\n+\n+\tif (!abs_len)\n+\t\treturn \"./\";\n+\telse if (!base_len)\n \t\treturn abs;\n-\twhile (base[i]) {\n+\n+\twhile (i < base_len && j < abs_len && base[i] == abs[j]) {\n \t\tif (is_dir_sep(base[i])) {\n-\t\t\tif (!is_dir_sep(abs[j]))\n-\t\t\t\treturn abs;\n \t\t\twhile (is_dir_sep(base[i]))\n \t\t\t\ti++;\n \t\t\twhile (is_dir_sep(abs[j]))\n \t\t\t\tj++;\n-\t\t\tcontinue;\n-\t\t} else if (abs[j] != base[i]) {\n+\t\t\tbase_off = i;\n+\t\t\tabs_off = j;\n+\t\t} else {\n+\t\t\ti++;\n+\t\t\tj++;\n+\t\t}\n+\t}\n+\n+\tif (\n+\t    /* base seems like prefix of abs */\n+\t    i >= base_len &&\n+\t    /*\n+\t     * but \"/foo\" is not a prefix of \"/foobar\"\n+\t     * (i.e. base not end with '/')\n+\t     */\n+\t    base_off < base_len) {\n+\t\tif (j >= abs_len) {\n+\t\t\t/* abs=\"/a/b\", base=\"/a/b\" */\n+\t\t\tabs_off = abs_len;\n+\t\t} else if (is_dir_sep(abs[j])) {\n+\t\t\t/* abs=\"/a/b/c\", base=\"/a/b\" */\n+\t\t\twhile (is_dir_sep(abs[j]))\n+\t\t\t\tj++;\n+\t\t\tabs_off = j;\n+\t\t} else {\n+\t\t\t/* abs=\"/a/bbb/c\", base=\"/a/b\" */\n+\t\t\ti = base_off;\n+\t\t}\n+\t} else if (\n+\t\t   /* abs is short than base (prefix of base) */\n+\t\t   j >= abs_len &&\n+\t\t   /* abs not end with '/' */\n+\t\t   abs_off < abs_len) {\n+\t\tif (is_dir_sep(base[i])) {\n+\t\t\t/* abs=\"/a/b\", base=\"/a/b/c/\" */\n+\t\t\twhile (is_dir_sep(base[i]))\n+\t\t\t\ti++;\n+\t\t\tabs_off = abs_len;\n+\t\t}\n+\t}\n+\tabs += abs_off;\n+\tabs_len -= abs_off;\n+\n+\tif (i >= base_len) {\n+\t\tif (!abs_len)\n+\t\t\treturn \"./\";\n+\t\telse\n \t\t\treturn abs;\n+\t}\n+\n+\tstrbuf_reset(sb);\n+\tstrbuf_grow(sb, abs_len);\n+\n+\twhile (i < base_len) {\n+\t\tif (is_dir_sep(base[i])) {\n+\t\t\tstrbuf_addstr(sb, \"../\");\n+\t\t\twhile (is_dir_sep(base[i]))\n+\t\t\t\ti++;\n+\t\t\tcontinue;\n \t\t}\n \t\ti++;\n-\t\tj++;\n \t}\n-\tif (\n-\t    /* \"/foo\" is a prefix of \"/foo\" */\n-\t    abs[j] &&\n-\t    /* \"/foo\" is not a prefix of \"/foobar\" */\n-\t    !is_dir_sep(base[i-1]) && !is_dir_sep(abs[j])\n-\t   )\n-\t\treturn abs;\n-\twhile (is_dir_sep(abs[j]))\n-\t\tj++;\n-\tif (!abs[j])\n-\t\tstrcpy(buf, \".\");\n-\telse\n-\t\tstrcpy(buf, abs + j);\n-\treturn buf;\n+\tif (!is_dir_sep(base[base_len - 1]))\n+\t\tstrbuf_addstr(sb, \"../\");\n+\n+\tstrbuf_addstr(sb, abs);\n+\n+\treturn sb->buf;\n }\n \n /*\ndiff --git a/setup.c b/setup.c\nindex 94c1e..0d9ea 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -360,6 +360,7 @@ int is_inside_work_tree(void)\n \n void setup_work_tree(void)\n {\n+\tstruct strbuf sb = STRBUF_INIT;\n \tconst char *work_tree, *git_dir;\n \tstatic int initialized = 0;\n \n@@ -379,8 +380,10 @@ void setup_work_tree(void)\n \tif (getenv(GIT_WORK_TREE_ENVIRONMENT))\n \t\tsetenv(GIT_WORK_TREE_ENVIRONMENT, \".\", 1);\n \n-\tset_git_dir(relative_path(git_dir, work_tree));\n+\tset_git_dir(relative_path(git_dir, work_tree, &sb));\n \tinitialized = 1;\n+\n+\tstrbuf_release(&sb);\n }\n \n static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\ndiff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh\nindex 2199b..dfe47 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -191,22 +191,19 @@ test_expect_success SYMLINKS 'real path works on symlinks' '\n relative_path /a/b/c/\t/a/b/\t\tc/\n relative_path /a/b/c/\t/a/b\t\tc/\n relative_path /a//b//c/\t//a/b//\t\tc/\n-relative_path /a/b\t/a/b\t\t.\n-relative_path /a/b/\t/a/b\t\t.\n-relative_path /a\t/a/b\t\t/a\n-relative_path /\t\t/a/b/\t\t/\n-relative_path /a/c\t/a/b/\t\t/a/c\n-relative_path /a/c\t/a/b\t\t/a/c\n+relative_path /a/b\t/a/b\t\t./\n+relative_path /a/b/\t/a/b\t\t./\n+relative_path /a\t/a/b\t\t../\n+relative_path /\t\t/a/b/\t\t../../\n+relative_path /a/c\t/a/b/\t\t../c\n+relative_path /a/c\t/a/b\t\t../c\n relative_path /a/b\t\"<empty>\"\t/a/b\n relative_path /a/b \t\"<null>\"\t/a/b\n-relative_path \"<empty>\"\t/a/b\t\t\"(empty)\"\n-relative_path \"<empty>\"\t\"<empty>\"\t\"(empty)\"\n-relative_path \"<empty>\"\t\"<null>\"\t\"(empty)\"\n-relative_path \"<null>\"\t\"<empty>\"\t\"(null)\"\n-relative_path \"<null>\"\t\"<null>\"\t\"(null)\"\n-\n-test_expect_failure 'relative path: <null> /a/b => segfault' '\n-\ttest-path-utils relative_path \"<null>\" \"/a/b\"\n-'\n+relative_path \"<empty>\"\t/a/b\t\t./\n+relative_path \"<empty>\"\t\"<empty>\"\t./\n+relative_path \"<empty>\"\t\"<null>\"\t./\n+relative_path \"<null>\"\t\"<empty>\"\t./\n+relative_path \"<null>\"\t\"<null>\"\t./\n+relative_path \"<null>\"\t/a/b\t\t./\n \n test_done\ndiff --git a/test-path-utils.c b/test-path-utils.c\nindex 18267..bc4ad 100644\n--- a/test-path-utils.c\n+++ b/test-path-utils.c\n@@ -117,14 +117,16 @@ int main(int argc, char **argv)\n \t}\n \n \tif (argc == 4 && !strcmp(argv[1], \"relative_path\")) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n \t\tconst char *abs, *base, *rel;\n \t\tPARSE_ARGV_STRING(abs, argv[2]);\n \t\tPARSE_ARGV_STRING(base, argv[3]);\n-\t\trel = relative_path(abs, base);\n+\t\trel = relative_path(abs, base, &sb);\n \t\tif (!rel)\n \t\t\tputs(\"(null)\");\n \t\telse\n \t\t\tputs(strlen(rel) > 0 ? rel : \"(empty)\");\n+\t\tstrbuf_release(&sb);\n \t\treturn 0;\n \t}\n \n-- \n1.8.3.rc2.26.g7472058\n"},{"id":"217778","messageId":"2833373ff27b064b34d6a2bd827ceb05df272d62.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"[PATCH v12 03/15] quote.c: remove path_relative, use relative_path instead","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:18:55Z","receivedAt":"2013-05-18T03:18:55Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Since there is an enhanced version of relative_path() in path.c,\nremove duplicate counterpart path_relative() in quote.c.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n quote.c | 55 ++-----------------------------------------------------\n 1 file changed, 2 insertions(+), 53 deletions(-)\n\ndiff --git a/quote.c b/quote.c\nindex 91122..64ff3 100644\n--- a/quote.c\n+++ b/quote.c\n@@ -312,75 +312,24 @@ void write_name_quotedpfx(const char *pfx, size_t pfxlen,\n \tfputc(terminator, fp);\n }\n \n-static const char *path_relative(const char *in, int len,\n-\t\t\t\t struct strbuf *sb, const char *prefix,\n-\t\t\t\t int prefix_len);\n-\n void write_name_quoted_relative(const char *name, size_t len,\n \t\t\t\tconst char *prefix, size_t prefix_len,\n \t\t\t\tFILE *fp, int terminator)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n \n-\tname = path_relative(name, len, &sb, prefix, prefix_len);\n+\tname = relative_path(name, prefix, &sb);\n \twrite_name_quoted(name, fp, terminator);\n \n \tstrbuf_release(&sb);\n }\n \n-/*\n- * Give path as relative to prefix.\n- *\n- * The strbuf may or may not be used, so do not assume it contains the\n- * returned path.\n- */\n-static const char *path_relative(const char *in, int len,\n-\t\t\t\t struct strbuf *sb, const char *prefix,\n-\t\t\t\t int prefix_len)\n-{\n-\tint off, i;\n-\n-\tif (len < 0)\n-\t\tlen = strlen(in);\n-\tif (prefix_len < 0) {\n-\t\tif (prefix)\n-\t\t\tprefix_len = strlen(prefix);\n-\t\telse\n-\t\t\tprefix_len = 0;\n-\t}\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-\tstrbuf_reset(sb);\n-\tstrbuf_grow(sb, len);\n-\n-\twhile (i < prefix_len) {\n-\t\tif (prefix[i] == '/')\n-\t\t\tstrbuf_addstr(sb, \"../\");\n-\t\ti++;\n-\t}\n-\tstrbuf_add(sb, in, len);\n-\n-\treturn sb->buf;\n-}\n-\n /* quote path as relative to the given prefix */\n char *quote_path_relative(const char *in, int len,\n \t\t\t  struct strbuf *out, const char *prefix)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n-\tconst char *rel = path_relative(in, len, &sb, prefix, -1);\n+\tconst char *rel = relative_path(in, prefix, &sb);\n \tstrbuf_reset(out);\n \tquote_c_style_counted(rel, strlen(rel), out, NULL, 0);\n \tstrbuf_release(&sb);\n-- \n1.8.3.rc2.26.g7472058\n"},{"id":"217779","messageId":"fdbb3acad0dfa2dbf6b558918196567f836034ea.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"[PATCH v12 04/15] Refactor quote_path_relative, remove unused params","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:18:56Z","receivedAt":"2013-05-18T03:18:56Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"After substitute path_relative() in quote.c with relative_path() from\npath.c, parameters (such as len and prefix_len) are obsolete in function\nquote_path_relative(). Remove unused parameters and change the order of\nparameters for quote_path_relative() function.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n builtin/clean.c    | 18 +++++++++---------\n builtin/grep.c     |  5 ++---\n builtin/ls-files.c |  2 +-\n quote.c            |  7 ++-----\n quote.h            |  4 ++--\n wt-status.c        | 17 ++++++++---------\n 6 files changed, 24 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 04e39..f77f95 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -56,7 +56,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \tif ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&\n \t\t\t!resolve_gitlink_ref(path->buf, \"HEAD\", submodule_head)) {\n \t\tif (!quiet) {\n-\t\t\tquote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);\n+\t\t\tquote_path_relative(path->buf, prefix, &quoted);\n \t\t\tprintf(dry_run ?  _(msg_would_skip_git_dir) : _(msg_skip_git_dir),\n \t\t\t\t\tquoted.buf);\n \t\t}\n@@ -70,7 +70,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \t\t/* an empty dir could be removed even if it is unreadble */\n \t\tres = dry_run ? 0 : rmdir(path->buf);\n \t\tif (res) {\n-\t\t\tquote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);\n+\t\t\tquote_path_relative(path->buf, prefix, &quoted);\n \t\t\twarning(_(msg_warn_remove_failed), quoted.buf);\n \t\t\t*dir_gone = 0;\n \t\t}\n@@ -94,7 +94,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \t\t\tif (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))\n \t\t\t\tret = 1;\n \t\t\tif (gone) {\n-\t\t\t\tquote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);\n+\t\t\t\tquote_path_relative(path->buf, prefix, &quoted);\n \t\t\t\tstring_list_append(&dels, quoted.buf);\n \t\t\t} else\n \t\t\t\t*dir_gone = 0;\n@@ -102,10 +102,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \t\t} else {\n \t\t\tres = dry_run ? 0 : unlink(path->buf);\n \t\t\tif (!res) {\n-\t\t\t\tquote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);\n+\t\t\t\tquote_path_relative(path->buf, prefix, &quoted);\n \t\t\t\tstring_list_append(&dels, quoted.buf);\n \t\t\t} else {\n-\t\t\t\tquote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);\n+\t\t\t\tquote_path_relative(path->buf, prefix, &quoted);\n \t\t\t\twarning(_(msg_warn_remove_failed), quoted.buf);\n \t\t\t\t*dir_gone = 0;\n \t\t\t\tret = 1;\n@@ -127,7 +127,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \t\tif (!res)\n \t\t\t*dir_gone = 1;\n \t\telse {\n-\t\t\tquote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);\n+\t\t\tquote_path_relative(path->buf, prefix, &quoted);\n \t\t\twarning(_(msg_warn_remove_failed), quoted.buf);\n \t\t\t*dir_gone = 0;\n \t\t\tret = 1;\n@@ -262,7 +262,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\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\tqname = quote_path_relative(directory.buf, prefix, &buf);\n \t\t\t\t\tprintf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);\n \t\t\t\t}\n \t\t\t}\n@@ -272,11 +272,11 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \t\t\t\tcontinue;\n \t\t\tres = dry_run ? 0 : unlink(ent->name);\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(ent->name, prefix, &buf);\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(ent->name, prefix, &buf);\n \t\t\t\tprintf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);\n \t\t\t}\n \t\t}\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 159e65..a419c 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -286,8 +286,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,\n \tstruct strbuf pathbuf = STRBUF_INIT;\n \n \tif (opt->relative && opt->prefix_length) {\n-\t\tquote_path_relative(filename + tree_name_len, -1, &pathbuf,\n-\t\t\t\t    opt->prefix);\n+\t\tquote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf);\n \t\tstrbuf_insert(&pathbuf, 0, filename, tree_name_len);\n \t} else {\n \t\tstrbuf_addstr(&pathbuf, filename);\n@@ -318,7 +317,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)\n \tstruct strbuf buf = STRBUF_INIT;\n \n \tif (opt->relative && opt->prefix_length)\n-\t\tquote_path_relative(filename, -1, &buf, opt->prefix);\n+\t\tquote_path_relative(filename, opt->prefix, &buf);\n \telse\n \t\tstrbuf_addstr(&buf, filename);\n \ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 220207..00f07 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -389,7 +389,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, const char\n \t\tif (found_dup)\n \t\t\tcontinue;\n \n-\t\tname = quote_path_relative(pathspec[num], -1, &sb, prefix);\n+\t\tname = quote_path_relative(pathspec[num], prefix, &sb);\n \t\terror(\"pathspec '%s' did not match any file(s) known to git.\",\n \t\t      name);\n \t\terrors++;\ndiff --git a/quote.c b/quote.c\nindex 64ff3..ebb8 100644\n--- a/quote.c\n+++ b/quote.c\n@@ -325,8 +325,8 @@ void write_name_quoted_relative(const char *name, size_t len,\n }\n \n /* quote path as relative to the given prefix */\n-char *quote_path_relative(const char *in, int len,\n-\t\t\t  struct strbuf *out, const char *prefix)\n+char *quote_path_relative(const char *in, const char *prefix,\n+\t\t\t  struct strbuf *out)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n \tconst char *rel = relative_path(in, prefix, &sb);\n@@ -334,9 +334,6 @@ char *quote_path_relative(const char *in, int len,\n \tquote_c_style_counted(rel, strlen(rel), out, NULL, 0);\n \tstrbuf_release(&sb);\n \n-\tif (!out->len)\n-\t\tstrbuf_addstr(out, \"./\");\n-\n \treturn out->buf;\n }\n \ndiff --git a/quote.h b/quote.h\nindex 13315..5610159 100644\n--- a/quote.h\n+++ b/quote.h\n@@ -65,8 +65,8 @@ extern void write_name_quoted_relative(const char *name, size_t len,\n \t\tFILE *fp, int terminator);\n \n /* quote path as relative to the given prefix */\n-extern char *quote_path_relative(const char *in, int len,\n-\t\t\t  struct strbuf *out, const char *prefix);\n+extern char *quote_path_relative(const char *in, const char *prefix,\n+\t\t\t  struct strbuf *out);\n \n /* quoting as a string literal for other languages */\n extern void perl_quote_print(FILE *stream, const char *src);\ndiff --git a/wt-status.c b/wt-status.c\nindex bf84a..ef0fc 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -243,7 +243,7 @@ static void wt_status_print_unmerged_data(struct wt_status *s,\n \tstruct strbuf onebuf = STRBUF_INIT;\n \tconst char *one, *how = _(\"bug\");\n \n-\tone = quote_path(it->string, -1, &onebuf, s->prefix);\n+\tone = quote_path(it->string, s->prefix, &onebuf);\n \tstatus_printf(s, color(WT_STATUS_HEADER, s), \"\\t\");\n \tswitch (d->stagemask) {\n \tcase 1: how = _(\"both deleted:\"); break;\n@@ -297,8 +297,8 @@ static void wt_status_print_change_data(struct wt_status *s,\n \t\t    change_type);\n \t}\n \n-\tone = quote_path(one_name, -1, &onebuf, s->prefix);\n-\ttwo = quote_path(two_name, -1, &twobuf, s->prefix);\n+\tone = quote_path(one_name, s->prefix, &onebuf);\n+\ttwo = quote_path(two_name, s->prefix, &twobuf);\n \n \tstatus_printf(s, color(WT_STATUS_HEADER, s), \"\\t\");\n \tswitch (status) {\n@@ -706,8 +706,7 @@ static void wt_status_print_other(struct wt_status *s,\n \t\tstruct string_list_item *it;\n \t\tconst char *path;\n \t\tit = &(l->items[i]);\n-\t\tpath = quote_path(it->string, strlen(it->string),\n-\t\t\t\t  &buf, s->prefix);\n+\t\tpath = quote_path(it->string, s->prefix, &buf);\n \t\tif (column_active(s->colopts)) {\n \t\t\tstring_list_append(&output, path);\n \t\t\tcontinue;\n@@ -1289,7 +1288,7 @@ static void wt_shortstatus_unmerged(struct string_list_item *it,\n \t} else {\n \t\tstruct strbuf onebuf = STRBUF_INIT;\n \t\tconst char *one;\n-\t\tone = quote_path(it->string, -1, &onebuf, s->prefix);\n+\t\tone = quote_path(it->string, s->prefix, &onebuf);\n \t\tprintf(\" %s\\n\", one);\n \t\tstrbuf_release(&onebuf);\n \t}\n@@ -1317,7 +1316,7 @@ static void wt_shortstatus_status(struct string_list_item *it,\n \t\tstruct strbuf onebuf = STRBUF_INIT;\n \t\tconst char *one;\n \t\tif (d->head_path) {\n-\t\t\tone = quote_path(d->head_path, -1, &onebuf, s->prefix);\n+\t\t\tone = quote_path(d->head_path, s->prefix, &onebuf);\n \t\t\tif (*one != '\"' && strchr(one, ' ') != NULL) {\n \t\t\t\tputchar('\"');\n \t\t\t\tstrbuf_addch(&onebuf, '\"');\n@@ -1326,7 +1325,7 @@ static void wt_shortstatus_status(struct string_list_item *it,\n \t\t\tprintf(\"%s -> \", one);\n \t\t\tstrbuf_release(&onebuf);\n \t\t}\n-\t\tone = quote_path(it->string, -1, &onebuf, s->prefix);\n+\t\tone = quote_path(it->string, s->prefix, &onebuf);\n \t\tif (*one != '\"' && strchr(one, ' ') != NULL) {\n \t\t\tputchar('\"');\n \t\t\tstrbuf_addch(&onebuf, '\"');\n@@ -1345,7 +1344,7 @@ static void wt_shortstatus_other(struct string_list_item *it,\n \t} else {\n \t\tstruct strbuf onebuf = STRBUF_INIT;\n \t\tconst char *one;\n-\t\tone = quote_path(it->string, -1, &onebuf, s->prefix);\n+\t\tone = quote_path(it->string, s->prefix, &onebuf);\n \t\tcolor_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), \"%s\", sign);\n \t\tprintf(\" %s\\n\", one);\n \t\tstrbuf_release(&onebuf);\n-- \n1.8.3.rc2.26.g7472058\n"},{"id":"217783","messageId":"48e2072058302b355e0ec138727d1b481ac131c4.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"[PATCH v12 05/15] Refactor write_name_quoted_relative, remove unused params","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:18:57Z","receivedAt":"2013-05-18T03:18:57Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"After substitute path_relative() in quote.c with relative_path() from\npath.c, parameters (such as len and prefix_len) are obsolete in function\nwrite_name_quoted_relative(). Remove unused parameters from\nwrite_name_quoted_relative() and related functions.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n builtin/ls-files.c | 14 ++++++++------\n quote.c            |  3 +--\n quote.h            |  3 +--\n 3 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 00f07..6acff4 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -46,10 +46,12 @@ static const char *tag_modified = \"\";\n static const char *tag_skip_worktree = \"\";\n static const char *tag_resolve_undo = \"\";\n \n-static void write_name(const char* name, size_t len)\n+static void write_name(const char *name)\n {\n-\twrite_name_quoted_relative(name, len, prefix, prefix_len, stdout,\n-\t\t\tline_terminator);\n+\n+\t/* turn off prefix, if run with \"--full-name\" */\n+\twrite_name_quoted_relative(name, prefix_len ? prefix : NULL,\n+\t\t\t\t   stdout, line_terminator);\n }\n \n static void show_dir_entry(const char *tag, struct dir_entry *ent)\n@@ -63,7 +65,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)\n \t\treturn;\n \n \tfputs(tag, stdout);\n-\twrite_name(ent->name, ent->len);\n+\twrite_name(ent->name);\n }\n \n static void show_other_files(struct dir_struct *dir)\n@@ -163,7 +165,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)\n \t\t       find_unique_abbrev(ce->sha1,abbrev),\n \t\t       ce_stage(ce));\n \t}\n-\twrite_name(ce->name, ce_namelen(ce));\n+\twrite_name(ce->name);\n \tif (debug_mode) {\n \t\tprintf(\"  ctime: %d:%d\\n\", ce->ce_ctime.sec, ce->ce_ctime.nsec);\n \t\tprintf(\"  mtime: %d:%d\\n\", ce->ce_mtime.sec, ce->ce_mtime.nsec);\n@@ -196,7 +198,7 @@ static void show_ru_info(void)\n \t\t\tprintf(\"%s%06o %s %d\\t\", tag_resolve_undo, ui->mode[i],\n \t\t\t       find_unique_abbrev(ui->sha1[i], abbrev),\n \t\t\t       i + 1);\n-\t\t\twrite_name(path, len);\n+\t\t\twrite_name(path);\n \t\t}\n \t}\n }\ndiff --git a/quote.c b/quote.c\nindex ebb8..5c880 100644\n--- a/quote.c\n+++ b/quote.c\n@@ -312,8 +312,7 @@ void write_name_quotedpfx(const char *pfx, size_t pfxlen,\n \tfputc(terminator, fp);\n }\n \n-void write_name_quoted_relative(const char *name, size_t len,\n-\t\t\t\tconst char *prefix, size_t prefix_len,\n+void write_name_quoted_relative(const char *name, const char *prefix,\n \t\t\t\tFILE *fp, int terminator)\n {\n \tstruct strbuf sb = STRBUF_INIT;\ndiff --git a/quote.h b/quote.h\nindex 5610159..ed110 100644\n--- a/quote.h\n+++ b/quote.h\n@@ -60,8 +60,7 @@ extern void quote_two_c_style(struct strbuf *, const char *, const char *, int);\n extern void write_name_quoted(const char *name, FILE *, int terminator);\n extern void write_name_quotedpfx(const char *pfx, size_t pfxlen,\n                                  const char *name, FILE *, int terminator);\n-extern void write_name_quoted_relative(const char *name, size_t len,\n-\t\tconst char *prefix, size_t prefix_len,\n+extern void write_name_quoted_relative(const char *name, const char *prefix,\n \t\tFILE *fp, int terminator);\n \n /* quote path as relative to the given prefix */\n-- \n1.8.3.rc2.26.g7472058\n"},{"id":"217784","messageId":"271643036271eeaadb6b65c223905771be4bef71.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"[PATCH v12 06/15] git-clean: refactor git-clean into two phases","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:18:58Z","receivedAt":"2013-05-18T03:18:58Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Before introducing interactive git-clean, refactor git-clean operations\ninto two phases:\n\n * hold cleaning items in del_list,\n * and remove them in a separate loop at the end.\n\nWe will introduce interactive git-clean between the two phases. The\ninteractive git-clean will show what would be done and must confirm\nbefore do real cleaning.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n builtin/clean.c | 64 ++++++++++++++++++++++++++++++++++++++++-----------------\n 1 file changed, 45 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex f77f95..23e1f 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@@ -148,12 +149,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\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 +225,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,13 +245,8 @@ 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+\t\t\tdie_errno(\"Cannot lstat '%s'\", ent->name);\n \n \t\tif (pathspec) {\n \t\t\tmemset(seen, 0, argc > 0 ? argc : 1);\n@@ -257,33 +255,61 @@ 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, prefix, &buf);\n-\t\t\t\t\tprintf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);\n-\t\t\t\t}\n+\t\t\t\trel = relative_path(ent->name, prefix, &buf);\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 = relative_path(ent->name, prefix, &buf);\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, NULL, &buf);\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, prefix, &buf);\n+\t\t\t\tqname = quote_path_relative(item->string, NULL, &buf);\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, prefix, &buf);\n+\t\t\t\tqname = quote_path_relative(item->string, NULL, &buf);\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+\tstrbuf_release(&buf);\n+\tstring_list_clear(&del_list, 0);\n \tstring_list_clear(&exclude_list, 0);\n \treturn (errors != 0);\n }\n-- \n1.8.3.rc2.26.g7472058\n"},{"id":"217789","messageId":"29b78fa98bc895ab0e480ca2716cb5fdbf42e8c8.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"[PATCH v12 07/15] git-clean: add support for -i/--interactive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:18:59Z","receivedAt":"2013-05-18T03:18:59Z","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             | 57 +++++++++++++++++++++++++++++++++++++++++----\n 2 files changed, 60 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 23e1f..f28d6 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@@ -143,6 +144,50 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \treturn ret;\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, NULL, &buf);\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@@ -162,6 +207,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@@ -188,12 +234,12 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \tif (ignored && ignored_only)\n \t\tdie(_(\"-x and -X cannot be used together\"));\n \n-\tif (!dry_run && !force) {\n+\tif (!interactive && !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@@ -267,7 +313,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 && 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.rc2.26.g7472058\n"},{"id":"217785","messageId":"d93dbe148950472982968f171cc9ab228f36db46.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"[PATCH v12 08/15] git-clean: show items of del_list in columns","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:19:00Z","receivedAt":"2013-05-18T03:19:00Z","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 will solve this 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..e031b 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 the layout when list items in `git clean -i`, which always\n+\tshows files and directories in columns. See `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 f28d6..f25ba 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@@ -144,21 +151,46 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \treturn ret;\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, NULL, &buf);\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, NULL, &buf);\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@@ -184,7 +216,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.rc2.26.g7472058\n"},{"id":"217786","messageId":"43620e520f0c0ced0d2f1e08eddcacfc3fc0f24a.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"[PATCH v12 09/15] git-clean: add colors to interactive git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:19:01Z","receivedAt":"2013-05-18T03:19:01Z","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, such as \"color.interactive\" and\n\"color.interactive.<slot>\" for command `git-add--interactive`.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nComments-by: Matthieu Moy <Matthieu.Moy@imag.fr>\n---\n Documentation/config.txt | 17 +++++------\n builtin/clean.c          | 73 +++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 81 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex e031b..83613 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -876,16 +876,17 @@ The values of these variables may be specified as in color.branch.<slot>.\n \n color.interactive::\n \tWhen set to `always`, always use colors for interactive prompts\n-\tand displays (such as those used by \"git-add --interactive\").\n-\tWhen false (or `never`), never.  When set to `true` or `auto`, use\n-\tcolors only when the output is to the terminal. Defaults to false.\n+\tand displays (such as those used by \"git-add --interactive\" and\n+\t\"git-clean --interactive\"). When false (or `never`), never.\n+\tWhen set to `true` or `auto`, use colors only when the output is\n+\tto the terminal. Defaults to false.\n \n color.interactive.<slot>::\n-\tUse customized color for 'git add --interactive'\n-\toutput. `<slot>` may be `prompt`, `header`, `help` or `error`, for\n-\tfour distinct types of normal output from interactive\n-\tcommands.  The values of these variables may be specified as\n-\tin color.branch.<slot>.\n+\tUse customized color for 'git add --interactive' and 'git clean\n+\t--interactive' output. `<slot>` may be `prompt`, `header`, `help`\n+\tor `error`, for four distinct types of normal output from\n+\tinteractive commands.  The values of these variables may be\n+\tspecified as in color.branch.<slot>.\n \n color.pager::\n \tA boolean to enable/disable colored output when the pager is in\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex f25ba..0778a 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,82 @@ static const char *msg_skip_git_dir = N_(\"Skipping repository %s\\n\");\n static const char *msg_would_skip_git_dir = N_(\"Would skip repository %s\\n\");\n static const char *msg_warn_remove_failed = N_(\"failed to remove %s\");\n \n+static int clean_use_color = -1;\n+static char clean_colors[][COLOR_MAXLEN] = {\n+\tGIT_COLOR_RESET,\n+\tGIT_COLOR_NORMAL,\t/* PLAIN */\n+\tGIT_COLOR_BOLD_BLUE,\t/* PROMPT */\n+\tGIT_COLOR_BOLD,\t\t/* HEADER */\n+\tGIT_COLOR_BOLD_RED,\t/* HELP */\n+\tGIT_COLOR_BOLD_RED,\t/* ERROR */\n+};\n+enum color_clean {\n+\tCLEAN_COLOR_RESET = 0,\n+\tCLEAN_COLOR_PLAIN = 1,\n+\tCLEAN_COLOR_PROMPT = 2,\n+\tCLEAN_COLOR_HEADER = 3,\n+\tCLEAN_COLOR_HELP = 4,\n+\tCLEAN_COLOR_ERROR = 5,\n+};\n+\n+static int parse_clean_color_slot(const char *var)\n+{\n+\tif (!strcasecmp(var, \"reset\"))\n+\t\treturn CLEAN_COLOR_RESET;\n+\tif (!strcasecmp(var, \"plain\"))\n+\t\treturn CLEAN_COLOR_PLAIN;\n+\tif (!strcasecmp(var, \"prompt\"))\n+\t\treturn CLEAN_COLOR_PROMPT;\n+\tif (!strcasecmp(var, \"header\"))\n+\t\treturn CLEAN_COLOR_HEADER;\n+\tif (!strcasecmp(var, \"help\"))\n+\t\treturn CLEAN_COLOR_HELP;\n+\tif (!strcasecmp(var, \"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 +\n+\t\t\t\t\t\t  strlen(\"color.interactive.\"));\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@@ -184,14 +251,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.rc2.26.g7472058\n"},{"id":"217788","messageId":"294fa89cfe20b9e3be0eb0feb96736f596db91a8.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"[PATCH v12 10/15] git-clean: use a git-add-interactive compatible UI","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:19:02Z","receivedAt":"2013-05-18T03:19:02Z","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 | 457 ++++++++++++++++++++++++++++++++++++++++++++++++++++----\n 1 file changed, 428 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 0778a..19cc 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 flags;\n+};\n+\n+#define MENU_RETURN_NO_LOOP\t\t10\n+\n+struct menu_item {\n+\tchar hotkey;\n+\tconst char *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)\n {\n \tif (!strcasecmp(var, \"reset\"))\n@@ -240,54 +270,423 @@ 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+\tstruct string_list menu_list = STRING_LIST_INIT_DUP;\n+\tstruct strbuf menu = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct menu_item *menu_item;\n+\tstruct string_list_item *string_list_item;\n+\tint i;\n+\n+\tswitch (stuff->type) {\n+\tdefault:\n+\t\tdie(\"Bad type of menu_staff when print menu\");\n+\tcase MENU_STUFF_TYPE_MENU_ITEM:\n+\t\tmenu_item = (struct menu_item *)stuff->stuff;\n+\t\tfor (i = 0; i < stuff->nr; i++, menu_item++) {\n+\t\t\tconst char *p;\n+\t\t\tint highlighted = 0;\n+\n+\t\t\tp = menu_item->title;\n+\t\t\tif ((*chosen)[i] < 0)\n+\t\t\t\t(*chosen)[i] = menu_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 == menu_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\tbreak;\n+\tcase MENU_STUFF_TYPE_STRING_LIST:\n+\t\ti = 0;\n+\t\tfor_each_string_list_item(string_list_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\",\n+\t\t\t\t    (*chosen)[i] ? \"*\" : \" \", i+1, string_list_item->string);\n+\t\t\tstring_list_append(&menu_list, menu.buf);\n+\t\t\tstrbuf_reset(&menu);\n+\t\t\ti++;\n+\t\t}\n+\t\tbreak;\n+\t}\n+\n+\tpretty_print_menus(&menu_list);\n+\n+\tstrbuf_release(&menu);\n+\tstrbuf_release(&buf);\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, **ptr;\n+\tstruct menu_item *menu_item;\n+\tstruct string_list_item *string_list_item;\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 (ptr = choice_list; *ptr; ptr++) {\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(*ptr);\n+\t\tif (!(*ptr)->len)\n+\t\t\tcontinue;\n+\n+\t\t/* Input that begins with '-'; unchoose */\n+\t\tif (*(*ptr)->buf == '-') {\n+\t\t\tchoose = 0;\n+\t\t\tstrbuf_remove((*ptr), 0, 1);\n+\t\t}\n+\n+\t\tis_range = 0;\n+\t\tis_number = 1;\n+\t\tfor(p = (*ptr)->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((*ptr)->buf);\n+\t\t\ttop = bottom;\n+\t\t} else if (is_range) {\n+\t\t\tbottom = atoi((*ptr)->buf);\n+\t\t\t/* a range can be specified like 5-7 or 5- */\n+\t\t\tif (!*(strchr((*ptr)->buf, '-') + 1)) {\n+\t\t\t\ttop = menu_stuff->nr;\n+\t\t\t} else {\n+\t\t\t\ttop = atoi(strchr((*ptr)->buf, '-') + 1);\n+\t\t\t}\n+\t\t} else if (!strcmp((*ptr)->buf, \"*\")) {\n+\t\t\tbottom = 1;\n+\t\t\ttop = menu_stuff->nr;\n+\t\t} else {\n+\t\t\tswitch (menu_stuff->type) {\n+\t\t\tdefault:\n+\t\t\t\tdie(\"Bad type of menu_stuff when parse choice\");\n+\t\t\tcase MENU_STUFF_TYPE_MENU_ITEM:\n+\t\t\t\tmenu_item = (struct menu_item *)menu_stuff->stuff;\n+\t\t\t\tfor (i = 0; i < menu_stuff->nr; i++, menu_item++) {\n+\t\t\t\t\tif (((*ptr)->len == 1 &&\n+\t\t\t\t\t     *(*ptr)->buf == menu_item->hotkey) ||\n+\t\t\t\t\t    !strcasecmp((*ptr)->buf, menu_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\tbreak;\n+\t\t\tcase MENU_STUFF_TYPE_STRING_LIST:\n+\t\t\t\tstring_list_item = ((struct string_list *)menu_stuff->stuff)->items;\n+\t\t\t\tfor (i = 0; i < menu_stuff->nr; i++, string_list_item++) {\n+\t\t\t\t\tif (!strcasecmp((*ptr)->buf, string_list_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\tbreak;\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)?\"), (*ptr)->buf);\n+\t\t\tclean_print_color(CLEAN_COLOR_RESET);\n+\t\t\tcontinue;\n+\t\t}\n+\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/* chosen will be initialized by print_highlight_menu_stuff */\n+\t\tprint_highlight_menu_stuff(stuff, &chosen);\n+\n+\t\tif (opts->flags & 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->flags & 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->flags & 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->flags & MENU_OPTS_SINGLETON) && !choice.len)\n+\t\t\tbreak;\n+\n+\t\tnr = parse_choice(stuff,\n+\t\t\t\t  opts->flags & MENU_OPTS_SINGLETON,\n+\t\t\t\t  choice,\n+\t\t\t\t  &chosen);\n+\n+\t\tif (opts->flags & MENU_OPTS_SINGLETON) {\n+\t\t\tif (nr)\n+\t\t\t\tbreak;\n+\t\t} else if (opts->flags & 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/*\n+\t\t * recalculate nr, if return back from menu directly with\n+\t\t * default selections.\n+\t\t */\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 = N_(\"*** Commands ***\");\n+\t\tmenu_opts.prompt = N_(\"What now\");\n+\t\tmenu_opts.flags = 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.rc2.26.g7472058\n"},{"id":"217787","messageId":"b6fc7ce33f911a89b7dfe88ac190107db0488301.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"[PATCH v12 11/15] git-clean: add filter by pattern interactive action","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:19:03Z","receivedAt":"2013-05-18T03:19:03Z","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 the\nuser chooses this action, user can input space-separated patterns (the\nsame syntax as gitignore), and each clean candidate that matches with\none of the patterns will be excluded from cleaning. When the user feels\nit's OK, presses ENTER and backs to the confirmation dialog.\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 19cc..e5b1d 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -615,6 +615,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@@ -627,6 +693,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@@ -642,6 +709,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.rc2.26.g7472058\n"},{"id":"217792","messageId":"793f76704c0266135bb0bb8624222adf9c8f9059.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"[PATCH v12 12/15] git-clean: add select by numbers interactive action","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:19:04Z","receivedAt":"2013-05-18T03:19:04Z","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 e5b1d..fd6e6 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -681,6 +681,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 = N_(\"Select items to delete\");\n+\tmenu_opts.flags = 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 (chosen[j] == 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@@ -694,6 +731,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@@ -710,6 +748,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.rc2.26.g7472058\n"},{"id":"217791","messageId":"310f735ebd82252bdce5e5a5202790104ff611a7.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"[PATCH v12 13/15] git-clean: add ask each interactive action","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:19:05Z","receivedAt":"2013-05-18T03:19:05Z","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 fd6e6..7d34b 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -718,6 +718,40 @@ static int select_by_numbers_cmd(void)\n \treturn 0;\n }\n \n+static int ask_each_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, NULL, &buf);\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, \"yes\", 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@@ -732,6 +766,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@@ -749,6 +784,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, ask_each_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.rc2.26.g7472058\n"},{"id":"217790","messageId":"40c32dd368ffcaa4e5d10c85a04d464524fa6396.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"[PATCH v12 14/15] git-clean: add documentation for interactive git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:19:06Z","receivedAt":"2013-05-18T03:19:06Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add new section \"Interactive mode\" for documentation of interactive\ngit-clean.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/git-clean.txt | 65 +++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 63 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt\nindex 186e34..5bf76 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,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: filter by pattern    3: select by numbers\n+        4: ask each             5: quit                 6: help\n+    What now> 1\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+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+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.rc2.26.g7472058\n"},{"id":"217793","messageId":"99e259f34c9dba5ab8447a5b7c0992d37f01016a.1368846844.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"[PATCH v12 15/15] test: add t7301 for git-clean--interactive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-18T03:19:07Z","receivedAt":"2013-05-18T03:19:07Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add test cases for git-clean--interactive.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n t/t7301-clean-interactive.sh | 439 +++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 439 insertions(+)\n create mode 100755 t/t7301-clean-interactive.sh\n\ndiff --git a/t/t7301-clean-interactive.sh b/t/t7301-clean-interactive.sh\nnew file mode 100755\nindex 00000..4e605\n--- /dev/null\n+++ b/t/t7301-clean-interactive.sh\n@@ -0,0 +1,439 @@\n+#!/bin/sh\n+\n+test_description='git clean -i basic tests'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\n+\tmkdir -p src &&\n+\ttouch src/part1.c Makefile &&\n+\techo build >.gitignore &&\n+\techo \\*.o >>.gitignore &&\n+\tgit add . &&\n+\tgit commit -m setup &&\n+\ttouch src/part2.c README &&\n+\tgit add .\n+\n+'\n+\n+test_expect_success 'git clean -i (clean)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\techo c | git clean -i &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest ! -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -i (quit)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\techo q | git clean -i &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -i (Ctrl+D)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\techo \"\\04\" | git clean -i &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (filter all)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo f; echo \"*\"; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (filter patterns)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo f; echo \"part3.* *.out\"; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest ! -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest ! -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (filter patterns 2)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo f; echo \"* !*.out\"; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (select - all)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo \"*\"; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest ! -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest ! -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (select - none)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (select - number)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo 3; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (select - number 2)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo 2 3; echo 5; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest ! -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (select - number 3)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo 3,4 5; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (select - range)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo 1,3-4; echo 2; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest ! -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest ! -f docs/manual.txt &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (select - range 2)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo 4- 1; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest ! -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (inverse select)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo \"*\"; echo -5- 1 -2; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (ask)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest ! -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (ask - Ctrl+D)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo a; echo Y; echo no; echo yes; echo \"\\04\") | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id with prefix and path (filter)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(cd build/ && \\\n+\t (echo f; echo \"docs\"; echo \"*.h\"; echo ; echo c) | \\\n+\t git clean -id ..) &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id with prefix and path (select by name)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(cd build/ && \\\n+\t (echo s; echo \"../docs/\"; echo \"../src/part3.c\"; \\\n+\t  echo \"../src/part4.c\";  echo; echo c) | \\\n+\t git clean -id ..) &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest ! -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id with prefix and path (ask)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(cd build/ && \\\n+\t (echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \\\n+\t git clean -id ..) &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest ! -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_done\n-- \n1.8.3.rc2.26.g7472058\n"},{"id":"218016","messageId":"7vk3mts1wy.fsf@alter.siamese.dyndns.org","threadId":"33862","inReplyTo":"cover.1368846844.git.worldhello.net@gmail.com","subject":"Re: [PATCH v12 00/15] Interactive git-clean","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-05-20T22:48:45Z","receivedAt":"2013-05-20T22:48:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Will replace what has been queued on 'pu' with trivial style fixups\n(haven't had a chance to make time to read it through).  Thanks.\n"},{"id":"218092","messageId":"7vhahwm5n6.fsf@alter.siamese.dyndns.org","threadId":"33862","inReplyTo":"ea0e3b1148ba1614916a8b1cca8d2d046acd047c.1368846844.git.worldhello.net@gmail.com","subject":"Re: [PATCH v12 01/15] test: add test cases for relative_path","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-05-21T20:37:01Z","receivedAt":"2013-05-21T20:37:01Z","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> diff --git a/test-path-utils.c b/test-path-utils.c\n> index 0092cb..18267 100644\n> --- a/test-path-utils.c\n> +++ b/test-path-utils.c\n> @@ -1,6 +1,19 @@\n>  #include \"cache.h\"\n>  #include \"string-list.h\"\n>  \n> +#define PARSE_ARGV_STRING(var, input) do { \\\n> +\tif (!strcmp(input, \"<null>\")) { \\\n> +\t\tvar = NULL; \\\n> +\t} else if (!strcmp(input, \"<empty>\")) { \\\n> +\t\tvar = \"\"; \\\n> +\t} else if (*input == '<' || *input == '(') { \\\n> +\t\tfprintf(stderr, \"Bad value: %s\\n\", input); \\\n> +\t\treturn 1; \\\n> +\t} else { \\\n> +\t\tvar = input; \\\n> +\t} \\\n> +} while (0)\n\nYuck. Why does it have to be a macro like this?  I do not think\n\"because we may return\" justifies it.\n\n>  /*\n>   * A \"string_list_each_func_t\" function that normalizes an entry from\n>   * GIT_CEILING_DIRECTORIES.  If the path is unusable for some reason,\n> @@ -103,6 +116,18 @@ int main(int argc, char **argv)\n>  \t\treturn 0;\n>  \t}\n>  \n> +\tif (argc == 4 && !strcmp(argv[1], \"relative_path\")) {\n> +\t\tconst char *abs, *base, *rel;\n> +\t\tPARSE_ARGV_STRING(abs, argv[2]);\n> +\t\tPARSE_ARGV_STRING(base, argv[3]);\n> +\t\trel = relative_path(abs, base);\n> +\t\tif (!rel)\n> +\t\t\tputs(\"(null)\");\n> +\t\telse\n> +\t\t\tputs(strlen(rel) > 0 ? rel : \"(empty)\");\n> +\t\treturn 0;\n> +\t}\n> +\n>  \tfprintf(stderr, \"%s: unknown function name: %s\\n\", argv[0],\n>  \t\targv[1] ? argv[1] : \"(there was none)\");\n>  \treturn 1;\n"},{"id":"218121","messageId":"cover.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"7vhahwm5n6.fsf@alter.siamese.dyndns.org","subject":"[PATCH v13 00/15] interactive git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:21Z","receivedAt":"2013-05-22T01:40:21Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"2013/5/22 Junio C Hamano <gitster@pobox.com>:\n> Jiang Xin <worldhello.net@gmail.com> writes:\n>\n>> diff --git a/test-path-utils.c b/test-path-utils.c\n>> index 0092cb..18267 100644\n>> --- a/test-path-utils.c\n>> +++ b/test-path-utils.c\n>> @@ -1,6 +1,19 @@\n>>  #include \"cache.h\"\n>>  #include \"string-list.h\"\n>>\n>> +#define PARSE_ARGV_STRING(var, input) do { \\\n>> +     if (!strcmp(input, \"<null>\")) { \\\n>> +             var = NULL; \\\n>> +     } else if (!strcmp(input, \"<empty>\")) { \\\n>> +             var = \"\"; \\\n>> +     } else if (*input == '<' || *input == '(') { \\\n>> +             fprintf(stderr, \"Bad value: %s\\n\", input); \\\n>> +             return 1; \\\n>> +     } else { \\\n>> +             var = input; \\\n>> +     } \\\n>> +} while (0)\n>\n> Yuck. Why does it have to be a macro like this?  I do not think\n> \"because we may return\" justifies it.\n\nRewrite patch 01/15 for this, and only patch 01/15 and 02/15 need update.\nPlease review.\n\nJiang Xin (15):\n  test: add test cases for relative_path\n  path.c: refactor relative_path(), not only strip prefix\n  quote.c: remove path_relative, use relative_path instead\n  Refactor quote_path_relative, remove unused params\n  Refactor write_name_quoted_relative, remove unused params\n  git-clean: refactor git-clean into two phases\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: add documentation for interactive git-clean\n  test: add t7301 for git-clean--interactive\n\n Documentation/config.txt     |  21 +-\n Documentation/git-clean.txt  |  71 +++-\n builtin/clean.c              | 778 +++++++++++++++++++++++++++++++++++++++++--\n builtin/grep.c               |   5 +-\n builtin/ls-files.c           |  16 +-\n cache.h                      |   2 +-\n path.c                       | 112 +++++--\n quote.c                      |  65 +---\n quote.h                      |   7 +-\n setup.c                      |   5 +-\n t/t0060-path-utils.sh        |  23 ++\n t/t7301-clean-interactive.sh | 439 ++++++++++++++++++++++++\n test-path-utils.c            |  27 ++\n wt-status.c                  |  17 +-\n 14 files changed, 1437 insertions(+), 151 deletions(-)\n create mode 100755 t/t7301-clean-interactive.sh\n\n-- \n1.8.3.rc3.368.g7c798dd\n"},{"id":"218134","messageId":"12d11d43c239adde88b878558da52c37a3d77cfa.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1369186574.git.worldhello.net@gmail.com","subject":"[PATCH v13 01/15] test: add test cases for relative_path","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:22Z","receivedAt":"2013-05-22T01:40:22Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add subcommand \"relative_path\" in test-path-utils, and add test cases\nin t0060.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\n---\n t/t0060-path-utils.sh | 26 ++++++++++++++++++++++++++\n test-path-utils.c     | 25 +++++++++++++++++++++++++\n 2 files changed, 51 insertions(+)\n\ndiff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh\nindex 09a42..2199b 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -12,6 +12,11 @@ norm_path() {\n \t\"test \\\"\\$(test-path-utils normalize_path_copy '$1')\\\" = '$2'\"\n }\n \n+relative_path() {\n+\ttest_expect_success $4 \"relative path: $1 $2 => $3\" \\\n+\t\"test \\\"\\$(test-path-utils relative_path '$1' '$2')\\\" = '$3'\"\n+}\n+\n # On Windows, we are using MSYS's bash, which mangles the paths.\n # Absolute paths are anchored at the MSYS installation directory,\n # which means that the path / accounts for this many characters:\n@@ -183,4 +188,25 @@ test_expect_success SYMLINKS 'real path works on symlinks' '\n \ttest \"$sym\" = \"$(test-path-utils real_path \"$dir2/syml\")\"\n '\n \n+relative_path /a/b/c/\t/a/b/\t\tc/\n+relative_path /a/b/c/\t/a/b\t\tc/\n+relative_path /a//b//c/\t//a/b//\t\tc/\n+relative_path /a/b\t/a/b\t\t.\n+relative_path /a/b/\t/a/b\t\t.\n+relative_path /a\t/a/b\t\t/a\n+relative_path /\t\t/a/b/\t\t/\n+relative_path /a/c\t/a/b/\t\t/a/c\n+relative_path /a/c\t/a/b\t\t/a/c\n+relative_path /a/b\t\"<empty>\"\t/a/b\n+relative_path /a/b \t\"<null>\"\t/a/b\n+relative_path \"<empty>\"\t/a/b\t\t\"(empty)\"\n+relative_path \"<empty>\"\t\"<empty>\"\t\"(empty)\"\n+relative_path \"<empty>\"\t\"<null>\"\t\"(empty)\"\n+relative_path \"<null>\"\t\"<empty>\"\t\"(null)\"\n+relative_path \"<null>\"\t\"<null>\"\t\"(null)\"\n+\n+test_expect_failure 'relative path: <null> /a/b => segfault' '\n+\ttest-path-utils relative_path \"<null>\" \"/a/b\"\n+'\n+\n test_done\ndiff --git a/test-path-utils.c b/test-path-utils.c\nindex 0092cb..dcc530 100644\n--- a/test-path-utils.c\n+++ b/test-path-utils.c\n@@ -28,6 +28,19 @@ static int normalize_ceiling_entry(struct string_list_item *item, void *unused)\n \treturn 1;\n }\n \n+static void normalize_argv_string(const char **var, const char *input)\n+{\n+\tif (!strcmp(input, \"<null>\"))\n+\t\t*var = NULL;\n+\telse if (!strcmp(input, \"<empty>\"))\n+\t\t*var = \"\";\n+\telse\n+\t\t*var = input;\n+\n+\tif (*var && (**var == '<' || **var == '('))\n+\t\tdie(\"Bad value: %s\\n\", input);\n+}\n+\n int main(int argc, char **argv)\n {\n \tif (argc == 3 && !strcmp(argv[1], \"normalize_path_copy\")) {\n@@ -103,6 +116,18 @@ int main(int argc, char **argv)\n \t\treturn 0;\n \t}\n \n+\tif (argc == 4 && !strcmp(argv[1], \"relative_path\")) {\n+\t\tconst char *abs, *base, *rel;\n+\t\tnormalize_argv_string(&abs, argv[2]);\n+\t\tnormalize_argv_string(&base, argv[3]);\n+\t\trel = relative_path(abs, base);\n+\t\tif (!rel)\n+\t\t\tputs(\"(null)\");\n+\t\telse\n+\t\t\tputs(strlen(rel) > 0 ? rel : \"(empty)\");\n+\t\treturn 0;\n+\t}\n+\n \tfprintf(stderr, \"%s: unknown function name: %s\\n\", argv[0],\n \t\targv[1] ? argv[1] : \"(there was none)\");\n \treturn 1;\n-- \n1.8.3.rc3.368.g7c798dd\n"},{"id":"218122","messageId":"d730f00edb09449bf299be8d2083c895a1028c18.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1369186574.git.worldhello.net@gmail.com","subject":"[PATCH v13 02/15] path.c: refactor relative_path(), not only strip prefix","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:23Z","receivedAt":"2013-05-22T01:40:23Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Original design of relative_path() is simple, just strip the prefix\n(*base) from the absolute path (*abs). In most cases, we need a real\nrelative path, such as: ../foo, ../../bar. That's why there is another\nreimplementation (path_relative()) in quote.c.\n\nRefactor relative_path() in path.c to return real relative path, so\nthat user can reuse this function without reimplement his/her own.\nI will use this method for interactive git-clean later. Some of the\nimplementations are borrowed from path_relative() in quote.c.\n\nDifferent results for relative_path() before and after this refactor:\n\n    abs path  base path  relative (original)  relative (refactor)\n    ========  =========  ===================  ===================\n    /a/b/c/   /a/b       c/                   c/\n    /a/b//c/  //a///b/   c/                   c/\n    /a/b      /a/b       .                    ./\n    /a/b/     /a/b       .                    ./\n    /a        /a/b/      /a                   ../\n    /         /a/b/      /                    ../../\n    /a/c      /a/b/      /a/c                 ../c\n    /a/b      (empty)    /a/b                 /a/b\n    /a/b      (null)     /a/b                 /a/b\n    (empty)   /a/b       (empty)              ./\n    (null)    (empty)    (null)               ./\n    (null)    /a/b       (segfault)           ./\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n cache.h               |   2 +-\n path.c                | 112 +++++++++++++++++++++++++++++++++++++++-----------\n setup.c               |   5 ++-\n t/t0060-path-utils.sh |  27 ++++++------\n test-path-utils.c     |   4 +-\n 5 files changed, 107 insertions(+), 43 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 94ca1..4016c 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -737,7 +737,7 @@ int is_directory(const char *);\n const char *real_path(const char *path);\n const char *real_path_if_valid(const char *path);\n const char *absolute_path(const char *path);\n-const char *relative_path(const char *abs, const char *base);\n+const char *relative_path(const char *abs, const char *base, struct strbuf *sb);\n int normalize_path_copy(char *dst, const char *src);\n int longest_ancestor_length(const char *path, struct string_list *prefixes);\n char *strip_path_suffix(const char *path, const char *suffix);\ndiff --git a/path.c b/path.c\nindex 04ff..0174d 100644\n--- a/path.c\n+++ b/path.c\n@@ -441,42 +441,104 @@ int adjust_shared_perm(const char *path)\n \treturn 0;\n }\n \n-const char *relative_path(const char *abs, const char *base)\n+/*\n+ * Give path as relative to prefix.\n+ *\n+ * The strbuf may or may not be used, so do not assume it contains the\n+ * returned path.\n+ */\n+const char *relative_path(const char *abs, const char *base,\n+\t\t\t  struct strbuf *sb)\n {\n-\tstatic char buf[PATH_MAX + 1];\n-\tint i = 0, j = 0;\n-\n-\tif (!base || !base[0])\n+\tint abs_off, base_off, i, j;\n+\tint abs_len, base_len;\n+\n+\tabs_len = abs ? strlen(abs) : 0;\n+\tbase_len = base ? strlen(base) : 0;\n+\tabs_off = 0;\n+\tbase_off = 0;\n+\ti = 0;\n+\tj = 0;\n+\n+\tif (!abs_len)\n+\t\treturn \"./\";\n+\telse if (!base_len)\n \t\treturn abs;\n-\twhile (base[i]) {\n+\n+\twhile (i < base_len && j < abs_len && base[i] == abs[j]) {\n \t\tif (is_dir_sep(base[i])) {\n-\t\t\tif (!is_dir_sep(abs[j]))\n-\t\t\t\treturn abs;\n \t\t\twhile (is_dir_sep(base[i]))\n \t\t\t\ti++;\n \t\t\twhile (is_dir_sep(abs[j]))\n \t\t\t\tj++;\n-\t\t\tcontinue;\n-\t\t} else if (abs[j] != base[i]) {\n+\t\t\tbase_off = i;\n+\t\t\tabs_off = j;\n+\t\t} else {\n+\t\t\ti++;\n+\t\t\tj++;\n+\t\t}\n+\t}\n+\n+\tif (\n+\t    /* base seems like prefix of abs */\n+\t    i >= base_len &&\n+\t    /*\n+\t     * but \"/foo\" is not a prefix of \"/foobar\"\n+\t     * (i.e. base not end with '/')\n+\t     */\n+\t    base_off < base_len) {\n+\t\tif (j >= abs_len) {\n+\t\t\t/* abs=\"/a/b\", base=\"/a/b\" */\n+\t\t\tabs_off = abs_len;\n+\t\t} else if (is_dir_sep(abs[j])) {\n+\t\t\t/* abs=\"/a/b/c\", base=\"/a/b\" */\n+\t\t\twhile (is_dir_sep(abs[j]))\n+\t\t\t\tj++;\n+\t\t\tabs_off = j;\n+\t\t} else {\n+\t\t\t/* abs=\"/a/bbb/c\", base=\"/a/b\" */\n+\t\t\ti = base_off;\n+\t\t}\n+\t} else if (\n+\t\t   /* abs is short than base (prefix of base) */\n+\t\t   j >= abs_len &&\n+\t\t   /* abs not end with '/' */\n+\t\t   abs_off < abs_len) {\n+\t\tif (is_dir_sep(base[i])) {\n+\t\t\t/* abs=\"/a/b\", base=\"/a/b/c/\" */\n+\t\t\twhile (is_dir_sep(base[i]))\n+\t\t\t\ti++;\n+\t\t\tabs_off = abs_len;\n+\t\t}\n+\t}\n+\tabs += abs_off;\n+\tabs_len -= abs_off;\n+\n+\tif (i >= base_len) {\n+\t\tif (!abs_len)\n+\t\t\treturn \"./\";\n+\t\telse\n \t\t\treturn abs;\n+\t}\n+\n+\tstrbuf_reset(sb);\n+\tstrbuf_grow(sb, abs_len);\n+\n+\twhile (i < base_len) {\n+\t\tif (is_dir_sep(base[i])) {\n+\t\t\tstrbuf_addstr(sb, \"../\");\n+\t\t\twhile (is_dir_sep(base[i]))\n+\t\t\t\ti++;\n+\t\t\tcontinue;\n \t\t}\n \t\ti++;\n-\t\tj++;\n \t}\n-\tif (\n-\t    /* \"/foo\" is a prefix of \"/foo\" */\n-\t    abs[j] &&\n-\t    /* \"/foo\" is not a prefix of \"/foobar\" */\n-\t    !is_dir_sep(base[i-1]) && !is_dir_sep(abs[j])\n-\t   )\n-\t\treturn abs;\n-\twhile (is_dir_sep(abs[j]))\n-\t\tj++;\n-\tif (!abs[j])\n-\t\tstrcpy(buf, \".\");\n-\telse\n-\t\tstrcpy(buf, abs + j);\n-\treturn buf;\n+\tif (!is_dir_sep(base[base_len - 1]))\n+\t\tstrbuf_addstr(sb, \"../\");\n+\n+\tstrbuf_addstr(sb, abs);\n+\n+\treturn sb->buf;\n }\n \n /*\ndiff --git a/setup.c b/setup.c\nindex 94c1e..0d9ea 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -360,6 +360,7 @@ int is_inside_work_tree(void)\n \n void setup_work_tree(void)\n {\n+\tstruct strbuf sb = STRBUF_INIT;\n \tconst char *work_tree, *git_dir;\n \tstatic int initialized = 0;\n \n@@ -379,8 +380,10 @@ void setup_work_tree(void)\n \tif (getenv(GIT_WORK_TREE_ENVIRONMENT))\n \t\tsetenv(GIT_WORK_TREE_ENVIRONMENT, \".\", 1);\n \n-\tset_git_dir(relative_path(git_dir, work_tree));\n+\tset_git_dir(relative_path(git_dir, work_tree, &sb));\n \tinitialized = 1;\n+\n+\tstrbuf_release(&sb);\n }\n \n static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\ndiff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh\nindex 2199b..dfe47 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -191,22 +191,19 @@ test_expect_success SYMLINKS 'real path works on symlinks' '\n relative_path /a/b/c/\t/a/b/\t\tc/\n relative_path /a/b/c/\t/a/b\t\tc/\n relative_path /a//b//c/\t//a/b//\t\tc/\n-relative_path /a/b\t/a/b\t\t.\n-relative_path /a/b/\t/a/b\t\t.\n-relative_path /a\t/a/b\t\t/a\n-relative_path /\t\t/a/b/\t\t/\n-relative_path /a/c\t/a/b/\t\t/a/c\n-relative_path /a/c\t/a/b\t\t/a/c\n+relative_path /a/b\t/a/b\t\t./\n+relative_path /a/b/\t/a/b\t\t./\n+relative_path /a\t/a/b\t\t../\n+relative_path /\t\t/a/b/\t\t../../\n+relative_path /a/c\t/a/b/\t\t../c\n+relative_path /a/c\t/a/b\t\t../c\n relative_path /a/b\t\"<empty>\"\t/a/b\n relative_path /a/b \t\"<null>\"\t/a/b\n-relative_path \"<empty>\"\t/a/b\t\t\"(empty)\"\n-relative_path \"<empty>\"\t\"<empty>\"\t\"(empty)\"\n-relative_path \"<empty>\"\t\"<null>\"\t\"(empty)\"\n-relative_path \"<null>\"\t\"<empty>\"\t\"(null)\"\n-relative_path \"<null>\"\t\"<null>\"\t\"(null)\"\n-\n-test_expect_failure 'relative path: <null> /a/b => segfault' '\n-\ttest-path-utils relative_path \"<null>\" \"/a/b\"\n-'\n+relative_path \"<empty>\"\t/a/b\t\t./\n+relative_path \"<empty>\"\t\"<empty>\"\t./\n+relative_path \"<empty>\"\t\"<null>\"\t./\n+relative_path \"<null>\"\t\"<empty>\"\t./\n+relative_path \"<null>\"\t\"<null>\"\t./\n+relative_path \"<null>\"\t/a/b\t\t./\n \n test_done\ndiff --git a/test-path-utils.c b/test-path-utils.c\nindex dcc530..95ef4 100644\n--- a/test-path-utils.c\n+++ b/test-path-utils.c\n@@ -117,14 +117,16 @@ int main(int argc, char **argv)\n \t}\n \n \tif (argc == 4 && !strcmp(argv[1], \"relative_path\")) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n \t\tconst char *abs, *base, *rel;\n \t\tnormalize_argv_string(&abs, argv[2]);\n \t\tnormalize_argv_string(&base, argv[3]);\n-\t\trel = relative_path(abs, base);\n+\t\trel = relative_path(abs, base, &sb);\n \t\tif (!rel)\n \t\t\tputs(\"(null)\");\n \t\telse\n \t\t\tputs(strlen(rel) > 0 ? rel : \"(empty)\");\n+\t\tstrbuf_release(&sb);\n \t\treturn 0;\n \t}\n \n-- \n1.8.3.rc3.368.g7c798dd\n"},{"id":"218126","messageId":"ef1890e84a0ac4ccf5d4e37605e1fafde8561b45.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1369186574.git.worldhello.net@gmail.com","subject":"[PATCH v13 03/15] quote.c: remove path_relative, use relative_path instead","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:24Z","receivedAt":"2013-05-22T01:40:24Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Since there is an enhanced version of relative_path() in path.c,\nremove duplicate counterpart path_relative() in quote.c.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n quote.c | 55 ++-----------------------------------------------------\n 1 file changed, 2 insertions(+), 53 deletions(-)\n\ndiff --git a/quote.c b/quote.c\nindex 91122..64ff3 100644\n--- a/quote.c\n+++ b/quote.c\n@@ -312,75 +312,24 @@ void write_name_quotedpfx(const char *pfx, size_t pfxlen,\n \tfputc(terminator, fp);\n }\n \n-static const char *path_relative(const char *in, int len,\n-\t\t\t\t struct strbuf *sb, const char *prefix,\n-\t\t\t\t int prefix_len);\n-\n void write_name_quoted_relative(const char *name, size_t len,\n \t\t\t\tconst char *prefix, size_t prefix_len,\n \t\t\t\tFILE *fp, int terminator)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n \n-\tname = path_relative(name, len, &sb, prefix, prefix_len);\n+\tname = relative_path(name, prefix, &sb);\n \twrite_name_quoted(name, fp, terminator);\n \n \tstrbuf_release(&sb);\n }\n \n-/*\n- * Give path as relative to prefix.\n- *\n- * The strbuf may or may not be used, so do not assume it contains the\n- * returned path.\n- */\n-static const char *path_relative(const char *in, int len,\n-\t\t\t\t struct strbuf *sb, const char *prefix,\n-\t\t\t\t int prefix_len)\n-{\n-\tint off, i;\n-\n-\tif (len < 0)\n-\t\tlen = strlen(in);\n-\tif (prefix_len < 0) {\n-\t\tif (prefix)\n-\t\t\tprefix_len = strlen(prefix);\n-\t\telse\n-\t\t\tprefix_len = 0;\n-\t}\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-\tstrbuf_reset(sb);\n-\tstrbuf_grow(sb, len);\n-\n-\twhile (i < prefix_len) {\n-\t\tif (prefix[i] == '/')\n-\t\t\tstrbuf_addstr(sb, \"../\");\n-\t\ti++;\n-\t}\n-\tstrbuf_add(sb, in, len);\n-\n-\treturn sb->buf;\n-}\n-\n /* quote path as relative to the given prefix */\n char *quote_path_relative(const char *in, int len,\n \t\t\t  struct strbuf *out, const char *prefix)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n-\tconst char *rel = path_relative(in, len, &sb, prefix, -1);\n+\tconst char *rel = relative_path(in, prefix, &sb);\n \tstrbuf_reset(out);\n \tquote_c_style_counted(rel, strlen(rel), out, NULL, 0);\n \tstrbuf_release(&sb);\n-- \n1.8.3.rc3.368.g7c798dd\n"},{"id":"218123","messageId":"ff44eaf8c54f1a858abf68159674357faab2cf1d.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1369186574.git.worldhello.net@gmail.com","subject":"[PATCH v13 04/15] Refactor quote_path_relative, remove unused params","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:25Z","receivedAt":"2013-05-22T01:40:25Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"After substitute path_relative() in quote.c with relative_path() from\npath.c, parameters (such as len and prefix_len) are obsolete in function\nquote_path_relative(). Remove unused parameters and change the order of\nparameters for quote_path_relative() function.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/clean.c    | 18 +++++++++---------\n builtin/grep.c     |  5 ++---\n builtin/ls-files.c |  2 +-\n quote.c            |  7 ++-----\n quote.h            |  4 ++--\n wt-status.c        | 17 ++++++++---------\n 6 files changed, 24 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 04e39..f77f95 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -56,7 +56,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \tif ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&\n \t\t\t!resolve_gitlink_ref(path->buf, \"HEAD\", submodule_head)) {\n \t\tif (!quiet) {\n-\t\t\tquote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);\n+\t\t\tquote_path_relative(path->buf, prefix, &quoted);\n \t\t\tprintf(dry_run ?  _(msg_would_skip_git_dir) : _(msg_skip_git_dir),\n \t\t\t\t\tquoted.buf);\n \t\t}\n@@ -70,7 +70,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \t\t/* an empty dir could be removed even if it is unreadble */\n \t\tres = dry_run ? 0 : rmdir(path->buf);\n \t\tif (res) {\n-\t\t\tquote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);\n+\t\t\tquote_path_relative(path->buf, prefix, &quoted);\n \t\t\twarning(_(msg_warn_remove_failed), quoted.buf);\n \t\t\t*dir_gone = 0;\n \t\t}\n@@ -94,7 +94,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \t\t\tif (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))\n \t\t\t\tret = 1;\n \t\t\tif (gone) {\n-\t\t\t\tquote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);\n+\t\t\t\tquote_path_relative(path->buf, prefix, &quoted);\n \t\t\t\tstring_list_append(&dels, quoted.buf);\n \t\t\t} else\n \t\t\t\t*dir_gone = 0;\n@@ -102,10 +102,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \t\t} else {\n \t\t\tres = dry_run ? 0 : unlink(path->buf);\n \t\t\tif (!res) {\n-\t\t\t\tquote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);\n+\t\t\t\tquote_path_relative(path->buf, prefix, &quoted);\n \t\t\t\tstring_list_append(&dels, quoted.buf);\n \t\t\t} else {\n-\t\t\t\tquote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);\n+\t\t\t\tquote_path_relative(path->buf, prefix, &quoted);\n \t\t\t\twarning(_(msg_warn_remove_failed), quoted.buf);\n \t\t\t\t*dir_gone = 0;\n \t\t\t\tret = 1;\n@@ -127,7 +127,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \t\tif (!res)\n \t\t\t*dir_gone = 1;\n \t\telse {\n-\t\t\tquote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);\n+\t\t\tquote_path_relative(path->buf, prefix, &quoted);\n \t\t\twarning(_(msg_warn_remove_failed), quoted.buf);\n \t\t\t*dir_gone = 0;\n \t\t\tret = 1;\n@@ -262,7 +262,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\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\tqname = quote_path_relative(directory.buf, prefix, &buf);\n \t\t\t\t\tprintf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);\n \t\t\t\t}\n \t\t\t}\n@@ -272,11 +272,11 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \t\t\t\tcontinue;\n \t\t\tres = dry_run ? 0 : unlink(ent->name);\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(ent->name, prefix, &buf);\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(ent->name, prefix, &buf);\n \t\t\t\tprintf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);\n \t\t\t}\n \t\t}\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 159e65..a419c 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -286,8 +286,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,\n \tstruct strbuf pathbuf = STRBUF_INIT;\n \n \tif (opt->relative && opt->prefix_length) {\n-\t\tquote_path_relative(filename + tree_name_len, -1, &pathbuf,\n-\t\t\t\t    opt->prefix);\n+\t\tquote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf);\n \t\tstrbuf_insert(&pathbuf, 0, filename, tree_name_len);\n \t} else {\n \t\tstrbuf_addstr(&pathbuf, filename);\n@@ -318,7 +317,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)\n \tstruct strbuf buf = STRBUF_INIT;\n \n \tif (opt->relative && opt->prefix_length)\n-\t\tquote_path_relative(filename, -1, &buf, opt->prefix);\n+\t\tquote_path_relative(filename, opt->prefix, &buf);\n \telse\n \t\tstrbuf_addstr(&buf, filename);\n \ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 220207..00f07 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -389,7 +389,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, const char\n \t\tif (found_dup)\n \t\t\tcontinue;\n \n-\t\tname = quote_path_relative(pathspec[num], -1, &sb, prefix);\n+\t\tname = quote_path_relative(pathspec[num], prefix, &sb);\n \t\terror(\"pathspec '%s' did not match any file(s) known to git.\",\n \t\t      name);\n \t\terrors++;\ndiff --git a/quote.c b/quote.c\nindex 64ff3..ebb8 100644\n--- a/quote.c\n+++ b/quote.c\n@@ -325,8 +325,8 @@ void write_name_quoted_relative(const char *name, size_t len,\n }\n \n /* quote path as relative to the given prefix */\n-char *quote_path_relative(const char *in, int len,\n-\t\t\t  struct strbuf *out, const char *prefix)\n+char *quote_path_relative(const char *in, const char *prefix,\n+\t\t\t  struct strbuf *out)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n \tconst char *rel = relative_path(in, prefix, &sb);\n@@ -334,9 +334,6 @@ char *quote_path_relative(const char *in, int len,\n \tquote_c_style_counted(rel, strlen(rel), out, NULL, 0);\n \tstrbuf_release(&sb);\n \n-\tif (!out->len)\n-\t\tstrbuf_addstr(out, \"./\");\n-\n \treturn out->buf;\n }\n \ndiff --git a/quote.h b/quote.h\nindex 13315..5610159 100644\n--- a/quote.h\n+++ b/quote.h\n@@ -65,8 +65,8 @@ extern void write_name_quoted_relative(const char *name, size_t len,\n \t\tFILE *fp, int terminator);\n \n /* quote path as relative to the given prefix */\n-extern char *quote_path_relative(const char *in, int len,\n-\t\t\t  struct strbuf *out, const char *prefix);\n+extern char *quote_path_relative(const char *in, const char *prefix,\n+\t\t\t  struct strbuf *out);\n \n /* quoting as a string literal for other languages */\n extern void perl_quote_print(FILE *stream, const char *src);\ndiff --git a/wt-status.c b/wt-status.c\nindex bf84a..ef0fc 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -243,7 +243,7 @@ static void wt_status_print_unmerged_data(struct wt_status *s,\n \tstruct strbuf onebuf = STRBUF_INIT;\n \tconst char *one, *how = _(\"bug\");\n \n-\tone = quote_path(it->string, -1, &onebuf, s->prefix);\n+\tone = quote_path(it->string, s->prefix, &onebuf);\n \tstatus_printf(s, color(WT_STATUS_HEADER, s), \"\\t\");\n \tswitch (d->stagemask) {\n \tcase 1: how = _(\"both deleted:\"); break;\n@@ -297,8 +297,8 @@ static void wt_status_print_change_data(struct wt_status *s,\n \t\t    change_type);\n \t}\n \n-\tone = quote_path(one_name, -1, &onebuf, s->prefix);\n-\ttwo = quote_path(two_name, -1, &twobuf, s->prefix);\n+\tone = quote_path(one_name, s->prefix, &onebuf);\n+\ttwo = quote_path(two_name, s->prefix, &twobuf);\n \n \tstatus_printf(s, color(WT_STATUS_HEADER, s), \"\\t\");\n \tswitch (status) {\n@@ -706,8 +706,7 @@ static void wt_status_print_other(struct wt_status *s,\n \t\tstruct string_list_item *it;\n \t\tconst char *path;\n \t\tit = &(l->items[i]);\n-\t\tpath = quote_path(it->string, strlen(it->string),\n-\t\t\t\t  &buf, s->prefix);\n+\t\tpath = quote_path(it->string, s->prefix, &buf);\n \t\tif (column_active(s->colopts)) {\n \t\t\tstring_list_append(&output, path);\n \t\t\tcontinue;\n@@ -1289,7 +1288,7 @@ static void wt_shortstatus_unmerged(struct string_list_item *it,\n \t} else {\n \t\tstruct strbuf onebuf = STRBUF_INIT;\n \t\tconst char *one;\n-\t\tone = quote_path(it->string, -1, &onebuf, s->prefix);\n+\t\tone = quote_path(it->string, s->prefix, &onebuf);\n \t\tprintf(\" %s\\n\", one);\n \t\tstrbuf_release(&onebuf);\n \t}\n@@ -1317,7 +1316,7 @@ static void wt_shortstatus_status(struct string_list_item *it,\n \t\tstruct strbuf onebuf = STRBUF_INIT;\n \t\tconst char *one;\n \t\tif (d->head_path) {\n-\t\t\tone = quote_path(d->head_path, -1, &onebuf, s->prefix);\n+\t\t\tone = quote_path(d->head_path, s->prefix, &onebuf);\n \t\t\tif (*one != '\"' && strchr(one, ' ') != NULL) {\n \t\t\t\tputchar('\"');\n \t\t\t\tstrbuf_addch(&onebuf, '\"');\n@@ -1326,7 +1325,7 @@ static void wt_shortstatus_status(struct string_list_item *it,\n \t\t\tprintf(\"%s -> \", one);\n \t\t\tstrbuf_release(&onebuf);\n \t\t}\n-\t\tone = quote_path(it->string, -1, &onebuf, s->prefix);\n+\t\tone = quote_path(it->string, s->prefix, &onebuf);\n \t\tif (*one != '\"' && strchr(one, ' ') != NULL) {\n \t\t\tputchar('\"');\n \t\t\tstrbuf_addch(&onebuf, '\"');\n@@ -1345,7 +1344,7 @@ static void wt_shortstatus_other(struct string_list_item *it,\n \t} else {\n \t\tstruct strbuf onebuf = STRBUF_INIT;\n \t\tconst char *one;\n-\t\tone = quote_path(it->string, -1, &onebuf, s->prefix);\n+\t\tone = quote_path(it->string, s->prefix, &onebuf);\n \t\tcolor_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), \"%s\", sign);\n \t\tprintf(\" %s\\n\", one);\n \t\tstrbuf_release(&onebuf);\n-- \n1.8.3.rc3.368.g7c798dd\n"},{"id":"218136","messageId":"aa9edf610ab88f692e1a15c6d0129dbc73039973.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1369186574.git.worldhello.net@gmail.com","subject":"[PATCH v13 05/15] Refactor write_name_quoted_relative, remove unused params","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:26Z","receivedAt":"2013-05-22T01:40:26Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"After substitute path_relative() in quote.c with relative_path() from\npath.c, parameters (such as len and prefix_len) are obsolete in function\nwrite_name_quoted_relative(). Remove unused parameters from\nwrite_name_quoted_relative() and related functions.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/ls-files.c | 14 ++++++++------\n quote.c            |  3 +--\n quote.h            |  3 +--\n 3 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 00f07..6acff4 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -46,10 +46,12 @@ static const char *tag_modified = \"\";\n static const char *tag_skip_worktree = \"\";\n static const char *tag_resolve_undo = \"\";\n \n-static void write_name(const char* name, size_t len)\n+static void write_name(const char *name)\n {\n-\twrite_name_quoted_relative(name, len, prefix, prefix_len, stdout,\n-\t\t\tline_terminator);\n+\n+\t/* turn off prefix, if run with \"--full-name\" */\n+\twrite_name_quoted_relative(name, prefix_len ? prefix : NULL,\n+\t\t\t\t   stdout, line_terminator);\n }\n \n static void show_dir_entry(const char *tag, struct dir_entry *ent)\n@@ -63,7 +65,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)\n \t\treturn;\n \n \tfputs(tag, stdout);\n-\twrite_name(ent->name, ent->len);\n+\twrite_name(ent->name);\n }\n \n static void show_other_files(struct dir_struct *dir)\n@@ -163,7 +165,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)\n \t\t       find_unique_abbrev(ce->sha1,abbrev),\n \t\t       ce_stage(ce));\n \t}\n-\twrite_name(ce->name, ce_namelen(ce));\n+\twrite_name(ce->name);\n \tif (debug_mode) {\n \t\tprintf(\"  ctime: %d:%d\\n\", ce->ce_ctime.sec, ce->ce_ctime.nsec);\n \t\tprintf(\"  mtime: %d:%d\\n\", ce->ce_mtime.sec, ce->ce_mtime.nsec);\n@@ -196,7 +198,7 @@ static void show_ru_info(void)\n \t\t\tprintf(\"%s%06o %s %d\\t\", tag_resolve_undo, ui->mode[i],\n \t\t\t       find_unique_abbrev(ui->sha1[i], abbrev),\n \t\t\t       i + 1);\n-\t\t\twrite_name(path, len);\n+\t\t\twrite_name(path);\n \t\t}\n \t}\n }\ndiff --git a/quote.c b/quote.c\nindex ebb8..5c880 100644\n--- a/quote.c\n+++ b/quote.c\n@@ -312,8 +312,7 @@ void write_name_quotedpfx(const char *pfx, size_t pfxlen,\n \tfputc(terminator, fp);\n }\n \n-void write_name_quoted_relative(const char *name, size_t len,\n-\t\t\t\tconst char *prefix, size_t prefix_len,\n+void write_name_quoted_relative(const char *name, const char *prefix,\n \t\t\t\tFILE *fp, int terminator)\n {\n \tstruct strbuf sb = STRBUF_INIT;\ndiff --git a/quote.h b/quote.h\nindex 5610159..ed110 100644\n--- a/quote.h\n+++ b/quote.h\n@@ -60,8 +60,7 @@ extern void quote_two_c_style(struct strbuf *, const char *, const char *, int);\n extern void write_name_quoted(const char *name, FILE *, int terminator);\n extern void write_name_quotedpfx(const char *pfx, size_t pfxlen,\n                                  const char *name, FILE *, int terminator);\n-extern void write_name_quoted_relative(const char *name, size_t len,\n-\t\tconst char *prefix, size_t prefix_len,\n+extern void write_name_quoted_relative(const char *name, const char *prefix,\n \t\tFILE *fp, int terminator);\n \n /* quote path as relative to the given prefix */\n-- \n1.8.3.rc3.368.g7c798dd\n"},{"id":"218125","messageId":"32d9227e03102158264d518865fc7682697b9da5.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1369186574.git.worldhello.net@gmail.com","subject":"[PATCH v13 06/15] git-clean: refactor git-clean into two phases","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:27Z","receivedAt":"2013-05-22T01:40:27Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Before introducing interactive git-clean, refactor git-clean operations\ninto two phases:\n\n * hold cleaning items in del_list,\n * and remove them in a separate loop at the end.\n\nWe will introduce interactive git-clean between the two phases. The\ninteractive git-clean will show what would be done and must confirm\nbefore do real cleaning.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/clean.c | 64 ++++++++++++++++++++++++++++++++++++++++-----------------\n 1 file changed, 45 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex f77f95..77ec1 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@@ -148,12 +149,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\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 +225,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,13 +245,8 @@ 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+\t\t\tdie_errno(\"Cannot lstat '%s'\", ent->name);\n \n \t\tif (pathspec) {\n \t\t\tmemset(seen, 0, argc > 0 ? argc : 1);\n@@ -257,33 +255,61 @@ 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, prefix, &buf);\n-\t\t\t\t\tprintf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);\n-\t\t\t\t}\n+\t\t\t\trel = relative_path(ent->name, prefix, &buf);\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 = relative_path(ent->name, prefix, &buf);\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+\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, NULL, &buf);\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, prefix, &buf);\n+\t\t\t\tqname = quote_path_relative(item->string, NULL, &buf);\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, prefix, &buf);\n+\t\t\t\tqname = quote_path_relative(item->string, NULL, &buf);\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+\tstrbuf_release(&buf);\n+\tstring_list_clear(&del_list, 0);\n \tstring_list_clear(&exclude_list, 0);\n \treturn (errors != 0);\n }\n-- \n1.8.3.rc3.368.g7c798dd\n"},{"id":"218127","messageId":"e3c8092ef76149ef8eba0955d9310b0e6f20f5ae.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1369186574.git.worldhello.net@gmail.com","subject":"[PATCH v13 07/15] git-clean: add support for -i/--interactive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:28Z","receivedAt":"2013-05-22T01:40:28Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-clean.txt | 10 ++++++--\n builtin/clean.c             | 57 +++++++++++++++++++++++++++++++++++++++++----\n 2 files changed, 60 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 77ec1..698fb 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@@ -143,6 +144,50 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \treturn ret;\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, NULL, &buf);\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@@ -162,6 +207,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@@ -188,12 +234,12 @@ int cmd_clean(int argc, const char **argv, const char *prefix)\n \tif (ignored && ignored_only)\n \t\tdie(_(\"-x and -X cannot be used together\"));\n \n-\tif (!dry_run && !force) {\n+\tif (!interactive && !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@@ -267,7 +313,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 && 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.rc3.368.g7c798dd\n"},{"id":"218124","messageId":"dec60b3fed0e01159c8f7b3b485b1e6374c61fe9.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1369186574.git.worldhello.net@gmail.com","subject":"[PATCH v13 08/15] git-clean: show items of del_list in columns","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:29Z","receivedAt":"2013-05-22T01:40:29Z","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 will solve this problem.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nComments-by: Matthieu Moy <Matthieu.Moy@imag.fr>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\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..e031b 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 the layout when list items in `git clean -i`, which always\n+\tshows files and directories in columns. See `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 698fb..75cc6 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@@ -144,21 +151,46 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,\n \treturn ret;\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, NULL, &buf);\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, NULL, &buf);\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@@ -184,7 +216,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.rc3.368.g7c798dd\n"},{"id":"218128","messageId":"81c2d0095f99dc0d407cb5bc6af3346a3f72d016.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1369186574.git.worldhello.net@gmail.com","subject":"[PATCH v13 09/15] git-clean: add colors to interactive git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:30Z","receivedAt":"2013-05-22T01:40:30Z","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, such as \"color.interactive\" and\n\"color.interactive.<slot>\" for command `git-add--interactive`.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nComments-by: Matthieu Moy <Matthieu.Moy@imag.fr>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config.txt | 17 +++++------\n builtin/clean.c          | 73 +++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 81 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex e031b..83613 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -876,16 +876,17 @@ The values of these variables may be specified as in color.branch.<slot>.\n \n color.interactive::\n \tWhen set to `always`, always use colors for interactive prompts\n-\tand displays (such as those used by \"git-add --interactive\").\n-\tWhen false (or `never`), never.  When set to `true` or `auto`, use\n-\tcolors only when the output is to the terminal. Defaults to false.\n+\tand displays (such as those used by \"git-add --interactive\" and\n+\t\"git-clean --interactive\"). When false (or `never`), never.\n+\tWhen set to `true` or `auto`, use colors only when the output is\n+\tto the terminal. Defaults to false.\n \n color.interactive.<slot>::\n-\tUse customized color for 'git add --interactive'\n-\toutput. `<slot>` may be `prompt`, `header`, `help` or `error`, for\n-\tfour distinct types of normal output from interactive\n-\tcommands.  The values of these variables may be specified as\n-\tin color.branch.<slot>.\n+\tUse customized color for 'git add --interactive' and 'git clean\n+\t--interactive' output. `<slot>` may be `prompt`, `header`, `help`\n+\tor `error`, for four distinct types of normal output from\n+\tinteractive commands.  The values of these variables may be\n+\tspecified as in color.branch.<slot>.\n \n color.pager::\n \tA boolean to enable/disable colored output when the pager is in\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 75cc6..dfa99b 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,82 @@ static const char *msg_skip_git_dir = N_(\"Skipping repository %s\\n\");\n static const char *msg_would_skip_git_dir = N_(\"Would skip repository %s\\n\");\n static const char *msg_warn_remove_failed = N_(\"failed to remove %s\");\n \n+static int clean_use_color = -1;\n+static char clean_colors[][COLOR_MAXLEN] = {\n+\tGIT_COLOR_RESET,\n+\tGIT_COLOR_NORMAL,\t/* PLAIN */\n+\tGIT_COLOR_BOLD_BLUE,\t/* PROMPT */\n+\tGIT_COLOR_BOLD,\t\t/* HEADER */\n+\tGIT_COLOR_BOLD_RED,\t/* HELP */\n+\tGIT_COLOR_BOLD_RED,\t/* ERROR */\n+};\n+enum color_clean {\n+\tCLEAN_COLOR_RESET = 0,\n+\tCLEAN_COLOR_PLAIN = 1,\n+\tCLEAN_COLOR_PROMPT = 2,\n+\tCLEAN_COLOR_HEADER = 3,\n+\tCLEAN_COLOR_HELP = 4,\n+\tCLEAN_COLOR_ERROR = 5,\n+};\n+\n+static int parse_clean_color_slot(const char *var)\n+{\n+\tif (!strcasecmp(var, \"reset\"))\n+\t\treturn CLEAN_COLOR_RESET;\n+\tif (!strcasecmp(var, \"plain\"))\n+\t\treturn CLEAN_COLOR_PLAIN;\n+\tif (!strcasecmp(var, \"prompt\"))\n+\t\treturn CLEAN_COLOR_PROMPT;\n+\tif (!strcasecmp(var, \"header\"))\n+\t\treturn CLEAN_COLOR_HEADER;\n+\tif (!strcasecmp(var, \"help\"))\n+\t\treturn CLEAN_COLOR_HELP;\n+\tif (!strcasecmp(var, \"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 +\n+\t\t\t\t\t\t  strlen(\"color.interactive.\"));\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@@ -184,14 +251,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.rc3.368.g7c798dd\n"},{"id":"218132","messageId":"a712b9c71e3bd8f018a7cb6a98a2da4c7cd7e4bc.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1369186574.git.worldhello.net@gmail.com","subject":"[PATCH v13 10/15] git-clean: use a git-add-interactive compatible UI","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:31Z","receivedAt":"2013-05-22T01:40:31Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/clean.c | 456 ++++++++++++++++++++++++++++++++++++++++++++++++++++----\n 1 file changed, 427 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex dfa99b..df887 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 flags;\n+};\n+\n+#define MENU_RETURN_NO_LOOP\t\t10\n+\n+struct menu_item {\n+\tchar hotkey;\n+\tconst char *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)\n {\n \tif (!strcasecmp(var, \"reset\"))\n@@ -240,54 +270,422 @@ 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+\tstruct string_list menu_list = STRING_LIST_INIT_DUP;\n+\tstruct strbuf menu = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct menu_item *menu_item;\n+\tstruct string_list_item *string_list_item;\n+\tint i;\n+\n+\tswitch (stuff->type) {\n+\tdefault:\n+\t\tdie(\"Bad type of menu_staff when print menu\");\n+\tcase MENU_STUFF_TYPE_MENU_ITEM:\n+\t\tmenu_item = (struct menu_item *)stuff->stuff;\n+\t\tfor (i = 0; i < stuff->nr; i++, menu_item++) {\n+\t\t\tconst char *p;\n+\t\t\tint highlighted = 0;\n+\n+\t\t\tp = menu_item->title;\n+\t\t\tif ((*chosen)[i] < 0)\n+\t\t\t\t(*chosen)[i] = menu_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 == menu_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\tbreak;\n+\tcase MENU_STUFF_TYPE_STRING_LIST:\n+\t\ti = 0;\n+\t\tfor_each_string_list_item(string_list_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\",\n+\t\t\t\t    (*chosen)[i] ? \"*\" : \" \", i+1, string_list_item->string);\n+\t\t\tstring_list_append(&menu_list, menu.buf);\n+\t\t\tstrbuf_reset(&menu);\n+\t\t\ti++;\n+\t\t}\n+\t\tbreak;\n+\t}\n+\n+\tpretty_print_menus(&menu_list);\n+\n+\tstrbuf_release(&menu);\n+\tstrbuf_release(&buf);\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, **ptr;\n+\tstruct menu_item *menu_item;\n+\tstruct string_list_item *string_list_item;\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 (ptr = choice_list; *ptr; ptr++) {\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(*ptr);\n+\t\tif (!(*ptr)->len)\n+\t\t\tcontinue;\n+\n+\t\t/* Input that begins with '-'; unchoose */\n+\t\tif (*(*ptr)->buf == '-') {\n+\t\t\tchoose = 0;\n+\t\t\tstrbuf_remove((*ptr), 0, 1);\n+\t\t}\n+\n+\t\tis_range = 0;\n+\t\tis_number = 1;\n+\t\tfor (p = (*ptr)->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((*ptr)->buf);\n+\t\t\ttop = bottom;\n+\t\t} else if (is_range) {\n+\t\t\tbottom = atoi((*ptr)->buf);\n+\t\t\t/* a range can be specified like 5-7 or 5- */\n+\t\t\tif (!*(strchr((*ptr)->buf, '-') + 1))\n+\t\t\t\ttop = menu_stuff->nr;\n+\t\t\telse\n+\t\t\t\ttop = atoi(strchr((*ptr)->buf, '-') + 1);\n+\t\t} else if (!strcmp((*ptr)->buf, \"*\")) {\n+\t\t\tbottom = 1;\n+\t\t\ttop = menu_stuff->nr;\n+\t\t} else {\n+\t\t\tswitch (menu_stuff->type) {\n+\t\t\tdefault:\n+\t\t\t\tdie(\"Bad type of menu_stuff when parse choice\");\n+\t\t\tcase MENU_STUFF_TYPE_MENU_ITEM:\n+\t\t\t\tmenu_item = (struct menu_item *)menu_stuff->stuff;\n+\t\t\t\tfor (i = 0; i < menu_stuff->nr; i++, menu_item++) {\n+\t\t\t\t\tif (((*ptr)->len == 1 &&\n+\t\t\t\t\t     *(*ptr)->buf == menu_item->hotkey) ||\n+\t\t\t\t\t    !strcasecmp((*ptr)->buf, menu_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\tbreak;\n+\t\t\tcase MENU_STUFF_TYPE_STRING_LIST:\n+\t\t\t\tstring_list_item = ((struct string_list *)menu_stuff->stuff)->items;\n+\t\t\t\tfor (i = 0; i < menu_stuff->nr; i++, string_list_item++) {\n+\t\t\t\t\tif (!strcasecmp((*ptr)->buf, string_list_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\tbreak;\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)?\"), (*ptr)->buf);\n+\t\t\tclean_print_color(CLEAN_COLOR_RESET);\n+\t\t\tcontinue;\n+\t\t}\n+\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+\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/* chosen will be initialized by print_highlight_menu_stuff */\n+\t\tprint_highlight_menu_stuff(stuff, &chosen);\n+\n+\t\tif (opts->flags & 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->flags & 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->flags & 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->flags & MENU_OPTS_SINGLETON) && !choice.len)\n+\t\t\tbreak;\n+\n+\t\tnr = parse_choice(stuff,\n+\t\t\t\t  opts->flags & MENU_OPTS_SINGLETON,\n+\t\t\t\t  choice,\n+\t\t\t\t  &chosen);\n+\n+\t\tif (opts->flags & MENU_OPTS_SINGLETON) {\n+\t\t\tif (nr)\n+\t\t\t\tbreak;\n+\t\t} else if (opts->flags & 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/*\n+\t\t * recalculate nr, if return back from menu directly with\n+\t\t * default selections.\n+\t\t */\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-\tstruct strbuf confirm = STRBUF_INIT;\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 = N_(\"*** Commands ***\");\n+\t\tmenu_opts.prompt = N_(\"What now\");\n+\t\tmenu_opts.flags = 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.rc3.368.g7c798dd\n"},{"id":"218131","messageId":"84ce3ae6d2f7669491180c9bc9dd63870c1147da.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1369186574.git.worldhello.net@gmail.com","subject":"[PATCH v13 11/15] git-clean: add filter by pattern interactive action","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:32Z","receivedAt":"2013-05-22T01:40:32Z","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 the\nuser chooses this action, user can input space-separated patterns (the\nsame syntax as gitignore), and each clean candidate that matches with\none of the patterns will be excluded from cleaning. When the user feels\nit's OK, presses ENTER and backs to the confirmation dialog.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-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 df887..36369 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -614,6 +614,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@@ -626,6 +692,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@@ -641,6 +708,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.rc3.368.g7c798dd\n"},{"id":"218130","messageId":"b2726ac91c770a5bfb752a5daa7c133b4d680c34.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1369186574.git.worldhello.net@gmail.com","subject":"[PATCH v13 12/15] git-clean: add select by numbers interactive action","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:33Z","receivedAt":"2013-05-22T01:40:33Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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 36369..643a5e 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -680,6 +680,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 = N_(\"Select items to delete\");\n+\tmenu_opts.flags = 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 (chosen[j] == 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@@ -693,6 +730,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@@ -709,6 +747,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.rc3.368.g7c798dd\n"},{"id":"218129","messageId":"14fed4c2721c7b4f81fe57f207b1757ef8e65e2c.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1369186574.git.worldhello.net@gmail.com","subject":"[PATCH v13 13/15] git-clean: add ask each interactive action","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:34Z","receivedAt":"2013-05-22T01:40:34Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.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 643a5e..bf03a 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -717,6 +717,40 @@ static int select_by_numbers_cmd(void)\n \treturn 0;\n }\n \n+static int ask_each_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, NULL, &buf);\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, \"yes\", 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@@ -731,6 +765,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@@ -748,6 +783,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, ask_each_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.rc3.368.g7c798dd\n"},{"id":"218135","messageId":"933513155a6f8bc3a383691280da673ca7cedc24.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1369186574.git.worldhello.net@gmail.com","subject":"[PATCH v13 14/15] git-clean: add documentation for interactive git-clean","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:35Z","receivedAt":"2013-05-22T01:40:35Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add new section \"Interactive mode\" for documentation of interactive\ngit-clean.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-clean.txt | 65 +++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 63 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt\nindex 186e34..5bf76 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,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: filter by pattern    3: select by numbers\n+        4: ask each             5: quit                 6: help\n+    What now> 1\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+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+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.rc3.368.g7c798dd\n"},{"id":"218133","messageId":"2dd5d7db36c5ea524d58f10e150aa02f913422f0.1369186574.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"cover.1369186574.git.worldhello.net@gmail.com","subject":"[PATCH v13 15/15] test: add t7301 for git-clean--interactive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-22T01:40:36Z","receivedAt":"2013-05-22T01:40:36Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add test cases for git-clean--interactive.\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t7301-clean-interactive.sh | 439 +++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 439 insertions(+)\n create mode 100755 t/t7301-clean-interactive.sh\n\ndiff --git a/t/t7301-clean-interactive.sh b/t/t7301-clean-interactive.sh\nnew file mode 100755\nindex 00000..4e605\n--- /dev/null\n+++ b/t/t7301-clean-interactive.sh\n@@ -0,0 +1,439 @@\n+#!/bin/sh\n+\n+test_description='git clean -i basic tests'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\n+\tmkdir -p src &&\n+\ttouch src/part1.c Makefile &&\n+\techo build >.gitignore &&\n+\techo \\*.o >>.gitignore &&\n+\tgit add . &&\n+\tgit commit -m setup &&\n+\ttouch src/part2.c README &&\n+\tgit add .\n+\n+'\n+\n+test_expect_success 'git clean -i (clean)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\techo c | git clean -i &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest ! -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -i (quit)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\techo q | git clean -i &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -i (Ctrl+D)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\techo \"\\04\" | git clean -i &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (filter all)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo f; echo \"*\"; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (filter patterns)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo f; echo \"part3.* *.out\"; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest ! -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest ! -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (filter patterns 2)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo f; echo \"* !*.out\"; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (select - all)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo \"*\"; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest ! -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest ! -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (select - none)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (select - number)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo 3; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (select - number 2)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo 2 3; echo 5; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest ! -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (select - number 3)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo 3,4 5; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (select - range)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo 1,3-4; echo 2; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest ! -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest ! -f docs/manual.txt &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (select - range 2)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo 4- 1; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest ! -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (inverse select)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo s; echo \"*\"; echo -5- 1 -2; echo; echo c) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (ask)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest ! -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id (ask - Ctrl+D)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(echo a; echo Y; echo no; echo yes; echo \"\\04\") | \\\n+\tgit clean -id &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id with prefix and path (filter)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(cd build/ && \\\n+\t (echo f; echo \"docs\"; echo \"*.h\"; echo ; echo c) | \\\n+\t git clean -id ..) &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id with prefix and path (select by name)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(cd build/ && \\\n+\t (echo s; echo \"../docs/\"; echo \"../src/part3.c\"; \\\n+\t  echo \"../src/part4.c\";  echo; echo c) | \\\n+\t git clean -id ..) &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest -f a.out &&\n+\ttest ! -f docs/manual.txt &&\n+\ttest ! -f src/part3.c &&\n+\ttest -f src/part3.h &&\n+\ttest ! -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_expect_success 'git clean -id with prefix and path (ask)' '\n+\n+\tmkdir -p build docs &&\n+\ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n+\tdocs/manual.txt obj.o build/lib.so &&\n+\t(cd build/ && \\\n+\t (echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \\\n+\t git clean -id ..) &&\n+\ttest -f Makefile &&\n+\ttest -f README &&\n+\ttest -f src/part1.c &&\n+\ttest -f src/part2.c &&\n+\ttest ! -f a.out &&\n+\ttest ! -f docs/manual.txt &&\n+\ttest -f src/part3.c &&\n+\ttest ! -f src/part3.h &&\n+\ttest -f src/part4.c &&\n+\ttest -f src/part4.h &&\n+\ttest -f obj.o &&\n+\ttest -f build/lib.so\n+\n+'\n+\n+test_done\n-- \n1.8.3.rc3.368.g7c798dd\n"},{"id":"218156","messageId":"519C7CA1.2060402@alum.mit.edu","threadId":"33862","inReplyTo":"d730f00edb09449bf299be8d2083c895a1028c18.1369186574.git.worldhello.net@gmail.com","subject":"Re: [PATCH v13 02/15] path.c: refactor relative_path(), not only strip prefix","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2013-05-22T08:06:57Z","receivedAt":"2013-05-22T08:06:57Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"Sorry for coming late to the party.\n\nOn 05/22/2013 03:40 AM, Jiang Xin wrote:\n> Original design of relative_path() is simple, just strip the prefix\n> (*base) from the absolute path (*abs). In most cases, we need a real\n> relative path, such as: ../foo, ../../bar. That's why there is another\n> reimplementation (path_relative()) in quote.c.\n> \n> Refactor relative_path() in path.c to return real relative path, so\n> that user can reuse this function without reimplement his/her own.\n> I will use this method for interactive git-clean later. Some of the\n> implementations are borrowed from path_relative() in quote.c.\n> \n> Different results for relative_path() before and after this refactor:\n> \n>     abs path  base path  relative (original)  relative (refactor)\n>     ========  =========  ===================  ===================\n>     /a/b/c/   /a/b       c/                   c/\n>     /a/b//c/  //a///b/   c/                   c/\n>     /a/b      /a/b       .                    ./\n>     /a/b/     /a/b       .                    ./\n>     /a        /a/b/      /a                   ../\n>     /         /a/b/      /                    ../../\n>     /a/c      /a/b/      /a/c                 ../c\n>     /a/b      (empty)    /a/b                 /a/b\n>     /a/b      (null)     /a/b                 /a/b\n>     (empty)   /a/b       (empty)              ./\n>     (null)    (empty)    (null)               ./\n>     (null)    /a/b       (segfault)           ./\n\nThe old and new versions both seem to be (differently) inconsistent\nabout when the output has a trailing slash.  What is the rule?\n\n> diff --git a/path.c b/path.c\n> index 04ff..0174d 100644\n> --- a/path.c\n> +++ b/path.c\n> @@ -441,42 +441,104 @@ int adjust_shared_perm(const char *path)\n>  \treturn 0;\n>  }\n>  \n> -const char *relative_path(const char *abs, const char *base)\n> +/*\n> + * Give path as relative to prefix.\n> + *\n> + * The strbuf may or may not be used, so do not assume it contains the\n> + * returned path.\n> + */\n> +const char *relative_path(const char *abs, const char *base,\n> +\t\t\t  struct strbuf *sb)\n\nThanks for adding documentation.  But I think it could be improved:\n\n* The comment refers to \"path\" and \"prefix\" but the function parameters\nare \"abs\" and \"base\".  I suggest making them agree.\n\n* Who owns the memory pointed to by the return value?\n\n* The comment says that \"the strbuf may or may not be used\".  So why is\nit part of the interface?  (I suppose it is because the strbuf might be\ngiven ownership of the returned memory if it has to be allocated.)\nWould it be more straightforward to *always* return the result in the\nstrbuf?\n\n* Please document when the return value contains a trailing slash and\nalso that superfluous internal slashes are (apparently) normalized away.\n\n* Leading double-slashes have a special meaning on some operating\nsystems.  The old and new versions of this function both seem to ignore\ndifferences between initial slashes.  Perhaps somebody who knows the\nrules better could say whether this is an issue but I guess the problem\nwould rarely be encountered in practice.\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\nhttp://softwareswirl.blogspot.com/\n"},{"id":"218188","messageId":"7v38tfkmpm.fsf@alter.siamese.dyndns.org","threadId":"33862","inReplyTo":"519C7CA1.2060402@alum.mit.edu","subject":"Re: [PATCH v13 02/15] path.c: refactor relative_path(), not only strip prefix","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-05-22T16:23:33Z","receivedAt":"2013-05-22T16:23:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael Haggerty <mhagger@alum.mit.edu> writes:\n\n>> Different results for relative_path() before and after this refactor:\n>> \n>>     abs path  base path  relative (original)  relative (refactor)\n>>     ========  =========  ===================  ===================\n>>     /a/b/c/   /a/b       c/                   c/\n>>     /a/b//c/  //a///b/   c/                   c/\n>>     /a/b      /a/b       .                    ./\n>>     /a/b/     /a/b       .                    ./\n>>     /a        /a/b/      /a                   ../\n>>     /         /a/b/      /                    ../../\n>>     /a/c      /a/b/      /a/c                 ../c\n>>     /a/b      (empty)    /a/b                 /a/b\n>>     /a/b      (null)     /a/b                 /a/b\n>>     (empty)   /a/b       (empty)              ./\n>>     (null)    (empty)    (null)               ./\n>>     (null)    /a/b       (segfault)           ./\n>\n> The old and new versions both seem to be (differently) inconsistent\n> about when the output has a trailing slash.  What is the rule?\n\nThat is a good point.  At least adding / at the end of \".\" seems\nunneeded, given that the output in some cases have no trailing\nslash, forcing a caller who wanted to get a directory to append a\ntrailing path components to it to check if it needs to add one\nbefore doing so.  Always adding a slash / to the output may sound\nconsistent, but it is not quite; e.g. \"/a/c based on /a/b is ../c\"\ncase may be referring to a non directory /a/c and ensuring a\ntrailing slash to produce ../c/ is actively wrong.\n\n\"The caller knows\" rule might work (I am thinking aloud, without\nlooking at existing callers to see what would break, only to see if\na consistent and simple-to-explain rule is possible).\n\nWhen the caller asks to turn /a/b relative to /a/b (or /a/b/,\n/a//b/./), then we do not know (or we do not want to know) if the\ncaller means it to be a directory with the intention of appending\nsomething after it, so just return \".\".  When the caller asks to\nturn /a/b/ relative to the same base, return \"./\" with the trailing\nslash.  Remember if the \"abs path\" side had a trailing slash,\nnormalize both input (turning \"/./\" into \"/\" and \"foo/bar/../\" into\nfoo/\", squashing multiple slashes into one, etc.) and then strip the\ntrailing slash from them, do the usual \"comparison and replacement\nof leading path components with series of ../\" and then append a\nslash if the original had one, or something?\n\n>> index 04ff..0174d 100644\n>> --- a/path.c\n>> +++ b/path.c\n>> @@ -441,42 +441,104 @@ int adjust_shared_perm(const char *path)\n>>  \treturn 0;\n>>  }\n>>  \n>> -const char *relative_path(const char *abs, const char *base)\n>> +/*\n>> + * Give path as relative to prefix.\n>> + *\n>> + * The strbuf may or may not be used, so do not assume it contains the\n>> + * returned path.\n>> + */\n>> +const char *relative_path(const char *abs, const char *base,\n>> +\t\t\t  struct strbuf *sb)\n>\n> Thanks for adding documentation.  But I think it could be improved:\n>\n> * The comment refers to \"path\" and \"prefix\" but the function parameters\n> are \"abs\" and \"base\".  I suggest making them agree.\n>\n> * Who owns the memory pointed to by the return value?\n>\n> * The comment says that \"the strbuf may or may not be used\".  So why is\n> it part of the interface?  (I suppose it is because the strbuf might be\n> given ownership of the returned memory if it has to be allocated.)\n> Would it be more straightforward to *always* return the result in the\n> strbuf?\n\nThis comes from the original in quote.c, I think.  The caller\nsupplies a strbuf as a scratchpad area and releasing it is the\ncaller's responsibility.  If the function does not need any\nscratchpad area (i.e. the answer is a substring of the input), the\nresult may point into the abs.\n\nSo the calling convention is:\n\n\tstruct strbuf scratch = STRBUF_INIT;\n\tconst char *result = relative(abs, base, &scratch);\n        use(result);\n        strbuf_release(&scratch);\n\nand the lifetime rule is \"consume it before either abs or scratch\nis changed\".\n"},{"id":"218549","messageId":"CANYiYbGiAdMtOwdAf1cgV74cJjaM1dABTE6LEe+LAWoEaUBSXw@mail.gmail.com","threadId":"33862","inReplyTo":"519C7CA1.2060402@alum.mit.edu","subject":"Re: [PATCH v13 02/15] path.c: refactor relative_path(), not only strip prefix","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-26T00:21:14Z","receivedAt":"2013-05-26T00:21:14Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"2013/5/22 Michael Haggerty <mhagger@alum.mit.edu>:\n> Sorry for coming late to the party.\n\nI am on a business travel, and respond late also. ;-)\n\n>\n> On 05/22/2013 03:40 AM, Jiang Xin wrote:\n>> Different results for relative_path() before and after this refactor:\n>>\n>>     abs path  base path  relative (original)  relative (refactor)\n>>     ========  =========  ===================  ===================\n>>     /a/b/c/   /a/b       c/                   c/\n>>     /a/b//c/  //a///b/   c/                   c/\n>>     /a/b      /a/b       .                    ./\n>>     /a/b/     /a/b       .                    ./\n>>     /a        /a/b/      /a                   ../\n>>     /         /a/b/      /                    ../../\n>>     /a/c      /a/b/      /a/c                 ../c\n>>     /a/b      (empty)    /a/b                 /a/b\n>>     /a/b      (null)     /a/b                 /a/b\n>>     (empty)   /a/b       (empty)              ./\n>>     (null)    (empty)    (null)               ./\n>>     (null)    /a/b       (segfault)           ./\n>\n> The old and new versions both seem to be (differently) inconsistent\n> about when the output has a trailing slash.  What is the rule?\n\nThe reason for introducing patch 02/15 is that we don't want to reinvent\nthe wheel. Patch 06/15 (git-clean: refactor git-clean into two phases)\nneeds to save relative_path of each git-clean candidate file/directory in\ndel_list, but the public method in path.c (i.e. relative_path) is not\npowerful, and static method in quote.c (i.e. path_relative) can note be\nused directly. One way is to enhanced relative_path in path.c, like this\npatch.\n\nSince we combine the two methods (relative_path in path.c and\npath_relative in quote.c), the new relative_path must be compatible\nwith the original two methods.\n\nrelative_path in path.c\n=======================\n\nrelative_path is called in one place:\n\n        if (getenv(GIT_WORK_TREE_ENVIRONMENT))\n                setenv(GIT_WORK_TREE_ENVIRONMENT, \".\", 1);\n\n        set_git_dir(relative_path(git_dir, work_tree));\n        initialized = 1;\n\nand set_git_dir only set the environment GIT_DIR_ENVIRONMENT\nlike this:\n\n        int set_git_dir(const char *path)\n        {\n                if (setenv(GIT_DIR_ENVIRONMENT, path, 1))\n                        return error(\"Could not set GIT_DIR to '%s'\", path);\n                setup_git_env();\n                return 0;\n        }\n\nSo the only restraint for relative_path is that the return value can\nnot be blank. If the abs and base arguments for relative_path are\nthe same, the return value should be \".\" (\"./\" is also OK), then\nset the envionment GIT_DIR_ENVIRONMENT to \".\" (or \"./\").\n\npath_relative in quote.c\n========================\n\nWe can not simply move \"path_relative\" in quote.c to \"relative_path\"\nin path.c directly. It is because:\n\n* The arguments for \"relative_path\" are from user input. So must\n   validate (remove duplicate slash) before use. But \"path_relative\"\n   does not check duplicate slash in arguments.\n\n* \"path_relative\" will return blank string, if abs and base are the same.\n\nAlso I noticed that \"quote_path_relative\" of quote.c (which calls\npath_relative) will transform the blank string from path_relative to\n\"./\" (not \".\")\n\n        char *quote_path_relative(const char *in, int len,\n        ...\n                const char *rel = path_relative(in, len, &sb, prefix, -1);\n                ...\n                if (!out->len)\n                        strbuf_addstr(out, \"./\");\n\nThat's why the \"path_relative\" in path.c refactor the output of \".\" into \"./\".\n\n>\n>> diff --git a/path.c b/path.c\n>> index 04ff..0174d 100644\n>> --- a/path.c\n>> +++ b/path.c\n>> @@ -441,42 +441,104 @@ int adjust_shared_perm(const char *path)\n>>       return 0;\n>>  }\n>>\n>> -const char *relative_path(const char *abs, const char *base)\n>> +/*\n>> + * Give path as relative to prefix.\n>> + *\n>> + * The strbuf may or may not be used, so do not assume it contains the\n>> + * returned path.\n>> + */\n>> +const char *relative_path(const char *abs, const char *base,\n>> +                       struct strbuf *sb)\n>\n> Thanks for adding documentation.  But I think it could be improved:\n>\n> * The comment refers to \"path\" and \"prefix\" but the function parameters\n> are \"abs\" and \"base\".  I suggest making them agree.\n\nYes, it will be nice to update the comments.\n\n> * Who owns the memory pointed to by the return value?\n>\n> * The comment says that \"the strbuf may or may not be used\".  So why is\n> it part of the interface?  (I suppose it is because the strbuf might be\n> given ownership of the returned memory if it has to be allocated.)\n> Would it be more straightforward to *always* return the result in the\n> strbuf?\n>\n> * Please document when the return value contains a trailing slash and\n> also that superfluous internal slashes are (apparently) normalized away.\n>\n> * Leading double-slashes have a special meaning on some operating\n> systems.  The old and new versions of this function both seem to ignore\n> differences between initial slashes.  Perhaps somebody who knows the\n> rules better could say whether this is an issue but I guess the problem\n> would rarely be encountered in practice.\n\nSee Junio's reply.\n\n>\n> Michael\n>\n> --\n> Michael Haggerty\n> mhagger@alum.mit.edu\n> http://softwareswirl.blogspot.com/\n\n\n\n-- \n蒋鑫\n\n北京群英汇信息技术有限公司\n邮件: worldhello.net@gmail.com\n网址: http://www.ossxp.com/\n博客: http://www.worldhello.net/\n微博: http://weibo.com/gotgit/\n电话: 18601196889\n"},{"id":"218943","messageId":"1ef36bab8e64a5fd55e431213fe88662ac35f36d.1369896222.git.worldhello.net@gmail.com","threadId":"33862","inReplyTo":"CANYiYbGiAdMtOwdAf1cgV74cJjaM1dABTE6LEe+LAWoEaUBSXw@mail.gmail.com","subject":"Re: [PATCH v13 02/15] path.c: refactor relative_path(), not only strip prefix","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2013-05-30T06:56:36Z","receivedAt":"2013-05-30T06:56:36Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"2013/5/26 Jiang Xin <worldhello.net@gmail.com>:\n> 2013/5/22 Michael Haggerty <mhagger@alum.mit.edu>:\n>\n>> The old and new versions both seem to be (differently) inconsistent\n>> about when the output has a trailing slash.  What is the rule?\n>\n> The reason for introducing patch 02/15 is that we don't want to reinvent\n> the wheel. Patch 06/15 (git-clean: refactor git-clean into two phases)\n> needs to save relative_path of each git-clean candidate file/directory in\n> del_list, but the public method in path.c (i.e. relative_path) is not\n> powerful, and static method in quote.c (i.e. path_relative) can note be\n> used directly. One way is to enhanced relative_path in path.c, like this\n> patch.\n>\n> Since we combine the two methods (relative_path in path.c and\n> path_relative in quote.c), the new relative_path must be compatible\n> with the original two methods.\n>\n> relative_path in path.c\n> =======================\n>\n> relative_path is called only once in setup.c:\n>\n>         if (getenv(GIT_WORK_TREE_ENVIRONMENT))\n>                 setenv(GIT_WORK_TREE_ENVIRONMENT, \".\", 1);\n>\n>         set_git_dir(relative_path(git_dir, work_tree));\n>         initialized = 1;\n>\n> and set_git_dir only set the environment GIT_DIR_ENVIRONMENT\n> like this:\n>\n>         int set_git_dir(const char *path)\n>         {\n>                 if (setenv(GIT_DIR_ENVIRONMENT, path, 1))\n>                         return error(\"Could not set GIT_DIR to '%s'\", path);\n>                 setup_git_env();\n>                 return 0;\n>         }\n>\n> So the only restriction for relative_path is that the return value can\n> not be blank. If the abs and base arguments for relative_path are\n> the same, the return value should be \".\" (\"./\" is also OK), then\n> set the envionment GIT_DIR_ENVIRONMENT to \".\" (or \"./\").\n>\n> path_relative in quote.c\n> ========================\n>\n> We can not simply move \"path_relative\" in quote.c to \"relative_path\"\n> in path.c directly. It is because:\n>\n> * The arguments for \"relative_path\" are from user input. So must\n>    validate (remove duplicate slash) before use. But \"path_relative\"\n>    does not check duplicate slash in arguments.\n>\n> * \"path_relative\" will return blank string, if abs and base are the same.\n>\n> Also I noticed that \"quote_path_relative\" of quote.c (which calls\n> path_relative) will transform the blank string from path_relative to\n> \"./\" (not \".\")\n>\n>         char *quote_path_relative(const char *in, int len,\n>         ...\n>                 const char *rel = path_relative(in, len, &sb, prefix, -1);\n>                 ...\n>                 if (!out->len)\n>                         strbuf_addstr(out, \"./\");\n>\n> That's why the \"path_relative\" in path.c refactor the output of \".\" into \"./\".\n>\nHi, Junio\n\nI have updated comment and commit log in this patch. You can substitute\ncommit bd4d1 (path.c: refactor relative_path(), not only strip prefix)\nin jx/clean-interactive branch with this one.\n\n-- 8< --\nSubject: [PATCH] path.c: refactor relative_path(), not only strip prefix\n\nOriginal design of relative_path() is simple, just strip the prefix\n(*base) from the absolute path (*abs). In most cases, we need a real\nrelative path, such as: ../foo, ../../bar. That's why there is another\nreimplementation (path_relative()) in quote.c.\n\nBorrowed some codes from path_relative() in quote.c to refactor\nrelative_path() in path.c, so that it could return real relative path,\nand user can reuse this function without reimplement his/her own.\nThe function path_relative() in quote.c will be substituted, and I\nwould use the new relative_path() function when implement the\ninteractive git-clean later.\n\nDifferent results for relative_path() before and after this refactor:\n\n    abs path  base path  relative (original)  relative (refactor)\n    ========  =========  ===================  ===================\n    /a/b/c/   /a/b       c/                   c/\n    /a/b//c/  //a///b/   c/                   c/\n    /a/b      /a/b       .                    ./\n    /a/b/     /a/b       .                    ./\n    /a        /a/b/      /a                   ../\n    /         /a/b/      /                    ../../\n    /a/c      /a/b/      /a/c                 ../c\n    /a/b      (empty)    /a/b                 /a/b\n    /a/b      (null)     /a/b                 /a/b\n    (empty)   /a/b       (empty)              ./\n    (null)    (empty)    (null)               ./\n    (null)    /a/b       (segfault)           ./\n\nYou may notice that return value \".\" has been changed to \"./\".\nIt is because:\n\n * Function quote_path_relative() in quote.c will show the relative\n   path as \"./\" if abs(in) and base(prefix) are the same.\n\n * Function relative_path() is called only once (in setup.c), and\n   it will be OK for the return value as \"./\" instead of \".\".\n\nSigned-off-by: Jiang Xin <worldhello.net@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n cache.h               |   2 +-\n path.c                | 112 +++++++++++++++++++++++++++++++++++++++-----------\n setup.c               |   5 ++-\n t/t0060-path-utils.sh |  27 ++++++------\n test-path-utils.c     |   4 +-\n 5 files changed, 107 insertions(+), 43 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 94ca1..4016c 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -737,7 +737,7 @@ int is_directory(const char *);\n const char *real_path(const char *path);\n const char *real_path_if_valid(const char *path);\n const char *absolute_path(const char *path);\n-const char *relative_path(const char *abs, const char *base);\n+const char *relative_path(const char *abs, const char *base, struct strbuf *sb);\n int normalize_path_copy(char *dst, const char *src);\n int longest_ancestor_length(const char *path, struct string_list *prefixes);\n char *strip_path_suffix(const char *path, const char *suffix);\ndiff --git a/path.c b/path.c\nindex 04ff..8ff16 100644\n--- a/path.c\n+++ b/path.c\n@@ -441,42 +441,104 @@ int adjust_shared_perm(const char *path)\n \treturn 0;\n }\n \n-const char *relative_path(const char *abs, const char *base)\n+/*\n+ * Give relative path for abs to base.\n+ *\n+ * The strbuf may or may not be used, so do not assume it contains the\n+ * returned path.\n+ */\n+const char *relative_path(const char *abs, const char *base,\n+\t\t\t  struct strbuf *sb)\n {\n-\tstatic char buf[PATH_MAX + 1];\n-\tint i = 0, j = 0;\n-\n-\tif (!base || !base[0])\n+\tint abs_off, base_off, i, j;\n+\tint abs_len, base_len;\n+\n+\tabs_len = abs ? strlen(abs) : 0;\n+\tbase_len = base ? strlen(base) : 0;\n+\tabs_off = 0;\n+\tbase_off = 0;\n+\ti = 0;\n+\tj = 0;\n+\n+\tif (!abs_len)\n+\t\treturn \"./\";\n+\telse if (!base_len)\n \t\treturn abs;\n-\twhile (base[i]) {\n+\n+\twhile (i < base_len && j < abs_len && base[i] == abs[j]) {\n \t\tif (is_dir_sep(base[i])) {\n-\t\t\tif (!is_dir_sep(abs[j]))\n-\t\t\t\treturn abs;\n \t\t\twhile (is_dir_sep(base[i]))\n \t\t\t\ti++;\n \t\t\twhile (is_dir_sep(abs[j]))\n \t\t\t\tj++;\n-\t\t\tcontinue;\n-\t\t} else if (abs[j] != base[i]) {\n+\t\t\tbase_off = i;\n+\t\t\tabs_off = j;\n+\t\t} else {\n+\t\t\ti++;\n+\t\t\tj++;\n+\t\t}\n+\t}\n+\n+\tif (\n+\t    /* base seems like prefix of abs */\n+\t    i >= base_len &&\n+\t    /*\n+\t     * but \"/foo\" is not a prefix of \"/foobar\"\n+\t     * (i.e. base not end with '/')\n+\t     */\n+\t    base_off < base_len) {\n+\t\tif (j >= abs_len) {\n+\t\t\t/* abs=\"/a/b\", base=\"/a/b\" */\n+\t\t\tabs_off = abs_len;\n+\t\t} else if (is_dir_sep(abs[j])) {\n+\t\t\t/* abs=\"/a/b/c\", base=\"/a/b\" */\n+\t\t\twhile (is_dir_sep(abs[j]))\n+\t\t\t\tj++;\n+\t\t\tabs_off = j;\n+\t\t} else {\n+\t\t\t/* abs=\"/a/bbb/c\", base=\"/a/b\" */\n+\t\t\ti = base_off;\n+\t\t}\n+\t} else if (\n+\t\t   /* abs is short than base (prefix of base) */\n+\t\t   j >= abs_len &&\n+\t\t   /* abs not end with '/' */\n+\t\t   abs_off < abs_len) {\n+\t\tif (is_dir_sep(base[i])) {\n+\t\t\t/* abs=\"/a/b\", base=\"/a/b/c/\" */\n+\t\t\twhile (is_dir_sep(base[i]))\n+\t\t\t\ti++;\n+\t\t\tabs_off = abs_len;\n+\t\t}\n+\t}\n+\tabs += abs_off;\n+\tabs_len -= abs_off;\n+\n+\tif (i >= base_len) {\n+\t\tif (!abs_len)\n+\t\t\treturn \"./\";\n+\t\telse\n \t\t\treturn abs;\n+\t}\n+\n+\tstrbuf_reset(sb);\n+\tstrbuf_grow(sb, abs_len);\n+\n+\twhile (i < base_len) {\n+\t\tif (is_dir_sep(base[i])) {\n+\t\t\tstrbuf_addstr(sb, \"../\");\n+\t\t\twhile (is_dir_sep(base[i]))\n+\t\t\t\ti++;\n+\t\t\tcontinue;\n \t\t}\n \t\ti++;\n-\t\tj++;\n \t}\n-\tif (\n-\t    /* \"/foo\" is a prefix of \"/foo\" */\n-\t    abs[j] &&\n-\t    /* \"/foo\" is not a prefix of \"/foobar\" */\n-\t    !is_dir_sep(base[i-1]) && !is_dir_sep(abs[j])\n-\t   )\n-\t\treturn abs;\n-\twhile (is_dir_sep(abs[j]))\n-\t\tj++;\n-\tif (!abs[j])\n-\t\tstrcpy(buf, \".\");\n-\telse\n-\t\tstrcpy(buf, abs + j);\n-\treturn buf;\n+\tif (!is_dir_sep(base[base_len - 1]))\n+\t\tstrbuf_addstr(sb, \"../\");\n+\n+\tstrbuf_addstr(sb, abs);\n+\n+\treturn sb->buf;\n }\n \n /*\ndiff --git a/setup.c b/setup.c\nindex 94c1e..0d9ea 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -360,6 +360,7 @@ int is_inside_work_tree(void)\n \n void setup_work_tree(void)\n {\n+\tstruct strbuf sb = STRBUF_INIT;\n \tconst char *work_tree, *git_dir;\n \tstatic int initialized = 0;\n \n@@ -379,8 +380,10 @@ void setup_work_tree(void)\n \tif (getenv(GIT_WORK_TREE_ENVIRONMENT))\n \t\tsetenv(GIT_WORK_TREE_ENVIRONMENT, \".\", 1);\n \n-\tset_git_dir(relative_path(git_dir, work_tree));\n+\tset_git_dir(relative_path(git_dir, work_tree, &sb));\n \tinitialized = 1;\n+\n+\tstrbuf_release(&sb);\n }\n \n static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\ndiff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh\nindex 2199b..dfe47 100755\n--- a/t/t0060-path-utils.sh\n+++ b/t/t0060-path-utils.sh\n@@ -191,22 +191,19 @@ test_expect_success SYMLINKS 'real path works on symlinks' '\n relative_path /a/b/c/\t/a/b/\t\tc/\n relative_path /a/b/c/\t/a/b\t\tc/\n relative_path /a//b//c/\t//a/b//\t\tc/\n-relative_path /a/b\t/a/b\t\t.\n-relative_path /a/b/\t/a/b\t\t.\n-relative_path /a\t/a/b\t\t/a\n-relative_path /\t\t/a/b/\t\t/\n-relative_path /a/c\t/a/b/\t\t/a/c\n-relative_path /a/c\t/a/b\t\t/a/c\n+relative_path /a/b\t/a/b\t\t./\n+relative_path /a/b/\t/a/b\t\t./\n+relative_path /a\t/a/b\t\t../\n+relative_path /\t\t/a/b/\t\t../../\n+relative_path /a/c\t/a/b/\t\t../c\n+relative_path /a/c\t/a/b\t\t../c\n relative_path /a/b\t\"<empty>\"\t/a/b\n relative_path /a/b \t\"<null>\"\t/a/b\n-relative_path \"<empty>\"\t/a/b\t\t\"(empty)\"\n-relative_path \"<empty>\"\t\"<empty>\"\t\"(empty)\"\n-relative_path \"<empty>\"\t\"<null>\"\t\"(empty)\"\n-relative_path \"<null>\"\t\"<empty>\"\t\"(null)\"\n-relative_path \"<null>\"\t\"<null>\"\t\"(null)\"\n-\n-test_expect_failure 'relative path: <null> /a/b => segfault' '\n-\ttest-path-utils relative_path \"<null>\" \"/a/b\"\n-'\n+relative_path \"<empty>\"\t/a/b\t\t./\n+relative_path \"<empty>\"\t\"<empty>\"\t./\n+relative_path \"<empty>\"\t\"<null>\"\t./\n+relative_path \"<null>\"\t\"<empty>\"\t./\n+relative_path \"<null>\"\t\"<null>\"\t./\n+relative_path \"<null>\"\t/a/b\t\t./\n \n test_done\ndiff --git a/test-path-utils.c b/test-path-utils.c\nindex dcc530..95ef4 100644\n--- a/test-path-utils.c\n+++ b/test-path-utils.c\n@@ -117,14 +117,16 @@ int main(int argc, char **argv)\n \t}\n \n \tif (argc == 4 && !strcmp(argv[1], \"relative_path\")) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n \t\tconst char *abs, *base, *rel;\n \t\tnormalize_argv_string(&abs, argv[2]);\n \t\tnormalize_argv_string(&base, argv[3]);\n-\t\trel = relative_path(abs, base);\n+\t\trel = relative_path(abs, base, &sb);\n \t\tif (!rel)\n \t\t\tputs(\"(null)\");\n \t\telse\n \t\t\tputs(strlen(rel) > 0 ? rel : \"(empty)\");\n+\t\tstrbuf_release(&sb);\n \t\treturn 0;\n \t}\n \n-- \n1.8.3.rc3.368.g7c798dd\n"}]}