Merge branch 'js/regexec-buf' into maint
[gitweb.git] / grep.c
diff --git a/grep.c b/grep.c
index 8ed56236f049a47490f2c14d999a13e21b98fd2c..1194d35b5d06d7960d4464884952e755914e2519 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -4,6 +4,8 @@
 #include "xdiff-interface.h"
 #include "diff.h"
 #include "diffcore.h"
+#include "commit.h"
+#include "quote.h"
 
 static int grep_source_load(struct grep_source *gs);
 static int grep_source_is_binary(struct grep_source *gs);
@@ -161,17 +163,7 @@ void grep_init(struct grep_opt *opt, const char *prefix)
        color_set(opt->color_sep, def->color_sep);
 }
 
-void grep_commit_pattern_type(enum grep_pattern_type pattern_type, struct grep_opt *opt)
-{
-       if (pattern_type != GREP_PATTERN_TYPE_UNSPECIFIED)
-               grep_set_pattern_type_option(pattern_type, opt);
-       else if (opt->pattern_type_option != GREP_PATTERN_TYPE_UNSPECIFIED)
-               grep_set_pattern_type_option(opt->pattern_type_option, opt);
-       else if (opt->extended_regexp_option)
-               grep_set_pattern_type_option(GREP_PATTERN_TYPE_ERE, opt);
-}
-
-void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt)
+static void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt)
 {
        switch (pattern_type) {
        case GREP_PATTERN_TYPE_UNSPECIFIED:
@@ -203,6 +195,16 @@ void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct gr
        }
 }
 
+void grep_commit_pattern_type(enum grep_pattern_type pattern_type, struct grep_opt *opt)
+{
+       if (pattern_type != GREP_PATTERN_TYPE_UNSPECIFIED)
+               grep_set_pattern_type_option(pattern_type, opt);
+       else if (opt->pattern_type_option != GREP_PATTERN_TYPE_UNSPECIFIED)
+               grep_set_pattern_type_option(opt->pattern_type_option, opt);
+       else if (opt->extended_regexp_option)
+               grep_set_pattern_type_option(GREP_PATTERN_TYPE_ERE, opt);
+}
+
 static struct grep_pat *create_grep_pat(const char *pat, size_t patlen,
                                        const char *origin, int no,
                                        enum grep_pat_token t,
@@ -322,11 +324,16 @@ static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt)
        int erroffset;
        int options = PCRE_MULTILINE;
 
-       if (opt->ignore_case)
+       if (opt->ignore_case) {
+               if (has_non_ascii(p->pattern))
+                       p->pcre_tables = pcre_maketables();
                options |= PCRE_CASELESS;
+       }
+       if (is_utf8_locale() && has_non_ascii(p->pattern))
+               options |= PCRE_UTF8;
 
        p->pcre_regexp = pcre_compile(p->pattern, options, &error, &erroffset,
-                       NULL);
+                                     p->pcre_tables);
        if (!p->pcre_regexp)
                compile_regexp_failed(p, error);
 
@@ -360,6 +367,7 @@ static void free_pcre_regexp(struct grep_pat *p)
 {
        pcre_free(p->pcre_regexp);
        pcre_free(p->pcre_extra_info);
+       pcre_free((void *)p->pcre_tables);
 }
 #else /* !USE_LIBPCRE */
 static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt)
@@ -396,26 +404,68 @@ static int is_fixed(const char *s, size_t len)
        return 1;
 }
 
+static void compile_fixed_regexp(struct grep_pat *p, struct grep_opt *opt)
+{
+       struct strbuf sb = STRBUF_INIT;
+       int err;
+       int regflags;
+
+       basic_regex_quote_buf(&sb, p->pattern);
+       regflags = opt->regflags & ~REG_EXTENDED;
+       if (opt->ignore_case)
+               regflags |= REG_ICASE;
+       err = regcomp(&p->regexp, sb.buf, regflags);
+       if (opt->debug)
+               fprintf(stderr, "fixed %s\n", sb.buf);
+       strbuf_release(&sb);
+       if (err) {
+               char errbuf[1024];
+               regerror(err, &p->regexp, errbuf, sizeof(errbuf));
+               regfree(&p->regexp);
+               compile_regexp_failed(p, errbuf);
+       }
+}
+
 static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
 {
+       int icase, ascii_only;
        int err;
 
        p->word_regexp = opt->word_regexp;
        p->ignore_case = opt->ignore_case;
+       icase          = opt->regflags & REG_ICASE || p->ignore_case;
+       ascii_only     = !has_non_ascii(p->pattern);
 
+       /*
+        * Even when -F (fixed) asks us to do a non-regexp search, we
+        * may not be able to correctly case-fold when -i
+        * (ignore-case) is asked (in which case, we'll synthesize a
+        * regexp to match the pattern that matches regexp special
+        * characters literally, while ignoring case differences).  On
+        * the other hand, even without -F, if the pattern does not
+        * have any regexp special characters and there is no need for
+        * case-folding search, we can internally turn it into a
+        * simple string match using kws.  p->fixed tells us if we
+        * want to use kws.
+        */
        if (opt->fixed || is_fixed(p->pattern, p->patternlen))
-               p->fixed = 1;
+               p->fixed = !icase || ascii_only;
        else
                p->fixed = 0;
 
        if (p->fixed) {
-               if (opt->regflags & REG_ICASE || p->ignore_case)
-                       p->kws = kwsalloc(tolower_trans_tbl);
-               else
-                       p->kws = kwsalloc(NULL);
+               p->kws = kwsalloc(icase ? tolower_trans_tbl : NULL);
                kwsincr(p->kws, p->pattern, p->patternlen);
                kwsprep(p->kws);
                return;
+       } else if (opt->fixed) {
+               /*
+                * We come here when the pattern has the non-ascii
+                * characters we cannot case-fold, and asked to
+                * ignore-case.
+                */
+               compile_fixed_regexp(p, opt);
+               return;
        }
 
        if (opt->pcre) {
@@ -643,10 +693,10 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
        for (p = opt->header_list; p; p = p->next) {
                if (p->token != GREP_PATTERN_HEAD)
-                       die("bug: a non-header pattern in grep header list.");
+                       die("BUG: a non-header pattern in grep header list.");
                if (p->field < GREP_HEADER_FIELD_MIN ||
                    GREP_HEADER_FIELD_MAX <= p->field)
-                       die("bug: unknown header field %d", p->field);
+                       die("BUG: unknown header field %d", p->field);
                compile_regexp(p, opt);
        }
 
@@ -659,7 +709,7 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
                h = compile_pattern_atom(&pp);
                if (!h || pp != p->next)
-                       die("bug: malformed header expr");
+                       die("BUG: malformed header expr");
                if (!header_group[p->field]) {
                        header_group[p->field] = h;
                        continue;
@@ -1386,9 +1436,17 @@ static int fill_textconv_grep(struct userdiff_driver *driver,
        return 0;
 }
 
+static int is_empty_line(const char *bol, const char *eol)
+{
+       while (bol < eol && isspace(*bol))
+               bol++;
+       return bol == eol;
+}
+
 static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int collect_hits)
 {
        char *bol;
+       char *peek_bol = NULL;
        unsigned long left;
        unsigned lno = 1;
        unsigned last_hit = 0;
@@ -1446,7 +1504,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                case GREP_BINARY_TEXT:
                        break;
                default:
-                       die("bug: unknown binary handling mode");
+                       die("BUG: unknown binary handling mode");
                }
        }
 
@@ -1533,8 +1591,24 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                                show_function = 1;
                        goto next_line;
                }
-               if (show_function && match_funcname(opt, gs, bol, eol))
-                       show_function = 0;
+               if (show_function && (!peek_bol || peek_bol < bol)) {
+                       unsigned long peek_left = left;
+                       char *peek_eol = eol;
+
+                       /*
+                        * Trailing empty lines are not interesting.
+                        * Peek past them to see if they belong to the
+                        * body of the current function.
+                        */
+                       peek_bol = bol;
+                       while (is_empty_line(peek_bol, peek_eol)) {
+                               peek_bol = peek_eol + 1;
+                               peek_eol = end_of_line(peek_bol, &peek_left);
+                       }
+
+                       if (match_funcname(opt, gs, peek_bol, peek_eol))
+                               show_function = 0;
+               }
                if (show_function ||
                    (last_hit && lno <= last_hit + opt->post_context)) {
                        /* If the last hit is within the post context,
@@ -1722,7 +1796,7 @@ static int grep_source_load_file(struct grep_source *gs)
        if (lstat(filename, &st) < 0) {
        err_ret:
                if (errno != ENOENT)
-                       error(_("'%s': %s"), filename, strerror(errno));
+                       error_errno(_("failed to stat '%s'"), filename);
                return -1;
        }
        if (!S_ISREG(st.st_mode))
@@ -1733,7 +1807,7 @@ static int grep_source_load_file(struct grep_source *gs)
                goto err_ret;
        data = xmallocz(size);
        if (st.st_size != read_in_full(i, data, size)) {
-               error(_("'%s': short read %s"), filename, strerror(errno));
+               error_errno(_("'%s': short read"), filename);
                close(i);
                free(data);
                return -1;