builtin-grep: wildcard pathspec fixes
authorJunio C Hamano <junkio@cox.net>
Mon, 1 May 2006 19:27:56 +0000 (12:27 -0700)
committerJunio C Hamano <junkio@cox.net>
Mon, 1 May 2006 19:31:04 +0000 (12:31 -0700)
This tweaks the pathspec wildcard used in builtin-grep to match
that of ls-files.  With this:

git grep -e DEBUG -- '*/Kconfig*'

would work like the shell script version, and you could even do:

git grep -e DEBUG --cached -- '*/Kconfig*' ;# from index
git grep -e DEBUG v2.6.12 -- '*/Kconfig*' ;# from rev

Signed-off-by: Junio C Hamano <junkio@cox.net>
builtin-grep.c

index 36150bf4ef3486f653ec31c75a15d9a55a3b32ab..653b65ea1040a6d4e41f0c32df6c6766e04bc8d0 100644 (file)
 #include "revision.h"
 #include "builtin.h"
 #include <regex.h>
+#include <fnmatch.h>
 
+/*
+ * git grep pathspecs are somewhat different from diff-tree pathspecs;
+ * pathname wildcards are allowed.
+ */
 static int pathspec_matches(struct diff_options *opt, const char *name)
 {
-       int i, j;
-       int namelen;
+       int namelen, i;
        if (!opt->nr_paths)
                return 1;
        namelen = strlen(name);
        for (i = 0; i < opt->nr_paths; i++) {
                const char *match = opt->paths[i];
                int matchlen = opt->pathlens[i];
-               if (matchlen <= namelen) {
-                       if (!strncmp(name, match, matchlen))
-                               return 1;
+               const char *slash, *cp;
+
+               if ((matchlen <= namelen) &&
+                   !strncmp(name, match, matchlen) &&
+                   (match[matchlen-1] == '/' ||
+                    name[matchlen] == '\0' || name[matchlen] == '/'))
+                       return 1;
+               if (!fnmatch(match, name, 0))
+                       return 1;
+               if (name[namelen-1] != '/')
                        continue;
-               }
-               /* If name is "Documentation" and pathspec is
-                * "Documentation/", they should match.  Maybe
-                * we would want to strip it in get_pathspec()???
+
+               /* We are being asked if the name directory is worth
+                * descending into.
+                *
+                * Find the longest leading directory name that does
+                * not have metacharacter in the pathspec; the name
+                * we are looking at must overlap with that directory.
                 */
-               if (strncmp(name, match, namelen))
-                       continue;
-               for (j = namelen; j < matchlen; j++)
-                       if (match[j] != '/')
+               for (cp = match, slash = NULL; cp - match < matchlen; cp++) {
+                       char ch = *cp;
+                       if (ch == '/')
+                               slash = cp;
+                       if (ch == '*' || ch == '[')
                                break;
-               if (matchlen <= j)
-                       return 1;
+               }
+               if (!slash)
+                       slash = match; /* toplevel */
+               else
+                       slash++;
+               if (namelen <= slash - match) {
+                       /* Looking at "Documentation/" and
+                        * the pattern says "Documentation/howto/", or
+                        * "Documentation/diff*.txt".
+                        */
+                       if (!memcmp(match, name, namelen))
+                               return 1;
+               }
+               else {
+                       /* Looking at "Documentation/howto/" and
+                        * the pattern says "Documentation/h*".
+                        */
+                       if (!memcmp(match, name, slash - match))
+                               return 1;
+               }
        }
        return 0;
 }
@@ -232,17 +265,17 @@ static int grep_tree(struct grep_opt *opt, struct rev_info *revs,
        int hit = 0;
        const char *path;
        const unsigned char *sha1;
-       char *down_base;
+       char *down;
        char *path_buf = xmalloc(PATH_MAX + strlen(tree_name) + 100);
 
        if (tree_name[0]) {
                int offset = sprintf(path_buf, "%s:", tree_name);
-               down_base = path_buf + offset;
-               strcat(down_base, base);
+               down = path_buf + offset;
+               strcat(down, base);
        }
        else {
-               down_base = path_buf;
-               strcpy(down_base, base);
+               down = path_buf;
+               strcpy(down, base);
        }
        len = strlen(path_buf);
 
@@ -252,7 +285,14 @@ static int grep_tree(struct grep_opt *opt, struct rev_info *revs,
                pathlen = strlen(path);
                strcpy(path_buf + len, path);
 
-               if (!pathspec_matches(&revs->diffopt, down_base))
+               if (S_ISDIR(mode))
+                       /* Match "abc/" against pathspec to
+                        * decide if we want to descend into "abc"
+                        * directory.
+                        */
+                       strcpy(path_buf + len + pathlen, "/");
+
+               if (!pathspec_matches(&revs->diffopt, down))
                        ;
                else if (S_ISREG(mode))
                        hit |= grep_sha1(opt, sha1, path_buf);
@@ -264,9 +304,8 @@ static int grep_tree(struct grep_opt *opt, struct rev_info *revs,
                        if (!data)
                                die("unable to read tree (%s)",
                                    sha1_to_hex(sha1));
-                       strcpy(path_buf + len + pathlen, "/");
                        sub.buf = data;
-                       hit = grep_tree(opt, revs, &sub, tree_name, down_base);
+                       hit |= grep_tree(opt, revs, &sub, tree_name, down);
                        free(data);
                }
                update_tree_entry(tree);