diff options
Diffstat (limited to 'third_party/git/grep.c')
-rw-r--r-- | third_party/git/grep.c | 2209 |
1 files changed, 2209 insertions, 0 deletions
diff --git a/third_party/git/grep.c b/third_party/git/grep.c new file mode 100644 index 000000000000..13232a904aca --- /dev/null +++ b/third_party/git/grep.c @@ -0,0 +1,2209 @@ +#include "cache.h" +#include "config.h" +#include "grep.h" +#include "object-store.h" +#include "userdiff.h" +#include "xdiff-interface.h" +#include "diff.h" +#include "diffcore.h" +#include "commit.h" +#include "quote.h" +#include "help.h" + +static int grep_source_load(struct grep_source *gs); +static int grep_source_is_binary(struct grep_source *gs, + struct index_state *istate); + +static struct grep_opt grep_defaults; + +#ifdef USE_LIBPCRE2 +static pcre2_general_context *pcre2_global_context; + +static void *pcre2_malloc(PCRE2_SIZE size, MAYBE_UNUSED void *memory_data) +{ + return malloc(size); +} + +static void pcre2_free(void *pointer, MAYBE_UNUSED void *memory_data) +{ + free(pointer); +} +#endif + +static const char *color_grep_slots[] = { + [GREP_COLOR_CONTEXT] = "context", + [GREP_COLOR_FILENAME] = "filename", + [GREP_COLOR_FUNCTION] = "function", + [GREP_COLOR_LINENO] = "lineNumber", + [GREP_COLOR_COLUMNNO] = "column", + [GREP_COLOR_MATCH_CONTEXT] = "matchContext", + [GREP_COLOR_MATCH_SELECTED] = "matchSelected", + [GREP_COLOR_SELECTED] = "selected", + [GREP_COLOR_SEP] = "separator", +}; + +static void std_output(struct grep_opt *opt, const void *buf, size_t size) +{ + fwrite(buf, size, 1, stdout); +} + +static void color_set(char *dst, const char *color_bytes) +{ + xsnprintf(dst, COLOR_MAXLEN, "%s", color_bytes); +} + +/* + * Initialize the grep_defaults template with hardcoded defaults. + * We could let the compiler do this, but without C99 initializers + * the code gets unwieldy and unreadable, so... + */ +void init_grep_defaults(struct repository *repo) +{ + struct grep_opt *opt = &grep_defaults; + static int run_once; + + if (run_once) + return; + run_once++; + + memset(opt, 0, sizeof(*opt)); + opt->repo = repo; + opt->relative = 1; + opt->pathname = 1; + opt->max_depth = -1; + opt->pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED; + color_set(opt->colors[GREP_COLOR_CONTEXT], ""); + color_set(opt->colors[GREP_COLOR_FILENAME], ""); + color_set(opt->colors[GREP_COLOR_FUNCTION], ""); + color_set(opt->colors[GREP_COLOR_LINENO], ""); + color_set(opt->colors[GREP_COLOR_COLUMNNO], ""); + color_set(opt->colors[GREP_COLOR_MATCH_CONTEXT], GIT_COLOR_BOLD_RED); + color_set(opt->colors[GREP_COLOR_MATCH_SELECTED], GIT_COLOR_BOLD_RED); + color_set(opt->colors[GREP_COLOR_SELECTED], ""); + color_set(opt->colors[GREP_COLOR_SEP], GIT_COLOR_CYAN); + opt->only_matching = 0; + opt->color = -1; + opt->output = std_output; +} + +static int parse_pattern_type_arg(const char *opt, const char *arg) +{ + if (!strcmp(arg, "default")) + return GREP_PATTERN_TYPE_UNSPECIFIED; + else if (!strcmp(arg, "basic")) + return GREP_PATTERN_TYPE_BRE; + else if (!strcmp(arg, "extended")) + return GREP_PATTERN_TYPE_ERE; + else if (!strcmp(arg, "fixed")) + return GREP_PATTERN_TYPE_FIXED; + else if (!strcmp(arg, "perl")) + return GREP_PATTERN_TYPE_PCRE; + die("bad %s argument: %s", opt, arg); +} + +define_list_config_array_extra(color_grep_slots, {"match"}); + +/* + * Read the configuration file once and store it in + * the grep_defaults template. + */ +int grep_config(const char *var, const char *value, void *cb) +{ + struct grep_opt *opt = &grep_defaults; + const char *slot; + + if (userdiff_config(var, value) < 0) + return -1; + + if (!strcmp(var, "grep.extendedregexp")) { + opt->extended_regexp_option = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "grep.patterntype")) { + opt->pattern_type_option = parse_pattern_type_arg(var, value); + return 0; + } + + if (!strcmp(var, "grep.linenumber")) { + opt->linenum = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "grep.column")) { + opt->columnnum = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "grep.fullname")) { + opt->relative = !git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "color.grep")) + opt->color = git_config_colorbool(var, value); + if (!strcmp(var, "color.grep.match")) { + if (grep_config("color.grep.matchcontext", value, cb) < 0) + return -1; + if (grep_config("color.grep.matchselected", value, cb) < 0) + return -1; + } else if (skip_prefix(var, "color.grep.", &slot)) { + int i = LOOKUP_CONFIG(color_grep_slots, slot); + char *color; + + if (i < 0) + return -1; + color = opt->colors[i]; + if (!value) + return config_error_nonbool(var); + return color_parse(value, color); + } + 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). + * + * If using PCRE, make sure that the library is configured + * to use the same allocator as Git (e.g. nedmalloc on Windows). + * + * Any allocated memory needs to be released in grep_destroy(). + */ +void grep_init(struct grep_opt *opt, struct repository *repo, const char *prefix) +{ + struct grep_opt *def = &grep_defaults; + int i; + +#if defined(USE_LIBPCRE2) + if (!pcre2_global_context) + pcre2_global_context = pcre2_general_context_create( + pcre2_malloc, pcre2_free, NULL); +#endif + +#ifdef USE_LIBPCRE1 + pcre_malloc = malloc; + pcre_free = free; +#endif + + memset(opt, 0, 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; + + opt->only_matching = def->only_matching; + opt->color = def->color; + opt->extended_regexp_option = def->extended_regexp_option; + opt->pattern_type_option = def->pattern_type_option; + opt->linenum = def->linenum; + opt->columnnum = def->columnnum; + opt->max_depth = def->max_depth; + opt->pathname = def->pathname; + opt->relative = def->relative; + opt->output = def->output; + + for (i = 0; i < NR_GREP_COLORS; i++) + color_set(opt->colors[i], def->colors[i]); +} + +void grep_destroy(void) +{ +#ifdef USE_LIBPCRE2 + pcre2_general_context_free(pcre2_global_context); +#endif +} + +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: +#ifdef USE_LIBPCRE2 + opt->pcre2 = 1; +#else + /* + * It's important that pcre1 always be assigned to + * even when there's no USE_LIBPCRE* defined. We still + * call the PCRE stub function, it just dies with + * "cannot use Perl-compatible regexes[...]". + */ + opt->pcre1 = 1; +#endif + 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, + enum grep_header_field field) +{ + struct grep_pat *p = xcalloc(1, sizeof(*p)); + p->pattern = xmemdupz(pat, patlen); + p->patternlen = patlen; + p->origin = origin; + p->no = no; + p->token = t; + p->field = field; + return p; +} + +static void do_append_grep_pat(struct grep_pat ***tail, struct grep_pat *p) +{ + **tail = p; + *tail = &p->next; + p->next = NULL; + + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + for (;;) { + struct grep_pat *new_pat; + size_t len = 0; + char *cp = p->pattern + p->patternlen, *nl = NULL; + while (++len <= p->patternlen) { + if (*(--cp) == '\n') { + nl = cp; + break; + } + } + if (!nl) + break; + new_pat = create_grep_pat(nl + 1, len - 1, p->origin, + p->no, p->token, p->field); + new_pat->next = p->next; + if (!p->next) + *tail = &new_pat->next; + p->next = new_pat; + *nl = '\0'; + p->patternlen -= len; + } + break; + default: + break; + } +} + +void append_header_grep_pattern(struct grep_opt *opt, + enum grep_header_field field, const char *pat) +{ + struct grep_pat *p = create_grep_pat(pat, strlen(pat), "header", 0, + GREP_PATTERN_HEAD, field); + if (field == GREP_HEADER_REFLOG) + opt->use_reflog_filter = 1; + do_append_grep_pat(&opt->header_tail, p); +} + +void append_grep_pattern(struct grep_opt *opt, const char *pat, + const char *origin, int no, enum grep_pat_token t) +{ + append_grep_pat(opt, pat, strlen(pat), origin, no, t); +} + +void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, + const char *origin, int no, enum grep_pat_token t) +{ + struct grep_pat *p = create_grep_pat(pat, patlen, origin, no, t, 0); + do_append_grep_pat(&opt->pattern_tail, p); +} + +struct grep_opt *grep_opt_dup(const struct grep_opt *opt) +{ + struct grep_pat *pat; + struct grep_opt *ret = xmalloc(sizeof(struct grep_opt)); + *ret = *opt; + + ret->pattern_list = NULL; + ret->pattern_tail = &ret->pattern_list; + + for(pat = opt->pattern_list; pat != NULL; pat = pat->next) + { + if(pat->token == GREP_PATTERN_HEAD) + append_header_grep_pattern(ret, pat->field, + pat->pattern); + else + append_grep_pat(ret, pat->pattern, pat->patternlen, + pat->origin, pat->no, pat->token); + } + + return ret; +} + +static NORETURN void compile_regexp_failed(const struct grep_pat *p, + const char *error) +{ + char where[1024]; + + if (p->no) + xsnprintf(where, sizeof(where), "In '%s' at %d, ", p->origin, p->no); + else if (p->origin) + xsnprintf(where, sizeof(where), "%s, ", p->origin); + else + where[0] = 0; + + die("%s'%s': %s", where, p->pattern, error); +} + +static int is_fixed(const char *s, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (is_regex_special(s[i])) + return 0; + } + + return 1; +} + +#ifdef USE_LIBPCRE1 +static void compile_pcre1_regexp(struct grep_pat *p, const struct grep_opt *opt) +{ + const char *error; + int erroffset; + int options = PCRE_MULTILINE; + int study_options = 0; + + if (opt->ignore_case) { + if (!opt->ignore_locale && has_non_ascii(p->pattern)) + p->pcre1_tables = pcre_maketables(); + options |= PCRE_CASELESS; + } + if (!opt->ignore_locale && is_utf8_locale() && has_non_ascii(p->pattern)) + options |= PCRE_UTF8; + + p->pcre1_regexp = pcre_compile(p->pattern, options, &error, &erroffset, + p->pcre1_tables); + if (!p->pcre1_regexp) + compile_regexp_failed(p, error); + +#if defined(PCRE_CONFIG_JIT) && !defined(NO_LIBPCRE1_JIT) + pcre_config(PCRE_CONFIG_JIT, &p->pcre1_jit_on); + if (opt->debug) + fprintf(stderr, "pcre1_jit_on=%d\n", p->pcre1_jit_on); + + if (p->pcre1_jit_on) + study_options = PCRE_STUDY_JIT_COMPILE; +#endif + + p->pcre1_extra_info = pcre_study(p->pcre1_regexp, study_options, &error); + if (!p->pcre1_extra_info && error) + die("%s", error); +} + +static int pcre1match(struct grep_pat *p, const char *line, const char *eol, + regmatch_t *match, int eflags) +{ + int ovector[30], ret, flags = PCRE_NO_UTF8_CHECK; + + if (eflags & REG_NOTBOL) + flags |= PCRE_NOTBOL; + + ret = pcre_exec(p->pcre1_regexp, p->pcre1_extra_info, line, + eol - line, 0, flags, ovector, + ARRAY_SIZE(ovector)); + + if (ret < 0 && ret != PCRE_ERROR_NOMATCH) + die("pcre_exec failed with error code %d", ret); + if (ret > 0) { + ret = 0; + match->rm_so = ovector[0]; + match->rm_eo = ovector[1]; + } + + return ret; +} + +static void free_pcre1_regexp(struct grep_pat *p) +{ + pcre_free(p->pcre1_regexp); +#ifdef PCRE_CONFIG_JIT + if (p->pcre1_jit_on) + pcre_free_study(p->pcre1_extra_info); + else +#endif + pcre_free(p->pcre1_extra_info); + pcre_free((void *)p->pcre1_tables); +} +#else /* !USE_LIBPCRE1 */ +static void compile_pcre1_regexp(struct grep_pat *p, const struct grep_opt *opt) +{ + die("cannot use Perl-compatible regexes when not compiled with USE_LIBPCRE"); +} + +static int pcre1match(struct grep_pat *p, const char *line, const char *eol, + regmatch_t *match, int eflags) +{ + return 1; +} + +static void free_pcre1_regexp(struct grep_pat *p) +{ +} +#endif /* !USE_LIBPCRE1 */ + +#ifdef USE_LIBPCRE2 +static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt) +{ + int error; + PCRE2_UCHAR errbuf[256]; + PCRE2_SIZE erroffset; + int options = PCRE2_MULTILINE; + int jitret; + int patinforet; + size_t jitsizearg; + + assert(opt->pcre2); + + p->pcre2_compile_context = NULL; + + /* pcre2_global_context is initialized in append_grep_pattern */ + if (opt->ignore_case) { + if (!opt->ignore_locale && has_non_ascii(p->pattern)) { + if (!pcre2_global_context) + BUG("pcre2_global_context uninitialized"); + p->pcre2_tables = pcre2_maketables(pcre2_global_context); + p->pcre2_compile_context = pcre2_compile_context_create(NULL); + pcre2_set_character_tables(p->pcre2_compile_context, + p->pcre2_tables); + } + 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; + + p->pcre2_pattern = pcre2_compile((PCRE2_SPTR)p->pattern, + p->patternlen, options, &error, &erroffset, + p->pcre2_compile_context); + + if (p->pcre2_pattern) { + p->pcre2_match_data = pcre2_match_data_create_from_pattern(p->pcre2_pattern, NULL); + if (!p->pcre2_match_data) + die("Couldn't allocate PCRE2 match data"); + } else { + pcre2_get_error_message(error, errbuf, sizeof(errbuf)); + compile_regexp_failed(p, (const char *)&errbuf); + } + + pcre2_config(PCRE2_CONFIG_JIT, &p->pcre2_jit_on); + if (opt->debug) + fprintf(stderr, "pcre2_jit_on=%d\n", p->pcre2_jit_on); + if (p->pcre2_jit_on) { + jitret = pcre2_jit_compile(p->pcre2_pattern, PCRE2_JIT_COMPLETE); + if (jitret) + die("Couldn't JIT the PCRE2 pattern '%s', got '%d'\n", p->pattern, jitret); + + /* + * The pcre2_config(PCRE2_CONFIG_JIT, ...) call just + * tells us whether the library itself supports JIT, + * but to see whether we're going to be actually using + * JIT we need to extract PCRE2_INFO_JITSIZE from the + * pattern *after* we do pcre2_jit_compile() above. + * + * This is because if the pattern contains the + * (*NO_JIT) verb (see pcre2syntax(3)) + * pcre2_jit_compile() will exit early with 0. If we + * then proceed to call pcre2_jit_match() further down + * the line instead of pcre2_match() we'll either + * segfault (pre PCRE 10.31) or run into a fatal error + * (post PCRE2 10.31) + */ + patinforet = pcre2_pattern_info(p->pcre2_pattern, PCRE2_INFO_JITSIZE, &jitsizearg); + if (patinforet) + BUG("pcre2_pattern_info() failed: %d", patinforet); + if (jitsizearg == 0) { + p->pcre2_jit_on = 0; + if (opt->debug) + fprintf(stderr, "pcre2_jit_on=%d: (*NO_JIT) in regex\n", + p->pcre2_jit_on); + return; + } + } +} + +static int pcre2match(struct grep_pat *p, const char *line, const char *eol, + regmatch_t *match, int eflags) +{ + int ret, flags = 0; + PCRE2_SIZE *ovector; + PCRE2_UCHAR errbuf[256]; + + if (eflags & REG_NOTBOL) + flags |= PCRE2_NOTBOL; + + if (p->pcre2_jit_on) + ret = pcre2_jit_match(p->pcre2_pattern, (unsigned char *)line, + eol - line, 0, flags, p->pcre2_match_data, + NULL); + else + ret = pcre2_match(p->pcre2_pattern, (unsigned char *)line, + eol - line, 0, flags, p->pcre2_match_data, + NULL); + + if (ret < 0 && ret != PCRE2_ERROR_NOMATCH) { + pcre2_get_error_message(ret, errbuf, sizeof(errbuf)); + die("%s failed with error code %d: %s", + (p->pcre2_jit_on ? "pcre2_jit_match" : "pcre2_match"), ret, + errbuf); + } + if (ret > 0) { + ovector = pcre2_get_ovector_pointer(p->pcre2_match_data); + ret = 0; + match->rm_so = (int)ovector[0]; + match->rm_eo = (int)ovector[1]; + } + + return ret; +} + +static void free_pcre2_pattern(struct grep_pat *p) +{ + pcre2_compile_context_free(p->pcre2_compile_context); + pcre2_code_free(p->pcre2_pattern); + pcre2_match_data_free(p->pcre2_match_data); + free((void *)p->pcre2_tables); +} +#else /* !USE_LIBPCRE2 */ +static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt) +{ + /* + * Unreachable until USE_LIBPCRE2 becomes synonymous with + * USE_LIBPCRE. See the sibling comment in + * grep_set_pattern_type_option(). + */ + die("cannot use Perl-compatible regexes when not compiled with USE_LIBPCRE"); +} + +static int pcre2match(struct grep_pat *p, const char *line, const char *eol, + regmatch_t *match, int eflags) +{ + return 1; +} + +static void free_pcre2_pattern(struct grep_pat *p) +{ +} + +static void compile_fixed_regexp(struct grep_pat *p, struct grep_opt *opt) +{ + struct strbuf sb = STRBUF_INIT; + int err; + int regflags = 0; + + basic_regex_quote_buf(&sb, p->pattern); + 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)); + compile_regexp_failed(p, errbuf); + } +} +#endif /* !USE_LIBPCRE2 */ + +static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) +{ + int err; + int regflags = REG_NEWLINE; + + p->word_regexp = opt->word_regexp; + p->ignore_case = opt->ignore_case; + p->fixed = opt->fixed; + + if (memchr(p->pattern, 0, p->patternlen) && !opt->pcre2) + 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); +#ifdef USE_LIBPCRE2 + if (!p->fixed && !p->is_fixed) { + const char *no_jit = "(*NO_JIT)"; + const int no_jit_len = strlen(no_jit); + if (starts_with(p->pattern, no_jit) && + is_fixed(p->pattern + no_jit_len, + p->patternlen - no_jit_len)) + p->is_fixed = 1; + } +#endif + if (p->fixed || p->is_fixed) { +#ifdef USE_LIBPCRE2 + opt->pcre2 = 1; + if (p->is_fixed) { + compile_pcre2_pattern(p, opt); + } else { + /* + * E.g. t7811-grep-open.sh relies on the + * pattern being restored. + */ + char *old_pattern = p->pattern; + size_t old_patternlen = p->patternlen; + struct strbuf sb = STRBUF_INIT; + + /* + * There is the PCRE2_LITERAL flag, but it's + * only in PCRE v2 10.30 and later. Needing to + * ifdef our way around that and dealing with + * it + PCRE2_MULTILINE being an error is more + * complex than just quoting this ourselves. + */ + strbuf_add(&sb, "\\Q", 2); + strbuf_add(&sb, p->pattern, p->patternlen); + strbuf_add(&sb, "\\E", 2); + + p->pattern = sb.buf; + p->patternlen = sb.len; + compile_pcre2_pattern(p, opt); + p->pattern = old_pattern; + p->patternlen = old_patternlen; + strbuf_release(&sb); + } +#else /* !USE_LIBPCRE2 */ + compile_fixed_regexp(p, opt); +#endif /* !USE_LIBPCRE2 */ + return; + } + + if (opt->pcre2) { + compile_pcre2_pattern(p, opt); + return; + } + + if (opt->pcre1) { + compile_pcre1_regexp(p, opt); + return; + } + + if (p->ignore_case) + regflags |= REG_ICASE; + if (opt->extended_regexp_option) + regflags |= REG_EXTENDED; + err = regcomp(&p->regexp, p->pattern, regflags); + if (err) { + char errbuf[1024]; + regerror(err, &p->regexp, errbuf, 1024); + compile_regexp_failed(p, errbuf); + } +} + +static struct grep_expr *compile_pattern_or(struct grep_pat **); +static struct grep_expr *compile_pattern_atom(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x; + + p = *list; + if (!p) + return NULL; + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + 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_or(list); + 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; + + p = *list; + if (!p) + return NULL; + 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; + + 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; + + 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) +{ + return compile_pattern_or(list); +} + +static void indent(int in) +{ + while (in-- > 0) + fputc(' ', stderr); +} + +static void dump_grep_pat(struct grep_pat *p) +{ + switch (p->token) { + case GREP_AND: fprintf(stderr, "*and*"); break; + case GREP_OPEN_PAREN: fprintf(stderr, "*(*"); break; + case GREP_CLOSE_PAREN: fprintf(stderr, "*)*"); break; + case GREP_NOT: fprintf(stderr, "*not*"); break; + case GREP_OR: fprintf(stderr, "*or*"); break; + + case GREP_PATTERN: fprintf(stderr, "pattern"); break; + case GREP_PATTERN_HEAD: fprintf(stderr, "pattern_head"); break; + case GREP_PATTERN_BODY: fprintf(stderr, "pattern_body"); break; + } + + switch (p->token) { + default: break; + case GREP_PATTERN_HEAD: + fprintf(stderr, "<head %d>", p->field); break; + case GREP_PATTERN_BODY: + fprintf(stderr, "<body>"); break; + } + switch (p->token) { + default: break; + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + case GREP_PATTERN: + fprintf(stderr, "%.*s", (int)p->patternlen, p->pattern); + break; + } + fputc('\n', stderr); +} + +static void dump_grep_expression_1(struct grep_expr *x, int in) +{ + indent(in); + switch (x->node) { + case GREP_NODE_TRUE: + fprintf(stderr, "true\n"); + break; + case GREP_NODE_ATOM: + dump_grep_pat(x->u.atom); + break; + case GREP_NODE_NOT: + fprintf(stderr, "(not\n"); + dump_grep_expression_1(x->u.unary, in+1); + indent(in); + fprintf(stderr, ")\n"); + break; + case GREP_NODE_AND: + fprintf(stderr, "(and\n"); + dump_grep_expression_1(x->u.binary.left, in+1); + dump_grep_expression_1(x->u.binary.right, in+1); + indent(in); + fprintf(stderr, ")\n"); + break; + case GREP_NODE_OR: + fprintf(stderr, "(or\n"); + dump_grep_expression_1(x->u.binary.left, in+1); + dump_grep_expression_1(x->u.binary.right, in+1); + indent(in); + fprintf(stderr, ")\n"); + break; + } +} + +static void dump_grep_expression(struct grep_opt *opt) +{ + struct grep_expr *x = opt->pattern_expression; + + if (opt->all_match) + fprintf(stderr, "[all-match]\n"); + dump_grep_expression_1(x, 0); + fflush(NULL); +} + +static struct grep_expr *grep_true_expr(void) +{ + struct grep_expr *z = xcalloc(1, sizeof(*z)); + z->node = GREP_NODE_TRUE; + 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; + struct grep_expr *header_expr; + struct grep_expr *(header_group[GREP_HEADER_FIELD_MAX]); + enum grep_header_field fld; + + if (!opt->header_list) + return NULL; + + for (p = opt->header_list; p; p = p->next) { + if (p->token != GREP_PATTERN_HEAD) + BUG("a non-header pattern in grep header list."); + if (p->field < GREP_HEADER_FIELD_MIN || + GREP_HEADER_FIELD_MAX <= p->field) + BUG("unknown header field %d", p->field); + compile_regexp(p, opt); + } + + for (fld = 0; fld < GREP_HEADER_FIELD_MAX; fld++) + header_group[fld] = NULL; + + for (p = opt->header_list; p; p = p->next) { + struct grep_expr *h; + struct grep_pat *pp = p; + + h = compile_pattern_atom(&pp); + if (!h || pp != p->next) + BUG("malformed header expr"); + if (!header_group[p->field]) { + header_group[p->field] = h; + continue; + } + header_group[p->field] = grep_or_expr(h, header_group[p->field]); + } + + header_expr = NULL; + + for (fld = 0; fld < GREP_HEADER_FIELD_MAX; fld++) { + if (!header_group[fld]) + continue; + if (!header_expr) + header_expr = grep_true_expr(); + header_expr = grep_or_expr(header_group[fld], header_expr); + } + return header_expr; +} + +static struct grep_expr *grep_splice_or(struct grep_expr *x, struct grep_expr *y) +{ + struct grep_expr *z = x; + + while (x) { + assert(x->node == GREP_NODE_OR); + if (x->u.binary.right && + x->u.binary.right->node == GREP_NODE_TRUE) { + x->u.binary.right = y; + break; + } + x = x->u.binary.right; + } + return z; +} + +static void compile_grep_patterns_real(struct grep_opt *opt) +{ + struct grep_pat *p; + struct grep_expr *header_expr = prep_header_patterns(opt); + + for (p = opt->pattern_list; p; p = p->next) { + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + compile_regexp(p, opt); + break; + default: + opt->extended = 1; + break; + } + } + + if (opt->all_match || header_expr) + opt->extended = 1; + else if (!opt->extended && !opt->debug) + return; + + p = opt->pattern_list; + if (p) + opt->pattern_expression = compile_pattern_expr(&p); + if (p) + die("incomplete pattern expression: %s", p->pattern); + + if (!header_expr) + return; + + if (!opt->pattern_expression) + opt->pattern_expression = header_expr; + else if (opt->all_match) + opt->pattern_expression = grep_splice_or(header_expr, + opt->pattern_expression); + else + opt->pattern_expression = grep_or_expr(opt->pattern_expression, + header_expr); + opt->all_match = 1; +} + +void compile_grep_patterns(struct grep_opt *opt) +{ + compile_grep_patterns_real(opt); + if (opt->debug) + dump_grep_expression(opt); +} + +static void free_pattern_expr(struct grep_expr *x) +{ + switch (x->node) { + case GREP_NODE_TRUE: + case GREP_NODE_ATOM: + break; + case GREP_NODE_NOT: + free_pattern_expr(x->u.unary); + break; + case GREP_NODE_AND: + case GREP_NODE_OR: + free_pattern_expr(x->u.binary.left); + free_pattern_expr(x->u.binary.right); + break; + } + free(x); +} + +void free_grep_patterns(struct grep_opt *opt) +{ + struct grep_pat *p, *n; + + for (p = opt->pattern_list; p; p = n) { + n = p->next; + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + if (p->pcre1_regexp) + free_pcre1_regexp(p); + else if (p->pcre2_pattern) + free_pcre2_pattern(p); + else + regfree(&p->regexp); + free(p->pattern); + break; + default: + break; + } + free(p); + } + + if (!opt->extended) + return; + free_pattern_expr(opt->pattern_expression); +} + +static char *end_of_line(char *cp, unsigned long *left) +{ + unsigned long l = *left; + while (l && *cp != '\n') { + l--; + cp++; + } + *left = l; + return cp; +} + +static int word_char(char ch) +{ + return isalnum(ch) || ch == '_'; +} + +static void output_color(struct grep_opt *opt, const void *data, size_t size, + const char *color) +{ + if (want_color(opt->color) && color && color[0]) { + opt->output(opt, color, strlen(color)); + opt->output(opt, data, size); + opt->output(opt, GIT_COLOR_RESET, strlen(GIT_COLOR_RESET)); + } else + opt->output(opt, data, size); +} + +static void output_sep(struct grep_opt *opt, char sign) +{ + if (opt->null_following_name) + opt->output(opt, "\0", 1); + else + output_color(opt, &sign, 1, opt->colors[GREP_COLOR_SEP]); +} + +static void show_name(struct grep_opt *opt, const char *name) +{ + output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]); + opt->output(opt, opt->null_following_name ? "\0" : "\n", 1); +} + +static int patmatch(struct grep_pat *p, char *line, char *eol, + regmatch_t *match, int eflags) +{ + int hit; + + if (p->pcre1_regexp) + hit = !pcre1match(p, line, eol, match, eflags); + else if (p->pcre2_pattern) + hit = !pcre2match(p, line, eol, match, eflags); + else + hit = !regexec_buf(&p->regexp, line, eol - line, 1, match, + eflags); + + return hit; +} + +static int strip_timestamp(char *bol, char **eol_p) +{ + char *eol = *eol_p; + int ch; + + while (bol < --eol) { + if (*eol != '>') + continue; + *eol_p = ++eol; + ch = *eol; + *eol = '\0'; + return ch; + } + return 0; +} + +static struct { + const char *field; + size_t len; +} header_field[] = { + { "author ", 7 }, + { "committer ", 10 }, + { "reflog ", 7 }, +}; + +static int match_one_pattern(struct grep_pat *p, char *bol, 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); + + if (hit && p->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) || + !word_char(bol[pmatch[0].rm_so-1])) && + ((pmatch[0].rm_eo == (eol-bol)) || + !word_char(bol[pmatch[0].rm_eo])) ) + ; + else + hit = 0; + + /* Words consist of at least one character. */ + if (pmatch->rm_so == pmatch->rm_eo) + 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! + * Forward to the next possible start, i.e. the + * next position following a non-word char. + */ + bol = pmatch[0].rm_so + bol + 1; + while (word_char(bol[-1]) && bol < eol) + bol++; + eflags |= REG_NOTBOL; + if (bol < 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; + } + 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, + 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; + break; + case GREP_NODE_ATOM: + { + regmatch_t tmp; + h = match_one_pattern(x->u.atom, bol, eol, ctx, + &tmp, 0); + if (h && (*col < 0 || tmp.rm_so < *col)) + *col = tmp.rm_so; + } + break; + case GREP_NODE_NOT: + /* + * Upon visiting a GREP_NODE_NOT, col and icol become swapped. + */ + h = !match_expr_eval(opt, x->u.unary, bol, eol, ctx, icol, col, + 0); + break; + case GREP_NODE_AND: + h = match_expr_eval(opt, x->u.binary.left, bol, eol, ctx, col, + icol, 0); + if (h || opt->columnnum) { + /* + * Don't short-circuit AND when given --column, since a + * NOT earlier in the tree may turn this into an OR. In + * this case, see the below comment. + */ + h &= match_expr_eval(opt, x->u.binary.right, bol, eol, + ctx, col, icol, 0); + } + break; + case GREP_NODE_OR: + if (!(collect_hits || opt->columnnum)) { + /* + * Don't short-circuit OR when given --column (or + * collecting hits) to ensure we don't skip a later + * child that would produce an earlier match. + */ + return (match_expr_eval(opt, x->u.binary.left, bol, eol, + ctx, col, icol, 0) || + match_expr_eval(opt, x->u.binary.right, bol, + eol, ctx, col, icol, 0)); + } + h = match_expr_eval(opt, x->u.binary.left, bol, eol, ctx, col, + icol, 0); + if (collect_hits) + x->u.binary.left->hit |= h; + h |= match_expr_eval(opt, x->u.binary.right, bol, eol, ctx, col, + icol, collect_hits); + break; + default: + die("Unexpected node type (internal error) %d", x->node); + } + if (collect_hits) + x->hit |= h; + return h; +} + +static int match_expr(struct grep_opt *opt, char *bol, char *eol, + enum grep_context ctx, ssize_t *col, + ssize_t *icol, int collect_hits) +{ + struct grep_expr *x = opt->pattern_expression; + 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, + ssize_t *col, ssize_t *icol, + enum grep_context ctx, int collect_hits) +{ + struct grep_pat *p; + int hit = 0; + + if (opt->extended) + return match_expr(opt, bol, eol, ctx, col, icol, + collect_hits); + + /* we do not call with collect_hits without being extended */ + for (p = opt->pattern_list; p; p = p->next) { + regmatch_t tmp; + if (match_one_pattern(p, bol, eol, ctx, &tmp, 0)) { + hit |= 1; + if (!opt->columnnum) { + /* + * Without --column, any single match on a line + * is enough to know that it needs to be + * printed. With --column, scan _all_ patterns + * to find the earliest. + */ + break; + } + if (*col < 0 || tmp.rm_so < *col) + *col = tmp.rm_so; + } + } + return hit; +} + +static int match_next_pattern(struct grep_pat *p, char *bol, char *eol, + enum grep_context ctx, + regmatch_t *pmatch, int eflags) +{ + regmatch_t match; + + if (!match_one_pattern(p, bol, eol, ctx, &match, eflags)) + return 0; + if (match.rm_so < 0 || match.rm_eo < 0) + return 0; + if (pmatch->rm_so >= 0 && pmatch->rm_eo >= 0) { + if (match.rm_so > pmatch->rm_so) + return 1; + if (match.rm_so == pmatch->rm_so && match.rm_eo < pmatch->rm_eo) + return 1; + } + pmatch->rm_so = match.rm_so; + pmatch->rm_eo = match.rm_eo; + return 1; +} + +static int next_match(struct grep_opt *opt, char *bol, char *eol, + enum grep_context ctx, regmatch_t *pmatch, 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) { + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + hit |= match_next_pattern(p, bol, eol, ctx, + pmatch, eflags); + break; + default: + break; + } + } + } + return hit; +} + +static void show_line_header(struct grep_opt *opt, const char *name, + unsigned lno, ssize_t cno, char sign) +{ + if (opt->heading && opt->last_shown == 0) { + output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]); + opt->output(opt, "\n", 1); + } + opt->last_shown = lno; + + if (!opt->heading && opt->pathname) { + output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]); + output_sep(opt, sign); + } + if (opt->linenum) { + char buf[32]; + xsnprintf(buf, sizeof(buf), "%d", lno); + output_color(opt, buf, strlen(buf), opt->colors[GREP_COLOR_LINENO]); + output_sep(opt, sign); + } + /* + * Treat 'cno' as the 1-indexed offset from the start of a non-context + * line to its first match. Otherwise, 'cno' is 0 indicating that we are + * being called with a context line. + */ + if (opt->columnnum && cno) { + char buf[32]; + xsnprintf(buf, sizeof(buf), "%"PRIuMAX, (uintmax_t)cno); + output_color(opt, buf, strlen(buf), opt->colors[GREP_COLOR_COLUMNNO]); + output_sep(opt, sign); + } +} + +static void show_line(struct grep_opt *opt, char *bol, char *eol, + const char *name, unsigned lno, ssize_t cno, char sign) +{ + int rest = eol - bol; + const char *match_color = NULL; + const char *line_color = NULL; + + if (opt->file_break && opt->last_shown == 0) { + if (opt->show_hunk_mark) + opt->output(opt, "\n", 1); + } else if (opt->pre_context || opt->post_context || opt->funcbody) { + if (opt->last_shown == 0) { + if (opt->show_hunk_mark) { + output_color(opt, "--", 2, opt->colors[GREP_COLOR_SEP]); + opt->output(opt, "\n", 1); + } + } else if (lno > opt->last_shown + 1) { + output_color(opt, "--", 2, opt->colors[GREP_COLOR_SEP]); + opt->output(opt, "\n", 1); + } + } + if (!opt->only_matching) { + /* + * In case the line we're being called with contains more than + * one match, leave printing each header to the loop below. + */ + show_line_header(opt, name, lno, cno, sign); + } + 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) { + if (sign == ':') + match_color = opt->colors[GREP_COLOR_MATCH_SELECTED]; + else + match_color = opt->colors[GREP_COLOR_MATCH_CONTEXT]; + if (sign == ':') + line_color = opt->colors[GREP_COLOR_SELECTED]; + else if (sign == '-') + line_color = opt->colors[GREP_COLOR_CONTEXT]; + else if (sign == '=') + line_color = opt->colors[GREP_COLOR_FUNCTION]; + } + *eol = '\0'; + while (next_match(opt, bol, eol, ctx, &match, eflags)) { + if (match.rm_so == match.rm_eo) + break; + + if (opt->only_matching) + show_line_header(opt, name, lno, cno, sign); + else + output_color(opt, bol, match.rm_so, line_color); + output_color(opt, bol + match.rm_so, + match.rm_eo - match.rm_so, match_color); + if (opt->only_matching) + opt->output(opt, "\n", 1); + bol += match.rm_eo; + cno += match.rm_eo; + rest -= match.rm_eo; + eflags = REG_NOTBOL; + } + *eol = ch; + } + if (!opt->only_matching) { + output_color(opt, bol, rest, line_color); + opt->output(opt, "\n", 1); + } +} + +int grep_use_locks; + +/* + * This lock protects access to the gitattributes machinery, which is + * not thread-safe. + */ +pthread_mutex_t grep_attr_mutex; + +static inline void grep_attr_lock(void) +{ + if (grep_use_locks) + pthread_mutex_lock(&grep_attr_mutex); +} + +static inline void grep_attr_unlock(void) +{ + if (grep_use_locks) + pthread_mutex_unlock(&grep_attr_mutex); +} + +static int match_funcname(struct grep_opt *opt, struct grep_source *gs, char *bol, char *eol) +{ + xdemitconf_t *xecfg = opt->priv; + if (xecfg && !xecfg->find_func) { + grep_source_load_driver(gs, opt->repo->index); + if (gs->driver->funcname.pattern) { + const struct userdiff_funcname *pe = &gs->driver->funcname; + xdiff_set_find_func(xecfg, pe->pattern, pe->cflags); + } else { + xecfg = opt->priv = NULL; + } + } + + if (xecfg) { + char buf[1]; + return xecfg->find_func(bol, eol - bol, buf, 1, + xecfg->find_func_priv) >= 0; + } + + if (bol == eol) + return 0; + if (isalpha(*bol) || *bol == '_' || *bol == '$') + return 1; + return 0; +} + +static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs, + char *bol, unsigned lno) +{ + while (bol > gs->buf) { + char *eol = --bol; + + while (bol > gs->buf && bol[-1] != '\n') + bol--; + lno--; + + if (lno <= opt->last_shown) + break; + + if (match_funcname(opt, gs, bol, eol)) { + show_line(opt, bol, eol, gs->name, lno, 0, '='); + break; + } + } +} + +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) +{ + unsigned cur = lno, from = 1, funcname_lno = 0, orig_from; + int funcname_needed = !!opt->funcname, comment_needed = 0; + + if (opt->pre_context < lno) + from = lno - opt->pre_context; + if (from <= opt->last_shown) + from = opt->last_shown + 1; + orig_from = from; + if (opt->funcbody) { + if (match_funcname(opt, gs, bol, end)) + comment_needed = 1; + else + funcname_needed = 1; + from = opt->last_shown + 1; + } + + /* Rewind. */ + while (bol > gs->buf && cur > from) { + char *next_bol = bol; + char *eol = --bol; + + while (bol > gs->buf && bol[-1] != '\n') + bol--; + cur--; + if (comment_needed && (is_empty_line(bol, eol) || + match_funcname(opt, gs, bol, eol))) { + comment_needed = 0; + from = orig_from; + if (cur < from) { + cur++; + bol = next_bol; + break; + } + } + if (funcname_needed && match_funcname(opt, gs, bol, eol)) { + funcname_lno = cur; + funcname_needed = 0; + if (opt->funcbody) + comment_needed = 1; + else + from = orig_from; + } + } + + /* We need to look even further back to find a function signature. */ + if (opt->funcname && funcname_needed) + show_funcname_line(opt, gs, bol, cur); + + /* Back forward. */ + while (cur < lno) { + char *eol = bol, sign = (cur == funcname_lno) ? '=' : '-'; + + while (*eol != '\n') + eol++; + show_line(opt, bol, eol, gs->name, cur, 0, sign); + bol = eol + 1; + cur++; + } +} + +static int should_lookahead(struct grep_opt *opt) +{ + struct grep_pat *p; + + if (opt->extended) + return 0; /* punt for too complex stuff */ + if (opt->invert) + return 0; + for (p = opt->pattern_list; p; p = p->next) { + if (p->token != GREP_PATTERN) + return 0; /* punt for "header only" and stuff */ + } + return 1; +} + +static int look_ahead(struct grep_opt *opt, + unsigned long *left_p, + unsigned *lno_p, + char **bol_p) +{ + unsigned lno = *lno_p; + char *bol = *bol_p; + struct grep_pat *p; + char *sp, *last_bol; + regoff_t earliest = -1; + + for (p = opt->pattern_list; p; p = p->next) { + int hit; + regmatch_t m; + + hit = patmatch(p, bol, bol + *left_p, &m, 0); + if (!hit || m.rm_so < 0 || m.rm_eo < 0) + continue; + if (earliest < 0 || m.rm_so < earliest) + earliest = m.rm_so; + } + + if (earliest < 0) { + *bol_p = bol + *left_p; + *left_p = 0; + return 1; + } + for (sp = bol + earliest; bol < sp && sp[-1] != '\n'; sp--) + ; /* find the beginning of the line */ + last_bol = sp; + + for (sp = bol; sp < last_bol; sp++) { + if (*sp == '\n') + lno++; + } + *left_p -= last_bol - bol; + *bol_p = last_bol; + *lno_p = lno; + return 0; +} + +static int fill_textconv_grep(struct repository *r, + struct userdiff_driver *driver, + struct grep_source *gs) +{ + struct diff_filespec *df; + char *buf; + size_t size; + + if (!driver || !driver->textconv) + return grep_source_load(gs); + + /* + * The textconv interface is intimately tied to diff_filespecs, so we + * have to pretend to be one. If we could unify the grep_source + * and diff_filespec structs, this mess could just go away. + */ + df = alloc_filespec(gs->path); + switch (gs->type) { + case GREP_SOURCE_OID: + fill_filespec(df, gs->identifier, 1, 0100644); + break; + case GREP_SOURCE_FILE: + fill_filespec(df, &null_oid, 0, 0100644); + break; + default: + BUG("attempt to textconv something without a path?"); + } + + /* + * fill_textconv is not remotely thread-safe; it modifies the global + * diff tempfile structure, writes to the_repo's odb and might + * internally call thread-unsafe functions such as the + * prepare_packed_git() lazy-initializator. Because of the last two, we + * must ensure mutual exclusion between this call and the object reading + * API, thus we use obj_read_lock() here. + * + * TODO: allowing text conversion to run in parallel with object + * reading operations might increase performance in the multithreaded + * non-worktreee git-grep with --textconv. + */ + obj_read_lock(); + size = fill_textconv(r, driver, df, &buf); + obj_read_unlock(); + free_filespec(df); + + /* + * The normal fill_textconv usage by the diff machinery would just keep + * the textconv'd buf separate from the diff_filespec. But much of the + * grep code passes around a grep_source and assumes that its "buf" + * pointer is the beginning of the thing we are searching. So let's + * install our textconv'd version into the grep_source, taking care not + * to leak any existing buffer. + */ + grep_source_clear_data(gs); + gs->buf = buf; + gs->size = size; + + 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; + int binary_match_only = 0; + unsigned count = 0; + int try_lookahead = 0; + int show_function = 0; + struct userdiff_driver *textconv = NULL; + enum grep_context ctx = GREP_CONTEXT_HEAD; + xdemitconf_t xecfg; + + if (!opt->status_only && gs->name == NULL) + BUG("grep call which could print a name requires " + "grep_source.name be non-NULL"); + + if (!opt->output) + opt->output = std_output; + + if (opt->pre_context || opt->post_context || opt->file_break || + opt->funcbody) { + /* Show hunk marks, except for the first file. */ + if (opt->last_shown) + opt->show_hunk_mark = 1; + /* + * If we're using threads then we can't easily identify + * the first file. Always put hunk marks in that case + * and skip the very first one later in work_done(). + */ + if (opt->output != std_output) + opt->show_hunk_mark = 1; + } + opt->last_shown = 0; + + if (opt->allow_textconv) { + grep_source_load_driver(gs, opt->repo->index); + /* + * We might set up the shared textconv cache data here, which + * is not thread-safe. Also, get_oid_with_context() and + * parse_object() might be internally called. As they are not + * currenty thread-safe and might be racy with object reading, + * obj_read_lock() must be called. + */ + grep_attr_lock(); + obj_read_lock(); + textconv = userdiff_get_textconv(opt->repo, gs->driver); + obj_read_unlock(); + grep_attr_unlock(); + } + + /* + * We know the result of a textconv is text, so we only have to care + * about binary handling if we are not using it. + */ + if (!textconv) { + switch (opt->binary) { + case GREP_BINARY_DEFAULT: + if (grep_source_is_binary(gs, opt->repo->index)) + binary_match_only = 1; + break; + case GREP_BINARY_NOMATCH: + if (grep_source_is_binary(gs, opt->repo->index)) + return 0; /* Assume unmatch */ + break; + case GREP_BINARY_TEXT: + break; + default: + BUG("unknown binary handling mode"); + } + } + + memset(&xecfg, 0, sizeof(xecfg)); + opt->priv = &xecfg; + + try_lookahead = should_lookahead(opt); + + if (fill_textconv_grep(opt->repo, textconv, gs) < 0) + return 0; + + bol = gs->buf; + left = gs->size; + while (left) { + char *eol, ch; + int hit; + ssize_t cno; + ssize_t col = -1, icol = -1; + + /* + * look_ahead() skips quickly to the line that possibly + * has the next hit; don't call it if we need to do + * something more than just skipping the current line + * in response to an unmatch for the current line. E.g. + * inside a post-context window, we will show the current + * line as a context around the previous hit when it + * doesn't hit. + */ + if (try_lookahead + && !(last_hit + && (show_function || + lno <= last_hit + opt->post_context)) + && 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; + + /* "grep -v -e foo -e bla" should list lines + * that do not have either, so inversion should + * be done outside. + */ + if (opt->invert) + hit = !hit; + if (opt->unmatch_name_only) { + if (hit) + return 0; + goto next_line; + } + if (hit) { + count++; + if (opt->status_only) + return 1; + if (opt->name_only) { + show_name(opt, gs->name); + return 1; + } + if (opt->count) + goto next_line; + if (binary_match_only) { + opt->output(opt, "Binary file ", 12); + output_color(opt, gs->name, strlen(gs->name), + opt->colors[GREP_COLOR_FILENAME]); + opt->output(opt, " matches\n", 9); + return 1; + } + /* Hit at this line. If we haven't shown the + * pre-context lines, we would need to show them. + */ + if (opt->pre_context || opt->funcbody) + show_pre_context(opt, gs, bol, eol, lno); + else if (opt->funcname) + show_funcname_line(opt, gs, bol, lno); + cno = opt->invert ? icol : col; + if (cno < 0) { + /* + * A negative cno indicates that there was no + * match on the line. We are thus inverted and + * being asked to show all lines that _don't_ + * match a given expression. Therefore, set cno + * to 0 to suggest the whole line matches. + */ + cno = 0; + } + show_line(opt, bol, eol, gs->name, lno, cno + 1, ':'); + last_hit = lno; + if (opt->funcbody) + show_function = 1; + goto next_line; + } + 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, + * we need to show this line. + */ + show_line(opt, bol, eol, gs->name, lno, col + 1, '-'); + } + + next_line: + bol = eol + 1; + if (!left) + break; + left--; + lno++; + } + + if (collect_hits) + return 0; + + if (opt->status_only) + return opt->unmatch_name_only; + if (opt->unmatch_name_only) { + /* We did not see any hit, so we want to show this */ + show_name(opt, gs->name); + return 1; + } + + xdiff_clear_find_func(&xecfg); + opt->priv = NULL; + + /* NEEDSWORK: + * The real "grep -c foo *.c" gives many "bar.c:0" lines, + * which feels mostly useless but sometimes useful. Maybe + * make it another option? For now suppress them. + */ + if (opt->count && count) { + char buf[32]; + if (opt->pathname) { + output_color(opt, gs->name, strlen(gs->name), + opt->colors[GREP_COLOR_FILENAME]); + output_sep(opt, ':'); + } + xsnprintf(buf, sizeof(buf), "%u\n", count); + opt->output(opt, buf, strlen(buf)); + return 1; + } + return !!last_hit; +} + +static void clr_hit_marker(struct grep_expr *x) +{ + /* All-hit markers are meaningful only at the very top level + * OR node. + */ + while (1) { + x->hit = 0; + if (x->node != GREP_NODE_OR) + return; + x->u.binary.left->hit = 0; + x = x->u.binary.right; + } +} + +static int chk_hit_marker(struct grep_expr *x) +{ + /* Top level nodes have hit markers. See if they all are hits */ + while (1) { + if (x->node != GREP_NODE_OR) + return x->hit; + if (!x->u.binary.left->hit) + return 0; + x = x->u.binary.right; + } +} + +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) + 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); + grep_source_1(opt, gs, 1); + + if (!chk_hit_marker(opt->pattern_expression)) + return 0; + + return grep_source_1(opt, gs, 0); +} + +int grep_buffer(struct grep_opt *opt, 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; + + r = grep_source(opt, &gs); + + grep_source_clear(&gs); + return r; +} + +void grep_source_init(struct grep_source *gs, enum grep_source_type type, + const char *name, const char *path, + const void *identifier) +{ + gs->type = type; + gs->name = xstrdup_or_null(name); + gs->path = xstrdup_or_null(path); + gs->buf = NULL; + gs->size = 0; + gs->driver = NULL; + + 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_clear(struct grep_source *gs) +{ + FREE_AND_NULL(gs->name); + FREE_AND_NULL(gs->path); + FREE_AND_NULL(gs->identifier); + grep_source_clear_data(gs); +} + +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); + gs->size = 0; + break; + case GREP_SOURCE_BUF: + /* leave user-provided buf intact */ + break; + } +} + +static int grep_source_load_oid(struct grep_source *gs) +{ + enum object_type type; + + gs->buf = read_object_file(gs->identifier, &type, &gs->size); + if (!gs->buf) + return error(_("'%s': unable to read %s"), + gs->name, + oid_to_hex(gs->identifier)); + return 0; +} + +static int grep_source_load_file(struct grep_source *gs) +{ + const char *filename = gs->identifier; + struct stat st; + char *data; + size_t size; + int i; + + if (lstat(filename, &st) < 0) { + err_ret: + if (errno != ENOENT) + error_errno(_("failed to stat '%s'"), filename); + return -1; + } + if (!S_ISREG(st.st_mode)) + return -1; + size = xsize_t(st.st_size); + i = open(filename, O_RDONLY); + if (i < 0) + goto err_ret; + data = xmallocz(size); + if (st.st_size != read_in_full(i, data, size)) { + error_errno(_("'%s': short read"), filename); + close(i); + free(data); + return -1; + } + close(i); + + gs->buf = data; + gs->size = size; + return 0; +} + +static int grep_source_load(struct grep_source *gs) +{ + if (gs->buf) + return 0; + + switch (gs->type) { + case GREP_SOURCE_FILE: + return grep_source_load_file(gs); + case GREP_SOURCE_OID: + return grep_source_load_oid(gs); + case GREP_SOURCE_BUF: + return gs->buf ? 0 : -1; + } + BUG("invalid grep_source type to load"); +} + +void grep_source_load_driver(struct grep_source *gs, + struct index_state *istate) +{ + if (gs->driver) + return; + + grep_attr_lock(); + if (gs->path) + gs->driver = userdiff_find_by_path(istate, gs->path); + if (!gs->driver) + gs->driver = userdiff_find_by_name("default"); + grep_attr_unlock(); +} + +static int grep_source_is_binary(struct grep_source *gs, + struct index_state *istate) +{ + grep_source_load_driver(gs, istate); + if (gs->driver->binary != -1) + return gs->driver->binary; + + if (!grep_source_load(gs)) + return buffer_is_binary(gs->buf, gs->size); + + return 0; +} |