Code

Make path in tree view look nicer
[git.git] / builtin-grep.c
index 69b7c4862af3bfcbc9aea2cb52abaa65b3e8490b..ed87a5550c7dab6de56327b8acc0e3e2c897e39e 100644 (file)
@@ -123,6 +123,7 @@ struct grep_opt {
        struct grep_pat *pattern_list;
        struct grep_pat **pattern_tail;
        struct grep_expr *pattern_expression;
+       int prefix_length;
        regex_t regexp;
        unsigned linenum:1;
        unsigned invert:1;
@@ -136,6 +137,8 @@ struct grep_opt {
 #define GREP_BINARY_TEXT       2
        unsigned binary:2;
        unsigned extended:1;
+       unsigned relative:1;
+       unsigned pathname:1;
        int regflags;
        unsigned pre_context;
        unsigned post_context;
@@ -173,61 +176,12 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
        }
 }
 
-#if DEBUG
-static inline void indent(int in)
-{
-       int i;
-       for (i = 0; i < in; i++) putchar(' ');
-}
-
-static void dump_pattern_exp(struct grep_expr *x, int in)
-{
-       switch (x->node) {
-       case GREP_NODE_ATOM:
-               indent(in);
-               puts(x->u.atom->pattern);
-               break;
-       case GREP_NODE_NOT:
-               indent(in);
-               puts("--not");
-               dump_pattern_exp(x->u.unary, in+1);
-               break;
-       case GREP_NODE_AND:
-               dump_pattern_exp(x->u.binary.left, in+1);
-               indent(in);
-               puts("--and");
-               dump_pattern_exp(x->u.binary.right, in+1);
-               break;
-       case GREP_NODE_OR:
-               dump_pattern_exp(x->u.binary.left, in+1);
-               indent(in);
-               puts("--or");
-               dump_pattern_exp(x->u.binary.right, in+1);
-               break;
-       }
-}
-
-static void looking_at(const char *msg, struct grep_pat **list)
-{
-       struct grep_pat *p = *list;
-       fprintf(stderr, "%s: looking at ", msg);
-       if (!p)
-               fprintf(stderr, "empty\n");
-       else
-               fprintf(stderr, "<%s>\n", p->pattern);
-}
-#else
-#define looking_at(a,b) do {} while(0)
-#endif
-
 static struct grep_expr *compile_pattern_expr(struct grep_pat **);
 static struct grep_expr *compile_pattern_atom(struct grep_pat **list)
 {
        struct grep_pat *p;
        struct grep_expr *x;
 
-       looking_at("atom", list);
-
        p = *list;
        switch (p->token) {
        case GREP_PATTERN: /* atom */
@@ -255,8 +209,6 @@ static struct grep_expr *compile_pattern_not(struct grep_pat **list)
        struct grep_pat *p;
        struct grep_expr *x;
 
-       looking_at("not", list);
-
        p = *list;
        switch (p->token) {
        case GREP_NOT:
@@ -279,8 +231,6 @@ static struct grep_expr *compile_pattern_and(struct grep_pat **list)
        struct grep_pat *p;
        struct grep_expr *x, *y, *z;
 
-       looking_at("and", list);
-
        x = compile_pattern_not(list);
        p = *list;
        if (p && p->token == GREP_AND) {
@@ -304,8 +254,6 @@ static struct grep_expr *compile_pattern_or(struct grep_pat **list)
        struct grep_pat *p;
        struct grep_expr *x, *y, *z;
 
-       looking_at("or", list);
-
        x = compile_pattern_and(list);
        p = *list;
        if (x && p && p->token != GREP_CLOSE_PAREN) {
@@ -323,8 +271,6 @@ static struct grep_expr *compile_pattern_or(struct grep_pat **list)
 
 static struct grep_expr *compile_pattern_expr(struct grep_pat **list)
 {
-       looking_at("expr", list);
-
        return compile_pattern_or(list);
 }
 
@@ -348,9 +294,6 @@ static void compile_patterns(struct grep_opt *opt)
         */
        p = opt->pattern_list;
        opt->pattern_expression = compile_pattern_expr(&p);
-#if DEBUG
-       dump_pattern_exp(opt->pattern_expression, 0);
-#endif
        if (p)
                die("incomplete pattern expression: %s", p->pattern);
 }
@@ -374,7 +317,8 @@ static int word_char(char ch)
 static void show_line(struct grep_opt *opt, const char *bol, const char *eol,
                      const char *name, unsigned lno, char sign)
 {
-       printf("%s%c", name, sign);
+       if (opt->pathname)
+               printf("%s%c", name, sign);
        if (opt->linenum)
                printf("%d%c", lno, sign);
        printf("%.*s\n", (int)(eol-bol), bol);
@@ -388,9 +332,7 @@ static int buffer_is_binary(const char *ptr, unsigned long size)
 {
        if (FIRST_FEW_BYTES < size)
                size = FIRST_FEW_BYTES;
-       if (memchr(ptr, 0, size))
-               return 1;
-       return 0;
+       return !!memchr(ptr, 0, size);
 }
 
 static int fixmatch(const char *pattern, char *line, regmatch_t *match)
@@ -410,8 +352,10 @@ static int fixmatch(const char *pattern, char *line, regmatch_t *match)
 static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol)
 {
        int hit = 0;
+       int at_true_bol = 1;
        regmatch_t pmatch[10];
 
+ again:
        if (!opt->fixed) {
                regex_t *exp = &p->regexp;
                hit = !regexec(exp, bol, ARRAY_SIZE(pmatch),
@@ -422,22 +366,35 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol
        }
 
        if (hit && opt->word_regexp) {
-               /* Match beginning must be either
-                * beginning of the line, or at word
-                * boundary (i.e. the last char must
-                * not be alnum or underscore).
-                */
                if ((pmatch[0].rm_so < 0) ||
                    (eol - bol) <= pmatch[0].rm_so ||
                    (pmatch[0].rm_eo < 0) ||
                    (eol - bol) < pmatch[0].rm_eo)
                        die("regexp returned nonsense");
-               if (pmatch[0].rm_so != 0 &&
-                   word_char(bol[pmatch[0].rm_so-1]))
-                       hit = 0;
-               if (pmatch[0].rm_eo != (eol-bol) &&
-                   word_char(bol[pmatch[0].rm_eo]))
+
+               /* Match beginning must be either beginning of the
+                * line, or at word boundary (i.e. the last char must
+                * not be a word char).  Similarly, match end must be
+                * either end of the line, or at word boundary
+                * (i.e. the next char must not be a word char).
+                */
+               if ( ((pmatch[0].rm_so == 0 && at_true_bol) ||
+                     !word_char(bol[pmatch[0].rm_so-1])) &&
+                    ((pmatch[0].rm_eo == (eol-bol)) ||
+                     !word_char(bol[pmatch[0].rm_eo])) )
+                       ;
+               else
                        hit = 0;
+
+               if (!hit && pmatch[0].rm_so + bol + 1 < eol) {
+                       /* There could be more than one match on the
+                        * line, and the first match might not be
+                        * strict word match.  But later ones could be!
+                        */
+                       bol = pmatch[0].rm_so + bol + 1;
+                       at_true_bol = 0;
+                       goto again;
+               }
        }
        return hit;
 }
@@ -617,19 +574,40 @@ static int grep_buffer(struct grep_opt *opt, const char *name,
        return !!last_hit;
 }
 
-static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name)
+static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name, int tree_name_len)
 {
        unsigned long size;
        char *data;
        char type[20];
+       char *to_free = NULL;
        int hit;
+
        data = read_sha1_file(sha1, type, &size);
        if (!data) {
                error("'%s': unable to read %s", name, sha1_to_hex(sha1));
                return 0;
        }
+       if (opt->relative && opt->prefix_length) {
+               static char name_buf[PATH_MAX];
+               char *cp;
+               int name_len = strlen(name) - opt->prefix_length + 1;
+
+               if (!tree_name_len)
+                       name += opt->prefix_length;
+               else {
+                       if (ARRAY_SIZE(name_buf) <= name_len)
+                               cp = to_free = xmalloc(name_len);
+                       else
+                               cp = name_buf;
+                       memcpy(cp, name, tree_name_len);
+                       strcpy(cp + tree_name_len,
+                              name + tree_name_len + opt->prefix_length);
+                       name = cp;
+               }
+       }
        hit = grep_buffer(opt, name, data, size);
        free(data);
+       free(to_free);
        return hit;
 }
 
@@ -659,6 +637,8 @@ static int grep_file(struct grep_opt *opt, const char *filename)
                return 0;
        }
        close(i);
+       if (opt->relative && opt->prefix_length)
+               filename += opt->prefix_length;
        i = grep_buffer(opt, filename, data, st.st_size);
        free(data);
        return i;
@@ -705,7 +685,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
        char *argptr = randarg;
        struct grep_pat *p;
 
-       if (opt->extended)
+       if (opt->extended || (opt->relative && opt->prefix_length))
                return -1;
        len = nr = 0;
        push_arg("grep");
@@ -713,6 +693,8 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                push_arg("-F");
        if (opt->linenum)
                push_arg("-n");
+       if (!opt->pathname)
+               push_arg("-h");
        if (opt->regflags & REG_EXTENDED)
                push_arg("-E");
        if (opt->regflags & REG_ICASE)
@@ -830,7 +812,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
                if (!pathspec_matches(paths, ce->name))
                        continue;
                if (cached)
-                       hit |= grep_sha1(opt, ce->sha1, ce->name);
+                       hit |= grep_sha1(opt, ce->sha1, ce->name, 0);
                else
                        hit |= grep_file(opt, ce->name);
        }
@@ -845,11 +827,12 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
        int hit = 0;
        struct name_entry entry;
        char *down;
-       char *path_buf = xmalloc(PATH_MAX + strlen(tree_name) + 100);
+       int tn_len = strlen(tree_name);
+       char *path_buf = xmalloc(PATH_MAX + tn_len + 100);
 
-       if (tree_name[0]) {
-               int offset = sprintf(path_buf, "%s:", tree_name);
-               down = path_buf + offset;
+       if (tn_len) {
+               tn_len = sprintf(path_buf, "%s:", tree_name);
+               down = path_buf + tn_len;
                strcat(down, base);
        }
        else {
@@ -871,7 +854,7 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
                if (!pathspec_matches(paths, down))
                        ;
                else if (S_ISREG(entry.mode))
-                       hit |= grep_sha1(opt, entry.sha1, path_buf);
+                       hit |= grep_sha1(opt, entry.sha1, path_buf, tn_len);
                else if (S_ISDIR(entry.mode)) {
                        char type[20];
                        struct tree_desc sub;
@@ -892,7 +875,7 @@ static int grep_object(struct grep_opt *opt, const char **paths,
                       struct object *obj, const char *name)
 {
        if (obj->type == OBJ_BLOB)
-               return grep_sha1(opt, obj->sha1, name);
+               return grep_sha1(opt, obj->sha1, name, 0);
        if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
                struct tree_desc tree;
                void *data;
@@ -930,6 +913,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        int i;
 
        memset(&opt, 0, sizeof(opt));
+       opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0;
+       opt.relative = 1;
+       opt.pathname = 1;
        opt.pattern_tail = &opt.pattern_list;
        opt.regflags = REG_NEWLINE;
 
@@ -989,10 +975,12 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                        opt.linenum = 1;
                        continue;
                }
+               if (!strcmp("-h", arg)) {
+                       opt.pathname = 0;
+                       continue;
+               }
                if (!strcmp("-H", arg)) {
-                       /* We always show the pathname, so this
-                        * is a noop.
-                        */
+                       opt.pathname = 1;
                        continue;
                }
                if (!strcmp("-l", arg) ||
@@ -1067,7 +1055,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                                /* ignore empty line like grep does */
                                if (!buf[0])
                                        continue;
-                               add_pattern(&opt, strdup(buf), argv[1], ++lno,
+                               add_pattern(&opt, xstrdup(buf), argv[1], ++lno,
                                            GREP_PATTERN);
                        }
                        fclose(patterns);
@@ -1103,6 +1091,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                        }
                        die(emsg_missing_argument, arg);
                }
+               if (!strcmp("--full-name", arg)) {
+                       opt.relative = 0;
+                       continue;
+               }
                if (!strcmp("--", arg)) {
                        /* later processing wants to have this at argv[1] */
                        argv--;
@@ -1161,8 +1153,15 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                        verify_filename(prefix, argv[j]);
        }
 
-       if (i < argc)
+       if (i < argc) {
                paths = get_pathspec(prefix, argv + i);
+               if (opt.prefix_length && opt.relative) {
+                       /* Make sure we do not get outside of paths */
+                       for (i = 0; paths[i]; i++)
+                               if (strncmp(prefix, paths[i], opt.prefix_length))
+                                       die("git-grep: cannot generate relative filenames containing '..'");
+               }
+       }
        else if (prefix) {
                paths = xcalloc(2, sizeof(const char *));
                paths[0] = prefix;