From 64acde94efd2906c3e20560c31c2957ac0b242c4 Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:35:25 +0700 Subject: move struct pathspec and related functions to pathspec.[ch] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'pathspec.h') diff --git a/pathspec.h b/pathspec.h index db0184a1ac..788406885f 100644 --- a/pathspec.h +++ b/pathspec.h @@ -1,6 +1,27 @@ #ifndef PATHSPEC_H #define PATHSPEC_H +#define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies GFNM_ONESTAR */ + +struct pathspec { + const char **raw; /* get_pathspec() result, not freed by free_pathspec() */ + int nr; + unsigned int has_wildcard:1; + unsigned int recursive:1; + int max_depth; + struct pathspec_item { + const char *match; + int len; + int nowildcard_len; + int flags; + } *items; +}; + +extern int init_pathspec(struct pathspec *, const char **); +extern void free_pathspec(struct pathspec *); + +extern int limit_pathspec_to_literal(void); + extern char *find_pathspecs_matching_against_index(const char **pathspec); extern void add_pathspec_matches_against_index(const char **pathspec, char *seen, int specs); extern const char *check_path_for_gitlink(const char *path); -- cgit v1.3 From e4d92cdcd9450af7ae4c40de2b7c5c27a1faa138 Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:35:27 +0700 Subject: pathspec: add copy_pathspec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because free_pathspec wants to free "items" pointer in the pathspec structure, a simple structure assignment is not enough if you want to copy an existing pathspec into another. Freeing the original will damage the copy unless a deep copy is made. Note that the strings in pathspec->items->match and the array pathspec->raw[] are still shared between the original and the copy. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/mv.c | 13 +++++++------ pathspec.c | 8 ++++++++ pathspec.h | 1 + 3 files changed, 16 insertions(+), 6 deletions(-) (limited to 'pathspec.h') diff --git a/builtin/mv.c b/builtin/mv.c index 034fec92a1..16ce99b499 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -15,8 +15,9 @@ static const char * const builtin_mv_usage[] = { NULL }; -static const char **copy_pathspec(const char *prefix, const char **pathspec, - int count, int base_name) +static const char **internal_copy_pathspec(const char *prefix, + const char **pathspec, + int count, int base_name) { int i; const char **result = xmalloc((count + 1) * sizeof(const char *)); @@ -81,17 +82,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die(_("index file corrupt")); - source = copy_pathspec(prefix, argv, argc, 0); + source = internal_copy_pathspec(prefix, argv, argc, 0); modes = xcalloc(argc, sizeof(enum update_mode)); - dest_path = copy_pathspec(prefix, argv + argc, 1, 0); + dest_path = internal_copy_pathspec(prefix, argv + argc, 1, 0); if (dest_path[0][0] == '\0') /* special case: "." was normalized to "" */ - destination = copy_pathspec(dest_path[0], argv, argc, 1); + destination = internal_copy_pathspec(dest_path[0], argv, argc, 1); else if (!lstat(dest_path[0], &st) && S_ISDIR(st.st_mode)) { dest_path[0] = add_slash(dest_path[0]); - destination = copy_pathspec(dest_path[0], argv, argc, 1); + destination = internal_copy_pathspec(dest_path[0], argv, argc, 1); } else { if (argc != 1) die("destination '%s' is not a directory", dest_path[0]); diff --git a/pathspec.c b/pathspec.c index 403095ba8c..8fe56cd8e9 100644 --- a/pathspec.c +++ b/pathspec.c @@ -249,3 +249,11 @@ const char **get_pathspec(const char *prefix, const char **pathspec) return NULL; return pathspec; } + +void copy_pathspec(struct pathspec *dst, const struct pathspec *src) +{ + *dst = *src; + dst->items = xmalloc(sizeof(struct pathspec_item) * dst->nr); + memcpy(dst->items, src->items, + sizeof(struct pathspec_item) * dst->nr); +} diff --git a/pathspec.h b/pathspec.h index 788406885f..a621676db8 100644 --- a/pathspec.h +++ b/pathspec.h @@ -18,6 +18,7 @@ struct pathspec { }; extern int init_pathspec(struct pathspec *, const char **); +extern void copy_pathspec(struct pathspec *dst, const struct pathspec *src); extern void free_pathspec(struct pathspec *); extern int limit_pathspec_to_literal(void); -- cgit v1.3 From 87323bdace47c3d464a783f48b16617720e8eeee Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:35:28 +0700 Subject: add parse_pathspec() that converts cmdline args to struct pathspec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently to fill a struct pathspec, we do: const char **paths; paths = get_pathspec(prefix, argv); ... init_pathspec(&pathspec, paths); "paths" can only carry bare strings, which loses information from command line arguments such as pathspec magic or the prefix part's length for each argument. parse_pathspec() is introduced to combine the two calls into one. The plan is gradually replace all get_pathspec() and init_pathspec() with parse_pathspec(). get_pathspec() now becomes a thin wrapper of parse_pathspec(). parse_pathspec() allows the caller to reject the pathspec magics that it does not support. When a new pathspec magic is introduced, we can enable it per command after making sure that all underlying code has no problem with the new magic. "flags" parameter is currently unused. But it would allow callers to pass certain instructions to parse_pathspec, for example forcing literal pathspec when no magic is used. With the introduction of parse_pathspec, there are now two functions that can initialize struct pathspec: init_pathspec and parse_pathspec. Any semantic changes in struct pathspec must be reflected in both functions. init_pathspec() will be phased out in favor of parse_pathspec(). Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/technical/api-setup.txt | 19 +++- dir.c | 4 +- dir.h | 2 + pathspec.c | 168 ++++++++++++++++++++++++++-------- pathspec.h | 11 +++ 5 files changed, 163 insertions(+), 41 deletions(-) (limited to 'pathspec.h') diff --git a/Documentation/technical/api-setup.txt b/Documentation/technical/api-setup.txt index 4f63a04d7d..90d1aff625 100644 --- a/Documentation/technical/api-setup.txt +++ b/Documentation/technical/api-setup.txt @@ -8,6 +8,23 @@ Talk about * is_inside_git_dir() * is_inside_work_tree() * setup_work_tree() -* get_pathspec() (Dscho) + +Pathspec +-------- + +See glossary-context.txt for the syntax of pathspec. In memory, a +pathspec set is represented by "struct pathspec" and is prepared by +parse_pathspec(). This function takes several arguments: + +- magic_mask specifies what features that are NOT supported by the + following code. If a user attempts to use such a feature, + parse_pathspec() can reject it early. + +- flags specifies other things that the caller wants parse_pathspec to + perform. + +- prefix and args come from cmd_* functions + +get_pathspec() is obsolete and should never be used in new code. diff --git a/dir.c b/dir.c index b0599dd6e4..5f86e467c4 100644 --- a/dir.c +++ b/dir.c @@ -381,7 +381,7 @@ int match_pathspec_depth(const struct pathspec *ps, /* * Return the length of the "simple" part of a path match limiter. */ -static int simple_length(const char *match) +int simple_length(const char *match) { int len = -1; @@ -393,7 +393,7 @@ static int simple_length(const char *match) } } -static int no_wildcard(const char *string) +int no_wildcard(const char *string) { return string[simple_length(string)] == '\0'; } diff --git a/dir.h b/dir.h index 3d6b80c933..229ccc8d79 100644 --- a/dir.h +++ b/dir.h @@ -128,6 +128,8 @@ struct dir_struct { #define MATCHED_RECURSIVELY 1 #define MATCHED_FNMATCH 2 #define MATCHED_EXACTLY 3 +extern int simple_length(const char *match); +extern int no_wildcard(const char *string); extern char *common_prefix(const char **pathspec); extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen); extern int match_pathspec_depth(const struct pathspec *pathspec, diff --git a/pathspec.c b/pathspec.c index 8fe56cd8e9..ce942dbccf 100644 --- a/pathspec.c +++ b/pathspec.c @@ -103,10 +103,6 @@ void die_if_path_beyond_symlink(const char *path, const char *prefix) /* * Magic pathspec * - * NEEDSWORK: These need to be moved to dir.h or even to a new - * pathspec.h when we restructure get_pathspec() users to use the - * "struct pathspec" interface. - * * Possible future magic semantics include stuff like: * * { PATHSPEC_NOGLOB, '!', "noglob" }, @@ -115,7 +111,6 @@ void die_if_path_beyond_symlink(const char *path, const char *prefix) * { PATHSPEC_REGEXP, '\0', "regexp" }, * */ -#define PATHSPEC_FROMTOP (1<<0) static struct pathspec_magic { unsigned bit; @@ -127,7 +122,7 @@ static struct pathspec_magic { /* * Take an element of a pathspec and check for magic signatures. - * Append the result to the prefix. + * Append the result to the prefix. Return the magic bitmap. * * For now, we only parse the syntax and throw out anything other than * "top" magic. @@ -138,10 +133,15 @@ static struct pathspec_magic { * the prefix part must always match literally, and a single stupid * string cannot express such a case. */ -static const char *prefix_pathspec(const char *prefix, int prefixlen, const char *elt) +static unsigned prefix_pathspec(struct pathspec_item *item, + unsigned *p_short_magic, + const char **raw, unsigned flags, + const char *prefix, int prefixlen, + const char *elt) { - unsigned magic = 0; + unsigned magic = 0, short_magic = 0; const char *copyfrom = elt; + char *match; int i; if (elt[0] != ':') { @@ -184,7 +184,7 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const char break; for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) if (pathspec_magic[i].mnemonic == ch) { - magic |= pathspec_magic[i].bit; + short_magic |= pathspec_magic[i].bit; break; } if (ARRAY_SIZE(pathspec_magic) <= i) @@ -195,15 +195,128 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const char copyfrom++; } + magic |= short_magic; + *p_short_magic = short_magic; + if (magic & PATHSPEC_FROMTOP) - return xstrdup(copyfrom); + match = xstrdup(copyfrom); else - return prefix_path(prefix, prefixlen, copyfrom); + match = prefix_path(prefix, prefixlen, copyfrom); + *raw = item->match = match; + item->len = strlen(item->match); + if (limit_pathspec_to_literal()) + item->nowildcard_len = item->len; + else + item->nowildcard_len = simple_length(item->match); + item->flags = 0; + if (item->nowildcard_len < item->len && + item->match[item->nowildcard_len] == '*' && + no_wildcard(item->match + item->nowildcard_len + 1)) + item->flags |= PATHSPEC_ONESTAR; + return magic; +} + +static int pathspec_item_cmp(const void *a_, const void *b_) +{ + struct pathspec_item *a, *b; + + a = (struct pathspec_item *)a_; + b = (struct pathspec_item *)b_; + return strcmp(a->match, b->match); +} + +static void NORETURN unsupported_magic(const char *pattern, + unsigned magic, + unsigned short_magic) +{ + struct strbuf sb = STRBUF_INIT; + int i, n; + for (n = i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { + const struct pathspec_magic *m = pathspec_magic + i; + if (!(magic & m->bit)) + continue; + if (sb.len) + strbuf_addstr(&sb, " "); + if (short_magic & m->bit) + strbuf_addf(&sb, "'%c'", m->mnemonic); + else + strbuf_addf(&sb, "'%s'", m->name); + n++; + } + /* + * We may want to substitute "this command" with a command + * name. E.g. when add--interactive dies when running + * "checkout -p" + */ + die(_("%s: pathspec magic not supported by this command: %s"), + pattern, sb.buf); +} + +/* + * Given command line arguments and a prefix, convert the input to + * pathspec. die() if any magic in magic_mask is used. + */ +void parse_pathspec(struct pathspec *pathspec, + unsigned magic_mask, unsigned flags, + const char *prefix, const char **argv) +{ + struct pathspec_item *item; + const char *entry = argv ? *argv : NULL; + int i, n, prefixlen; + + memset(pathspec, 0, sizeof(*pathspec)); + + /* No arguments, no prefix -> no pathspec */ + if (!entry && !prefix) + return; + + /* No arguments with prefix -> prefix pathspec */ + if (!entry) { + static const char *raw[2]; + + pathspec->items = item = xmalloc(sizeof(*item)); + memset(item, 0, sizeof(*item)); + item->match = prefix; + item->nowildcard_len = item->len = strlen(prefix); + raw[0] = prefix; + raw[1] = NULL; + pathspec->nr = 1; + pathspec->raw = raw; + return; + } + + n = 0; + while (argv[n]) + n++; + + pathspec->nr = n; + pathspec->items = item = xmalloc(sizeof(*item) * n); + pathspec->raw = argv; + prefixlen = prefix ? strlen(prefix) : 0; + + for (i = 0; i < n; i++) { + unsigned short_magic; + entry = argv[i]; + + item[i].magic = prefix_pathspec(item + i, &short_magic, + argv + i, flags, + prefix, prefixlen, entry); + if (item[i].magic & magic_mask) + unsupported_magic(entry, + item[i].magic & magic_mask, + short_magic); + if (item[i].nowildcard_len < item[i].len) + pathspec->has_wildcard = 1; + pathspec->magic |= item[i].magic; + } + + qsort(pathspec->items, pathspec->nr, + sizeof(struct pathspec_item), pathspec_item_cmp); } /* * N.B. get_pathspec() is deprecated in favor of the "struct pathspec" - * based interface - see pathspec_magic above. + * based interface - see pathspec.c:parse_pathspec(). * * Arguments: * - prefix - a path relative to the root of the working tree @@ -222,32 +335,11 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const char */ const char **get_pathspec(const char *prefix, const char **pathspec) { - const char *entry = *pathspec; - const char **src, **dst; - int prefixlen; - - if (!prefix && !entry) - return NULL; - - if (!entry) { - static const char *spec[2]; - spec[0] = prefix; - spec[1] = NULL; - return spec; - } - - /* Otherwise we have to re-write the entries.. */ - src = pathspec; - dst = pathspec; - prefixlen = prefix ? strlen(prefix) : 0; - while (*src) { - *(dst++) = prefix_pathspec(prefix, prefixlen, *src); - src++; - } - *dst = NULL; - if (!*pathspec) - return NULL; - return pathspec; + struct pathspec ps; + parse_pathspec(&ps, + PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP, + 0, prefix, pathspec); + return ps.raw; } void copy_pathspec(struct pathspec *dst, const struct pathspec *src) diff --git a/pathspec.h b/pathspec.h index a621676db8..937ec91a0d 100644 --- a/pathspec.h +++ b/pathspec.h @@ -1,6 +1,10 @@ #ifndef PATHSPEC_H #define PATHSPEC_H +/* Pathspec magic */ +#define PATHSPEC_FROMTOP (1<<0) +#define PATHSPEC_ALL_MAGIC PATHSPEC_FROMTOP + #define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies GFNM_ONESTAR */ struct pathspec { @@ -8,9 +12,11 @@ struct pathspec { int nr; unsigned int has_wildcard:1; unsigned int recursive:1; + unsigned magic; int max_depth; struct pathspec_item { const char *match; + unsigned magic; int len; int nowildcard_len; int flags; @@ -18,6 +24,11 @@ struct pathspec { }; extern int init_pathspec(struct pathspec *, const char **); +extern void parse_pathspec(struct pathspec *pathspec, + unsigned magic_mask, + unsigned flags, + const char *prefix, + const char **args); extern void copy_pathspec(struct pathspec *dst, const struct pathspec *src); extern void free_pathspec(struct pathspec *); -- cgit v1.3 From d2ce1331953e9f4306e00f5edf3a6ce6e3330e9e Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:35:29 +0700 Subject: parse_pathspec: save original pathspec for reporting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We usually use pathspec_item's match field for pathspec error reporting. However "match" (or "raw") does not show the magic part, which will play more important role later on. Preserve exact user input for reporting. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- dir.c | 1 + pathspec.c | 2 ++ pathspec.h | 1 + 3 files changed, 4 insertions(+) (limited to 'pathspec.h') diff --git a/dir.c b/dir.c index 5f86e467c4..308028e5b1 100644 --- a/dir.c +++ b/dir.c @@ -1599,6 +1599,7 @@ int init_pathspec(struct pathspec *pathspec, const char **paths) const char *path = paths[i]; item->match = path; + item->original = path; item->len = strlen(path); item->flags = 0; if (limit_pathspec_to_literal()) { diff --git a/pathspec.c b/pathspec.c index ce942dbccf..f94beb6075 100644 --- a/pathspec.c +++ b/pathspec.c @@ -203,6 +203,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, else match = prefix_path(prefix, prefixlen, copyfrom); *raw = item->match = match; + item->original = elt; item->len = strlen(item->match); if (limit_pathspec_to_literal()) item->nowildcard_len = item->len; @@ -277,6 +278,7 @@ void parse_pathspec(struct pathspec *pathspec, pathspec->items = item = xmalloc(sizeof(*item)); memset(item, 0, sizeof(*item)); item->match = prefix; + item->original = prefix; item->nowildcard_len = item->len = strlen(prefix); raw[0] = prefix; raw[1] = NULL; diff --git a/pathspec.h b/pathspec.h index 937ec91a0d..cc5841b77f 100644 --- a/pathspec.h +++ b/pathspec.h @@ -16,6 +16,7 @@ struct pathspec { int max_depth; struct pathspec_item { const char *match; + const char *original; unsigned magic; int len; int nowildcard_len; -- cgit v1.3 From fc12261fea778cabd08dec707169cb88ecdbc647 Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:35:30 +0700 Subject: parse_pathspec: add PATHSPEC_PREFER_{CWD,FULL} flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have two ways of dealing with empty pathspec: 1. limit it to current prefix 2. match the entire working directory Some commands go with #1, some #2. get_pathspec() and parse_pathspec() only support #1. Make parse_pathspec() reject empty pathspec by default. #1 and #2 can be specified via new flags. This makes it more expressive about default behavior at command level. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.c | 13 ++++++++++++- pathspec.h | 4 ++++ 2 files changed, 16 insertions(+), 1 deletion(-) (limited to 'pathspec.h') diff --git a/pathspec.c b/pathspec.c index f94beb6075..6d99a3dced 100644 --- a/pathspec.c +++ b/pathspec.c @@ -271,10 +271,20 @@ void parse_pathspec(struct pathspec *pathspec, if (!entry && !prefix) return; + if ((flags & PATHSPEC_PREFER_CWD) && + (flags & PATHSPEC_PREFER_FULL)) + die("BUG: PATHSPEC_PREFER_CWD and PATHSPEC_PREFER_FULL are incompatible"); + /* No arguments with prefix -> prefix pathspec */ if (!entry) { static const char *raw[2]; + if (flags & PATHSPEC_PREFER_FULL) + return; + + if (!(flags & PATHSPEC_PREFER_CWD)) + die("BUG: PATHSPEC_PREFER_CWD requires arguments"); + pathspec->items = item = xmalloc(sizeof(*item)); memset(item, 0, sizeof(*item)); item->match = prefix; @@ -340,7 +350,8 @@ const char **get_pathspec(const char *prefix, const char **pathspec) struct pathspec ps; parse_pathspec(&ps, PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP, - 0, prefix, pathspec); + PATHSPEC_PREFER_CWD, + prefix, pathspec); return ps.raw; } diff --git a/pathspec.h b/pathspec.h index cc5841b77f..d630e8b1f9 100644 --- a/pathspec.h +++ b/pathspec.h @@ -24,6 +24,10 @@ struct pathspec { } *items; }; +/* parse_pathspec flags */ +#define PATHSPEC_PREFER_CWD (1<<0) /* No args means match cwd */ +#define PATHSPEC_PREFER_FULL (1<<1) /* No args means match everything */ + extern int init_pathspec(struct pathspec *, const char **); extern void parse_pathspec(struct pathspec *pathspec, unsigned magic_mask, -- cgit v1.3 From 6330a171996abbec7dac48788de851aea7d0a18f Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:35:32 +0700 Subject: parse_pathspec: add special flag for max_depth feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit match_pathspec_depth() and tree_entry_interesting() check max_depth field in order to support "git grep --max-depth". The feature activation is tied to "recursive" field, which led to some unwanted activation, e.g. 5c8eeb8 (diff-index: enable recursive pathspec matching in unpack_trees - 2012-01-15). This patch decouples the activation from "recursive" field, puts it in "magic" field instead. This makes sure that only "git grep" can activate this feature. And because parse_pathspec knows when the feature is not used, it does not need to sort pathspec (required for max_depth to work correctly). A small win for non-grep cases. Even though a new magic flag is introduced, no magic syntax is. The magic can be only enabled by parse_pathspec() caller. We might someday want to support ":(maxdepth:10)src." It all depends on actual use cases. max_depth feature cannot be enabled via init_pathspec() anymore. But that's ok because init_pathspec() is on its way to /dev/null. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/grep.c | 3 ++- diff-lib.c | 1 - dir.c | 8 ++++++-- pathspec.c | 8 ++++++-- pathspec.h | 6 +++++- tree-diff.c | 1 - tree-walk.c | 8 ++++++-- 7 files changed, 25 insertions(+), 10 deletions(-) (limited to 'pathspec.h') diff --git a/builtin/grep.c b/builtin/grep.c index 1a6c02804c..4bc075422e 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -858,7 +858,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } parse_pathspec(&pathspec, 0, - PATHSPEC_PREFER_CWD, + PATHSPEC_PREFER_CWD | + (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0), prefix, argv + i); pathspec.max_depth = opt.max_depth; pathspec.recursive = 1; diff --git a/diff-lib.c b/diff-lib.c index b6f4b21637..d4e17f7662 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -476,7 +476,6 @@ static int diff_cache(struct rev_info *revs, opts.dst_index = NULL; opts.pathspec = &revs->diffopt.pathspec; opts.pathspec->recursive = 1; - opts.pathspec->max_depth = -1; init_tree_desc(&t, tree->buffer, tree->size); return unpack_trees(1, &t, &opts); diff --git a/dir.c b/dir.c index 308028e5b1..e28bc0d636 100644 --- a/dir.c +++ b/dir.c @@ -341,7 +341,9 @@ int match_pathspec_depth(const struct pathspec *ps, int i, retval = 0; if (!ps->nr) { - if (!ps->recursive || ps->max_depth == -1) + if (!ps->recursive || + !(ps->magic & PATHSPEC_MAXDEPTH) || + ps->max_depth == -1) return MATCHED_RECURSIVELY; if (within_depth(name, namelen, 0, ps->max_depth)) @@ -358,7 +360,9 @@ int match_pathspec_depth(const struct pathspec *ps, if (seen && seen[i] == MATCHED_EXACTLY) continue; how = match_pathspec_item(ps->items+i, prefix, name, namelen); - if (ps->recursive && ps->max_depth != -1 && + if (ps->recursive && + (ps->magic & PATHSPEC_MAXDEPTH) && + ps->max_depth != -1 && how && how != MATCHED_FNMATCH) { int len = ps->items[i].len; if (name[len] == '/') diff --git a/pathspec.c b/pathspec.c index 6d99a3dced..06778fc756 100644 --- a/pathspec.c +++ b/pathspec.c @@ -267,6 +267,9 @@ void parse_pathspec(struct pathspec *pathspec, memset(pathspec, 0, sizeof(*pathspec)); + if (flags & PATHSPEC_MAXDEPTH_VALID) + pathspec->magic |= PATHSPEC_MAXDEPTH; + /* No arguments, no prefix -> no pathspec */ if (!entry && !prefix) return; @@ -322,8 +325,9 @@ void parse_pathspec(struct pathspec *pathspec, pathspec->magic |= item[i].magic; } - qsort(pathspec->items, pathspec->nr, - sizeof(struct pathspec_item), pathspec_item_cmp); + if (pathspec->magic & PATHSPEC_MAXDEPTH) + qsort(pathspec->items, pathspec->nr, + sizeof(struct pathspec_item), pathspec_item_cmp); } /* diff --git a/pathspec.h b/pathspec.h index d630e8b1f9..aa98597bda 100644 --- a/pathspec.h +++ b/pathspec.h @@ -3,7 +3,10 @@ /* Pathspec magic */ #define PATHSPEC_FROMTOP (1<<0) -#define PATHSPEC_ALL_MAGIC PATHSPEC_FROMTOP +#define PATHSPEC_MAXDEPTH (1<<1) +#define PATHSPEC_ALL_MAGIC \ + (PATHSPEC_FROMTOP | \ + PATHSPEC_MAXDEPTH) #define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies GFNM_ONESTAR */ @@ -27,6 +30,7 @@ struct pathspec { /* parse_pathspec flags */ #define PATHSPEC_PREFER_CWD (1<<0) /* No args means match cwd */ #define PATHSPEC_PREFER_FULL (1<<1) /* No args means match everything */ +#define PATHSPEC_MAXDEPTH_VALID (1<<2) /* max_depth field is valid */ extern int init_pathspec(struct pathspec *, const char **); extern void parse_pathspec(struct pathspec *pathspec, diff --git a/tree-diff.c b/tree-diff.c index ba01563a02..826512e84d 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -138,7 +138,6 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, /* Enable recursion indefinitely */ opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE); - opt->pathspec.max_depth = -1; strbuf_init(&base, PATH_MAX); strbuf_add(&base, base_str, baselen); diff --git a/tree-walk.c b/tree-walk.c index 72a96139f5..d399ca9dcc 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -637,7 +637,9 @@ enum interesting tree_entry_interesting(const struct name_entry *entry, entry_not_interesting : all_entries_not_interesting; if (!ps->nr) { - if (!ps->recursive || ps->max_depth == -1) + if (!ps->recursive || + !(ps->magic & PATHSPEC_MAXDEPTH) || + ps->max_depth == -1) return all_entries_interesting; return within_depth(base->buf + base_offset, baselen, !!S_ISDIR(entry->mode), @@ -658,7 +660,9 @@ enum interesting tree_entry_interesting(const struct name_entry *entry, if (!match_dir_prefix(base_str, match, matchlen)) goto match_wildcards; - if (!ps->recursive || ps->max_depth == -1) + if (!ps->recursive || + !(ps->magic & PATHSPEC_MAXDEPTH) || + ps->max_depth == -1) return all_entries_interesting; return within_depth(base_str + matchlen + 1, -- cgit v1.3 From b69bb3fc271911317c12fd6ecce6fe1e0301a2b3 Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:35:33 +0700 Subject: parse_pathspec: support stripping submodule trailing slashes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This flag is equivalent to builtin/ls-files.c:strip_trailing_slashes() and is intended to replace that function when ls-files is converted to use parse_pathspec. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.c | 9 +++++++++ pathspec.h | 2 ++ 2 files changed, 11 insertions(+) (limited to 'pathspec.h') diff --git a/pathspec.c b/pathspec.c index 06778fc756..1c07c23fe0 100644 --- a/pathspec.c +++ b/pathspec.c @@ -205,6 +205,15 @@ static unsigned prefix_pathspec(struct pathspec_item *item, *raw = item->match = match; item->original = elt; item->len = strlen(item->match); + + if ((flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) && + (item->len >= 1 && item->match[item->len - 1] == '/') && + (i = cache_name_pos(item->match, item->len - 1)) >= 0 && + S_ISGITLINK(active_cache[i]->ce_mode)) { + item->len--; + match[item->len] = '\0'; + } + if (limit_pathspec_to_literal()) item->nowildcard_len = item->len; else diff --git a/pathspec.h b/pathspec.h index aa98597bda..51448513e6 100644 --- a/pathspec.h +++ b/pathspec.h @@ -31,6 +31,8 @@ struct pathspec { #define PATHSPEC_PREFER_CWD (1<<0) /* No args means match cwd */ #define PATHSPEC_PREFER_FULL (1<<1) /* No args means match everything */ #define PATHSPEC_MAXDEPTH_VALID (1<<2) /* max_depth field is valid */ +/* strip the trailing slash if the given path is a gitlink */ +#define PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP (1<<3) extern int init_pathspec(struct pathspec *, const char **); extern void parse_pathspec(struct pathspec *pathspec, -- cgit v1.3 From 8745024422d4b38d8bf93d342b7b173f57cbceaa Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:35:34 +0700 Subject: parse_pathspec: support stripping/checking submodule paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PATHSPEC_SYMLINK_LEADING_PATH and _STRIP_SUBMODULE_SLASH_EXPENSIVE are respectively the alternate implementation of pathspec.c:die_if_path_beyond_symlink() and pathspec.c:check_path_for_gitlink(). They are intended to replace those functions when builtin/add.c and builtin/check-ignore.c are converted to use parse_pathspec. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.c | 26 ++++++++++++++++++++++++++ pathspec.h | 10 ++++++++++ 2 files changed, 36 insertions(+) (limited to 'pathspec.h') diff --git a/pathspec.c b/pathspec.c index 1c07c23fe0..e2a4f910f9 100644 --- a/pathspec.c +++ b/pathspec.c @@ -214,6 +214,26 @@ static unsigned prefix_pathspec(struct pathspec_item *item, match[item->len] = '\0'; } + if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE) + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + int ce_len = ce_namelen(ce); + + if (!S_ISGITLINK(ce->ce_mode)) + continue; + + if (item->len <= ce_len || match[ce_len] != '/' || + memcmp(ce->name, match, ce_len)) + continue; + if (item->len == ce_len + 1) { + /* strip trailing slash */ + item->len--; + match[item->len] = '\0'; + } else + die (_("Pathspec '%s' is in submodule '%.*s'"), + elt, ce_len, ce->name); + } + if (limit_pathspec_to_literal()) item->nowildcard_len = item->len; else @@ -329,6 +349,12 @@ void parse_pathspec(struct pathspec *pathspec, unsupported_magic(entry, item[i].magic & magic_mask, short_magic); + + if ((flags & PATHSPEC_SYMLINK_LEADING_PATH) && + has_symlink_leading_path(item[i].match, item[i].len)) { + die(_("pathspec '%s' is beyond a symbolic link"), entry); + } + if (item[i].nowildcard_len < item[i].len) pathspec->has_wildcard = 1; pathspec->magic |= item[i].magic; diff --git a/pathspec.h b/pathspec.h index 51448513e6..450fc03034 100644 --- a/pathspec.h +++ b/pathspec.h @@ -33,6 +33,16 @@ struct pathspec { #define PATHSPEC_MAXDEPTH_VALID (1<<2) /* max_depth field is valid */ /* strip the trailing slash if the given path is a gitlink */ #define PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP (1<<3) +/* die if a symlink is part of the given path's directory */ +#define PATHSPEC_SYMLINK_LEADING_PATH (1<<4) +/* + * This is like a combination of ..LEADING_PATH and .._SLASH_CHEAP + * (but not the same): it strips the trailing slash if the given path + * is a gitlink but also checks and dies if gitlink is part of the + * leading path (i.e. the given path goes beyond a submodule). It's + * safer than _SLASH_CHEAP and also more expensive. + */ +#define PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE (1<<5) extern int init_pathspec(struct pathspec *, const char **); extern void parse_pathspec(struct pathspec *pathspec, -- cgit v1.3 From dad2586a6b30da902d5f1979221eccc474425a7a Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:35:35 +0700 Subject: parse_pathspec: support prefixing original patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes 'original' suitable for passing to an external command because all pathspec magic is left in place, provided that the external command understands pathspec. The prefixing is needed because we usually launch a subcommand at worktree's top directory and the subcommand can no longer calculate the prefix itself. This slightly affects the original purpose of 'original' (i.e. reporting). We should report without prefixing. So only turn this flag on when you know you are about to pass the result straight away to an external command. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pathspec.c | 12 +++++++++++- pathspec.h | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'pathspec.h') diff --git a/pathspec.c b/pathspec.c index e2a4f910f9..ba6408a74c 100644 --- a/pathspec.c +++ b/pathspec.c @@ -203,7 +203,17 @@ static unsigned prefix_pathspec(struct pathspec_item *item, else match = prefix_path(prefix, prefixlen, copyfrom); *raw = item->match = match; - item->original = elt; + /* + * Prefix the pathspec (keep all magic) and assign to + * original. Useful for passing to another command. + */ + if (flags & PATHSPEC_PREFIX_ORIGIN) { + struct strbuf sb = STRBUF_INIT; + strbuf_add(&sb, elt, copyfrom - elt); + strbuf_addstr(&sb, match); + item->original = strbuf_detach(&sb, NULL); + } else + item->original = elt; item->len = strlen(item->match); if ((flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) && diff --git a/pathspec.h b/pathspec.h index 450fc03034..2e427d7f4c 100644 --- a/pathspec.h +++ b/pathspec.h @@ -43,6 +43,7 @@ struct pathspec { * safer than _SLASH_CHEAP and also more expensive. */ #define PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE (1<<5) +#define PATHSPEC_PREFIX_ORIGIN (1<<6) extern int init_pathspec(struct pathspec *, const char **); extern void parse_pathspec(struct pathspec *pathspec, -- cgit v1.3 From 8f4f8f4579fe8cc630e118cc736de2b8d5cf8e34 Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:35:36 +0700 Subject: guard against new pathspec magic in pathspec matching code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GUARD_PATHSPEC() marks pathspec-sensitive code, basically all those that touch anything in 'struct pathspec' except fields "nr" and "original". GUARD_PATHSPEC() is not supposed to fail. It's mainly to help the designers catch unsupported codepaths. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/technical/api-setup.txt | 19 +++++++++++++++++++ builtin/diff.c | 2 ++ dir.c | 2 ++ pathspec.h | 7 +++++++ tree-diff.c | 19 +++++++++++++++++++ tree-walk.c | 2 ++ 6 files changed, 51 insertions(+) (limited to 'pathspec.h') diff --git a/Documentation/technical/api-setup.txt b/Documentation/technical/api-setup.txt index 90d1aff625..540e455689 100644 --- a/Documentation/technical/api-setup.txt +++ b/Documentation/technical/api-setup.txt @@ -28,3 +28,22 @@ parse_pathspec(). This function takes several arguments: - prefix and args come from cmd_* functions get_pathspec() is obsolete and should never be used in new code. + +parse_pathspec() helps catch unsupported features and reject them +politely. At a lower level, different pathspec-related functions may +not support the same set of features. Such pathspec-sensitive +functions are guarded with GUARD_PATHSPEC(), which will die in an +unfriendly way when an unsupported feature is requested. + +The command designers are supposed to make sure that GUARD_PATHSPEC() +never dies. They have to make sure all unsupported features are caught +by parse_pathspec(), not by GUARD_PATHSPEC. grepping GUARD_PATHSPEC() +should give the designers all pathspec-sensitive codepaths and what +features they support. + +A similar process is applied when a new pathspec magic is added. The +designer lifts the GUARD_PATHSPEC restriction in the functions that +support the new magic. At the same time (s)he has to make sure this +new feature will be caught at parse_pathspec() in commands that cannot +handle the new magic in some cases. grepping parse_pathspec() should +help. diff --git a/builtin/diff.c b/builtin/diff.c index 9fc273d8cd..6bb41aff5d 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -367,6 +367,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } } if (rev.prune_data.nr) { + /* builtin_diff_b_f() */ + GUARD_PATHSPEC(&rev.prune_data, PATHSPEC_FROMTOP); if (!path) path = rev.prune_data.items[0].match; paths += rev.prune_data.nr; diff --git a/dir.c b/dir.c index e28bc0d636..19978d3d59 100644 --- a/dir.c +++ b/dir.c @@ -340,6 +340,8 @@ int match_pathspec_depth(const struct pathspec *ps, { int i, retval = 0; + GUARD_PATHSPEC(ps, PATHSPEC_FROMTOP | PATHSPEC_MAXDEPTH); + if (!ps->nr) { if (!ps->recursive || !(ps->magic & PATHSPEC_MAXDEPTH) || diff --git a/pathspec.h b/pathspec.h index 2e427d7f4c..6baf205862 100644 --- a/pathspec.h +++ b/pathspec.h @@ -27,6 +27,13 @@ struct pathspec { } *items; }; +#define GUARD_PATHSPEC(ps, mask) \ + do { \ + if ((ps)->magic & ~(mask)) \ + die("BUG:%s:%d: unsupported magic %x", \ + __FILE__, __LINE__, (ps)->magic & ~(mask)); \ + } while (0) + /* parse_pathspec flags */ #define PATHSPEC_PREFER_CWD (1<<0) /* No args means match cwd */ #define PATHSPEC_PREFER_FULL (1<<1) /* No args means match everything */ diff --git a/tree-diff.c b/tree-diff.c index 826512e84d..5a876148bb 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -198,6 +198,25 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co const char *paths[1]; int i; + /* + * follow-rename code is very specific, we need exactly one + * path. Magic that matches more than one path is not + * supported. + */ + GUARD_PATHSPEC(&opt->pathspec, PATHSPEC_FROMTOP); +#if 0 + /* + * We should reject wildcards as well. Unfortunately we + * haven't got a reliable way to detect that 'foo\*bar' in + * fact has no wildcards. nowildcard_len is merely a hint for + * optimization. Let it slip for now until wildmatch is taught + * about dry-run mode and returns wildcard info. + */ + if (opt->pathspec.has_wildcard) + die("BUG:%s:%d: wildcards are not supported", + __FILE__, __LINE__); +#endif + /* Remove the file creation entry from the diff queue, and remember it */ choice = q->queue[0]; q->nr = 0; diff --git a/tree-walk.c b/tree-walk.c index d399ca9dcc..37b157ed6e 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -636,6 +636,8 @@ enum interesting tree_entry_interesting(const struct name_entry *entry, enum interesting never_interesting = ps->has_wildcard ? entry_not_interesting : all_entries_not_interesting; + GUARD_PATHSPEC(ps, PATHSPEC_FROMTOP | PATHSPEC_MAXDEPTH); + if (!ps->nr) { if (!ps->recursive || !(ps->magic & PATHSPEC_MAXDEPTH) || -- cgit v1.3 From 931eab64ad24a742182c4e946a1138e677a8bd4e Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:35:45 +0700 Subject: check-ignore: convert to use parse_pathspec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check-ignore (at least the test suite) seems to rely on the pattern order. PATHSPEC_KEEP_ORDER is introduced to explictly express this. The lack of PATHSPEC_MAXDEPTH_VALID is sufficient because it's the only flag that reorders pathspecs, but it's less obvious that way. Cc: Adam Spiers Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/check-ignore.c | 35 ++++++++++++++++++++++------------- pathspec.c | 6 +++++- pathspec.h | 1 + t/t0008-ignores.sh | 8 ++++---- 4 files changed, 32 insertions(+), 18 deletions(-) (limited to 'pathspec.h') diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index 4a8fc707c7..d49c083525 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -64,37 +64,45 @@ static void output_exclude(const char *path, struct exclude *exclude) } static int check_ignore(struct dir_struct *dir, - const char *prefix, const char **pathspec) + const char *prefix, int argc, const char **argv) { - const char *path, *full_path; + const char *full_path; char *seen; int num_ignored = 0, dtype = DT_UNKNOWN, i; struct exclude *exclude; + struct pathspec pathspec; - if (!pathspec || !*pathspec) { + if (!argc) { if (!quiet) fprintf(stderr, "no pathspec given.\n"); return 0; } + /* + * check-ignore just needs paths. Magic beyond :/ is really + * irrelevant. + */ + parse_pathspec(&pathspec, + PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP, + PATHSPEC_SYMLINK_LEADING_PATH | + PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE | + PATHSPEC_KEEP_ORDER, + prefix, argv); + /* * look for pathspecs matching entries in the index, since these * should not be ignored, in order to be consistent with * 'git status', 'git add' etc. */ - seen = find_pathspecs_matching_against_index(pathspec); - for (i = 0; pathspec[i]; i++) { - path = pathspec[i]; - full_path = prefix_path(prefix, prefix - ? strlen(prefix) : 0, path); - full_path = check_path_for_gitlink(full_path); - die_if_path_beyond_symlink(full_path, prefix); + seen = find_pathspecs_matching_against_index(pathspec.raw); + for (i = 0; i < pathspec.nr; i++) { + full_path = pathspec.raw[i]; exclude = NULL; if (!seen[i]) { exclude = last_exclude_matching(dir, full_path, &dtype); } if (!quiet && (exclude || show_non_matching)) - output_exclude(path, exclude); + output_exclude(pathspec.items[i].original, exclude); if (exclude) num_ignored++; } @@ -120,7 +128,8 @@ static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix) strbuf_swap(&buf, &nbuf); } pathspec[0] = buf.buf; - num_ignored += check_ignore(dir, prefix, (const char **)pathspec); + num_ignored += check_ignore(dir, prefix, + 1, (const char **)pathspec); maybe_flush_or_die(stdout, "check-ignore to stdout"); } strbuf_release(&buf); @@ -166,7 +175,7 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix) if (stdin_paths) { num_ignored = check_ignore_stdin_paths(&dir, prefix); } else { - num_ignored = check_ignore(&dir, prefix, argv); + num_ignored = check_ignore(&dir, prefix, argc, argv); maybe_flush_or_die(stdout, "ignore to stdout"); } diff --git a/pathspec.c b/pathspec.c index ba6408a74c..5c9631a1a1 100644 --- a/pathspec.c +++ b/pathspec.c @@ -370,9 +370,13 @@ void parse_pathspec(struct pathspec *pathspec, pathspec->magic |= item[i].magic; } - if (pathspec->magic & PATHSPEC_MAXDEPTH) + + if (pathspec->magic & PATHSPEC_MAXDEPTH) { + if (flags & PATHSPEC_KEEP_ORDER) + die("BUG: PATHSPEC_MAXDEPTH_VALID and PATHSPEC_KEEP_ORDER are incompatible"); qsort(pathspec->items, pathspec->nr, sizeof(struct pathspec_item), pathspec_item_cmp); + } } /* diff --git a/pathspec.h b/pathspec.h index 6baf205862..02dded322f 100644 --- a/pathspec.h +++ b/pathspec.h @@ -51,6 +51,7 @@ struct pathspec { */ #define PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE (1<<5) #define PATHSPEC_PREFIX_ORIGIN (1<<6) +#define PATHSPEC_KEEP_ORDER (1<<7) extern int init_pathspec(struct pathspec *, const char **); extern void parse_pathspec(struct pathspec *pathspec, diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh index a56db804cb..2ced8e9729 100755 --- a/t/t0008-ignores.sh +++ b/t/t0008-ignores.sh @@ -432,7 +432,7 @@ test_expect_success_multi SYMLINKS 'symlink' ':: a/symlink' ' test_expect_success_multi SYMLINKS 'beyond a symlink' '' ' test_check_ignore "a/symlink/foo" 128 && - test_stderr "fatal: '\''a/symlink/foo'\'' is beyond a symbolic link" + test_stderr "fatal: pathspec '\''a/symlink/foo'\'' is beyond a symbolic link" ' test_expect_success_multi SYMLINKS 'beyond a symlink from subdirectory' '' ' @@ -440,7 +440,7 @@ test_expect_success_multi SYMLINKS 'beyond a symlink from subdirectory' '' ' cd a && test_check_ignore "symlink/foo" 128 ) && - test_stderr "fatal: '\''symlink/foo'\'' is beyond a symbolic link" + test_stderr "fatal: pathspec '\''symlink/foo'\'' is beyond a symbolic link" ' ############################################################################ @@ -449,7 +449,7 @@ test_expect_success_multi SYMLINKS 'beyond a symlink from subdirectory' '' ' test_expect_success_multi 'submodule' '' ' test_check_ignore "a/submodule/one" 128 && - test_stderr "fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''" + test_stderr "fatal: Pathspec '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''" ' test_expect_success_multi 'submodule from subdirectory' '' ' @@ -457,7 +457,7 @@ test_expect_success_multi 'submodule from subdirectory' '' ' cd a && test_check_ignore "submodule/one" 128 ) && - test_stderr "fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''" + test_stderr "fatal: Pathspec '\''submodule/one'\'' is in submodule '\''a/submodule'\''" ' ############################################################################ -- cgit v1.3 From 9a0872744315da67db3c81eb9270751e31fcc8f5 Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:35:59 +0700 Subject: remove init_pathspec() in favor of parse_pathspec() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While at there, move free_pathspec() to pathspec.c Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/blame.c | 8 +------- builtin/log.c | 2 +- builtin/ls-files.c | 11 +++++------ diff-lib.c | 2 +- dir.c | 58 ------------------------------------------------------ merge-recursive.c | 2 +- pathspec.c | 6 ++++++ pathspec.h | 1 - revision.c | 2 +- tree-diff.c | 10 +++++----- 10 files changed, 21 insertions(+), 81 deletions(-) (limited to 'pathspec.h') diff --git a/builtin/blame.c b/builtin/blame.c index 5bd721d2b9..56e3d6b7b4 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -408,7 +408,7 @@ static struct origin *find_origin(struct scoreboard *sb, paths[0] = origin->path; paths[1] = NULL; - init_pathspec(&diff_opts.pathspec, paths); + parse_pathspec(&diff_opts.pathspec, PATHSPEC_ALL_MAGIC, 0, "", paths); diff_setup_done(&diff_opts); if (is_null_sha1(origin->commit->object.sha1)) @@ -486,15 +486,12 @@ static struct origin *find_rename(struct scoreboard *sb, struct origin *porigin = NULL; struct diff_options diff_opts; int i; - const char *paths[2]; diff_setup(&diff_opts); DIFF_OPT_SET(&diff_opts, RECURSIVE); diff_opts.detect_rename = DIFF_DETECT_RENAME; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_opts.single_follow = origin->path; - paths[0] = NULL; - init_pathspec(&diff_opts.pathspec, paths); diff_setup_done(&diff_opts); if (is_null_sha1(origin->commit->object.sha1)) @@ -1064,7 +1061,6 @@ static int find_copy_in_parent(struct scoreboard *sb, int opt) { struct diff_options diff_opts; - const char *paths[1]; int i, j; int retval; struct blame_list *blame_list; @@ -1078,8 +1074,6 @@ static int find_copy_in_parent(struct scoreboard *sb, DIFF_OPT_SET(&diff_opts, RECURSIVE); diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - paths[0] = NULL; - init_pathspec(&diff_opts.pathspec, paths); diff_setup_done(&diff_opts); /* Try "find copies harder" on new path if requested; diff --git a/builtin/log.c b/builtin/log.c index e3222ed9f9..873af69476 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -503,7 +503,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) init_grep_defaults(); git_config(git_log_config, NULL); - init_pathspec(&match_all, NULL); + memset(&match_all, 0, sizeof(match_all)); init_revisions(&rev, prefix); rev.diff = 1; rev.always_show_header = 1; diff --git a/builtin/ls-files.c b/builtin/ls-files.c index c074e6fc7c..d3a0495f7d 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -315,13 +315,12 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) } if (prefix) { - static const char *(matchbuf[2]); - matchbuf[0] = prefix; - matchbuf[1] = NULL; - init_pathspec(&pathspec, matchbuf); - pathspec.items[0].nowildcard_len = pathspec.items[0].len; + static const char *(matchbuf[1]); + matchbuf[0] = NULL; + parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC, + PATHSPEC_PREFER_CWD, prefix, matchbuf); } else - init_pathspec(&pathspec, NULL); + memset(&pathspec, 0, sizeof(pathspec)); if (read_tree(tree, 1, &pathspec)) die("unable to read tree entries %s", tree_name); diff --git a/diff-lib.c b/diff-lib.c index d4e17f7662..0a95763063 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -501,7 +501,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) struct rev_info revs; init_revisions(&revs, NULL); - init_pathspec(&revs.prune_data, opt->pathspec.raw); + copy_pathspec(&revs.prune_data, &opt->pathspec); revs.diffopt = *opt; if (diff_cache(&revs, tree_sha1, NULL, 1)) diff --git a/dir.c b/dir.c index 019ad09b6e..959e6942c4 100644 --- a/dir.c +++ b/dir.c @@ -1580,64 +1580,6 @@ int remove_path(const char *name) return 0; } -static int pathspec_item_cmp(const void *a_, const void *b_) -{ - struct pathspec_item *a, *b; - - a = (struct pathspec_item *)a_; - b = (struct pathspec_item *)b_; - return strcmp(a->match, b->match); -} - -int init_pathspec(struct pathspec *pathspec, const char **paths) -{ - const char **p = paths; - int i; - - memset(pathspec, 0, sizeof(*pathspec)); - if (!p) - return 0; - while (*p) - p++; - pathspec->raw = paths; - pathspec->nr = p - paths; - if (!pathspec->nr) - return 0; - - pathspec->items = xmalloc(sizeof(struct pathspec_item)*pathspec->nr); - for (i = 0; i < pathspec->nr; i++) { - struct pathspec_item *item = pathspec->items+i; - const char *path = paths[i]; - - item->match = path; - item->original = path; - item->len = strlen(path); - item->flags = 0; - if (limit_pathspec_to_literal()) { - item->nowildcard_len = item->len; - } else { - item->nowildcard_len = simple_length(path); - if (item->nowildcard_len < item->len) { - pathspec->has_wildcard = 1; - if (path[item->nowildcard_len] == '*' && - no_wildcard(path + item->nowildcard_len + 1)) - item->flags |= PATHSPEC_ONESTAR; - } - } - } - - qsort(pathspec->items, pathspec->nr, - sizeof(struct pathspec_item), pathspec_item_cmp); - - return 0; -} - -void free_pathspec(struct pathspec *pathspec) -{ - free(pathspec->items); - pathspec->items = NULL; -} - int limit_pathspec_to_literal(void) { static int flag = -1; diff --git a/merge-recursive.c b/merge-recursive.c index ea9dbd307c..8395b9b08a 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -297,7 +297,7 @@ static int get_files_dirs(struct merge_options *o, struct tree *tree) { int n; struct pathspec match_all; - init_pathspec(&match_all, NULL); + memset(&match_all, 0, sizeof(match_all)); if (read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o)) return 0; n = o->current_file_set.nr + o->current_directory_set.nr; diff --git a/pathspec.c b/pathspec.c index 2774bda476..74f0203604 100644 --- a/pathspec.c +++ b/pathspec.c @@ -372,3 +372,9 @@ void copy_pathspec(struct pathspec *dst, const struct pathspec *src) memcpy(dst->items, src->items, sizeof(struct pathspec_item) * dst->nr); } + +void free_pathspec(struct pathspec *pathspec) +{ + free(pathspec->items); + pathspec->items = NULL; +} diff --git a/pathspec.h b/pathspec.h index 02dded322f..cadd46cca5 100644 --- a/pathspec.h +++ b/pathspec.h @@ -53,7 +53,6 @@ struct pathspec { #define PATHSPEC_PREFIX_ORIGIN (1<<6) #define PATHSPEC_KEEP_ORDER (1<<7) -extern int init_pathspec(struct pathspec *, const char **); extern void parse_pathspec(struct pathspec *pathspec, unsigned magic_mask, unsigned flags, diff --git a/revision.c b/revision.c index c2d44b7365..001623a921 100644 --- a/revision.c +++ b/revision.c @@ -1372,7 +1372,7 @@ static void prepare_show_merge(struct rev_info *revs) i++; } free_pathspec(&revs->prune_data); - init_pathspec(&revs->prune_data, prune); + parse_pathspec(&revs->prune_data, PATHSPEC_ALL_MAGIC, 0, "", prune); revs->limited = 1; } diff --git a/tree-diff.c b/tree-diff.c index f4c92f6e20..e1145c60e7 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -195,7 +195,6 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co struct diff_options diff_opts; struct diff_queue_struct *q = &diff_queued_diff; struct diff_filepair *choice; - const char *paths[1]; int i; /* @@ -228,8 +227,6 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co diff_opts.single_follow = opt->pathspec.raw[0]; diff_opts.break_opt = opt->break_opt; diff_opts.rename_score = opt->rename_score; - paths[0] = NULL; - init_pathspec(&diff_opts.pathspec, paths); diff_setup_done(&diff_opts); diff_tree(t1, t2, base, &diff_opts); diffcore_std(&diff_opts); @@ -247,14 +244,17 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co */ if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, opt->pathspec.raw[0])) { + const char *path[2]; + /* Switch the file-pairs around */ q->queue[i] = choice; choice = p; /* Update the path we use from now on.. */ + path[0] = p->one->path; + path[1] = NULL; free_pathspec(&opt->pathspec); - opt->pathspec.raw[0] = xstrdup(p->one->path); - init_pathspec(&opt->pathspec, opt->pathspec.raw); + parse_pathspec(&opt->pathspec, PATHSPEC_ALL_MAGIC, 0, "", path); /* * The caller expects us to return a set of vanilla -- cgit v1.3 From 84b8b5d1fa244bc591291a3cf18bf0fa9976ad17 Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:36:00 +0700 Subject: remove match_pathspec() in favor of match_pathspec_depth() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit match_pathspec_depth was created to replace match_pathspec (see 61cf282 (pathspec: add match_pathspec_depth() - 2010-12-15). It took more than two years, but the replacement finally happens :-) Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/add.c | 30 +++++++------- builtin/check-ignore.c | 4 +- dir.c | 107 ------------------------------------------------- dir.h | 1 - pathspec.c | 19 ++++----- pathspec.h | 4 +- 6 files changed, 25 insertions(+), 140 deletions(-) (limited to 'pathspec.h') diff --git a/builtin/add.c b/builtin/add.c index a47aeb4662..0b80fa8956 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -195,23 +195,21 @@ int add_files_to_cache(const char *prefix, } #define WARN_IMPLICIT_DOT (1u << 0) -static char *prune_directory(struct dir_struct *dir, const char **pathspec, +static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, int prefix, unsigned flag) { char *seen; - int i, specs; + int i; struct dir_entry **src, **dst; - for (specs = 0; pathspec[specs]; specs++) - /* nothing */; - seen = xcalloc(specs, 1); + seen = xcalloc(pathspec->nr, 1); src = dst = dir->entries; i = dir->nr; while (--i >= 0) { struct dir_entry *entry = *src++; - if (match_pathspec(pathspec, entry->name, entry->len, - prefix, seen)) + if (match_pathspec_depth(pathspec, entry->name, entry->len, + prefix, seen)) *dst++ = entry; else if (flag & WARN_IMPLICIT_DOT) /* @@ -225,7 +223,7 @@ static char *prune_directory(struct dir_struct *dir, const char **pathspec, warn_pathless_add(); } dir->nr = dst - dir->entries; - add_pathspec_matches_against_index(pathspec, seen, specs); + add_pathspec_matches_against_index(pathspec, seen); return seen; } @@ -523,7 +521,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) /* This picks up the paths that are not tracked */ baselen = fill_directory(&dir, implicit_dot ? &empty_pathspec : &pathspec); if (pathspec.nr) - seen = prune_directory(&dir, pathspec.raw, baselen, + seen = prune_directory(&dir, &pathspec, baselen, implicit_dot ? WARN_IMPLICIT_DOT : 0); } @@ -538,23 +536,23 @@ int cmd_add(int argc, const char **argv, const char *prefix) int i; if (!seen) - seen = find_pathspecs_matching_against_index(pathspec.raw); + seen = find_pathspecs_matching_against_index(&pathspec); /* * file_exists() assumes exact match */ GUARD_PATHSPEC(&pathspec, PATHSPEC_FROMTOP); - for (i = 0; pathspec.raw[i]; i++) { - if (!seen[i] && pathspec.raw[i][0] - && !file_exists(pathspec.raw[i])) { + for (i = 0; i < pathspec.nr; i++) { + const char *path = pathspec.items[i].match; + if (!seen[i] && !file_exists(path)) { if (ignore_missing) { int dtype = DT_UNKNOWN; - if (is_excluded(&dir, pathspec.raw[i], &dtype)) - dir_add_ignored(&dir, pathspec.raw[i], strlen(pathspec.raw[i])); + if (is_excluded(&dir, path, &dtype)) + dir_add_ignored(&dir, path, pathspec.items[i].len); } else die(_("pathspec '%s' did not match any files"), - pathspec.raw[i]); + pathspec.items[i].original); } } free(seen); diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index d49c083525..70537c8d30 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -94,9 +94,9 @@ static int check_ignore(struct dir_struct *dir, * should not be ignored, in order to be consistent with * 'git status', 'git add' etc. */ - seen = find_pathspecs_matching_against_index(pathspec.raw); + seen = find_pathspecs_matching_against_index(&pathspec); for (i = 0; i < pathspec.nr; i++) { - full_path = pathspec.raw[i]; + full_path = pathspec.items[i].match; exclude = NULL; if (!seen[i]) { exclude = last_exclude_matching(dir, full_path, &dtype); diff --git a/dir.c b/dir.c index 959e6942c4..9423fbb627 100644 --- a/dir.c +++ b/dir.c @@ -171,113 +171,6 @@ int within_depth(const char *name, int namelen, return 1; } -/* - * Does 'match' match the given name? - * A match is found if - * - * (1) the 'match' string is leading directory of 'name', or - * (2) the 'match' string is a wildcard and matches 'name', or - * (3) the 'match' string is exactly the same as 'name'. - * - * and the return value tells which case it was. - * - * It returns 0 when there is no match. - */ -static int match_one(const char *match, const char *name, int namelen) -{ - int matchlen; - int literal = limit_pathspec_to_literal(); - - /* If the match was just the prefix, we matched */ - if (!*match) - return MATCHED_RECURSIVELY; - - if (ignore_case) { - for (;;) { - unsigned char c1 = tolower(*match); - unsigned char c2 = tolower(*name); - if (c1 == '\0' || (!literal && is_glob_special(c1))) - break; - if (c1 != c2) - return 0; - match++; - name++; - namelen--; - } - } else { - for (;;) { - unsigned char c1 = *match; - unsigned char c2 = *name; - if (c1 == '\0' || (!literal && is_glob_special(c1))) - break; - if (c1 != c2) - return 0; - match++; - name++; - namelen--; - } - } - - /* - * If we don't match the matchstring exactly, - * we need to match by fnmatch - */ - matchlen = strlen(match); - if (strncmp_icase(match, name, matchlen)) { - if (literal) - return 0; - return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0; - } - - if (namelen == matchlen) - return MATCHED_EXACTLY; - if (match[matchlen-1] == '/' || name[matchlen] == '/') - return MATCHED_RECURSIVELY; - return 0; -} - -/* - * Given a name and a list of pathspecs, returns the nature of the - * closest (i.e. most specific) match of the name to any of the - * pathspecs. - * - * The caller typically calls this multiple times with the same - * pathspec and seen[] array but with different name/namelen - * (e.g. entries from the index) and is interested in seeing if and - * how each pathspec matches all the names it calls this function - * with. A mark is left in the seen[] array for each pathspec element - * indicating the closest type of match that element achieved, so if - * seen[n] remains zero after multiple invocations, that means the nth - * pathspec did not match any names, which could indicate that the - * user mistyped the nth pathspec. - */ -int match_pathspec(const char **pathspec, const char *name, int namelen, - int prefix, char *seen) -{ - int i, retval = 0; - - if (!pathspec) - return 1; - - name += prefix; - namelen -= prefix; - - for (i = 0; pathspec[i] != NULL; i++) { - int how; - const char *match = pathspec[i] + prefix; - if (seen && seen[i] == MATCHED_EXACTLY) - continue; - how = match_one(match, name, namelen); - if (how) { - if (retval < how) - retval = how; - if (seen && seen[i] < how) - seen[i] = how; - } - } - return retval; -} - /* * Does 'match' match the given name? * A match is found if diff --git a/dir.h b/dir.h index ba40e69bc5..7d051e368a 100644 --- a/dir.h +++ b/dir.h @@ -131,7 +131,6 @@ struct dir_struct { extern int simple_length(const char *match); extern int no_wildcard(const char *string); extern char *common_prefix(const struct pathspec *pathspec); -extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen); extern int match_pathspec_depth(const struct pathspec *pathspec, const char *name, int namelen, int prefix, char *seen); diff --git a/pathspec.c b/pathspec.c index 74f0203604..3d1386de85 100644 --- a/pathspec.c +++ b/pathspec.c @@ -15,8 +15,8 @@ * If seen[] has not already been written to, it may make sense * to use find_pathspecs_matching_against_index() instead. */ -void add_pathspec_matches_against_index(const char **pathspec, - char *seen, int specs) +void add_pathspec_matches_against_index(const struct pathspec *pathspec, + char *seen) { int num_unmatched = 0, i; @@ -26,14 +26,14 @@ void add_pathspec_matches_against_index(const char **pathspec, * mistakenly think that the user gave a pathspec that did not match * anything. */ - for (i = 0; i < specs; i++) + for (i = 0; i < pathspec->nr; i++) if (!seen[i]) num_unmatched++; if (!num_unmatched) return; for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; - match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen); + match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, seen); } } @@ -45,15 +45,10 @@ void add_pathspec_matches_against_index(const char **pathspec, * nature of the "closest" (i.e. most specific) matches which each of the * given pathspecs achieves against all items in the index. */ -char *find_pathspecs_matching_against_index(const char **pathspec) +char *find_pathspecs_matching_against_index(const struct pathspec *pathspec) { - char *seen; - int i; - - for (i = 0; pathspec[i]; i++) - ; /* just counting */ - seen = xcalloc(i, 1); - add_pathspec_matches_against_index(pathspec, seen, i); + char *seen = xcalloc(pathspec->nr, 1); + add_pathspec_matches_against_index(pathspec, seen); return seen; } diff --git a/pathspec.h b/pathspec.h index cadd46cca5..31a9c96237 100644 --- a/pathspec.h +++ b/pathspec.h @@ -63,8 +63,8 @@ extern void free_pathspec(struct pathspec *); extern int limit_pathspec_to_literal(void); -extern char *find_pathspecs_matching_against_index(const char **pathspec); -extern void add_pathspec_matches_against_index(const char **pathspec, char *seen, int specs); +extern char *find_pathspecs_matching_against_index(const struct pathspec *pathspec); +extern void add_pathspec_matches_against_index(const struct pathspec *pathspec, char *seen); extern const char *check_path_for_gitlink(const char *path); extern void die_if_path_beyond_symlink(const char *path, const char *prefix); -- cgit v1.3 From b3920bbdc51fc360bde70e7c19088acfe44b9c4b Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:36:02 +0700 Subject: rename field "raw" to "_raw" in struct pathspec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch is essentially no-op. It helps catching new use of this field though. This field is introduced as an intermediate step for the pathspec conversion and will be removed eventually. At this stage no more access sites should be introduced. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/ls-tree.c | 2 +- dir.c | 4 ++-- pathspec.c | 6 +++--- pathspec.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) (limited to 'pathspec.h') diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index bdb03f3662..1c4f48ea8f 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -36,7 +36,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname) if (ls_options & LS_RECURSIVE) return 1; - s = pathspec.raw; + s = pathspec._raw; if (!s) return 0; diff --git a/dir.c b/dir.c index 9423fbb627..bf224986db 100644 --- a/dir.c +++ b/dir.c @@ -152,7 +152,7 @@ int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec) len = common_prefix_len(pathspec); /* Read the directory and prune it */ - read_directory(dir, pathspec->nr ? pathspec->raw[0] : "", len, pathspec); + read_directory(dir, pathspec->nr ? pathspec->_raw[0] : "", len, pathspec); return len; } @@ -1293,7 +1293,7 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru if (has_symlink_leading_path(path, len)) return dir->nr; - simplify = create_simplify(pathspec ? pathspec->raw : NULL); + simplify = create_simplify(pathspec ? pathspec->_raw : NULL); if (!len || treat_leading_path(dir, path, len, simplify)) read_directory_recursive(dir, path, len, 0, simplify); free_simplify(simplify); diff --git a/pathspec.c b/pathspec.c index 3d1386de85..da802e22a0 100644 --- a/pathspec.c +++ b/pathspec.c @@ -287,7 +287,7 @@ void parse_pathspec(struct pathspec *pathspec, raw[0] = prefix; raw[1] = NULL; pathspec->nr = 1; - pathspec->raw = raw; + pathspec->_raw = raw; return; } @@ -297,7 +297,7 @@ void parse_pathspec(struct pathspec *pathspec, pathspec->nr = n; pathspec->items = item = xmalloc(sizeof(*item) * n); - pathspec->raw = argv; + pathspec->_raw = argv; prefixlen = prefix ? strlen(prefix) : 0; for (i = 0; i < n; i++) { @@ -357,7 +357,7 @@ const char **get_pathspec(const char *prefix, const char **pathspec) PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP, PATHSPEC_PREFER_CWD, prefix, pathspec); - return ps.raw; + return ps._raw; } void copy_pathspec(struct pathspec *dst, const struct pathspec *src) diff --git a/pathspec.h b/pathspec.h index 31a9c96237..0f6739dc67 100644 --- a/pathspec.h +++ b/pathspec.h @@ -11,7 +11,7 @@ #define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies GFNM_ONESTAR */ struct pathspec { - const char **raw; /* get_pathspec() result, not freed by free_pathspec() */ + const char **_raw; /* get_pathspec() result, not freed by free_pathspec() */ int nr; unsigned int has_wildcard:1; unsigned int recursive:1; -- cgit v1.3 From 645a29c40a12a4ade1b041dce246ae241c502a64 Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:36:03 +0700 Subject: parse_pathspec: make sure the prefix part is wildcard-free MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepending prefix to pathspec is a trick to workaround the fact that commands can be executed in a subdirectory, but all git commands run at worktree's root. The prefix part should always be treated as literal string. Make it so. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- cache.h | 2 ++ path.c | 15 ++++++++++++++- pathspec.c | 21 +++++++++++++++++---- pathspec.h | 2 +- setup.c | 24 ++++++++++++++++++++---- 5 files changed, 54 insertions(+), 10 deletions(-) (limited to 'pathspec.h') diff --git a/cache.h b/cache.h index b0ed117ba5..13e3c94bf0 100644 --- a/cache.h +++ b/cache.h @@ -414,6 +414,7 @@ extern void setup_work_tree(void); extern const char *setup_git_directory_gently(int *); extern const char *setup_git_directory(void); extern char *prefix_path(const char *prefix, int len, const char *path); +extern char *prefix_path_gently(const char *prefix, int len, int *remaining, const char *path); extern const char *prefix_filename(const char *prefix, int len, const char *path); extern int check_filename(const char *prefix, const char *name); extern void verify_filename(const char *prefix, @@ -741,6 +742,7 @@ const char *real_path(const char *path); const char *real_path_if_valid(const char *path); const char *absolute_path(const char *path); const char *relative_path(const char *abs, const char *base); +int normalize_path_copy_len(char *dst, const char *src, int *prefix_len); int normalize_path_copy(char *dst, const char *src); int longest_ancestor_length(const char *path, struct string_list *prefixes); char *strip_path_suffix(const char *path, const char *suffix); diff --git a/path.c b/path.c index 04ff1487ed..f4b49d6924 100644 --- a/path.c +++ b/path.c @@ -492,8 +492,14 @@ const char *relative_path(const char *abs, const char *base) * * Note that this function is purely textual. It does not follow symlinks, * verify the existence of the path, or make any system calls. + * + * prefix_len != NULL is for a specific case of prefix_pathspec(): + * assume that src == dst and src[0..prefix_len-1] is already + * normalized, any time "../" eats up to the prefix_len part, + * prefix_len is reduced. In the end prefix_len is the remaining + * prefix that has not been overridden by user pathspec. */ -int normalize_path_copy(char *dst, const char *src) +int normalize_path_copy_len(char *dst, const char *src, int *prefix_len) { char *dst0; @@ -568,11 +574,18 @@ int normalize_path_copy(char *dst, const char *src) /* Windows: dst[-1] cannot be backslash anymore */ while (dst0 < dst && dst[-1] != '/') dst--; + if (prefix_len && *prefix_len > dst - dst0) + *prefix_len = dst - dst0; } *dst = '\0'; return 0; } +int normalize_path_copy(char *dst, const char *src) +{ + return normalize_path_copy_len(dst, src, NULL); +} + /* * path = Canonical absolute path * prefixes = string_list containing normalized, absolute paths without diff --git a/pathspec.c b/pathspec.c index da802e22a0..71e5eaf1b1 100644 --- a/pathspec.c +++ b/pathspec.c @@ -150,10 +150,14 @@ static unsigned prefix_pathspec(struct pathspec_item *item, magic |= short_magic; *p_short_magic = short_magic; - if (magic & PATHSPEC_FROMTOP) + if (magic & PATHSPEC_FROMTOP) { match = xstrdup(copyfrom); - else - match = prefix_path(prefix, prefixlen, copyfrom); + prefixlen = 0; + } else { + match = prefix_path_gently(prefix, prefixlen, &prefixlen, copyfrom); + if (!match) + die(_("%s: '%s' is outside repository"), elt, copyfrom); + } *raw = item->match = match; /* * Prefix the pathspec (keep all magic) and assign to @@ -167,6 +171,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, } else item->original = elt; item->len = strlen(item->match); + item->prefix = prefixlen; if ((flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) && (item->len >= 1 && item->match[item->len - 1] == '/') && @@ -198,13 +203,20 @@ static unsigned prefix_pathspec(struct pathspec_item *item, if (limit_pathspec_to_literal()) item->nowildcard_len = item->len; - else + else { item->nowildcard_len = simple_length(item->match); + if (item->nowildcard_len < prefixlen) + item->nowildcard_len = prefixlen; + } item->flags = 0; if (item->nowildcard_len < item->len && item->match[item->nowildcard_len] == '*' && no_wildcard(item->match + item->nowildcard_len + 1)) item->flags |= PATHSPEC_ONESTAR; + + /* sanity checks, pathspec matchers assume these are sane */ + assert(item->nowildcard_len <= item->len && + item->prefix <= item->len); return magic; } @@ -284,6 +296,7 @@ void parse_pathspec(struct pathspec *pathspec, item->match = prefix; item->original = prefix; item->nowildcard_len = item->len = strlen(prefix); + item->prefix = item->len; raw[0] = prefix; raw[1] = NULL; pathspec->nr = 1; diff --git a/pathspec.h b/pathspec.h index 0f6739dc67..2f3532e8f1 100644 --- a/pathspec.h +++ b/pathspec.h @@ -21,7 +21,7 @@ struct pathspec { const char *match; const char *original; unsigned magic; - int len; + int len, prefix; int nowildcard_len; int flags; } *items; diff --git a/setup.c b/setup.c index d1ece5d16b..ff4499eaef 100644 --- a/setup.c +++ b/setup.c @@ -5,7 +5,19 @@ static int inside_git_dir = -1; static int inside_work_tree = -1; -static char *prefix_path_gently(const char *prefix, int len, const char *path) +/* + * Normalize "path", prepending the "prefix" for relative paths. If + * remaining_prefix is not NULL, return the actual prefix still + * remains in the path. For example, prefix = sub1/sub2/ and path is + * + * foo -> sub1/sub2/foo (full prefix) + * ../foo -> sub1/foo (remaining prefix is sub1/) + * ../../bar -> bar (no remaining prefix) + * ../../sub1/sub2/foo -> sub1/sub2/foo (but no remaining prefix) + * `pwd`/../bar -> sub1/bar (no remaining prefix) + */ +char *prefix_path_gently(const char *prefix, int len, + int *remaining_prefix, const char *path) { const char *orig = path; char *sanitized; @@ -13,13 +25,17 @@ static char *prefix_path_gently(const char *prefix, int len, const char *path) const char *temp = real_path(path); sanitized = xmalloc(len + strlen(temp) + 1); strcpy(sanitized, temp); + if (remaining_prefix) + *remaining_prefix = 0; } else { sanitized = xmalloc(len + strlen(path) + 1); if (len) memcpy(sanitized, prefix, len); strcpy(sanitized + len, path); + if (remaining_prefix) + *remaining_prefix = len; } - if (normalize_path_copy(sanitized, sanitized)) + if (normalize_path_copy_len(sanitized, sanitized, remaining_prefix)) goto error_out; if (is_absolute_path(orig)) { size_t root_len, len, total; @@ -44,7 +60,7 @@ static char *prefix_path_gently(const char *prefix, int len, const char *path) char *prefix_path(const char *prefix, int len, const char *path) { - char *r = prefix_path_gently(prefix, len, path); + char *r = prefix_path_gently(prefix, len, NULL, path); if (!r) die("'%s' is outside repository", path); return r; @@ -53,7 +69,7 @@ char *prefix_path(const char *prefix, int len, const char *path) int path_inside_repo(const char *prefix, const char *path) { int len = prefix ? strlen(prefix) : 0; - char *r = prefix_path_gently(prefix, len, path); + char *r = prefix_path_gently(prefix, len, NULL, path); if (r) { free(r); return 1; -- cgit v1.3 From 341003e715c9a7c0332d3f8f08c2f3696a057565 Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:36:05 +0700 Subject: kill limit_pathspec_to_literal() as it's only used by parse_pathspec() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- dir.c | 8 -------- pathspec.c | 8 ++++++-- pathspec.h | 2 -- 3 files changed, 6 insertions(+), 12 deletions(-) (limited to 'pathspec.h') diff --git a/dir.c b/dir.c index bf224986db..79465e7f4c 100644 --- a/dir.c +++ b/dir.c @@ -1473,14 +1473,6 @@ int remove_path(const char *name) return 0; } -int limit_pathspec_to_literal(void) -{ - static int flag = -1; - if (flag < 0) - flag = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0); - return flag; -} - /* * Frees memory within dir which was allocated for exclude lists and * the exclude_stack. Does not free dir itself. diff --git a/pathspec.c b/pathspec.c index 82ede57206..b2e3a8778c 100644 --- a/pathspec.c +++ b/pathspec.c @@ -91,11 +91,15 @@ static unsigned prefix_pathspec(struct pathspec_item *item, const char *prefix, int prefixlen, const char *elt) { + static int literal_global = -1; unsigned magic = 0, short_magic = 0; const char *copyfrom = elt, *long_magic_end = NULL; char *match; int i, pathspec_prefix = -1; + if (literal_global < 0) + literal_global = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0); + if (elt[0] != ':') { ; /* nothing to do */ } else if (elt[1] == '(') { @@ -184,7 +188,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, if (flags & PATHSPEC_PREFIX_ORIGIN) { struct strbuf sb = STRBUF_INIT; const char *start = elt; - if (prefixlen && !limit_pathspec_to_literal()) { + if (prefixlen && !literal_global) { /* Preserve the actual prefix length of each pattern */ if (long_magic_end) { strbuf_add(&sb, start, long_magic_end - start); @@ -232,7 +236,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, elt, ce_len, ce->name); } - if (limit_pathspec_to_literal()) + if (literal_global) item->nowildcard_len = item->len; else { item->nowildcard_len = simple_length(item->match); diff --git a/pathspec.h b/pathspec.h index 2f3532e8f1..7ef9896edb 100644 --- a/pathspec.h +++ b/pathspec.h @@ -61,8 +61,6 @@ extern void parse_pathspec(struct pathspec *pathspec, extern void copy_pathspec(struct pathspec *dst, const struct pathspec *src); extern void free_pathspec(struct pathspec *); -extern int limit_pathspec_to_literal(void); - extern char *find_pathspecs_matching_against_index(const struct pathspec *pathspec); extern void add_pathspec_matches_against_index(const struct pathspec *pathspec, char *seen); extern const char *check_path_for_gitlink(const char *path); -- cgit v1.3 From 5c6933d201fab183a9779dca0fe43bf2f1eca098 Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:36:06 +0700 Subject: pathspec: support :(literal) syntax for noglob pathspec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/glossary-content.txt | 20 ++++++++++++++++---- builtin/add.c | 2 +- builtin/diff.c | 2 +- dir.c | 15 ++++++++++++--- pathspec.c | 11 ++++++++--- pathspec.h | 4 +++- t/t6130-pathspec-noglob.sh | 18 ++++++++++++++++++ tree-diff.c | 2 +- tree-walk.c | 5 ++++- 9 files changed, 64 insertions(+), 15 deletions(-) (limited to 'pathspec.h') diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index dba5062b37..ca9f20f25f 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -322,10 +322,22 @@ and a close parentheses `)`, and the remainder is the pattern to match against the path. + The "magic signature" consists of an ASCII symbol that is not -alphanumeric. Currently only the slash `/` is recognized as a -"magic signature": it makes the pattern match from the root of -the working tree, even when you are running the command from -inside a subdirectory. +alphanumeric. ++ +-- +top `/`;; + The magic word `top` (mnemonic: `/`) makes the pattern match + from the root of the working tree, even when you are running + the command from inside a subdirectory. + +literal;; + Wildcards in the pattern such as `*` or `?` are treated + as literal characters. +-- ++ +Currently only the slash `/` is recognized as the "magic signature", +but it is envisioned that we will support more types of magic in later +versions of Git. + A pathspec with only a colon means "there is no pathspec". This form should not be combined with other pathspec. diff --git a/builtin/add.c b/builtin/add.c index 0b80fa8956..663ddd122c 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -541,7 +541,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) /* * file_exists() assumes exact match */ - GUARD_PATHSPEC(&pathspec, PATHSPEC_FROMTOP); + GUARD_PATHSPEC(&pathspec, PATHSPEC_FROMTOP | PATHSPEC_LITERAL); for (i = 0; i < pathspec.nr; i++) { const char *path = pathspec.items[i].match; diff --git a/builtin/diff.c b/builtin/diff.c index bb84ba0e46..2fb8c5dc0b 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -368,7 +368,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } if (rev.prune_data.nr) { /* builtin_diff_b_f() */ - GUARD_PATHSPEC(&rev.prune_data, PATHSPEC_FROMTOP); + GUARD_PATHSPEC(&rev.prune_data, PATHSPEC_FROMTOP | PATHSPEC_LITERAL); if (!path) path = rev.prune_data.items[0].match; paths += rev.prune_data.nr; diff --git a/dir.c b/dir.c index 79465e7f4c..50ec2f5478 100644 --- a/dir.c +++ b/dir.c @@ -108,7 +108,10 @@ static size_t common_prefix_len(const struct pathspec *pathspec) int n; size_t max = 0; - GUARD_PATHSPEC(pathspec, PATHSPEC_FROMTOP | PATHSPEC_MAXDEPTH); + GUARD_PATHSPEC(pathspec, + PATHSPEC_FROMTOP | + PATHSPEC_MAXDEPTH | + PATHSPEC_LITERAL); for (n = 0; n < pathspec->nr; n++) { size_t i = 0, len = 0; @@ -232,7 +235,10 @@ int match_pathspec_depth(const struct pathspec *ps, { int i, retval = 0; - GUARD_PATHSPEC(ps, PATHSPEC_FROMTOP | PATHSPEC_MAXDEPTH); + GUARD_PATHSPEC(ps, + PATHSPEC_FROMTOP | + PATHSPEC_MAXDEPTH | + PATHSPEC_LITERAL); if (!ps->nr) { if (!ps->recursive || @@ -1288,7 +1294,10 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru * Check out create_simplify() */ if (pathspec) - GUARD_PATHSPEC(pathspec, PATHSPEC_FROMTOP | PATHSPEC_MAXDEPTH); + GUARD_PATHSPEC(pathspec, + PATHSPEC_FROMTOP | + PATHSPEC_MAXDEPTH | + PATHSPEC_LITERAL); if (has_symlink_leading_path(path, len)) return dir->nr; diff --git a/pathspec.c b/pathspec.c index b2e3a8778c..6a16938cb6 100644 --- a/pathspec.c +++ b/pathspec.c @@ -70,6 +70,7 @@ static struct pathspec_magic { const char *name; } pathspec_magic[] = { { PATHSPEC_FROMTOP, '/', "top" }, + { PATHSPEC_LITERAL, 0, "literal" }, }; /* @@ -92,13 +93,15 @@ static unsigned prefix_pathspec(struct pathspec_item *item, const char *elt) { static int literal_global = -1; - unsigned magic = 0, short_magic = 0; + unsigned magic = 0, short_magic = 0, global_magic = 0; const char *copyfrom = elt, *long_magic_end = NULL; char *match; int i, pathspec_prefix = -1; if (literal_global < 0) literal_global = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0); + if (literal_global) + global_magic |= PATHSPEC_LITERAL; if (elt[0] != ':') { ; /* nothing to do */ @@ -164,6 +167,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, magic |= short_magic; *p_short_magic = short_magic; + magic |= global_magic; if (pathspec_prefix >= 0 && (prefixlen || (prefix && *prefix))) @@ -236,7 +240,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, elt, ce_len, ce->name); } - if (literal_global) + if (magic & PATHSPEC_LITERAL) item->nowildcard_len = item->len; else { item->nowildcard_len = simple_length(item->match); @@ -402,7 +406,8 @@ const char **get_pathspec(const char *prefix, const char **pathspec) { struct pathspec ps; parse_pathspec(&ps, - PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP, + PATHSPEC_ALL_MAGIC & + ~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL), PATHSPEC_PREFER_CWD, prefix, pathspec); return ps._raw; diff --git a/pathspec.h b/pathspec.h index 7ef9896edb..987d70c55a 100644 --- a/pathspec.h +++ b/pathspec.h @@ -4,9 +4,11 @@ /* Pathspec magic */ #define PATHSPEC_FROMTOP (1<<0) #define PATHSPEC_MAXDEPTH (1<<1) +#define PATHSPEC_LITERAL (1<<2) #define PATHSPEC_ALL_MAGIC \ (PATHSPEC_FROMTOP | \ - PATHSPEC_MAXDEPTH) + PATHSPEC_MAXDEPTH | \ + PATHSPEC_LITERAL) #define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies GFNM_ONESTAR */ diff --git a/t/t6130-pathspec-noglob.sh b/t/t6130-pathspec-noglob.sh index 39ef61994f..49c148e17e 100755 --- a/t/t6130-pathspec-noglob.sh +++ b/t/t6130-pathspec-noglob.sh @@ -47,18 +47,36 @@ test_expect_success 'no-glob option matches literally (vanilla)' ' test_cmp expect actual ' +test_expect_success 'no-glob option matches literally (vanilla)' ' + echo vanilla >expect && + git log --format=%s -- ":(literal)foo" >actual && + test_cmp expect actual +' + test_expect_success 'no-glob option matches literally (star)' ' echo star >expect && git --literal-pathspecs log --format=%s -- "f*" >actual && test_cmp expect actual ' +test_expect_success 'no-glob option matches literally (star)' ' + echo star >expect && + git log --format=%s -- ":(literal)f*" >actual && + test_cmp expect actual +' + test_expect_success 'no-glob option matches literally (bracket)' ' echo bracket >expect && git --literal-pathspecs log --format=%s -- "f[o][o]" >actual && test_cmp expect actual ' +test_expect_success 'no-glob option matches literally (bracket)' ' + echo bracket >expect && + git log --format=%s -- ":(literal)f[o][o]" >actual && + test_cmp expect actual +' + test_expect_success 'no-glob environment variable works' ' echo star >expect && GIT_LITERAL_PATHSPECS=1 git log --format=%s -- "f*" >actual && diff --git a/tree-diff.c b/tree-diff.c index 21a50d8ed3..ccf9d7c8fd 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -202,7 +202,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co * path. Magic that matches more than one path is not * supported. */ - GUARD_PATHSPEC(&opt->pathspec, PATHSPEC_FROMTOP); + GUARD_PATHSPEC(&opt->pathspec, PATHSPEC_FROMTOP | PATHSPEC_LITERAL); #if 0 /* * We should reject wildcards as well. Unfortunately we diff --git a/tree-walk.c b/tree-walk.c index 37b157ed6e..676bd7f3c8 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -636,7 +636,10 @@ enum interesting tree_entry_interesting(const struct name_entry *entry, enum interesting never_interesting = ps->has_wildcard ? entry_not_interesting : all_entries_not_interesting; - GUARD_PATHSPEC(ps, PATHSPEC_FROMTOP | PATHSPEC_MAXDEPTH); + GUARD_PATHSPEC(ps, + PATHSPEC_FROMTOP | + PATHSPEC_MAXDEPTH | + PATHSPEC_LITERAL); if (!ps->nr) { if (!ps->recursive || -- cgit v1.3 From bd30c2e48432c692f9e77d3529c9cf25117066bb Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:36:08 +0700 Subject: pathspec: support :(glob) syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit :(glob)path differs from plain pathspec that it uses wildmatch with WM_PATHNAME while the other uses fnmatch without FNM_PATHNAME. The difference lies in how '*' (and '**') is processed. With the introduction of :(glob) and :(literal) and their global options --[no]glob-pathspecs, the user can: - make everything literal by default via --noglob-pathspecs --literal-pathspecs cannot be used for this purpose as it disables _all_ pathspec magic. - individually turn on globbing with :(glob) - make everything globbing by default via --glob-pathspecs - individually turn off globbing with :(literal) The implication behind this is, there is no way to gain the default matching behavior (i.e. fnmatch without FNM_PATHNAME). You either get new globbing or literal. The old fnmatch behavior is considered deprecated and discouraged to use. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git.txt | 19 ++++++++++++ Documentation/glossary-content.txt | 29 ++++++++++++++++++ builtin/add.c | 9 ++++-- builtin/ls-tree.c | 2 +- cache.h | 2 ++ dir.c | 28 +++++++++-------- dir.h | 9 +++--- git.c | 8 +++++ pathspec.c | 47 +++++++++++++++++++++++++--- pathspec.h | 4 ++- t/t6130-pathspec-noglob.sh | 63 ++++++++++++++++++++++++++++++++++++++ tree-walk.c | 9 +++--- 12 files changed, 198 insertions(+), 31 deletions(-) (limited to 'pathspec.h') diff --git a/Documentation/git.txt b/Documentation/git.txt index 80139ae222..3571a1b7ce 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -454,6 +454,17 @@ help ...`. This is equivalent to setting the `GIT_LITERAL_PATHSPECS` environment variable to `1`. +--glob-pathspecs: + Add "glob" magic to all pathspec. This is equivalent to setting + the `GIT_GLOB_PATHSPECS` environment variable to `1`. Disabling + globbing on individual pathspecs can be done using pathspec + magic ":(literal)" + +--noglob-pathspecs: + Add "literal" magic to all pathspec. This is equivalent to setting + the `GIT_NOGLOB_PATHSPECS` environment variable to `1`. Enabling + globbing on individual pathspecs can be done using pathspec + magic ":(glob)" GIT COMMANDS ------------ @@ -860,6 +871,14 @@ GIT_LITERAL_PATHSPECS:: literal paths to Git (e.g., paths previously given to you by `git ls-tree`, `--raw` diff output, etc). +GIT_GLOB_PATHSPECS:: + Setting this variable to `1` will cause Git to treat all + pathspecs as glob patterns (aka "glob" magic). + +GIT_NOGLOB_PATHSPECS:: + Setting this variable to `1` will cause Git to treat all + pathspecs as literal (aka "literal" magic). + Discussion[[Discussion]] ------------------------ diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index ca9f20f25f..a3d9029ce7 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -333,6 +333,35 @@ top `/`;; literal;; Wildcards in the pattern such as `*` or `?` are treated as literal characters. + +glob;; + Git treats the pattern as a shell glob suitable for + consumption by fnmatch(3) with the FNM_PATHNAME flag: + wildcards in the pattern will not match a / in the pathname. + For example, "Documentation/{asterisk}.html" matches + "Documentation/git.html" but not "Documentation/ppc/ppc.html" + or "tools/perf/Documentation/perf.html". ++ +Two consecutive asterisks ("`**`") in patterns matched against +full pathname may have special meaning: + + - A leading "`**`" followed by a slash means match in all + directories. For example, "`**/foo`" matches file or directory + "`foo`" anywhere, the same as pattern "`foo`". "**/foo/bar" + matches file or directory "`bar`" anywhere that is directly + under directory "`foo`". + + - A trailing "/**" matches everything inside. For example, + "abc/**" matches all files inside directory "abc", relative + to the location of the `.gitignore` file, with infinite depth. + + - A slash followed by two consecutive asterisks then a slash + matches zero or more directories. For example, "`a/**/b`" + matches "`a/b`", "`a/x/b`", "`a/x/y/b`" and so on. + + - Other consecutive asterisks are considered invalid. ++ +Glob magic is incompatible with literal magic. -- + Currently only the slash `/` is recognized as the "magic signature", diff --git a/builtin/add.c b/builtin/add.c index 663ddd122c..1dab2464f6 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -541,11 +541,16 @@ int cmd_add(int argc, const char **argv, const char *prefix) /* * file_exists() assumes exact match */ - GUARD_PATHSPEC(&pathspec, PATHSPEC_FROMTOP | PATHSPEC_LITERAL); + GUARD_PATHSPEC(&pathspec, + PATHSPEC_FROMTOP | + PATHSPEC_LITERAL | + PATHSPEC_GLOB); for (i = 0; i < pathspec.nr; i++) { const char *path = pathspec.items[i].match; - if (!seen[i] && !file_exists(path)) { + if (!seen[i] && + ((pathspec.items[i].magic & PATHSPEC_GLOB) || + !file_exists(path))) { if (ignore_missing) { int dtype = DT_UNKNOWN; if (is_excluded(&dir, path, &dtype)) diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 1c4f48ea8f..7882352a9b 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -173,7 +173,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) * cannot be lifted until it is converted to use * match_pathspec_depth() or tree_entry_interesting() */ - parse_pathspec(&pathspec, 0, + parse_pathspec(&pathspec, PATHSPEC_GLOB, PATHSPEC_PREFER_CWD, prefix, argv + 1); for (i = 0; i < pathspec.nr; i++) diff --git a/cache.h b/cache.h index 13e3c94bf0..dc4d2ee22d 100644 --- a/cache.h +++ b/cache.h @@ -367,6 +367,8 @@ static inline enum object_type object_type(unsigned int mode) #define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF" #define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE" #define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS" +#define GIT_GLOB_PATHSPECS_ENVIRONMENT "GIT_GLOB_PATHSPECS" +#define GIT_NOGLOB_PATHSPECS_ENVIRONMENT "GIT_NOGLOB_PATHSPECS" /* * This environment variable is expected to contain a boolean indicating diff --git a/dir.c b/dir.c index 50ec2f5478..076bd462e5 100644 --- a/dir.c +++ b/dir.c @@ -52,26 +52,28 @@ int fnmatch_icase(const char *pattern, const char *string, int flags) return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0)); } -inline int git_fnmatch(const char *pattern, const char *string, - int flags, int prefix) +inline int git_fnmatch(const struct pathspec_item *item, + const char *pattern, const char *string, + int prefix) { - int fnm_flags = 0; - if (flags & GFNM_PATHNAME) - fnm_flags |= FNM_PATHNAME; if (prefix > 0) { if (strncmp(pattern, string, prefix)) return FNM_NOMATCH; pattern += prefix; string += prefix; } - if (flags & GFNM_ONESTAR) { + if (item->flags & PATHSPEC_ONESTAR) { int pattern_len = strlen(++pattern); int string_len = strlen(string); return string_len < pattern_len || strcmp(pattern, string + string_len - pattern_len); } - return fnmatch(pattern, string, fnm_flags); + if (item->magic & PATHSPEC_GLOB) + return wildmatch(pattern, string, WM_PATHNAME, NULL); + else + /* wildmatch has not learned no FNM_PATHNAME mode yet */ + return fnmatch(pattern, string, 0); } static int fnmatch_icase_mem(const char *pattern, int patternlen, @@ -111,7 +113,8 @@ static size_t common_prefix_len(const struct pathspec *pathspec) GUARD_PATHSPEC(pathspec, PATHSPEC_FROMTOP | PATHSPEC_MAXDEPTH | - PATHSPEC_LITERAL); + PATHSPEC_LITERAL | + PATHSPEC_GLOB); for (n = 0; n < pathspec->nr; n++) { size_t i = 0, len = 0; @@ -206,8 +209,7 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix, } if (item->nowildcard_len < item->len && - !git_fnmatch(match, name, - item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0, + !git_fnmatch(item, match, name, item->nowildcard_len - prefix)) return MATCHED_FNMATCH; @@ -238,7 +240,8 @@ int match_pathspec_depth(const struct pathspec *ps, GUARD_PATHSPEC(ps, PATHSPEC_FROMTOP | PATHSPEC_MAXDEPTH | - PATHSPEC_LITERAL); + PATHSPEC_LITERAL | + PATHSPEC_GLOB); if (!ps->nr) { if (!ps->recursive || @@ -1297,7 +1300,8 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru GUARD_PATHSPEC(pathspec, PATHSPEC_FROMTOP | PATHSPEC_MAXDEPTH | - PATHSPEC_LITERAL); + PATHSPEC_LITERAL | + PATHSPEC_GLOB); if (has_symlink_leading_path(path, len)) return dir->nr; diff --git a/dir.h b/dir.h index 7d051e368a..343ec7aa31 100644 --- a/dir.h +++ b/dir.h @@ -199,10 +199,9 @@ extern int fnmatch_icase(const char *pattern, const char *string, int flags); /* * The prefix part of pattern must not contains wildcards. */ -#define GFNM_PATHNAME 1 /* similar to FNM_PATHNAME */ -#define GFNM_ONESTAR 2 /* there is only _one_ wildcard, a star */ - -extern int git_fnmatch(const char *pattern, const char *string, - int flags, int prefix); +struct pathspec_item; +extern int git_fnmatch(const struct pathspec_item *item, + const char *pattern, const char *string, + int prefix); #endif diff --git a/git.c b/git.c index 4359086fd6..25096755f4 100644 --- a/git.c +++ b/git.c @@ -147,6 +147,14 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, "0", 1); if (envchanged) *envchanged = 1; + } else if (!strcmp(cmd, "--glob-pathspecs")) { + setenv(GIT_GLOB_PATHSPECS_ENVIRONMENT, "1", 1); + if (envchanged) + *envchanged = 1; + } else if (!strcmp(cmd, "--noglob-pathspecs")) { + setenv(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, "1", 1); + if (envchanged) + *envchanged = 1; } else if (!strcmp(cmd, "--shallow-file")) { (*argv)++; (*argc)--; diff --git a/pathspec.c b/pathspec.c index b6d8e74277..c1e6917897 100644 --- a/pathspec.c +++ b/pathspec.c @@ -57,7 +57,6 @@ char *find_pathspecs_matching_against_index(const struct pathspec *pathspec) * * Possible future magic semantics include stuff like: * - * { PATHSPEC_NOGLOB, '!', "noglob" }, * { PATHSPEC_ICASE, '\0', "icase" }, * { PATHSPEC_RECURSIVE, '*', "recursive" }, * { PATHSPEC_REGEXP, '\0', "regexp" }, @@ -71,6 +70,7 @@ static struct pathspec_magic { } pathspec_magic[] = { { PATHSPEC_FROMTOP, '/', "top" }, { PATHSPEC_LITERAL, 0, "literal" }, + { PATHSPEC_GLOB, '\0', "glob" }, }; /* @@ -93,6 +93,8 @@ static unsigned prefix_pathspec(struct pathspec_item *item, const char *elt) { static int literal_global = -1; + static int glob_global = -1; + static int noglob_global = -1; unsigned magic = 0, short_magic = 0, global_magic = 0; const char *copyfrom = elt, *long_magic_end = NULL; char *match; @@ -103,6 +105,22 @@ static unsigned prefix_pathspec(struct pathspec_item *item, if (literal_global) global_magic |= PATHSPEC_LITERAL; + if (glob_global < 0) + glob_global = git_env_bool(GIT_GLOB_PATHSPECS_ENVIRONMENT, 0); + if (glob_global) + global_magic |= PATHSPEC_GLOB; + + if (noglob_global < 0) + noglob_global = git_env_bool(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, 0); + + if (glob_global && noglob_global) + die(_("global 'glob' and 'noglob' pathspec settings are incompatible")); + + if ((global_magic & PATHSPEC_LITERAL) && + (global_magic & ~PATHSPEC_LITERAL)) + die(_("global 'literal' pathspec setting is incompatible " + "with all other global pathspec settings")); + if (elt[0] != ':' || literal_global) { ; /* nothing to do */ } else if (elt[1] == '(') { @@ -167,12 +185,24 @@ static unsigned prefix_pathspec(struct pathspec_item *item, magic |= short_magic; *p_short_magic = short_magic; + + /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specifed */ + if (noglob_global && !(magic & PATHSPEC_GLOB)) + global_magic |= PATHSPEC_LITERAL; + + /* --glob-pathspec is overriden by :(literal) */ + if ((global_magic & PATHSPEC_GLOB) && (magic & PATHSPEC_LITERAL)) + global_magic &= ~PATHSPEC_GLOB; + magic |= global_magic; if (pathspec_prefix >= 0 && (prefixlen || (prefix && *prefix))) die("BUG: 'prefix' magic is supposed to be used at worktree's root"); + if ((magic & PATHSPEC_LITERAL) && (magic & PATHSPEC_GLOB)) + die(_("%s: 'literal' and 'glob' are incompatible"), elt); + if (pathspec_prefix >= 0) { match = xstrdup(copyfrom); prefixlen = pathspec_prefix; @@ -248,10 +278,17 @@ static unsigned prefix_pathspec(struct pathspec_item *item, item->nowildcard_len = prefixlen; } item->flags = 0; - if (item->nowildcard_len < item->len && - item->match[item->nowildcard_len] == '*' && - no_wildcard(item->match + item->nowildcard_len + 1)) - item->flags |= PATHSPEC_ONESTAR; + if (magic & PATHSPEC_GLOB) { + /* + * FIXME: should we enable ONESTAR in _GLOB for + * pattern "* * / * . c"? + */ + } else { + if (item->nowildcard_len < item->len && + item->match[item->nowildcard_len] == '*' && + no_wildcard(item->match + item->nowildcard_len + 1)) + item->flags |= PATHSPEC_ONESTAR; + } /* sanity checks, pathspec matchers assume these are sane */ assert(item->nowildcard_len <= item->len && diff --git a/pathspec.h b/pathspec.h index 987d70c55a..cdf2fa39f6 100644 --- a/pathspec.h +++ b/pathspec.h @@ -5,10 +5,12 @@ #define PATHSPEC_FROMTOP (1<<0) #define PATHSPEC_MAXDEPTH (1<<1) #define PATHSPEC_LITERAL (1<<2) +#define PATHSPEC_GLOB (1<<3) #define PATHSPEC_ALL_MAGIC \ (PATHSPEC_FROMTOP | \ PATHSPEC_MAXDEPTH | \ - PATHSPEC_LITERAL) + PATHSPEC_LITERAL | \ + PATHSPEC_GLOB) #define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies GFNM_ONESTAR */ diff --git a/t/t6130-pathspec-noglob.sh b/t/t6130-pathspec-noglob.sh index 8551b026de..ea00d71e77 100755 --- a/t/t6130-pathspec-noglob.sh +++ b/t/t6130-pathspec-noglob.sh @@ -32,6 +32,16 @@ test_expect_success 'star pathspec globs' ' test_cmp expect actual ' +test_expect_success 'star pathspec globs' ' + cat >expect <<-\EOF && + bracket + star + vanilla + EOF + git log --format=%s -- ":(glob)f*" >actual && + test_cmp expect actual +' + test_expect_success 'bracket pathspec globs and matches literal brackets' ' cat >expect <<-\EOF && bracket @@ -41,6 +51,15 @@ test_expect_success 'bracket pathspec globs and matches literal brackets' ' test_cmp expect actual ' +test_expect_success 'bracket pathspec globs and matches literal brackets' ' + cat >expect <<-\EOF && + bracket + vanilla + EOF + git log --format=%s -- ":(glob)f[o][o]" >actual && + test_cmp expect actual +' + test_expect_success 'no-glob option matches literally (vanilla)' ' echo vanilla >expect && git --literal-pathspecs log --format=%s -- foo >actual && @@ -89,4 +108,48 @@ test_expect_success 'no-glob environment variable works' ' test_cmp expect actual ' +test_expect_success 'setup xxx/bar' ' + mkdir xxx && + test_commit xxx xxx/bar +' + +test_expect_success '**/ works with :(glob)' ' + cat >expect <<-\EOF && + xxx + unrelated + EOF + git log --format=%s -- ":(glob)**/bar" >actual && + test_cmp expect actual +' + +test_expect_success '**/ does not work with --noglob-pathspecs' ' + : >expect && + git --noglob-pathspecs log --format=%s -- "**/bar" >actual && + test_cmp expect actual +' + +test_expect_success '**/ works with :(glob) and --noglob-pathspecs' ' + cat >expect <<-\EOF && + xxx + unrelated + EOF + git --noglob-pathspecs log --format=%s -- ":(glob)**/bar" >actual && + test_cmp expect actual +' + +test_expect_success '**/ works with --glob-pathspecs' ' + cat >expect <<-\EOF && + xxx + unrelated + EOF + git --glob-pathspecs log --format=%s -- "**/bar" >actual && + test_cmp expect actual +' + +test_expect_success '**/ does not work with :(literal) and --glob-pathspecs' ' + : >expect && + git --glob-pathspecs log --format=%s -- ":(literal)**/bar" >actual && + test_cmp expect actual +' + test_done diff --git a/tree-walk.c b/tree-walk.c index 676bd7f3c8..a44f528b3b 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -639,7 +639,8 @@ enum interesting tree_entry_interesting(const struct name_entry *entry, GUARD_PATHSPEC(ps, PATHSPEC_FROMTOP | PATHSPEC_MAXDEPTH | - PATHSPEC_LITERAL); + PATHSPEC_LITERAL | + PATHSPEC_GLOB); if (!ps->nr) { if (!ps->recursive || @@ -685,8 +686,7 @@ enum interesting tree_entry_interesting(const struct name_entry *entry, return entry_interesting; if (item->nowildcard_len < item->len) { - if (!git_fnmatch(match + baselen, entry->path, - item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0, + if (!git_fnmatch(item, match + baselen, entry->path, item->nowildcard_len - baselen)) return entry_interesting; @@ -727,8 +727,7 @@ match_wildcards: strbuf_add(base, entry->path, pathlen); - if (!git_fnmatch(match, base->buf + base_offset, - item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0, + if (!git_fnmatch(item, match, base->buf + base_offset, item->nowildcard_len)) { strbuf_setlen(base, base_offset + baselen); return entry_interesting; -- cgit v1.3 From 93d935371654faf2956a4c37c1ca46f3195ee832 Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Sun, 14 Jul 2013 15:36:09 +0700 Subject: parse_pathspec: accept :(icase)path syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git.txt | 8 ++++ Documentation/glossary-content.txt | 3 ++ builtin/add.c | 6 ++- builtin/ls-tree.c | 2 +- cache.h | 1 + dir.c | 74 ++++++++++++++++++++++++----- git.c | 4 ++ pathspec.c | 9 +++- pathspec.h | 22 ++++++++- t/t6131-pathspec-icase.sh | 97 ++++++++++++++++++++++++++++++++++++++ tree-walk.c | 59 ++++++++++++++++++----- 11 files changed, 257 insertions(+), 28 deletions(-) create mode 100755 t/t6131-pathspec-icase.sh (limited to 'pathspec.h') diff --git a/Documentation/git.txt b/Documentation/git.txt index 3571a1b7ce..2c1f6f5f53 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -466,6 +466,10 @@ help ...`. globbing on individual pathspecs can be done using pathspec magic ":(glob)" +--icase-pathspecs: + Add "icase" magic to all pathspec. This is equivalent to setting + the `GIT_ICASE_PATHSPECS` environment variable to `1`. + GIT COMMANDS ------------ @@ -879,6 +883,10 @@ GIT_NOGLOB_PATHSPECS:: Setting this variable to `1` will cause Git to treat all pathspecs as literal (aka "literal" magic). +GIT_ICASE_PATHSPECS:: + Setting this variable to `1` will cause Git to treat all + pathspecs as case-insensitive. + Discussion[[Discussion]] ------------------------ diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index a3d9029ce7..13a64d3aac 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -334,6 +334,9 @@ literal;; Wildcards in the pattern such as `*` or `?` are treated as literal characters. +icase;; + Case insensitive match. + glob;; Git treats the pattern as a shell glob suitable for consumption by fnmatch(3) with the FNM_PATHNAME flag: diff --git a/builtin/add.c b/builtin/add.c index 1dab2464f6..9d52fc7915 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -544,12 +544,14 @@ int cmd_add(int argc, const char **argv, const char *prefix) GUARD_PATHSPEC(&pathspec, PATHSPEC_FROMTOP | PATHSPEC_LITERAL | - PATHSPEC_GLOB); + PATHSPEC_GLOB | + PATHSPEC_ICASE); for (i = 0; i < pathspec.nr; i++) { const char *path = pathspec.items[i].match; if (!seen[i] && - ((pathspec.items[i].magic & PATHSPEC_GLOB) || + ((pathspec.items[i].magic & + (PATHSPEC_GLOB | PATHSPEC_ICASE)) || !file_exists(path))) { if (ignore_missing) { int dtype = DT_UNKNOWN; diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 7882352a9b..f6d8215181 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -173,7 +173,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) * cannot be lifted until it is converted to use * match_pathspec_depth() or tree_entry_interesting() */ - parse_pathspec(&pathspec, PATHSPEC_GLOB, + parse_pathspec(&pathspec, PATHSPEC_GLOB | PATHSPEC_ICASE, PATHSPEC_PREFER_CWD, prefix, argv + 1); for (i = 0; i < pathspec.nr; i++) diff --git a/cache.h b/cache.h index dc4d2ee22d..3cff825d5c 100644 --- a/cache.h +++ b/cache.h @@ -369,6 +369,7 @@ static inline enum object_type object_type(unsigned int mode) #define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS" #define GIT_GLOB_PATHSPECS_ENVIRONMENT "GIT_GLOB_PATHSPECS" #define GIT_NOGLOB_PATHSPECS_ENVIRONMENT "GIT_NOGLOB_PATHSPECS" +#define GIT_ICASE_PATHSPECS_ENVIRONMENT "GIT_ICASE_PATHSPECS" /* * This environment variable is expected to contain a boolean indicating diff --git a/dir.c b/dir.c index 076bd462e5..8543736deb 100644 --- a/dir.c +++ b/dir.c @@ -57,7 +57,7 @@ inline int git_fnmatch(const struct pathspec_item *item, int prefix) { if (prefix > 0) { - if (strncmp(pattern, string, prefix)) + if (ps_strncmp(item, pattern, string, prefix)) return FNM_NOMATCH; pattern += prefix; string += prefix; @@ -66,14 +66,18 @@ inline int git_fnmatch(const struct pathspec_item *item, int pattern_len = strlen(++pattern); int string_len = strlen(string); return string_len < pattern_len || - strcmp(pattern, - string + string_len - pattern_len); + ps_strcmp(item, pattern, + string + string_len - pattern_len); } if (item->magic & PATHSPEC_GLOB) - return wildmatch(pattern, string, WM_PATHNAME, NULL); + return wildmatch(pattern, string, + WM_PATHNAME | + (item->magic & PATHSPEC_ICASE ? WM_CASEFOLD : 0), + NULL); else /* wildmatch has not learned no FNM_PATHNAME mode yet */ - return fnmatch(pattern, string, 0); + return fnmatch(pattern, string, + item->magic & PATHSPEC_ICASE ? FNM_CASEFOLD : 0); } static int fnmatch_icase_mem(const char *pattern, int patternlen, @@ -110,16 +114,27 @@ static size_t common_prefix_len(const struct pathspec *pathspec) int n; size_t max = 0; + /* + * ":(icase)path" is treated as a pathspec full of + * wildcard. In other words, only prefix is considered common + * prefix. If the pathspec is abc/foo abc/bar, running in + * subdir xyz, the common prefix is still xyz, not xuz/abc as + * in non-:(icase). + */ GUARD_PATHSPEC(pathspec, PATHSPEC_FROMTOP | PATHSPEC_MAXDEPTH | PATHSPEC_LITERAL | - PATHSPEC_GLOB); + PATHSPEC_GLOB | + PATHSPEC_ICASE); for (n = 0; n < pathspec->nr; n++) { - size_t i = 0, len = 0; - while (i < pathspec->items[n].nowildcard_len && - (n == 0 || i < max)) { + size_t i = 0, len = 0, item_len; + if (pathspec->items[n].magic & PATHSPEC_ICASE) + item_len = pathspec->items[n].prefix; + else + item_len = pathspec->items[n].nowildcard_len; + while (i < item_len && (n == 0 || i < max)) { char c = pathspec->items[n].match[i]; if (c != pathspec->items[0].match[i]) break; @@ -196,11 +211,44 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix, const char *match = item->match + prefix; int matchlen = item->len - prefix; + /* + * The normal call pattern is: + * 1. prefix = common_prefix_len(ps); + * 2. prune something, or fill_directory + * 3. match_pathspec_depth() + * + * 'prefix' at #1 may be shorter than the command's prefix and + * it's ok for #2 to match extra files. Those extras will be + * trimmed at #3. + * + * Suppose the pathspec is 'foo' and '../bar' running from + * subdir 'xyz'. The common prefix at #1 will be empty, thanks + * to "../". We may have xyz/foo _and_ XYZ/foo after #2. The + * user does not want XYZ/foo, only the "foo" part should be + * case-insensitive. We need to filter out XYZ/foo here. In + * other words, we do not trust the caller on comparing the + * prefix part when :(icase) is involved. We do exact + * comparison ourselves. + * + * Normally the caller (common_prefix_len() in fact) does + * _exact_ matching on name[-prefix+1..-1] and we do not need + * to check that part. Be defensive and check it anyway, in + * case common_prefix_len is changed, or a new caller is + * introduced that does not use common_prefix_len. + * + * If the penalty turns out too high when prefix is really + * long, maybe change it to + * strncmp(match, name, item->prefix - prefix) + */ + if (item->prefix && (item->magic & PATHSPEC_ICASE) && + strncmp(item->match, name - prefix, item->prefix)) + return 0; + /* If the match was just the prefix, we matched */ if (!*match) return MATCHED_RECURSIVELY; - if (matchlen <= namelen && !strncmp(match, name, matchlen)) { + if (matchlen <= namelen && !ps_strncmp(item, match, name, matchlen)) { if (matchlen == namelen) return MATCHED_EXACTLY; @@ -241,7 +289,8 @@ int match_pathspec_depth(const struct pathspec *ps, PATHSPEC_FROMTOP | PATHSPEC_MAXDEPTH | PATHSPEC_LITERAL | - PATHSPEC_GLOB); + PATHSPEC_GLOB | + PATHSPEC_ICASE); if (!ps->nr) { if (!ps->recursive || @@ -1301,7 +1350,8 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru PATHSPEC_FROMTOP | PATHSPEC_MAXDEPTH | PATHSPEC_LITERAL | - PATHSPEC_GLOB); + PATHSPEC_GLOB | + PATHSPEC_ICASE); if (has_symlink_leading_path(path, len)) return dir->nr; diff --git a/git.c b/git.c index 25096755f4..cebf8827da 100644 --- a/git.c +++ b/git.c @@ -155,6 +155,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) setenv(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, "1", 1); if (envchanged) *envchanged = 1; + } else if (!strcmp(cmd, "--icase-pathspecs")) { + setenv(GIT_ICASE_PATHSPECS_ENVIRONMENT, "1", 1); + if (envchanged) + *envchanged = 1; } else if (!strcmp(cmd, "--shallow-file")) { (*argv)++; (*argc)--; diff --git a/pathspec.c b/pathspec.c index c1e6917897..d9f4143222 100644 --- a/pathspec.c +++ b/pathspec.c @@ -57,7 +57,6 @@ char *find_pathspecs_matching_against_index(const struct pathspec *pathspec) * * Possible future magic semantics include stuff like: * - * { PATHSPEC_ICASE, '\0', "icase" }, * { PATHSPEC_RECURSIVE, '*', "recursive" }, * { PATHSPEC_REGEXP, '\0', "regexp" }, * @@ -71,6 +70,7 @@ static struct pathspec_magic { { PATHSPEC_FROMTOP, '/', "top" }, { PATHSPEC_LITERAL, 0, "literal" }, { PATHSPEC_GLOB, '\0', "glob" }, + { PATHSPEC_ICASE, '\0', "icase" }, }; /* @@ -95,6 +95,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, static int literal_global = -1; static int glob_global = -1; static int noglob_global = -1; + static int icase_global = -1; unsigned magic = 0, short_magic = 0, global_magic = 0; const char *copyfrom = elt, *long_magic_end = NULL; char *match; @@ -116,6 +117,12 @@ static unsigned prefix_pathspec(struct pathspec_item *item, if (glob_global && noglob_global) die(_("global 'glob' and 'noglob' pathspec settings are incompatible")); + + if (icase_global < 0) + icase_global = git_env_bool(GIT_ICASE_PATHSPECS_ENVIRONMENT, 0); + if (icase_global) + global_magic |= PATHSPEC_ICASE; + if ((global_magic & PATHSPEC_LITERAL) && (global_magic & ~PATHSPEC_LITERAL)) die(_("global 'literal' pathspec setting is incompatible " diff --git a/pathspec.h b/pathspec.h index cdf2fa39f6..04b632fa33 100644 --- a/pathspec.h +++ b/pathspec.h @@ -6,11 +6,13 @@ #define PATHSPEC_MAXDEPTH (1<<1) #define PATHSPEC_LITERAL (1<<2) #define PATHSPEC_GLOB (1<<3) +#define PATHSPEC_ICASE (1<<4) #define PATHSPEC_ALL_MAGIC \ (PATHSPEC_FROMTOP | \ PATHSPEC_MAXDEPTH | \ PATHSPEC_LITERAL | \ - PATHSPEC_GLOB) + PATHSPEC_GLOB | \ + PATHSPEC_ICASE) #define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies GFNM_ONESTAR */ @@ -65,6 +67,24 @@ extern void parse_pathspec(struct pathspec *pathspec, extern void copy_pathspec(struct pathspec *dst, const struct pathspec *src); extern void free_pathspec(struct pathspec *); +static inline int ps_strncmp(const struct pathspec_item *item, + const char *s1, const char *s2, size_t n) +{ + if (item->magic & PATHSPEC_ICASE) + return strncasecmp(s1, s2, n); + else + return strncmp(s1, s2, n); +} + +static inline int ps_strcmp(const struct pathspec_item *item, + const char *s1, const char *s2) +{ + if (item->magic & PATHSPEC_ICASE) + return strcasecmp(s1, s2); + else + return strcmp(s1, s2); +} + extern char *find_pathspecs_matching_against_index(const struct pathspec *pathspec); extern void add_pathspec_matches_against_index(const struct pathspec *pathspec, char *seen); extern const char *check_path_for_gitlink(const char *path); diff --git a/t/t6131-pathspec-icase.sh b/t/t6131-pathspec-icase.sh new file mode 100755 index 0000000000..3215eef525 --- /dev/null +++ b/t/t6131-pathspec-icase.sh @@ -0,0 +1,97 @@ +#!/bin/sh + +test_description='test case insensitive pathspec limiting' +. ./test-lib.sh + +test_expect_success 'create commits with glob characters' ' + test_commit bar bar && + test_commit bAr bAr && + test_commit BAR BAR && + mkdir foo && + test_commit foo/bar foo/bar && + test_commit foo/bAr foo/bAr && + test_commit foo/BAR foo/BAR && + mkdir fOo && + test_commit fOo/bar fOo/bar && + test_commit fOo/bAr fOo/bAr && + test_commit fOo/BAR fOo/BAR && + mkdir FOO && + test_commit FOO/bar FOO/bar && + test_commit FOO/bAr FOO/bAr && + test_commit FOO/BAR FOO/BAR +' + +test_expect_success 'tree_entry_interesting matches bar' ' + echo bar >expect && + git log --format=%s -- "bar" >actual && + test_cmp expect actual +' + +test_expect_success 'tree_entry_interesting matches :(icase)bar' ' + cat <<-EOF >expect && + BAR + bAr + bar + EOF + git log --format=%s -- ":(icase)bar" >actual && + test_cmp expect actual +' + +test_expect_success 'tree_entry_interesting matches :(icase)bar with prefix' ' + cat <<-EOF >expect && + fOo/BAR + fOo/bAr + fOo/bar + EOF + ( cd fOo && git log --format=%s -- ":(icase)bar" ) >actual && + test_cmp expect actual +' + +test_expect_success 'tree_entry_interesting matches :(icase)bar with empty prefix' ' + cat <<-EOF >expect && + FOO/BAR + FOO/bAr + FOO/bar + fOo/BAR + fOo/bAr + fOo/bar + foo/BAR + foo/bAr + foo/bar + EOF + ( cd fOo && git log --format=%s -- ":(icase)../foo/bar" ) >actual && + test_cmp expect actual +' + +test_expect_success 'match_pathspec_depth matches :(icase)bar' ' + cat <<-EOF >expect && + BAR + bAr + bar + EOF + git ls-files ":(icase)bar" >actual && + test_cmp expect actual +' + +test_expect_success 'match_pathspec_depth matches :(icase)bar with prefix' ' + cat <<-EOF >expect && + fOo/BAR + fOo/bAr + fOo/bar + EOF + ( cd fOo && git ls-files --full-name ":(icase)bar" ) >actual && + test_cmp expect actual +' + +test_expect_success 'match_pathspec_depth matches :(icase)bar with empty prefix' ' + cat <<-EOF >expect && + bar + fOo/BAR + fOo/bAr + fOo/bar + EOF + ( cd fOo && git ls-files --full-name ":(icase)bar" ../bar ) >actual && + test_cmp expect actual +' + +test_done diff --git a/tree-walk.c b/tree-walk.c index a44f528b3b..c366852553 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -489,13 +489,25 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch return retval; } -static int match_entry(const struct name_entry *entry, int pathlen, +static int match_entry(const struct pathspec_item *item, + const struct name_entry *entry, int pathlen, const char *match, int matchlen, enum interesting *never_interesting) { int m = -1; /* signals that we haven't called strncmp() */ - if (*never_interesting != entry_not_interesting) { + if (item->magic & PATHSPEC_ICASE) + /* + * "Never interesting" trick requires exact + * matching. We could do something clever with inexact + * matching, but it's trickier (and not to forget that + * strcasecmp is locale-dependent, at least in + * glibc). Just disable it for now. It can't be worse + * than the wildcard's codepath of '[Tt][Hi][Is][Ss]' + * pattern. + */ + *never_interesting = entry_not_interesting; + else if (*never_interesting != entry_not_interesting) { /* * We have not seen any match that sorts later * than the current path. @@ -541,7 +553,7 @@ static int match_entry(const struct name_entry *entry, int pathlen, * we cheated and did not do strncmp(), so we do * that here. */ - m = strncmp(match, entry->path, pathlen); + m = ps_strncmp(item, match, entry->path, pathlen); /* * If common part matched earlier then it is a hit, @@ -549,15 +561,39 @@ static int match_entry(const struct name_entry *entry, int pathlen, * leading directory and is shorter than match. */ if (!m) + /* + * match_entry does not check if the prefix part is + * matched case-sensitively. If the entry is a + * directory and part of prefix, it'll be rematched + * eventually by basecmp with special treatment for + * the prefix. + */ return 1; return 0; } -static int match_dir_prefix(const char *base, +/* :(icase)-aware string compare */ +static int basecmp(const struct pathspec_item *item, + const char *base, const char *match, int len) +{ + if (item->magic & PATHSPEC_ICASE) { + int ret, n = len > item->prefix ? item->prefix : len; + ret = strncmp(base, match, n); + if (ret) + return ret; + base += n; + match += n; + len -= n; + } + return ps_strncmp(item, base, match, len); +} + +static int match_dir_prefix(const struct pathspec_item *item, + const char *base, const char *match, int matchlen) { - if (strncmp(base, match, matchlen)) + if (basecmp(item, base, match, matchlen)) return 0; /* @@ -594,7 +630,7 @@ static int match_wildcard_base(const struct pathspec_item *item, */ if (baselen >= matchlen) { *matched = matchlen; - return !strncmp(base, match, matchlen); + return !basecmp(item, base, match, matchlen); } dirlen = matchlen; @@ -607,7 +643,7 @@ static int match_wildcard_base(const struct pathspec_item *item, * base ends with '/' so we are sure it really matches * directory */ - if (strncmp(base, match, baselen)) + if (basecmp(item, base, match, baselen)) return 0; *matched = baselen; } else @@ -640,7 +676,8 @@ enum interesting tree_entry_interesting(const struct name_entry *entry, PATHSPEC_FROMTOP | PATHSPEC_MAXDEPTH | PATHSPEC_LITERAL | - PATHSPEC_GLOB); + PATHSPEC_GLOB | + PATHSPEC_ICASE); if (!ps->nr) { if (!ps->recursive || @@ -663,7 +700,7 @@ enum interesting tree_entry_interesting(const struct name_entry *entry, if (baselen >= matchlen) { /* If it doesn't match, move along... */ - if (!match_dir_prefix(base_str, match, matchlen)) + if (!match_dir_prefix(item, base_str, match, matchlen)) goto match_wildcards; if (!ps->recursive || @@ -679,8 +716,8 @@ enum interesting tree_entry_interesting(const struct name_entry *entry, } /* Either there must be no base, or the base must match. */ - if (baselen == 0 || !strncmp(base_str, match, baselen)) { - if (match_entry(entry, pathlen, + if (baselen == 0 || !basecmp(item, base_str, match, baselen)) { + if (match_entry(item, entry, pathlen, match + baselen, matchlen - baselen, &never_interesting)) return entry_interesting; -- cgit v1.3