pathspec: create parse_short_magic function
[gitweb.git] / pathspec.c
index 86f2b449b1b43d7abb0917c07676304a666056a9..1b090184801a3b09623f480f5b78d48a7a9a225e 100644 (file)
@@ -74,13 +74,12 @@ static struct pathspec_magic {
        { PATHSPEC_EXCLUDE, '!', "exclude" },
 };
 
-static void prefix_short_magic(struct strbuf *sb, int prefixlen,
-                              unsigned short_magic)
+static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic)
 {
        int i;
        strbuf_addstr(sb, ":(");
        for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
-               if (short_magic & pathspec_magic[i].bit) {
+               if (magic & pathspec_magic[i].bit) {
                        if (sb->buf[sb->len - 1] != '(')
                                strbuf_addch(sb, ',');
                        strbuf_addstr(sb, pathspec_magic[i].name);
@@ -88,6 +87,110 @@ static void prefix_short_magic(struct strbuf *sb, int prefixlen,
        strbuf_addf(sb, ",prefix:%d)", prefixlen);
 }
 
+static inline int get_literal_global(void)
+{
+       static int literal = -1;
+
+       if (literal < 0)
+               literal = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);
+
+       return literal;
+}
+
+static inline int get_glob_global(void)
+{
+       static int glob = -1;
+
+       if (glob < 0)
+               glob = git_env_bool(GIT_GLOB_PATHSPECS_ENVIRONMENT, 0);
+
+       return glob;
+}
+
+static inline int get_noglob_global(void)
+{
+       static int noglob = -1;
+
+       if (noglob < 0)
+               noglob = git_env_bool(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, 0);
+
+       return noglob;
+}
+
+static inline int get_icase_global(void)
+{
+       static int icase = -1;
+
+       if (icase < 0)
+               icase = git_env_bool(GIT_ICASE_PATHSPECS_ENVIRONMENT, 0);
+
+       return icase;
+}
+
+static int get_global_magic(int element_magic)
+{
+       int global_magic = 0;
+
+       if (get_literal_global())
+               global_magic |= PATHSPEC_LITERAL;
+
+       /* --glob-pathspec is overridden by :(literal) */
+       if (get_glob_global() && !(element_magic & PATHSPEC_LITERAL))
+               global_magic |= PATHSPEC_GLOB;
+
+       if (get_glob_global() && get_noglob_global())
+               die(_("global 'glob' and 'noglob' pathspec settings are incompatible"));
+
+       if (get_icase_global())
+               global_magic |= PATHSPEC_ICASE;
+
+       if ((global_magic & PATHSPEC_LITERAL) &&
+           (global_magic & ~PATHSPEC_LITERAL))
+               die(_("global 'literal' pathspec setting is incompatible "
+                     "with all other global pathspec settings"));
+
+       /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specified */
+       if (get_noglob_global() && !(element_magic & PATHSPEC_GLOB))
+               global_magic |= PATHSPEC_LITERAL;
+
+       return global_magic;
+}
+
+/*
+ * Parse the pathspec element looking for short magic
+ *
+ * saves all magic in 'magic'
+ * returns the position in 'elem' after all magic has been parsed
+ */
+static const char *parse_short_magic(unsigned *magic, const char *elem)
+{
+       const char *pos;
+
+       for (pos = elem + 1; *pos && *pos != ':'; pos++) {
+               char ch = *pos;
+               int i;
+
+               if (!is_pathspec_magic(ch))
+                       break;
+
+               for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
+                       if (pathspec_magic[i].mnemonic == ch) {
+                               *magic |= pathspec_magic[i].bit;
+                               break;
+                       }
+               }
+
+               if (ARRAY_SIZE(pathspec_magic) <= i)
+                       die(_("Unimplemented pathspec magic '%c' in '%s'"),
+                           ch, elem);
+       }
+
+       if (*pos == ':')
+               pos++;
+
+       return pos;
+}
+
 /*
  * Take an element of a pathspec and check for magic signatures.
  * Append the result to the prefix. Return the magic bitmap.
@@ -101,52 +204,16 @@ static void prefix_short_magic(struct strbuf *sb, int prefixlen,
  * the prefix part must always match literally, and a single stupid
  * string cannot express such a case.
  */
-static unsigned prefix_pathspec(struct pathspec_item *item,
-                               unsigned *p_short_magic,
-                               const char **raw, unsigned flags,
+static unsigned prefix_pathspec(struct pathspec_item *item, unsigned flags,
                                const char *prefix, int prefixlen,
                                const char *elt)
 {
-       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;
+       unsigned magic = 0, element_magic = 0;
+       const char *copyfrom = elt;
        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 (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 (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 "
-                     "with all other global pathspec settings"));
-
-       if (flags & PATHSPEC_LITERAL_PATH)
-               global_magic = 0;
-
-       if (elt[0] != ':' || literal_global ||
+       if (elt[0] != ':' || get_literal_global() ||
            (flags & PATHSPEC_LITERAL_PATH)) {
                ; /* nothing to do */
        } else if (elt[1] == '(') {
@@ -166,7 +233,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item,
                        for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
                                if (strlen(pathspec_magic[i].name) == len &&
                                    !strncmp(pathspec_magic[i].name, copyfrom, len)) {
-                                       magic |= pathspec_magic[i].bit;
+                                       element_magic |= pathspec_magic[i].bit;
                                        break;
                                }
                                if (starts_with(copyfrom, "prefix:")) {
@@ -185,42 +252,19 @@ static unsigned prefix_pathspec(struct pathspec_item *item,
                }
                if (*copyfrom != ')')
                        die(_("Missing ')' at the end of pathspec magic in '%s'"), elt);
-               long_magic_end = copyfrom;
                copyfrom++;
        } else {
                /* shorthand */
-               for (copyfrom = elt + 1;
-                    *copyfrom && *copyfrom != ':';
-                    copyfrom++) {
-                       char ch = *copyfrom;
-
-                       if (!is_pathspec_magic(ch))
-                               break;
-                       for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
-                               if (pathspec_magic[i].mnemonic == ch) {
-                                       short_magic |= pathspec_magic[i].bit;
-                                       break;
-                               }
-                       if (ARRAY_SIZE(pathspec_magic) <= i)
-                               die(_("Unimplemented pathspec magic '%c' in '%s'"),
-                                   ch, elt);
-               }
-               if (*copyfrom == ':')
-                       copyfrom++;
+               copyfrom = parse_short_magic(&element_magic, elt);
        }
 
-       magic |= short_magic;
-       *p_short_magic = short_magic;
+       magic |= element_magic;
 
-       /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specified */
-       if (noglob_global && !(magic & PATHSPEC_GLOB))
-               global_magic |= PATHSPEC_LITERAL;
-
-       /* --glob-pathspec is overridden by :(literal) */
-       if ((global_magic & PATHSPEC_GLOB) && (magic & PATHSPEC_LITERAL))
-               global_magic &= ~PATHSPEC_GLOB;
-
-       magic |= global_magic;
+       /* PATHSPEC_LITERAL_PATH ignores magic */
+       if (flags & PATHSPEC_LITERAL_PATH)
+               magic = PATHSPEC_LITERAL;
+       else
+               magic |= get_global_magic(element_magic);
 
        if (pathspec_prefix >= 0 &&
            (prefixlen || (prefix && *prefix)))
@@ -240,27 +284,23 @@ static unsigned prefix_pathspec(struct pathspec_item *item,
                if (!match)
                        die(_("%s: '%s' is outside repository"), elt, copyfrom);
        }
-       *raw = item->match = match;
+       item->match = match;
        /*
         * Prefix the pathspec (keep all magic) and assign to
         * original. Useful for passing to another command.
         */
-       if (flags & PATHSPEC_PREFIX_ORIGIN) {
+       if ((flags & PATHSPEC_PREFIX_ORIGIN) &&
+           prefixlen && !get_literal_global()) {
                struct strbuf sb = STRBUF_INIT;
-               if (prefixlen && !literal_global) {
-                       /* Preserve the actual prefix length of each pattern */
-                       if (short_magic)
-                               prefix_short_magic(&sb, prefixlen, short_magic);
-                       else if (long_magic_end) {
-                               strbuf_add(&sb, elt, long_magic_end - elt);
-                               strbuf_addf(&sb, ",prefix:%d)", prefixlen);
-                       } else
-                               strbuf_addf(&sb, ":(prefix:%d)", prefixlen);
-               }
+
+               /* Preserve the actual prefix length of each pattern */
+               prefix_magic(&sb, prefixlen, element_magic);
+
                strbuf_addstr(&sb, match);
                item->original = strbuf_detach(&sb, NULL);
-       } else
-               item->original = elt;
+       } else {
+               item->original = xstrdup(elt);
+       }
        item->len = strlen(item->match);
        item->prefix = prefixlen;
 
@@ -328,22 +368,22 @@ static int pathspec_item_cmp(const void *a_, const void *b_)
 }
 
 static void NORETURN unsupported_magic(const char *pattern,
-                                      unsigned magic,
-                                      unsigned short_magic)
+                                      unsigned magic)
 {
        struct strbuf sb = STRBUF_INIT;
-       int i, n;
-       for (n = i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
+       int i;
+       for (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_addch(&sb, ' ');
-               if (short_magic & m->bit)
-                       strbuf_addf(&sb, "'%c'", m->mnemonic);
+                       strbuf_addstr(&sb, ", ");
+
+               if (m->mnemonic)
+                       strbuf_addf(&sb, _("'%s' (mnemonic: '%c')"),
+                                   m->name, m->mnemonic);
                else
                        strbuf_addf(&sb, "'%s'", m->name);
-               n++;
        }
        /*
         * We may want to substitute "this command" with a command
@@ -364,7 +404,7 @@ void parse_pathspec(struct pathspec *pathspec,
 {
        struct pathspec_item *item;
        const char *entry = argv ? *argv : NULL;
-       int i, n, prefixlen, nr_exclude = 0;
+       int i, n, prefixlen, warn_empty_string, nr_exclude = 0;
 
        memset(pathspec, 0, sizeof(*pathspec));
 
@@ -381,8 +421,6 @@ void parse_pathspec(struct pathspec *pathspec,
 
        /* No arguments with prefix -> prefix pathspec */
        if (!entry) {
-               static const char *raw[2];
-
                if (flags & PATHSPEC_PREFER_FULL)
                        return;
 
@@ -390,43 +428,40 @@ void parse_pathspec(struct pathspec *pathspec,
                        die("BUG: PATHSPEC_PREFER_CWD requires arguments");
 
                pathspec->items = item = xcalloc(1, sizeof(*item));
-               item->match = prefix;
-               item->original = prefix;
+               item->match = xstrdup(prefix);
+               item->original = xstrdup(prefix);
                item->nowildcard_len = item->len = strlen(prefix);
                item->prefix = item->len;
-               raw[0] = prefix;
-               raw[1] = NULL;
                pathspec->nr = 1;
-               pathspec->_raw = raw;
                return;
        }
 
        n = 0;
-       while (argv[n])
+       warn_empty_string = 1;
+       while (argv[n]) {
+               if (*argv[n] == '\0' && warn_empty_string) {
+                       warning(_("empty strings as pathspecs will be made invalid in upcoming releases. "
+                                 "please use . instead if you meant to match all paths"));
+                       warn_empty_string = 0;
+               }
                n++;
+       }
 
        pathspec->nr = n;
        ALLOC_ARRAY(pathspec->items, n);
        item = pathspec->items;
-       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,
+               item[i].magic = prefix_pathspec(item + i, flags,
                                                prefix, prefixlen, entry);
-               if ((flags & PATHSPEC_LITERAL_PATH) &&
-                   !(magic_mask & PATHSPEC_LITERAL))
-                       item[i].magic |= PATHSPEC_LITERAL;
+
                if (item[i].magic & PATHSPEC_EXCLUDE)
                        nr_exclude++;
                if (item[i].magic & magic_mask)
-                       unsupported_magic(entry,
-                                         item[i].magic & magic_mask,
-                                         short_magic);
+                       unsupported_magic(entry, item[i].magic & magic_mask);
 
                if ((flags & PATHSPEC_SYMLINK_LEADING_PATH) &&
                    has_symlink_leading_path(item[i].match, item[i].len)) {
@@ -450,45 +485,29 @@ void parse_pathspec(struct pathspec *pathspec,
        }
 }
 
-/*
- * N.B. get_pathspec() is deprecated in favor of the "struct pathspec"
- * based interface - see pathspec.c:parse_pathspec().
- *
- * Arguments:
- *  - prefix - a path relative to the root of the working tree
- *  - pathspec - a list of paths underneath the prefix path
- *
- * Iterates over pathspec, prepending each path with prefix,
- * and return the resulting list.
- *
- * If pathspec is empty, return a singleton list containing prefix.
- *
- * If pathspec and prefix are both empty, return an empty list.
- *
- * This is typically used by built-in commands such as add.c, in order
- * to normalize argv arguments provided to the built-in into a list of
- * paths to process, all relative to the root of the working tree.
- */
-const char **get_pathspec(const char *prefix, const char **pathspec)
-{
-       struct pathspec ps;
-       parse_pathspec(&ps,
-                      PATHSPEC_ALL_MAGIC &
-                      ~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL),
-                      PATHSPEC_PREFER_CWD,
-                      prefix, pathspec);
-       return ps._raw;
-}
-
 void copy_pathspec(struct pathspec *dst, const struct pathspec *src)
 {
+       int i;
+
        *dst = *src;
        ALLOC_ARRAY(dst->items, dst->nr);
        COPY_ARRAY(dst->items, src->items, dst->nr);
+
+       for (i = 0; i < dst->nr; i++) {
+               dst->items[i].match = xstrdup(src->items[i].match);
+               dst->items[i].original = xstrdup(src->items[i].original);
+       }
 }
 
 void clear_pathspec(struct pathspec *pathspec)
 {
+       int i;
+
+       for (i = 0; i < pathspec->nr; i++) {
+               free(pathspec->items[i].match);
+               free(pathspec->items[i].original);
+       }
        free(pathspec->items);
        pathspec->items = NULL;
+       pathspec->nr = 0;
 }