]> git.ipfire.org Git - thirdparty/git.git/blobdiff - grep.c
grep API: plug memory leaks by freeing "header_list"
[thirdparty/git.git] / grep.c
diff --git a/grep.c b/grep.c
index 424a39591b05e519d166e064a9f4e3e6d5973681..92ece4b7fa34a3ffb2a0c61b973b7cac33aa5b35 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -19,27 +19,6 @@ static void std_output(struct grep_opt *opt, const void *buf, size_t size)
        fwrite(buf, size, 1, stdout);
 }
 
-static struct grep_opt grep_defaults = {
-       .relative = 1,
-       .pathname = 1,
-       .max_depth = -1,
-       .pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED,
-       .colors = {
-               [GREP_COLOR_CONTEXT] = "",
-               [GREP_COLOR_FILENAME] = "",
-               [GREP_COLOR_FUNCTION] = "",
-               [GREP_COLOR_LINENO] = "",
-               [GREP_COLOR_COLUMNNO] = "",
-               [GREP_COLOR_MATCH_CONTEXT] = GIT_COLOR_BOLD_RED,
-               [GREP_COLOR_MATCH_SELECTED] = GIT_COLOR_BOLD_RED,
-               [GREP_COLOR_SELECTED] = "",
-               [GREP_COLOR_SEP] = GIT_COLOR_CYAN,
-       },
-       .only_matching = 0,
-       .color = -1,
-       .output = std_output,
-};
-
 static const char *color_grep_slots[] = {
        [GREP_COLOR_CONTEXT]        = "context",
        [GREP_COLOR_FILENAME]       = "filename",
@@ -75,20 +54,12 @@ define_list_config_array_extra(color_grep_slots, {"match"});
  */
 int grep_config(const char *var, const char *value, void *cb)
 {
-       struct grep_opt *opt = &grep_defaults;
+       struct grep_opt *opt = cb;
        const char *slot;
 
        if (userdiff_config(var, value) < 0)
                return -1;
 
-       /*
-        * The instance of grep_opt that we set up here is copied by
-        * grep_init() to be used by each individual invocation.
-        * When populating a new field of this structure here, be
-        * sure to think about ownership -- e.g., you might need to
-        * override the shallow copy in grep_init() with a deep copy.
-        */
-
        if (!strcmp(var, "grep.extendedregexp")) {
                opt->extended_regexp_option = git_config_bool(var, value);
                return 0;
@@ -134,78 +105,16 @@ int grep_config(const char *var, const char *value, void *cb)
        return 0;
 }
 
-/*
- * Initialize one instance of grep_opt and copy the
- * default values from the template we read the configuration
- * information in an earlier call to git_config(grep_config).
- */
-void grep_init(struct grep_opt *opt, struct repository *repo, const char *prefix)
+void grep_init(struct grep_opt *opt, struct repository *repo)
 {
-       *opt = grep_defaults;
+       struct grep_opt blank = GREP_OPT_INIT;
+       memcpy(opt, &blank, sizeof(*opt));
 
        opt->repo = repo;
-       opt->prefix = prefix;
-       opt->prefix_length = (prefix && *prefix) ? strlen(prefix) : 0;
        opt->pattern_tail = &opt->pattern_list;
        opt->header_tail = &opt->header_list;
 }
 
-static void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt)
-{
-       /*
-        * When committing to the pattern type by setting the relevant
-        * fields in grep_opt it's generally not necessary to zero out
-        * the fields we're not choosing, since they won't have been
-        * set by anything. The extended_regexp_option field is the
-        * only exception to this.
-        *
-        * This is because in the process of parsing grep.patternType
-        * & grep.extendedRegexp we set opt->pattern_type_option and
-        * opt->extended_regexp_option, respectively. We then
-        * internally use opt->extended_regexp_option to see if we're
-        * compiling an ERE. It must be unset if that's not actually
-        * the case.
-        */
-       if (pattern_type != GREP_PATTERN_TYPE_ERE &&
-           opt->extended_regexp_option)
-               opt->extended_regexp_option = 0;
-
-       switch (pattern_type) {
-       case GREP_PATTERN_TYPE_UNSPECIFIED:
-               /* fall through */
-
-       case GREP_PATTERN_TYPE_BRE:
-               break;
-
-       case GREP_PATTERN_TYPE_ERE:
-               opt->extended_regexp_option = 1;
-               break;
-
-       case GREP_PATTERN_TYPE_FIXED:
-               opt->fixed = 1;
-               break;
-
-       case GREP_PATTERN_TYPE_PCRE:
-               opt->pcre2 = 1;
-               break;
-       }
-}
-
-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)
-               /*
-                * This branch *must* happen after setting from the
-                * opt->pattern_type_option above, we don't want
-                * grep.extendedRegexp to override grep.patternType!
-                */
-               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,
@@ -362,6 +271,7 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
        int jitret;
        int patinforet;
        size_t jitsizearg;
+       int literal = !opt->ignore_case && (p->fixed || p->is_fixed);
 
        /*
         * Call pcre2_general_context_create() before calling any
@@ -382,11 +292,10 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
                }
                options |= PCRE2_CASELESS;
        }
-       if (!opt->ignore_locale && is_utf8_locale() && has_non_ascii(p->pattern) &&
-           !(!opt->ignore_case && (p->fixed || p->is_fixed)))
-               options |= (PCRE2_UTF | PCRE2_MATCH_INVALID_UTF);
+       if (!opt->ignore_locale && is_utf8_locale() && !literal)
+               options |= (PCRE2_UTF | PCRE2_UCP | PCRE2_MATCH_INVALID_UTF);
 
-#ifdef GIT_PCRE2_VERSION_10_36_OR_HIGHER
+#ifndef GIT_PCRE2_VERSION_10_36_OR_HIGHER
        /* Work around https://bugs.exim.org/show_bug.cgi?id=2642 fixed in 10.36 */
        if (PCRE2_MATCH_INVALID_UTF && options & (PCRE2_UTF | PCRE2_CASELESS))
                options |= PCRE2_NO_START_OPTIMIZE;
@@ -523,11 +432,17 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
        int err;
        int regflags = REG_NEWLINE;
 
+       if (opt->pattern_type_option == GREP_PATTERN_TYPE_UNSPECIFIED)
+               opt->pattern_type_option = (opt->extended_regexp_option
+                                           ? GREP_PATTERN_TYPE_ERE
+                                           : GREP_PATTERN_TYPE_BRE);
+
        p->word_regexp = opt->word_regexp;
        p->ignore_case = opt->ignore_case;
-       p->fixed = opt->fixed;
+       p->fixed = opt->pattern_type_option == GREP_PATTERN_TYPE_FIXED;
 
-       if (memchr(p->pattern, 0, p->patternlen) && !opt->pcre2)
+       if (opt->pattern_type_option != GREP_PATTERN_TYPE_PCRE &&
+           memchr(p->pattern, 0, p->patternlen))
                die(_("given pattern contains NULL byte (via -f <file>). This is only supported with -P under PCRE v2"));
 
        p->is_fixed = is_fixed(p->pattern, p->patternlen);
@@ -578,14 +493,14 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
                return;
        }
 
-       if (opt->pcre2) {
+       if (opt->pattern_type_option == GREP_PATTERN_TYPE_PCRE) {
                compile_pcre2_pattern(p, opt);
                return;
        }
 
        if (p->ignore_case)
                regflags |= REG_ICASE;
-       if (opt->extended_regexp_option)
+       if (opt->pattern_type_option == GREP_PATTERN_TYPE_ERE)
                regflags |= REG_EXTENDED;
        err = regcomp(&p->regexp, p->pattern, regflags);
        if (err) {
@@ -595,6 +510,35 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
        }
 }
 
+static struct grep_expr *grep_not_expr(struct grep_expr *expr)
+{
+       struct grep_expr *z = xcalloc(1, sizeof(*z));
+       z->node = GREP_NODE_NOT;
+       z->u.unary = expr;
+       return z;
+}
+
+static struct grep_expr *grep_binexp(enum grep_expr_node kind,
+                                    struct grep_expr *left,
+                                    struct grep_expr *right)
+{
+       struct grep_expr *z = xcalloc(1, sizeof(*z));
+       z->node = kind;
+       z->u.binary.left = left;
+       z->u.binary.right = right;
+       return z;
+}
+
+static struct grep_expr *grep_or_expr(struct grep_expr *left, struct grep_expr *right)
+{
+       return grep_binexp(GREP_NODE_OR, left, right);
+}
+
+static struct grep_expr *grep_and_expr(struct grep_expr *left, struct grep_expr *right)
+{
+       return grep_binexp(GREP_NODE_AND, left, right);
+}
+
 static struct grep_expr *compile_pattern_or(struct grep_pat **);
 static struct grep_expr *compile_pattern_atom(struct grep_pat **list)
 {
@@ -638,12 +582,10 @@ static struct grep_expr *compile_pattern_not(struct grep_pat **list)
                if (!p->next)
                        die("--not not followed by pattern expression");
                *list = p->next;
-               CALLOC_ARRAY(x, 1);
-               x->node = GREP_NODE_NOT;
-               x->u.unary = compile_pattern_not(list);
-               if (!x->u.unary)
+               x = compile_pattern_not(list);
+               if (!x)
                        die("--not followed by non pattern expression");
-               return x;
+               return grep_not_expr(x);
        default:
                return compile_pattern_atom(list);
        }
@@ -652,7 +594,7 @@ static struct grep_expr *compile_pattern_not(struct grep_pat **list)
 static struct grep_expr *compile_pattern_and(struct grep_pat **list)
 {
        struct grep_pat *p;
-       struct grep_expr *x, *y, *z;
+       struct grep_expr *x, *y;
 
        x = compile_pattern_not(list);
        p = *list;
@@ -665,11 +607,7 @@ static struct grep_expr *compile_pattern_and(struct grep_pat **list)
                y = compile_pattern_and(list);
                if (!y)
                        die("--and not followed by pattern expression");
-               CALLOC_ARRAY(z, 1);
-               z->node = GREP_NODE_AND;
-               z->u.binary.left = x;
-               z->u.binary.right = y;
-               return z;
+               return grep_and_expr(x, y);
        }
        return x;
 }
@@ -677,7 +615,7 @@ static struct grep_expr *compile_pattern_and(struct grep_pat **list)
 static struct grep_expr *compile_pattern_or(struct grep_pat **list)
 {
        struct grep_pat *p;
-       struct grep_expr *x, *y, *z;
+       struct grep_expr *x, *y;
 
        x = compile_pattern_and(list);
        p = *list;
@@ -685,11 +623,7 @@ static struct grep_expr *compile_pattern_or(struct grep_pat **list)
                y = compile_pattern_or(list);
                if (!y)
                        die("not a pattern expression %s", p->pattern);
-               CALLOC_ARRAY(z, 1);
-               z->node = GREP_NODE_OR;
-               z->u.binary.left = x;
-               z->u.binary.right = y;
-               return z;
+               return grep_or_expr(x, y);
        }
        return x;
 }
@@ -706,15 +640,6 @@ static struct grep_expr *grep_true_expr(void)
        return z;
 }
 
-static struct grep_expr *grep_or_expr(struct grep_expr *left, struct grep_expr *right)
-{
-       struct grep_expr *z = xcalloc(1, sizeof(*z));
-       z->node = GREP_NODE_OR;
-       z->u.binary.left = left;
-       z->u.binary.right = right;
-       return z;
-}
-
 static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 {
        struct grep_pat *p;
@@ -783,6 +708,7 @@ void compile_grep_patterns(struct grep_opt *opt)
 {
        struct grep_pat *p;
        struct grep_expr *header_expr = prep_header_patterns(opt);
+       int extended = 0;
 
        for (p = opt->pattern_list; p; p = p->next) {
                switch (p->token) {
@@ -792,14 +718,14 @@ void compile_grep_patterns(struct grep_opt *opt)
                        compile_regexp(p, opt);
                        break;
                default:
-                       opt->extended = 1;
+                       extended = 1;
                        break;
                }
        }
 
-       if (opt->all_match || header_expr)
-               opt->extended = 1;
-       else if (!opt->extended)
+       if (opt->all_match || opt->no_body_match || header_expr)
+               extended = 1;
+       else if (!extended)
                return;
 
        p = opt->pattern_list;
@@ -808,6 +734,9 @@ void compile_grep_patterns(struct grep_opt *opt)
        if (p)
                die("incomplete pattern expression: %s", p->pattern);
 
+       if (opt->no_body_match && opt->pattern_expression)
+               opt->pattern_expression = grep_not_expr(opt->pattern_expression);
+
        if (!header_expr)
                return;
 
@@ -840,11 +769,11 @@ static void free_pattern_expr(struct grep_expr *x)
        free(x);
 }
 
-void free_grep_patterns(struct grep_opt *opt)
+static void free_grep_pat(struct grep_pat *pattern)
 {
        struct grep_pat *p, *n;
 
-       for (p = opt->pattern_list; p; p = n) {
+       for (p = pattern; p; p = n) {
                n = p->next;
                switch (p->token) {
                case GREP_PATTERN: /* atom */
@@ -861,13 +790,18 @@ void free_grep_patterns(struct grep_opt *opt)
                }
                free(p);
        }
+}
 
-       if (!opt->extended)
-               return;
-       free_pattern_expr(opt->pattern_expression);
+void free_grep_patterns(struct grep_opt *opt)
+{
+       free_grep_pat(opt->pattern_list);
+       free_grep_pat(opt->header_list);
+
+       if (opt->pattern_expression)
+               free_pattern_expr(opt->pattern_expression);
 }
 
-static char *end_of_line(char *cp, unsigned long *left)
+static const char *end_of_line(const char *cp, unsigned long *left)
 {
        unsigned long l = *left;
        while (l && *cp != '\n') {
@@ -908,7 +842,8 @@ static void show_name(struct grep_opt *opt, const char *name)
        opt->output(opt, opt->null_following_name ? "\0" : "\n", 1);
 }
 
-static int patmatch(struct grep_pat *p, char *line, char *eol,
+static int patmatch(struct grep_pat *p,
+                   const char *line, const char *eol,
                    regmatch_t *match, int eflags)
 {
        int hit;
@@ -922,20 +857,16 @@ static int patmatch(struct grep_pat *p, char *line, char *eol,
        return hit;
 }
 
-static int strip_timestamp(char *bol, char **eol_p)
+static void strip_timestamp(const char *bol, const char **eol_p)
 {
-       char *eol = *eol_p;
-       int ch;
+       const char *eol = *eol_p;
 
        while (bol < --eol) {
                if (*eol != '>')
                        continue;
                *eol_p = ++eol;
-               ch = *eol;
-               *eol = '\0';
-               return ch;
+               break;
        }
-       return 0;
 }
 
 static struct {
@@ -947,37 +878,18 @@ static struct {
        { "reflog ", 7 },
 };
 
-static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
-                            enum grep_context ctx,
-                            regmatch_t *pmatch, int eflags)
+static int headerless_match_one_pattern(struct grep_pat *p,
+                                       const char *bol, const char *eol,
+                                       enum grep_context ctx,
+                                       regmatch_t *pmatch, int eflags)
 {
        int hit = 0;
-       int saved_ch = 0;
        const char *start = bol;
 
        if ((p->token != GREP_PATTERN) &&
            ((p->token == GREP_PATTERN_HEAD) != (ctx == GREP_CONTEXT_HEAD)))
                return 0;
 
-       if (p->token == GREP_PATTERN_HEAD) {
-               const char *field;
-               size_t len;
-               assert(p->field < ARRAY_SIZE(header_field));
-               field = header_field[p->field].field;
-               len = header_field[p->field].len;
-               if (strncmp(bol, field, len))
-                       return 0;
-               bol += len;
-               switch (p->field) {
-               case GREP_HEADER_AUTHOR:
-               case GREP_HEADER_COMMITTER:
-                       saved_ch = strip_timestamp(bol, &eol);
-                       break;
-               default:
-                       break;
-               }
-       }
-
  again:
        hit = patmatch(p, bol, eol, pmatch, eflags);
 
@@ -1021,8 +933,6 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
                                goto again;
                }
        }
-       if (p->token == GREP_PATTERN_HEAD && saved_ch)
-               *eol = saved_ch;
        if (hit) {
                pmatch[0].rm_so += bol - start;
                pmatch[0].rm_eo += bol - start;
@@ -1030,14 +940,43 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
        return hit;
 }
 
-static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, char *bol,
-                          char *eol, enum grep_context ctx, ssize_t *col,
+static int match_one_pattern(struct grep_pat *p,
+                            const char *bol, const char *eol,
+                            enum grep_context ctx, regmatch_t *pmatch,
+                            int eflags)
+{
+       const char *field;
+       size_t len;
+
+       if (p->token == GREP_PATTERN_HEAD) {
+               assert(p->field < ARRAY_SIZE(header_field));
+               field = header_field[p->field].field;
+               len = header_field[p->field].len;
+               if (strncmp(bol, field, len))
+                       return 0;
+               bol += len;
+
+               switch (p->field) {
+               case GREP_HEADER_AUTHOR:
+               case GREP_HEADER_COMMITTER:
+                       strip_timestamp(bol, &eol);
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       return headerless_match_one_pattern(p, bol, eol, ctx, pmatch, eflags);
+}
+
+
+static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x,
+                          const char *bol, const char *eol,
+                          enum grep_context ctx, ssize_t *col,
                           ssize_t *icol, int collect_hits)
 {
        int h = 0;
 
-       if (!x)
-               die("Not a valid grep expression");
        switch (x->node) {
        case GREP_NODE_TRUE:
                h = 1;
@@ -1050,6 +989,8 @@ static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, char *bol,
                        if (h && (*col < 0 || tmp.rm_so < *col))
                                *col = tmp.rm_so;
                }
+               if (x->u.atom->token == GREP_PATTERN_BODY)
+                       opt->body_hit |= h;
                break;
        case GREP_NODE_NOT:
                /*
@@ -1098,7 +1039,8 @@ static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, char *bol,
        return h;
 }
 
-static int match_expr(struct grep_opt *opt, char *bol, char *eol,
+static int match_expr(struct grep_opt *opt,
+                     const char *bol, const char *eol,
                      enum grep_context ctx, ssize_t *col,
                      ssize_t *icol, int collect_hits)
 {
@@ -1106,14 +1048,15 @@ static int match_expr(struct grep_opt *opt, char *bol, char *eol,
        return match_expr_eval(opt, x, bol, eol, ctx, col, icol, collect_hits);
 }
 
-static int match_line(struct grep_opt *opt, char *bol, char *eol,
+static int match_line(struct grep_opt *opt,
+                     const char *bol, const char *eol,
                      ssize_t *col, ssize_t *icol,
                      enum grep_context ctx, int collect_hits)
 {
        struct grep_pat *p;
        int hit = 0;
 
-       if (opt->extended)
+       if (opt->pattern_expression)
                return match_expr(opt, bol, eol, ctx, col, icol,
                                  collect_hits);
 
@@ -1138,13 +1081,14 @@ static int match_line(struct grep_opt *opt, char *bol, char *eol,
        return hit;
 }
 
-static int match_next_pattern(struct grep_pat *p, char *bol, char *eol,
+static int match_next_pattern(struct grep_pat *p,
+                             const char *bol, const char *eol,
                              enum grep_context ctx,
                              regmatch_t *pmatch, int eflags)
 {
        regmatch_t match;
 
-       if (!match_one_pattern(p, bol, eol, ctx, &match, eflags))
+       if (!headerless_match_one_pattern(p, bol, eol, ctx, &match, eflags))
                return 0;
        if (match.rm_so < 0 || match.rm_eo < 0)
                return 0;
@@ -1159,18 +1103,26 @@ static int match_next_pattern(struct grep_pat *p, char *bol, char *eol,
        return 1;
 }
 
-static int next_match(struct grep_opt *opt, char *bol, char *eol,
-                     enum grep_context ctx, regmatch_t *pmatch, int eflags)
+int grep_next_match(struct grep_opt *opt,
+                   const char *bol, const char *eol,
+                   enum grep_context ctx, regmatch_t *pmatch,
+                   enum grep_header_field field, int eflags)
 {
        struct grep_pat *p;
        int hit = 0;
 
        pmatch->rm_so = pmatch->rm_eo = -1;
        if (bol < eol) {
-               for (p = opt->pattern_list; p; p = p->next) {
+               for (p = ((ctx == GREP_CONTEXT_HEAD)
+                          ? opt->header_list : opt->pattern_list);
+                         p; p = p->next) {
                        switch (p->token) {
-                       case GREP_PATTERN: /* atom */
                        case GREP_PATTERN_HEAD:
+                               if ((field != GREP_HEADER_FIELD_MAX) &&
+                                   (p->field != field))
+                                       continue;
+                               /* fall thru */
+                       case GREP_PATTERN: /* atom */
                        case GREP_PATTERN_BODY:
                                hit |= match_next_pattern(p, bol, eol, ctx,
                                                          pmatch, eflags);
@@ -1215,7 +1167,8 @@ static void show_line_header(struct grep_opt *opt, const char *name,
        }
 }
 
-static void show_line(struct grep_opt *opt, char *bol, char *eol,
+static void show_line(struct grep_opt *opt,
+                     const char *bol, const char *eol,
                      const char *name, unsigned lno, ssize_t cno, char sign)
 {
        int rest = eol - bol;
@@ -1246,7 +1199,6 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
        if (opt->color || opt->only_matching) {
                regmatch_t match;
                enum grep_context ctx = GREP_CONTEXT_BODY;
-               int ch = *eol;
                int eflags = 0;
 
                if (opt->color) {
@@ -1261,8 +1213,8 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
                        else if (sign == '=')
                                line_color = opt->colors[GREP_COLOR_FUNCTION];
                }
-               *eol = '\0';
-               while (next_match(opt, bol, eol, ctx, &match, eflags)) {
+               while (grep_next_match(opt, bol, eol, ctx, &match,
+                                      GREP_HEADER_FIELD_MAX, eflags)) {
                        if (match.rm_so == match.rm_eo)
                                break;
 
@@ -1279,7 +1231,6 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
                        rest -= match.rm_eo;
                        eflags = REG_NOTBOL;
                }
-               *eol = ch;
        }
        if (!opt->only_matching) {
                output_color(opt, bol, rest, line_color);
@@ -1307,7 +1258,8 @@ static inline void grep_attr_unlock(void)
                pthread_mutex_unlock(&grep_attr_mutex);
 }
 
-static int match_funcname(struct grep_opt *opt, struct grep_source *gs, char *bol, char *eol)
+static int match_funcname(struct grep_opt *opt, struct grep_source *gs,
+                         const char *bol, const char *eol)
 {
        xdemitconf_t *xecfg = opt->priv;
        if (xecfg && !xecfg->find_func) {
@@ -1334,10 +1286,10 @@ static int match_funcname(struct grep_opt *opt, struct grep_source *gs, char *bo
 }
 
 static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
-                              char *bol, unsigned lno)
+                              const char *bol, unsigned lno)
 {
        while (bol > gs->buf) {
-               char *eol = --bol;
+               const char *eol = --bol;
 
                while (bol > gs->buf && bol[-1] != '\n')
                        bol--;
@@ -1356,7 +1308,7 @@ static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
 static int is_empty_line(const char *bol, const char *eol);
 
 static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
-                            char *bol, char *end, unsigned lno)
+                            const char *bol, const char *end, unsigned lno)
 {
        unsigned cur = lno, from = 1, funcname_lno = 0, orig_from;
        int funcname_needed = !!opt->funcname, comment_needed = 0;
@@ -1376,8 +1328,8 @@ static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
 
        /* Rewind. */
        while (bol > gs->buf && cur > from) {
-               char *next_bol = bol;
-               char *eol = --bol;
+               const char *next_bol = bol;
+               const char *eol = --bol;
 
                while (bol > gs->buf && bol[-1] != '\n')
                        bol--;
@@ -1408,7 +1360,7 @@ static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
 
        /* Back forward. */
        while (cur < lno) {
-               char *eol = bol, sign = (cur == funcname_lno) ? '=' : '-';
+               const char *eol = bol, sign = (cur == funcname_lno) ? '=' : '-';
 
                while (*eol != '\n')
                        eol++;
@@ -1422,7 +1374,7 @@ static int should_lookahead(struct grep_opt *opt)
 {
        struct grep_pat *p;
 
-       if (opt->extended)
+       if (opt->pattern_expression)
                return 0; /* punt for too complex stuff */
        if (opt->invert)
                return 0;
@@ -1436,12 +1388,12 @@ static int should_lookahead(struct grep_opt *opt)
 static int look_ahead(struct grep_opt *opt,
                      unsigned long *left_p,
                      unsigned *lno_p,
-                     char **bol_p)
+                     const char **bol_p)
 {
        unsigned lno = *lno_p;
-       char *bol = *bol_p;
+       const char *bol = *bol_p;
        struct grep_pat *p;
-       char *sp, *last_bol;
+       const char *sp, *last_bol;
        regoff_t earliest = -1;
 
        for (p = opt->pattern_list; p; p = p->next) {
@@ -1543,8 +1495,8 @@ static int is_empty_line(const char *bol, const char *eol)
 
 static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int collect_hits)
 {
-       char *bol;
-       char *peek_bol = NULL;
+       const char *bol;
+       const char *peek_bol = NULL;
        unsigned long left;
        unsigned lno = 1;
        unsigned last_hit = 0;
@@ -1626,7 +1578,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
        bol = gs->buf;
        left = gs->size;
        while (left) {
-               char *eol, ch;
+               const char *eol;
                int hit;
                ssize_t cno;
                ssize_t col = -1, icol = -1;
@@ -1647,14 +1599,11 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                    && look_ahead(opt, &left, &lno, &bol))
                        break;
                eol = end_of_line(bol, &left);
-               ch = *eol;
-               *eol = 0;
 
                if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol))
                        ctx = GREP_CONTEXT_BODY;
 
                hit = match_line(opt, bol, eol, &col, &icol, ctx, collect_hits);
-               *eol = ch;
 
                if (collect_hits)
                        goto next_line;
@@ -1670,7 +1619,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                                return 0;
                        goto next_line;
                }
-               if (hit) {
+               if (hit && (opt->max_count < 0 || count < opt->max_count)) {
                        count++;
                        if (opt->status_only)
                                return 1;
@@ -1713,7 +1662,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                }
                if (show_function && (!peek_bol || peek_bol < bol)) {
                        unsigned long peek_left = left;
-                       char *peek_eol = eol;
+                       const char *peek_eol = eol;
 
                        /*
                         * Trailing empty lines are not interesting.
@@ -1810,29 +1759,43 @@ int grep_source(struct grep_opt *opt, struct grep_source *gs)
         * we do not have to do the two-pass grep when we do not check
         * buffer-wide "all-match".
         */
-       if (!opt->all_match)
+       if (!opt->all_match && !opt->no_body_match)
                return grep_source_1(opt, gs, 0);
 
        /* Otherwise the toplevel "or" terms hit a bit differently.
         * We first clear hit markers from them.
         */
        clr_hit_marker(opt->pattern_expression);
+       opt->body_hit = 0;
        grep_source_1(opt, gs, 1);
 
-       if (!chk_hit_marker(opt->pattern_expression))
+       if (opt->all_match && !chk_hit_marker(opt->pattern_expression))
+               return 0;
+       if (opt->no_body_match && opt->body_hit)
                return 0;
 
        return grep_source_1(opt, gs, 0);
 }
 
-int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size)
+static void grep_source_init_buf(struct grep_source *gs,
+                                const char *buf,
+                                unsigned long size)
+{
+       gs->type = GREP_SOURCE_BUF;
+       gs->name = NULL;
+       gs->path = NULL;
+       gs->buf = buf;
+       gs->size = size;
+       gs->driver = NULL;
+       gs->identifier = NULL;
+}
+
+int grep_buffer(struct grep_opt *opt, const char *buf, unsigned long size)
 {
        struct grep_source gs;
        int r;
 
-       grep_source_init(&gs, GREP_SOURCE_BUF, NULL, NULL, NULL);
-       gs.buf = buf;
-       gs.size = size;
+       grep_source_init_buf(&gs, buf, size);
 
        r = grep_source(opt, &gs);
 
@@ -1840,28 +1803,30 @@ int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size)
        return r;
 }
 
-void grep_source_init(struct grep_source *gs, enum grep_source_type type,
-                     const char *name, const char *path,
-                     const void *identifier)
+void grep_source_init_file(struct grep_source *gs, const char *name,
+                          const char *path)
 {
-       gs->type = type;
+       gs->type = GREP_SOURCE_FILE;
        gs->name = xstrdup_or_null(name);
        gs->path = xstrdup_or_null(path);
        gs->buf = NULL;
        gs->size = 0;
        gs->driver = NULL;
+       gs->identifier = xstrdup(path);
+}
 
-       switch (type) {
-       case GREP_SOURCE_FILE:
-               gs->identifier = xstrdup(identifier);
-               break;
-       case GREP_SOURCE_OID:
-               gs->identifier = oiddup(identifier);
-               break;
-       case GREP_SOURCE_BUF:
-               gs->identifier = NULL;
-               break;
-       }
+void grep_source_init_oid(struct grep_source *gs, const char *name,
+                         const char *path, const struct object_id *oid,
+                         struct repository *repo)
+{
+       gs->type = GREP_SOURCE_OID;
+       gs->name = xstrdup_or_null(name);
+       gs->path = xstrdup_or_null(path);
+       gs->buf = NULL;
+       gs->size = 0;
+       gs->driver = NULL;
+       gs->identifier = oiddup(oid);
+       gs->repo = repo;
 }
 
 void grep_source_clear(struct grep_source *gs)
@@ -1877,7 +1842,9 @@ void grep_source_clear_data(struct grep_source *gs)
        switch (gs->type) {
        case GREP_SOURCE_FILE:
        case GREP_SOURCE_OID:
-               FREE_AND_NULL(gs->buf);
+               /* these types own the buffer */
+               free((char *)gs->buf);
+               gs->buf = NULL;
                gs->size = 0;
                break;
        case GREP_SOURCE_BUF:
@@ -1890,7 +1857,8 @@ static int grep_source_load_oid(struct grep_source *gs)
 {
        enum object_type type;
 
-       gs->buf = read_object_file(gs->identifier, &type, &gs->size);
+       gs->buf = repo_read_object_file(gs->repo, gs->identifier, &type,
+                                       &gs->size);
        if (!gs->buf)
                return error(_("'%s': unable to read %s"),
                             gs->name,