{"thread":{"id":"28653","subject":"[PATCH 0/6] Negation magic pathspec","startedAt":"2011-10-11T22:44:37Z","lastAt":"2011-10-13T06:06:55Z","messageCount":12,"participants":["Nguyễn Thái Ngọc Duy","Junio C Hamano","Nguyen Thai Ngoc Duy"],"isPatch":true,"patchVersion":1,"patchTotal":6},"messages":[{"id":"177406","messageId":"1318373083-13840-1-git-send-email-pclouds@gmail.com","threadId":"28653","inReplyTo":null,"subject":"[PATCH 0/6] Negation magic pathspec","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2011-10-11T22:44:37Z","receivedAt":"2011-10-11T22:44:37Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"After the last round toying with .gitignore mechanism as a way to\nexclude paths, I have finally got back to the negative pathspec.\n\nI'm still struggling with read_directory() rewrite so that struct\npathspec can be used throughout git, but now realized we can at least\nenable magic for certain commands and die() on those that don't.\nThis may help move magic pathspec patches forward.\n\nThe nice thing about this series is that negative pathspec patch is\nsmall and simple, much less headache to review than the previous\nversion (and as a consequence, not as powerful).\n\nSo here it is to gather comments whether we should go this way. Very\nmuch WIP, I have not even run \"make test\".\n\nNguyễn Thái Ngọc Duy (6):\n  Recognize magic pathspec as filenames\n  Replace has_wildcard with PATHSPEC_NOGLOB\n  Convert prefix_pathspec() to produce struct pathspec_item\n  Implement parse_pathspec()\n  Convert simple init_pathspec() cases to parse_pathspec()\n  Implement negative pathspec\n\n Documentation/glossary-content.txt |    8 ++--\n builtin/grep.c                     |    4 +-\n builtin/ls-files.c                 |    2 +-\n builtin/ls-tree.c                  |    6 +-\n builtin/reset.c                    |    2 +-\n cache.h                            |   29 +++++++++++-\n dir.c                              |   85 +++++++++++++++++++++++++++--------\n revision.c                         |    9 ++--\n setup.c                            |   56 +++++++++++-------------\n tree-walk.c                        |   44 ++++++++++++++++---\n 10 files changed, 169 insertions(+), 76 deletions(-)\n\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"177407","messageId":"1318373083-13840-2-git-send-email-pclouds@gmail.com","threadId":"28653","inReplyTo":"1318373083-13840-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 1/6] Recognize magic pathspec as filenames","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2011-10-11T22:44:38Z","receivedAt":"2011-10-11T22:44:38Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n .. so that \"git log :/\" works, not so sure this is correct though\n\n setup.c |   16 +++++++---------\n 1 files changed, 7 insertions(+), 9 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 27c1d47..08f605b 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -58,15 +58,8 @@ static void NORETURN die_verify_filename(const char *prefix, const char *arg)\n \tunsigned char sha1[20];\n \tunsigned mode;\n \n-\t/*\n-\t * Saying \"'(icase)foo' does not exist in the index\" when the\n-\t * user gave us \":(icase)foo\" is just stupid.  A magic pathspec\n-\t * begins with a colon and is followed by a non-alnum; do not\n-\t * let get_sha1_with_mode_1(only_to_die=1) to even trigger.\n-\t */\n-\tif (!(arg[0] == ':' && !isalnum(arg[1])))\n-\t\t/* try a detailed diagnostic ... */\n-\t\tget_sha1_with_mode_1(arg, sha1, &mode, 1, prefix);\n+\t/* try a detailed diagnostic ... */\n+\tget_sha1_with_mode_1(arg, sha1, &mode, 1, prefix);\n \n \t/* ... or fall back the most general message. */\n \tdie(\"ambiguous argument '%s': unknown revision or path not in the working tree.\\n\"\n@@ -85,6 +78,11 @@ void verify_filename(const char *prefix, const char *arg)\n {\n \tif (*arg == '-')\n \t\tdie(\"bad flag '%s' used after filename\", arg);\n+\n+\t/* If it's magic pathspec, just assume it's file name */\n+\tif (arg[0] == ':' && is_pathspec_magic(arg[1]))\n+\t\treturn;\n+\n \tif (check_filename(prefix, arg))\n \t\treturn;\n \tdie_verify_filename(prefix, arg);\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"177408","messageId":"1318373083-13840-3-git-send-email-pclouds@gmail.com","threadId":"28653","inReplyTo":"1318373083-13840-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 2/6] Replace has_wildcard with PATHSPEC_NOGLOB","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2011-10-11T22:44:39Z","receivedAt":"2011-10-11T22:44:39Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Note though \"noglob\" magic is not implemented yet, only its definition\nfor internal use.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/ls-files.c |    2 +-\n builtin/ls-tree.c  |    4 ++--\n cache.h            |   22 ++++++++++++++++++++--\n dir.c              |   11 +++++++----\n setup.c            |   17 -----------------\n tree-walk.c        |    7 +++----\n 6 files changed, 33 insertions(+), 30 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex e8a800d..e687157 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -326,7 +326,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix)\n \t\tmatchbuf[0] = prefix;\n \t\tmatchbuf[1] = NULL;\n \t\tinit_pathspec(&pathspec, matchbuf);\n-\t\tpathspec.items[0].use_wildcard = 0;\n+\t\tpathspec.items[0].magic = PATHSPEC_NOGLOB;\n \t} else\n \t\tinit_pathspec(&pathspec, NULL);\n \tif (read_tree(tree, 1, &pathspec))\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 6b666e1..f0fa7dd 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -168,8 +168,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \n \tinit_pathspec(&pathspec, get_pathspec(prefix, argv + 1));\n \tfor (i = 0; i < pathspec.nr; i++)\n-\t\tpathspec.items[i].use_wildcard = 0;\n-\tpathspec.has_wildcard = 0;\n+\t\tpathspec.items[i].magic = PATHSPEC_NOGLOB;\n+\tpathspec.magic |= PATHSPEC_NOGLOB;\n \ttree = parse_tree_indirect(sha1);\n \tif (!tree)\n \t\tdie(\"not a tree object\");\ndiff --git a/cache.h b/cache.h\nindex 82e12c8..fe46f1e 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -521,16 +521,34 @@ extern int index_name_is_other(const struct index_state *, const char *, int);\n extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);\n extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);\n \n+/*\n+ * Magic pathspec\n+ *\n+ * NEEDSWORK: These need to be moved to dir.h or even to a new\n+ * pathspec.h when we restructure get_pathspec() users to use the\n+ * \"struct pathspec\" interface.\n+ *\n+ * Possible future magic semantics include stuff like:\n+ *\n+ *\t{ PATHSPEC_NOGLOB, '!', \"noglob\" },\n+ *\t{ PATHSPEC_ICASE, '\\0', \"icase\" },\n+ *\t{ PATHSPEC_RECURSIVE, '*', \"recursive\" },\n+ *\t{ PATHSPEC_REGEXP, '\\0', \"regexp\" },\n+ *\n+ */\n+#define PATHSPEC_FROMTOP    (1<<0)\n+#define PATHSPEC_NOGLOB     (1<<1)\n+\n struct pathspec {\n \tconst char **raw; /* get_pathspec() result, not freed by free_pathspec() */\n \tint nr;\n-\tunsigned int has_wildcard:1;\n+\tunsigned magic;\n \tunsigned int recursive:1;\n \tint max_depth;\n \tstruct pathspec_item {\n \t\tconst char *match;\n \t\tint len;\n-\t\tunsigned int use_wildcard:1;\n+\t\tunsigned magic;\n \t} *items;\n };\n \ndiff --git a/dir.c b/dir.c\nindex 08281d2..6c82615 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -230,7 +230,7 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,\n \t\t\treturn MATCHED_RECURSIVELY;\n \t}\n \n-\tif (item->use_wildcard && !fnmatch(match, name, 0))\n+\tif (!(item->magic & PATHSPEC_NOGLOB) && !fnmatch(match, name, 0))\n \t\treturn MATCHED_FNMATCH;\n \n \treturn 0;\n@@ -1264,19 +1264,22 @@ int init_pathspec(struct pathspec *pathspec, const char **paths)\n \t\tp++;\n \tpathspec->raw = paths;\n \tpathspec->nr = p - paths;\n+\tpathspec->magic = PATHSPEC_NOGLOB;\n \tif (!pathspec->nr)\n \t\treturn 0;\n \n \tpathspec->items = xmalloc(sizeof(struct pathspec_item)*pathspec->nr);\n+\tmemset(pathspec->items, 0, sizeof(struct pathspec_item)*pathspec->nr);\n \tfor (i = 0; i < pathspec->nr; i++) {\n \t\tstruct pathspec_item *item = pathspec->items+i;\n \t\tconst char *path = paths[i];\n \n \t\titem->match = path;\n \t\titem->len = strlen(path);\n-\t\titem->use_wildcard = !no_wildcard(path);\n-\t\tif (item->use_wildcard)\n-\t\t\tpathspec->has_wildcard = 1;\n+\t\tif (no_wildcard(path))\n+\t\t\titem->magic |= PATHSPEC_NOGLOB;\n+\t\telse\n+\t\t\tpathspec->magic &= ~PATHSPEC_NOGLOB;\n \t}\n \n \tqsort(pathspec->items, pathspec->nr,\ndiff --git a/setup.c b/setup.c\nindex 08f605b..35db910 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -105,23 +105,6 @@ void verify_non_filename(const char *prefix, const char *arg)\n \t    \"Use '--' to separate filenames from revisions\", arg);\n }\n \n-/*\n- * Magic pathspec\n- *\n- * NEEDSWORK: These need to be moved to dir.h or even to a new\n- * pathspec.h when we restructure get_pathspec() users to use the\n- * \"struct pathspec\" interface.\n- *\n- * Possible future magic semantics include stuff like:\n- *\n- *\t{ PATHSPEC_NOGLOB, '!', \"noglob\" },\n- *\t{ PATHSPEC_ICASE, '\\0', \"icase\" },\n- *\t{ PATHSPEC_RECURSIVE, '*', \"recursive\" },\n- *\t{ PATHSPEC_REGEXP, '\\0', \"regexp\" },\n- *\n- */\n-#define PATHSPEC_FROMTOP    (1<<0)\n-\n static struct pathspec_magic {\n \tunsigned bit;\n \tchar mnemonic; /* this cannot be ':'! */\ndiff --git a/tree-walk.c b/tree-walk.c\nindex 808bb55..db07fd6 100644\n--- a/tree-walk.c\n+++ b/tree-walk.c\n@@ -586,7 +586,7 @@ int tree_entry_interesting(const struct name_entry *entry,\n {\n \tint i;\n \tint pathlen, baselen = base->len - base_offset;\n-\tint never_interesting = ps->has_wildcard ? 0 : -1;\n+\tint never_interesting = ps->magic & PATHSPEC_NOGLOB ? -1 : 0;\n \n \tif (!ps->nr) {\n \t\tif (!ps->recursive || ps->max_depth == -1)\n@@ -625,7 +625,7 @@ int tree_entry_interesting(const struct name_entry *entry,\n \t\t\t\t\t&never_interesting))\n \t\t\t\treturn 1;\n \n-\t\t\tif (ps->items[i].use_wildcard) {\n+\t\t\tif (!(ps->items[i].magic & PATHSPEC_NOGLOB)) {\n \t\t\t\tif (!fnmatch(match + baselen, entry->path, 0))\n \t\t\t\t\treturn 1;\n \n@@ -636,12 +636,11 @@ int tree_entry_interesting(const struct name_entry *entry,\n \t\t\t\tif (ps->recursive && S_ISDIR(entry->mode))\n \t\t\t\t\treturn 1;\n \t\t\t}\n-\n \t\t\tcontinue;\n \t\t}\n \n match_wildcards:\n-\t\tif (!ps->items[i].use_wildcard)\n+\t\tif (ps->items[i].magic & PATHSPEC_NOGLOB)\n \t\t\tcontinue;\n \n \t\t/*\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"177409","messageId":"1318373083-13840-4-git-send-email-pclouds@gmail.com","threadId":"28653","inReplyTo":"1318373083-13840-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 3/6] Convert prefix_pathspec() to produce struct pathspec_item","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2011-10-11T22:44:40Z","receivedAt":"2011-10-11T22:44:40Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"New field plain_len is used to mark the first part of 'match' where no\nmagic can be applied. It's not used though. tree_entry_interesting()\nshould learn to utilize it.\n\nNow we can show get_pathspec() what magic a pathspec has. Make sure\nonly certain magic is accepted because more will come in future and\nnot all of them can be converted to plain string like\nPATHSPEC_FROMTOP.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n match_pathspec_depth() should also check for unsupported magic..\n\n cache.h |    1 +\n setup.c |   22 +++++++++++++++++-----\n 2 files changed, 18 insertions(+), 5 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex fe46f1e..17df06f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -548,6 +548,7 @@ struct pathspec {\n \tstruct pathspec_item {\n \t\tconst char *match;\n \t\tint len;\n+\t\tint plain_len;\t/* match[0..plain_len-1] does not have any kind of magic*/\n \t\tunsigned magic;\n \t} *items;\n };\ndiff --git a/setup.c b/setup.c\nindex 35db910..8f1c2c0 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -126,7 +126,8 @@ static struct pathspec_magic {\n  * the prefix part must always match literally, and a single stupid\n  * string cannot express such a case.\n  */\n-static const char *prefix_pathspec(const char *prefix, int prefixlen, const char *elt)\n+static const char *prefix_pathspec(struct pathspec_item *item, const char *prefix,\n+\t\t\t\t   int prefixlen, const char *elt)\n {\n \tunsigned magic = 0;\n \tconst char *copyfrom = elt;\n@@ -181,10 +182,17 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const char\n \t\t\tcopyfrom++;\n \t}\n \n+\tmemset(item, 0, sizeof(*item));\n+\titem->magic = magic;\n+\n \tif (magic & PATHSPEC_FROMTOP)\n-\t\treturn xstrdup(copyfrom);\n-\telse\n-\t\treturn prefix_path(prefix, prefixlen, copyfrom);\n+\t\titem->match = xstrdup(copyfrom);\n+\telse {\n+\t\titem->match = prefix_path(prefix, prefixlen, copyfrom);\n+\t\titem->plain_len = prefixlen;\n+\t}\n+\titem->len = strlen(item->match);\n+\treturn 0;\n }\n \n const char **get_pathspec(const char *prefix, const char **pathspec)\n@@ -208,7 +216,11 @@ const char **get_pathspec(const char *prefix, const char **pathspec)\n \tdst = pathspec;\n \tprefixlen = prefix ? strlen(prefix) : 0;\n \twhile (*src) {\n-\t\t*(dst++) = prefix_pathspec(prefix, prefixlen, *src);\n+\t\tstruct pathspec_item item;\n+\t\tprefix_pathspec(&item, prefix, prefixlen, *src);\n+\t\tif (item.magic & ~PATHSPEC_FROMTOP)\n+\t\t\tdie(\"Unsupported magic pathspec %s\", *src);\n+\t\t*(dst++) = item.match;\n \t\tsrc++;\n \t}\n \t*dst = NULL;\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"177410","messageId":"1318373083-13840-5-git-send-email-pclouds@gmail.com","threadId":"28653","inReplyTo":"1318373083-13840-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 4/6] Implement parse_pathspec()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2011-10-11T22:44:41Z","receivedAt":"2011-10-11T22:44:41Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This function is the same as get_pathspec() except that it produces\nstruct pathspec directly.\n\nno_prefix code path is necessary because init_pathspec() can be called\nwithout get_pathspec_old() in \"diff --no-index\" case. Without this\nexception, prefix_path() will be eventually called and die because\nthere is not worktree.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h |    5 ++++\n dir.c   |   82 ++++++++++++++++++++++++++++++++++++++++++++++----------------\n setup.c |    4 +-\n 3 files changed, 68 insertions(+), 23 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 17df06f..719d4a3 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -443,6 +443,9 @@ extern void set_git_work_tree(const char *tree);\n \n #define ALTERNATE_DB_ENVIRONMENT \"GIT_ALTERNATE_OBJECT_DIRECTORIES\"\n \n+struct pathspec_item;\n+extern const char *prefix_pathspec(struct pathspec_item *item, const char *prefix,\n+\t\t\t\t   int prefixlen, const char *elt);\n extern const char **get_pathspec(const char *prefix, const char **pathspec);\n extern const char *pathspec_prefix(const char *prefix, const char **pathspec);\n extern void setup_work_tree(void);\n@@ -554,6 +557,8 @@ struct pathspec {\n };\n \n extern int init_pathspec(struct pathspec *, const char **);\n+extern int parse_pathspec(struct pathspec *pathspec, const char *prefix,\n+\t\t\t  int argc, const char **argv);\n extern void free_pathspec(struct pathspec *);\n extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);\n \ndiff --git a/dir.c b/dir.c\nindex 6c82615..d38af0f 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -18,6 +18,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, in\n \tint check_only, const struct path_simplify *simplify);\n static int get_dtype(struct dirent *de, const char *path, int len);\n \n+static const char *no_prefix = \"We do not want outside repository check.\";\n+\n /* helper string functions with support for the ignore_case flag */\n int strcmp_icase(const char *a, const char *b)\n {\n@@ -1252,34 +1254,62 @@ static int pathspec_item_cmp(const void *a_, const void *b_)\n \treturn strcmp(a->match, b->match);\n }\n \n-int init_pathspec(struct pathspec *pathspec, const char **paths)\n+extern const char *prefix_pathspec(struct pathspec_item *item, const char *prefix,\n+\t\t\t\t   int prefixlen, const char *elt);\n+int parse_pathspec(struct pathspec *pathspec, const char *prefix,\n+\t\t   int argc, const char **argv)\n {\n-\tconst char **p = paths;\n-\tint i;\n+\tstruct pathspec_item *pitem;\n+\tconst char **dst;\n+\tint prefixlen;\n \n \tmemset(pathspec, 0, sizeof(*pathspec));\n-\tif (!p)\n-\t\treturn 0;\n-\twhile (*p)\n-\t\tp++;\n-\tpathspec->raw = paths;\n-\tpathspec->nr = p - paths;\n-\tpathspec->magic = PATHSPEC_NOGLOB;\n-\tif (!pathspec->nr)\n+\n+\tif (argc == -1) {\n+\t\targc = 0;\n+\t\tfor (dst = argv; *dst; dst++)\n+\t\t\targc++;\n+\t}\n+\n+\tif ((!prefix || prefix == no_prefix) && !argc)\n \t\treturn 0;\n \n-\tpathspec->items = xmalloc(sizeof(struct pathspec_item)*pathspec->nr);\n-\tmemset(pathspec->items, 0, sizeof(struct pathspec_item)*pathspec->nr);\n-\tfor (i = 0; i < pathspec->nr; i++) {\n-\t\tstruct pathspec_item *item = pathspec->items+i;\n-\t\tconst char *path = paths[i];\n+\tif (!argc) {\n+\t\tstatic const char *spec[2];\n+\t\tspec[0] = prefix;\n+\t\tspec[1] = NULL;\n+\t\tinit_pathspec(pathspec, spec);\n+\t\tpathspec->items[0].plain_len = pathspec->items[0].len;\n+\t\treturn 0;\n+\t}\n \n-\t\titem->match = path;\n-\t\titem->len = strlen(path);\n-\t\tif (no_wildcard(path))\n-\t\t\titem->magic |= PATHSPEC_NOGLOB;\n+\tprefixlen = prefix && prefix != no_prefix ? strlen(prefix) : 0;\n+\tpathspec->nr = argc; /* be optimistic, lower it later if necessary */\n+\tpathspec->items = xmalloc(sizeof(struct pathspec_item) * argc);\n+\tpathspec->raw = argv;\n+\tpathspec->magic = PATHSPEC_NOGLOB;\n+\tpitem = pathspec->items;\n+\tdst = argv;\n+\n+\twhile (*argv) {\n+\t\tif (prefix == no_prefix) {\n+\t\t\tmemset(pitem, 0, sizeof(*pitem));\n+\t\t\tpitem->match = *argv;\n+\t\t\tpitem->len = strlen(pitem->match);\n+\t\t}\n+\t\telse\n+\t\t\tprefix_pathspec(pitem, prefix, prefixlen, *argv);\n+\t\t*dst = *argv++;\n+\t\tif (pitem->match && pitem->len) {\n+\t\t\tif (no_wildcard(pitem->match + pitem->plain_len))\n+\t\t\t\tpitem->magic |= PATHSPEC_NOGLOB;\n+\t\t\telse\n+\t\t\t\tpathspec->magic &= ~PATHSPEC_NOGLOB;\n+\t\t\tpitem++;\n+\t\t\tdst++;\n+\t\t}\n \t\telse\n-\t\t\tpathspec->magic &= ~PATHSPEC_NOGLOB;\n+\t\t\tpathspec->nr--;\n \t}\n \n \tqsort(pathspec->items, pathspec->nr,\n@@ -1288,8 +1318,18 @@ int init_pathspec(struct pathspec *pathspec, const char **paths)\n \treturn 0;\n }\n \n+int init_pathspec(struct pathspec *pathspec, const char **paths)\n+{\n+\tconst char **p = paths;\n+\twhile (p && *p)\n+\t\tp++;\n+\treturn parse_pathspec(pathspec, no_prefix, p - paths, paths);\n+}\n+\n void free_pathspec(struct pathspec *pathspec)\n {\n+\t/* memory leak: pathspec_item->match likely be xstrdup'd by\n+\t   prefix_pathspec */\n \tfree(pathspec->items);\n \tpathspec->items = NULL;\n }\ndiff --git a/setup.c b/setup.c\nindex 8f1c2c0..b074210 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -126,8 +126,8 @@ static struct pathspec_magic {\n  * the prefix part must always match literally, and a single stupid\n  * string cannot express such a case.\n  */\n-static const char *prefix_pathspec(struct pathspec_item *item, const char *prefix,\n-\t\t\t\t   int prefixlen, const char *elt)\n+const char *prefix_pathspec(struct pathspec_item *item, const char *prefix,\n+\t\t\t    int prefixlen, const char *elt)\n {\n \tunsigned magic = 0;\n \tconst char *copyfrom = elt;\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"177411","messageId":"1318373083-13840-6-git-send-email-pclouds@gmail.com","threadId":"28653","inReplyTo":"1318373083-13840-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 5/6] Convert simple init_pathspec() cases to parse_pathspec()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2011-10-11T22:44:42Z","receivedAt":"2011-10-11T22:44:42Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"These commands can now take advantage of new pathspec magic, if both\ntree_entry_interesting() and match_pathspec_depth() support them properly\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/grep.c    |    4 +---\n builtin/ls-tree.c |    2 +-\n builtin/reset.c   |    2 +-\n revision.c        |    9 +++++----\n 4 files changed, 8 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex a286692..e171a9d 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -759,7 +759,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tconst char *show_in_pager = NULL, *default_pager = \"dummy\";\n \tstruct grep_opt opt;\n \tstruct object_array list = OBJECT_ARRAY_INIT;\n-\tconst char **paths = NULL;\n \tstruct pathspec pathspec;\n \tstruct string_list path_list = STRING_LIST_INIT_NODUP;\n \tint i;\n@@ -1020,8 +1019,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t\tverify_filename(prefix, argv[j]);\n \t}\n \n-\tpaths = get_pathspec(prefix, argv + i);\n-\tinit_pathspec(&pathspec, paths);\n+\tparse_pathspec(&pathspec, prefix, -1, argv + i);\n \tpathspec.max_depth = opt.max_depth;\n \tpathspec.recursive = 1;\n \ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex f0fa7dd..b717bb2 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -166,7 +166,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \tif (get_sha1(argv[0], sha1))\n \t\tdie(\"Not a valid object name %s\", argv[0]);\n \n-\tinit_pathspec(&pathspec, get_pathspec(prefix, argv + 1));\n+\tparse_pathspec(&pathspec, prefix, -1, argv + 1);\n \tfor (i = 0; i < pathspec.nr; i++)\n \t\tpathspec.items[i].magic = PATHSPEC_NOGLOB;\n \tpathspec.magic |= PATHSPEC_NOGLOB;\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 811e8e2..8126e69 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -176,7 +176,7 @@ static int read_from_tree(const char *prefix, const char **argv,\n \tstruct diff_options opt;\n \n \tmemset(&opt, 0, sizeof(opt));\n-\tdiff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);\n+\tparse_pathspec(&opt.pathspec, prefix, -1, argv);\n \topt.output_format = DIFF_FORMAT_CALLBACK;\n \topt.format_callback = update_index_from_diff;\n \topt.format_callback_data = &index_was_discarded;\ndiff --git a/revision.c b/revision.c\nindex 9bae329..cba32e8 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1770,8 +1770,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \t\t */\n \t\tALLOC_GROW(prune_data.path, prune_data.nr+1, prune_data.alloc);\n \t\tprune_data.path[prune_data.nr++] = NULL;\n-\t\tinit_pathspec(&revs->prune_data,\n-\t\t\t      get_pathspec(revs->prefix, prune_data.path));\n+\t\tparse_pathspec(&revs->prune_data, revs->prefix, -1, prune_data.path);\n \t}\n \n \tif (revs->def == NULL)\n@@ -1804,12 +1803,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \t\trevs->limited = 1;\n \n \tif (revs->prune_data.nr) {\n-\t\tdiff_tree_setup_paths(revs->prune_data.raw, &revs->pruning);\n+\t\t/* Careful, we share a lot of pointers here, do not free 1st arg */\n+\t\tmemcpy(&revs->pruning.pathspec, &revs->prune_data, sizeof(struct pathspec));\n \t\t/* Can't prune commits with rename following: the paths change.. */\n \t\tif (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))\n \t\t\trevs->prune = 1;\n \t\tif (!revs->full_diff)\n-\t\t\tdiff_tree_setup_paths(revs->prune_data.raw, &revs->diffopt);\n+\t\t\t/* Careful, we share a lot of pointers here, do not free 1st arg */\n+\t\t\tmemcpy(&revs->diffopt.pathspec, &revs->prune_data, sizeof(struct pathspec));\n \t}\n \tif (revs->combine_merges)\n \t\trevs->ignore_merges = 0;\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"177412","messageId":"1318373083-13840-7-git-send-email-pclouds@gmail.com","threadId":"28653","inReplyTo":"1318373083-13840-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 6/6] Implement negative pathspec","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2011-10-11T22:44:43Z","receivedAt":"2011-10-11T22:44:43Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n I really like the mnemonic ^ but it's regex. \":^Documentation\" looks\n nicer than \":~Documentation\". Do we plan on supporting regex in\n pathspec too?\n\n We should mention these magic in a less obscure document. Glossary is\n mostly for developer discussions. git-diff may be a good place\n because it's one of the two frequently used commands (the other one\n is grep) that benefit magic the most (with a short reference from\n git.txt)\n\n Documentation/glossary-content.txt |    8 +++---\n cache.h                            |    1 +\n dir.c                              |    2 +\n setup.c                            |    1 +\n tree-walk.c                        |   37 +++++++++++++++++++++++++++++++++--\n 5 files changed, 42 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex 3595b58..9a2765d 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -319,12 +319,12 @@ top `/`;;\n \tThe magic word `top` (mnemonic: `/`) makes the pattern match\n \tfrom the root of the working tree, even when you are running\n \tthe command from inside a subdirectory.\n+\n+exclude `~`;;\n+\tThe magic word `exclude` (mnemonic: `~`) excludes paths that\n+\tmatch the pattern.\n --\n +\n-Currently only the slash `/` is recognized as the \"magic signature\",\n-but it is envisioned that we will support more types of magic in later\n-versions of git.\n-+\n A pathspec with only a colon means \"there is no pathspec\". This form\n should not be combined with other pathspec.\n \ndiff --git a/cache.h b/cache.h\nindex 719d4a3..75fe589 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -541,6 +541,7 @@ extern int ie_modified(const struct index_state *, struct cache_entry *, struct\n  */\n #define PATHSPEC_FROMTOP    (1<<0)\n #define PATHSPEC_NOGLOB     (1<<1)\n+#define PATHSPEC_NEGATE     (1<<2)\n \n struct pathspec {\n \tconst char **raw; /* get_pathspec() result, not freed by free_pathspec() */\ndiff --git a/dir.c b/dir.c\nindex d38af0f..46dd35f 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1305,6 +1305,8 @@ int parse_pathspec(struct pathspec *pathspec, const char *prefix,\n \t\t\t\tpitem->magic |= PATHSPEC_NOGLOB;\n \t\t\telse\n \t\t\t\tpathspec->magic &= ~PATHSPEC_NOGLOB;\n+\t\t\tif (pitem->magic & PATHSPEC_NEGATE)\n+\t\t\t\tpathspec->magic |= PATHSPEC_NEGATE;\n \t\t\tpitem++;\n \t\t\tdst++;\n \t\t}\ndiff --git a/setup.c b/setup.c\nindex b074210..42beb9b 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -111,6 +111,7 @@ static struct pathspec_magic {\n \tconst char *name;\n } pathspec_magic[] = {\n \t{ PATHSPEC_FROMTOP, '/', \"top\" },\n+\t{ PATHSPEC_NEGATE, '~', \"exclude\" },\n };\n \n /*\ndiff --git a/tree-walk.c b/tree-walk.c\nindex db07fd6..936b5da 100644\n--- a/tree-walk.c\n+++ b/tree-walk.c\n@@ -580,15 +580,17 @@ static int match_dir_prefix(const char *base, int baselen,\n  *  - zero for no\n  *  - negative for \"no, and no subsequent entries will be either\"\n  */\n-int tree_entry_interesting(const struct name_entry *entry,\n-\t\t\t   struct strbuf *base, int base_offset,\n-\t\t\t   const struct pathspec *ps)\n+static int tree_entry_interesting_1(const struct name_entry *entry,\n+\t\t\t\t    struct strbuf *base, int base_offset,\n+\t\t\t\t    const struct pathspec *ps, int negative_magic)\n {\n \tint i;\n \tint pathlen, baselen = base->len - base_offset;\n \tint never_interesting = ps->magic & PATHSPEC_NOGLOB ? -1 : 0;\n+\tint has_effective_pathspec = 0;\n \n \tif (!ps->nr) {\n+no_pathspec:\n \t\tif (!ps->recursive || ps->max_depth == -1)\n \t\t\treturn 2;\n \t\treturn !!within_depth(base->buf + base_offset, baselen,\n@@ -604,6 +606,12 @@ int tree_entry_interesting(const struct name_entry *entry,\n \t\tconst char *base_str = base->buf + base_offset;\n \t\tint matchlen = item->len;\n \n+\t\tif ((!negative_magic && !(item->magic & PATHSPEC_NEGATE)) ||\n+\t\t    ( negative_magic &&  (item->magic & PATHSPEC_NEGATE)))\n+\t\t\thas_effective_pathspec = 1;\n+\t\telse\n+\t\t\tcontinue;\n+\n \t\tif (baselen >= matchlen) {\n \t\t\t/* If it doesn't match, move along... */\n \t\t\tif (!match_dir_prefix(base_str, baselen, match, matchlen))\n@@ -663,5 +671,28 @@ match_wildcards:\n \t\tif (ps->recursive && S_ISDIR(entry->mode))\n \t\t\treturn 1;\n \t}\n+\n+\t/* the same effect with ps->nr == 0 */\n+\tif (!has_effective_pathspec)\n+\t\tgoto no_pathspec;\n+\n \treturn never_interesting; /* No matches */\n }\n+\n+int tree_entry_interesting(const struct name_entry *entry,\n+\t\t\t   struct strbuf *base, int base_offset,\n+\t\t\t   const struct pathspec *ps)\n+{\n+\tint next_ret, ret;\n+\n+\tret = tree_entry_interesting_1(entry, base, base_offset, ps, 0);\n+\tif (ps->magic & PATHSPEC_NEGATE) {\n+\t\tnext_ret = tree_entry_interesting_1(entry, base, base_offset, ps, 1);\n+\t\tswitch (next_ret) {\n+\t\tcase 2: ret = -1; break;\n+\t\tcase 1: ret = 0; break;\n+\t\tdefault: break;\n+\t\t}\n+\t}\n+\treturn ret;\n+}\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"177417","messageId":"7vwrcbce4w.fsf@alter.siamese.dyndns.org","threadId":"28653","inReplyTo":"1318373083-13840-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 0/6] Negation magic pathspec","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-11T23:17:03Z","receivedAt":"2011-10-11T23:17:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> I'm still struggling with read_directory() rewrite so that struct\n> pathspec can be used throughout git, but now realized we can at least\n> enable magic for certain commands and die() on those that don't.\n> This may help move magic pathspec patches forward.\n\nI actually was thinking that teaching those that take bare \"const char **\"\nto take \"struct pathspec *\" is much more important conversion before\nadding more magic to those that already do take \"struct pathspec *\". The\nones that ask \"does this path match the pathspec\" would automatically\nstart honoring negative or quantum or whatever magic once we teach\nthe magic to match_pathspec(), but the ones that take \"const char **\" will\nhave no way of doing so before getting converted to \"struct pathspec *\"\ninterface.  If some parts of the system knows more magic and people start\nplaying with them, the lack of the support in codepaths that haven't been\nconverted will become real pain point.\n\nThat is not to say that these 6 patch series are wasted patches. I just\nthink it may be doing things in a wrong order.\n"},{"id":"177484","messageId":"7v4nze7x61.fsf@alter.siamese.dyndns.org","threadId":"28653","inReplyTo":"1318373083-13840-2-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 1/6] Recognize magic pathspec as filenames","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-12T20:49:26Z","receivedAt":"2011-10-12T20:49:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  .. so that \"git log :/\" works, not so sure this is correct though\n\nAt least the first half should be in the commit log message, and also it\nshould contrast it against \"git log -- :/\".\n\nI doubt the approach taken by this particular patch (I do not know about\nthe rest of the series) is correct, as it breaks the symmetry between\nverify_filename() and verify_non_filename().\n\nWhen given a list of command line arguments, we do:\n\n (1) If there is \"--\", then no verification is needed. Everything before\n     the double-dash that is not a \"-flag\" will be interpreted as revs\n     (and get_sha1() will error out otherwise), and everything after the\n     double-dash will be used as an pathspec element.\n\n (2) If there is no \"--\", then earlier ones must be all revs and cannot be\n     pathnames in the working tree. The first argument that is not a rev\n     and everything after that must be a pathname in the working tree, and\n     must not be interpretable as revs.\n\nThat \"must be a pathname in the working tree\" is what verify_filename()\ndoes. and you say that \":/foo\" is OK to be a pathname in this patch.\n\nBut shouldn't the opposite \"cannot be pathnames in the working tree\" check\ndone by verify_non_filename() also be told the same logic? If \":/foo\" is\nOK to be a pathname, shouldn't check_filename() call in that function also\nbe avoided the same way?\n\nAnd once that happens, I think you will be back to square one.\n\n\"git log :/foo\" is ambiguous no matter how you slice it, if you are going\nto look at only the first character in the string. It could be asking to\nshow only commits that touch the path in the top-level directory \"foo\" and\nits subdirectories, or it could be asking to start traversal at a commit\nwith \"foo\" in the log message.\n\nLonghand magic pathspecs e.g. \":(icase)foo\" might have better chance, but\nnot by a wide margin. It can be a rev that names the blob object in the\nindex registered for the literal path \"'(icase)foo\", or it could be an\nelement in the pathspec to match [Ff][Oo][[Oo].\n\n>  setup.c |   16 +++++++---------\n>  1 files changed, 7 insertions(+), 9 deletions(-)\n>\n> diff --git a/setup.c b/setup.c\n> index 27c1d47..08f605b 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -58,15 +58,8 @@ static void NORETURN die_verify_filename(const char *prefix, const char *arg)\n>  \tunsigned char sha1[20];\n>  \tunsigned mode;\n>  \n> -\t/*\n> -\t * Saying \"'(icase)foo' does not exist in the index\" when the\n> -\t * user gave us \":(icase)foo\" is just stupid.  A magic pathspec\n> -\t * begins with a colon and is followed by a non-alnum; do not\n> -\t * let get_sha1_with_mode_1(only_to_die=1) to even trigger.\n> -\t */\n> -\tif (!(arg[0] == ':' && !isalnum(arg[1])))\n> -\t\t/* try a detailed diagnostic ... */\n> -\t\tget_sha1_with_mode_1(arg, sha1, &mode, 1, prefix);\n> +\t/* try a detailed diagnostic ... */\n> +\tget_sha1_with_mode_1(arg, sha1, &mode, 1, prefix);\n>  \n>  \t/* ... or fall back the most general message. */\n>  \tdie(\"ambiguous argument '%s': unknown revision or path not in the working tree.\\n\"\n> @@ -85,6 +78,11 @@ void verify_filename(const char *prefix, const char *arg)\n>  {\n>  \tif (*arg == '-')\n>  \t\tdie(\"bad flag '%s' used after filename\", arg);\n> +\n> +\t/* If it's magic pathspec, just assume it's file name */\n> +\tif (arg[0] == ':' && is_pathspec_magic(arg[1]))\n> +\t\treturn;\n> +\n>  \tif (check_filename(prefix, arg))\n>  \t\treturn;\n>  \tdie_verify_filename(prefix, arg);\n"},{"id":"177502","messageId":"7vbotl7mzv.fsf@alter.siamese.dyndns.org","threadId":"28653","inReplyTo":"1318373083-13840-6-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 5/6] Convert simple init_pathspec() cases to parse_pathspec()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-13T00:29:08Z","receivedAt":"2011-10-13T00:29:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"I gave a cursory look up to this one. I didn't read 4/6 very carefully\nthough.\n\nI think teaching the users of \"raw[]\" field of \"struct pathspec\" to use\ncorresponding \"items[]\" element should come before this series (up to\n5/6). After that, once parse_pathspec() API stabilizes, we should teach\nusers of get_pathspec() to get and pass around \"struct pathspec\" and when\nwe reach the point where we can get rid of use of \"raw[]\" field (the field\nitself can stay to serve as a debugging aid if we choose to), everybody\nthat uses pathspec would be ready to start taking more magic pathspecs.\n\nHaving said that, I think the series itself is going in the right\ndirection.\n\nThanks for working on this.\n"},{"id":"177507","messageId":"CACsJy8CY=myfLpAbEA0=LCm+tCgS7jzEAxH3rnwQt4-RXyjW9w@mail.gmail.com","threadId":"28653","inReplyTo":"7v4nze7x61.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 1/6] Recognize magic pathspec as filenames","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2011-10-13T04:23:39Z","receivedAt":"2011-10-13T04:23:39Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"2011/10/13 Junio C Hamano <gitster@pobox.com>:\n> \"git log :/foo\" is ambiguous no matter how you slice it, if you are going\n> to look at only the first character in the string. It could be asking to\n> show only commits that touch the path in the top-level directory \"foo\" and\n> its subdirectories, or it could be asking to start traversal at a commit\n> with \"foo\" in the log message.\n\nYeah, I forgot the \":/\" ref syntax. But because it's ambiguous anyway,\nshould we disallow \"git log :/foo\"? Either \"git log :/foo --\" or \"git\nlog -- :/foo\" is OK.\n\n> Longhand magic pathspecs e.g. \":(icase)foo\" might have better chance, but\n> not by a wide margin. It can be a rev that names the blob object in the\n> index registered for the literal path \"'(icase)foo\", or it could be an\n> element in the pathspec to match [Ff][Oo][[Oo].\n\nIt's unfortunate that \":\" has so many meanings attached to it. I hope\nthat our ambiguation detection code can be smart, based on context, to\navoid unnecessary \"--\". For example, \"git log :(icase)foo\" can only\nmean pathspec magic (I hope..)\n-- \nDuy\n"},{"id":"177513","messageId":"7vsjmx5ssg.fsf@alter.siamese.dyndns.org","threadId":"28653","inReplyTo":"CACsJy8CY=myfLpAbEA0=LCm+tCgS7jzEAxH3rnwQt4-RXyjW9w@mail.gmail.com","subject":"Re: [PATCH 1/6] Recognize magic pathspec as filenames","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-13T06:06:55Z","receivedAt":"2011-10-13T06:06:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:\n\n> It's unfortunate that \":\" has so many meanings attached to it.\n\nYeah, I still recall that there were people who said \":/$path looks\nsimilar to the way how the object at the $path in the index is named\" as\nif it were an advantage.  Maybe they were all misguided ;-).\n"}]}