X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=builtin-grep.c;h=93b7e07b30ced0b4f4bad544d9bd8dbedadf452c;hb=306ea2df03322ac8c29f4eb5a968acb7ef3c8f72;hp=d09ddf04850baa17842975ff54063f5acfce8cfe;hpb=bf78d1a6cb65fe877a8d90b9d4ec95678621ff17;p=git.git diff --git a/builtin-grep.c b/builtin-grep.c index d09ddf048..93b7e07b3 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -29,10 +29,11 @@ static int pathspec_matches(const char **paths, const char *name) int matchlen = strlen(match); const char *cp, *meta; - if ((matchlen <= namelen) && - !strncmp(name, match, matchlen) && - (match[matchlen-1] == '/' || - name[matchlen] == '\0' || name[matchlen] == '/')) + if (!matchlen || + ((matchlen <= namelen) && + !strncmp(name, match, matchlen) && + (match[matchlen-1] == '/' || + name[matchlen] == '\0' || name[matchlen] == '/'))) return 1; if (!fnmatch(match, name, 0)) return 1; @@ -81,17 +82,47 @@ static int pathspec_matches(const char **paths, const char *name) return 0; } +enum grep_pat_token { + GREP_PATTERN, + GREP_AND, + GREP_OPEN_PAREN, + GREP_CLOSE_PAREN, + GREP_NOT, + GREP_OR, +}; + struct grep_pat { struct grep_pat *next; const char *origin; int no; + enum grep_pat_token token; const char *pattern; regex_t regexp; }; +enum grep_expr_node { + GREP_NODE_ATOM, + GREP_NODE_NOT, + GREP_NODE_AND, + GREP_NODE_OR, +}; + +struct grep_expr { + enum grep_expr_node node; + union { + struct grep_pat *atom; + struct grep_expr *unary; + struct { + struct grep_expr *left; + struct grep_expr *right; + } binary; + } u; +}; + struct grep_opt { struct grep_pat *pattern_list; struct grep_pat **pattern_tail; + struct grep_expr *pattern_expression; regex_t regexp; unsigned linenum:1; unsigned invert:1; @@ -104,43 +135,224 @@ struct grep_opt { #define GREP_BINARY_NOMATCH 1 #define GREP_BINARY_TEXT 2 unsigned binary:2; + unsigned extended:1; int regflags; unsigned pre_context; unsigned post_context; }; static void add_pattern(struct grep_opt *opt, const char *pat, - const char *origin, int no) + const char *origin, int no, enum grep_pat_token t) { struct grep_pat *p = xcalloc(1, sizeof(*p)); p->pattern = pat; p->origin = origin; p->no = no; + p->token = t; *opt->pattern_tail = p; opt->pattern_tail = &p->next; p->next = NULL; } +static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) +{ + int err = regcomp(&p->regexp, p->pattern, opt->regflags); + if (err) { + char errbuf[1024]; + char where[1024]; + if (p->no) + sprintf(where, "In '%s' at %d, ", + p->origin, p->no); + else if (p->origin) + sprintf(where, "%s, ", p->origin); + else + where[0] = 0; + regerror(err, &p->regexp, errbuf, 1024); + regfree(&p->regexp); + die("%s'%s': %s", where, p->pattern, errbuf); + } +} + +#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 */ + x = xcalloc(1, sizeof (struct grep_expr)); + x->node = GREP_NODE_ATOM; + x->u.atom = p; + *list = p->next; + return x; + case GREP_OPEN_PAREN: + *list = p->next; + x = compile_pattern_expr(list); + if (!x) + return NULL; + if (!*list || (*list)->token != GREP_CLOSE_PAREN) + die("unmatched parenthesis"); + *list = (*list)->next; + return x; + default: + return NULL; + } +} + +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: + if (!p->next) + die("--not not followed by pattern expression"); + *list = p->next; + x = xcalloc(1, sizeof (struct grep_expr)); + x->node = GREP_NODE_NOT; + x->u.unary = compile_pattern_not(list); + if (!x->u.unary) + die("--not followed by non pattern expression"); + return x; + default: + return compile_pattern_atom(list); + } +} + +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) { + if (!p->next) + die("--and not followed by pattern expression"); + *list = p->next; + y = compile_pattern_and(list); + if (!y) + die("--and not followed by pattern expression"); + z = xcalloc(1, sizeof (struct grep_expr)); + z->node = GREP_NODE_AND; + z->u.binary.left = x; + z->u.binary.right = y; + return z; + } + return x; +} + +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) { + y = compile_pattern_or(list); + if (!y) + die("not a pattern expression %s", p->pattern); + z = xcalloc(1, sizeof (struct grep_expr)); + z->node = GREP_NODE_OR; + z->u.binary.left = x; + z->u.binary.right = y; + return z; + } + return x; +} + +static struct grep_expr *compile_pattern_expr(struct grep_pat **list) +{ + looking_at("expr", list); + + return compile_pattern_or(list); +} + static void compile_patterns(struct grep_opt *opt) { struct grep_pat *p; + + /* First compile regexps */ for (p = opt->pattern_list; p; p = p->next) { - int err = regcomp(&p->regexp, p->pattern, opt->regflags); - if (err) { - char errbuf[1024]; - char where[1024]; - if (p->no) - sprintf(where, "In '%s' at %d, ", - p->origin, p->no); - else if (p->origin) - sprintf(where, "%s, ", p->origin); - else - where[0] = 0; - regerror(err, &p->regexp, errbuf, 1024); - regfree(&p->regexp); - die("%s'%s': %s", where, p->pattern, errbuf); - } + if (p->token == GREP_PATTERN) + compile_regexp(p, opt); + else + opt->extended = 1; } + + if (!opt->extended) + return; + + /* Then bundle them up in an expression. + * A classic recursive descent parser would do. + */ + 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); } static char *end_of_line(char *cp, unsigned long *left) @@ -195,6 +407,94 @@ 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), + pmatch, 0); + } + else { + hit = !fixmatch(p->pattern, bol, pmatch); + } + + if (hit && opt->word_regexp) { + 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"); + + /* 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; +} + +static int match_expr_eval(struct grep_opt *opt, + struct grep_expr *x, + char *bol, char *eol) +{ + switch (x->node) { + case GREP_NODE_ATOM: + return match_one_pattern(opt, x->u.atom, bol, eol); + break; + case GREP_NODE_NOT: + return !match_expr_eval(opt, x->u.unary, bol, eol); + case GREP_NODE_AND: + return (match_expr_eval(opt, x->u.binary.left, bol, eol) && + match_expr_eval(opt, x->u.binary.right, bol, eol)); + case GREP_NODE_OR: + return (match_expr_eval(opt, x->u.binary.left, bol, eol) || + match_expr_eval(opt, x->u.binary.right, bol, eol)); + } + die("Unexpected node type (internal error) %d\n", x->node); +} + +static int match_expr(struct grep_opt *opt, char *bol, char *eol) +{ + struct grep_expr *x = opt->pattern_expression; + return match_expr_eval(opt, x, bol, eol); +} + +static int match_line(struct grep_opt *opt, char *bol, char *eol) +{ + struct grep_pat *p; + if (opt->extended) + return match_expr(opt, bol, eol); + for (p = opt->pattern_list; p; p = p->next) { + if (match_one_pattern(opt, p, bol, eol)) + return 1; + } + return 0; +} + static int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size) { @@ -230,46 +530,15 @@ static int grep_buffer(struct grep_opt *opt, const char *name, hunk_mark = "--\n"; while (left) { - regmatch_t pmatch[10]; char *eol, ch; int hit = 0; - struct grep_pat *p; eol = end_of_line(bol, &left); ch = *eol; *eol = 0; - for (p = opt->pattern_list; p; p = p->next) { - if (!opt->fixed) { - regex_t *exp = &p->regexp; - hit = !regexec(exp, bol, ARRAY_SIZE(pmatch), - pmatch, 0); - } - else { - hit = !fixmatch(p->pattern, bol, pmatch); - } + hit = match_line(opt, bol, eol); - 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])) - hit = 0; - } - if (hit) - break; - } /* "grep -v -e foo -e bla" should list lines * that do not have either, so inversion should * be done outside. @@ -445,12 +714,14 @@ static int exec_grep(int argc, const char **argv) static int external_grep(struct grep_opt *opt, const char **paths, int cached) { - int i, nr, argc, hit, len; + int i, nr, argc, hit, len, status; const char *argv[MAXARGS+1]; char randarg[ARGBUF]; char *argptr = randarg; struct grep_pat *p; + if (opt->extended) + return -1; len = nr = 0; push_arg("grep"); if (opt->fixed) @@ -459,6 +730,8 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) push_arg("-n"); if (opt->regflags & REG_EXTENDED) push_arg("-E"); + if (opt->regflags & REG_ICASE) + push_arg("-i"); if (opt->word_regexp) push_arg("-w"); if (opt->name_only) @@ -518,7 +791,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) argc = nr; for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; - const char *name; + char *name; if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode))) continue; if (!pathspec_matches(paths, ce->name)) @@ -533,12 +806,17 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) argv[argc++] = name; if (argc < MAXARGS) continue; - hit += exec_grep(argc, argv); + status = exec_grep(argc, argv); + if (0 < status) + hit = 1; argc = nr; } - if (argc > nr) - hit += exec_grep(argc, argv); - return 0; + if (argc > nr) { + status = exec_grep(argc, argv); + if (0 < status) + hit = 1; + } + return hit; } static int grep_cache(struct grep_opt *opt, const char **paths, int cached) @@ -578,11 +856,9 @@ static int grep_tree(struct grep_opt *opt, const char **paths, struct tree_desc *tree, const char *tree_name, const char *base) { - unsigned mode; int len; int hit = 0; - const char *path; - const unsigned char *sha1; + struct name_entry entry; char *down; char *path_buf = xmalloc(PATH_MAX + strlen(tree_name) + 100); @@ -597,36 +873,32 @@ static int grep_tree(struct grep_opt *opt, const char **paths, } len = strlen(path_buf); - while (tree->size) { - int pathlen; - sha1 = tree_entry_extract(tree, &path, &mode); - pathlen = strlen(path); - strcpy(path_buf + len, path); + while (tree_entry(tree, &entry)) { + strcpy(path_buf + len, entry.path); - if (S_ISDIR(mode)) + if (S_ISDIR(entry.mode)) /* Match "abc/" against pathspec to * decide if we want to descend into "abc" * directory. */ - strcpy(path_buf + len + pathlen, "/"); + strcpy(path_buf + len + entry.pathlen, "/"); if (!pathspec_matches(paths, down)) ; - else if (S_ISREG(mode)) - hit |= grep_sha1(opt, sha1, path_buf); - else if (S_ISDIR(mode)) { + else if (S_ISREG(entry.mode)) + hit |= grep_sha1(opt, entry.sha1, path_buf); + else if (S_ISDIR(entry.mode)) { char type[20]; struct tree_desc sub; void *data; - data = read_sha1_file(sha1, type, &sub.size); + data = read_sha1_file(entry.sha1, type, &sub.size); if (!data) die("unable to read tree (%s)", - sha1_to_hex(sha1)); + sha1_to_hex(entry.sha1)); sub.buf = data; hit |= grep_tree(opt, paths, &sub, tree_name, down); free(data); } - update_tree_entry(tree); } return hit; } @@ -634,10 +906,9 @@ static int grep_tree(struct grep_opt *opt, const char **paths, static int grep_object(struct grep_opt *opt, const char **paths, struct object *obj, const char *name) { - if (!strcmp(obj->type, blob_type)) + if (obj->type == OBJ_BLOB) return grep_sha1(opt, obj->sha1, name); - if (!strcmp(obj->type, commit_type) || - !strcmp(obj->type, tree_type)) { + if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) { struct tree_desc tree; void *data; int hit; @@ -650,20 +921,26 @@ static int grep_object(struct grep_opt *opt, const char **paths, free(data); return hit; } - die("unable to grep from object of type %s", obj->type); + die("unable to grep from object of type %s", typename(obj->type)); } static const char builtin_grep_usage[] = "git-grep