/*
* Builtin "git grep"
*
* Copyright (c) 2006 Junio C Hamano
*/
#include "cache.h"
#include "blob.h"
#include "tree.h"
#include "commit.h"
#include "tag.h"
#include "tree-walk.h"
#include "builtin.h"
#include <regex.h>
#include <fnmatch.h>
#include <sys/wait.h>
/*
* git grep pathspecs are somewhat different from diff-tree pathspecs;
* pathname wildcards are allowed.
*/
static int pathspec_matches(const char **paths, const char *name)
{
int namelen, i;
if (!paths || !*paths)
return 1;
namelen = strlen(name);
for (i = 0; paths[i]; i++) {
const char *match = paths[i];
int matchlen = strlen(match);
const char *cp, *meta;
if (!matchlen ||
((matchlen <= namelen) &&
!strncmp(name, match, matchlen) &&
(match[matchlen-1] == '/' ||
name[matchlen] == '\0' || name[matchlen] == '/')))
return 1;
if (!fnmatch(match, name, 0))
return 1;
if (name[namelen-1] != '/')
continue;
/* We are being asked if the directory ("name") is worth
* descending into.
*
* Find the longest leading directory name that does
* not have metacharacter in the pathspec; the name
* we are looking at must overlap with that directory.
*/
for (cp = match, meta = NULL; cp - match < matchlen; cp++) {
char ch = *cp;
if (ch == '*' || ch == '[' || ch == '?') {
meta = cp;
break;
}
}
if (!meta)
meta = cp; /* fully literal */
if (namelen <= meta - match) {
/* Looking at "Documentation/" and
* the pattern says "Documentation/howto/", or
* "Documentation/diff*.txt". The name we
* have should match prefix.
*/
if (!memcmp(match, name, namelen))
return 1;
continue;
}
if (meta - match < namelen) {
/* Looking at "Documentation/howto/" and
* the pattern says "Documentation/h*";
* match up to "Do.../h"; this avoids descending
* into "Documentation/technical/".
*/
if (!memcmp(match, name, meta - match))
return 1;
continue;
}
}
return 0;
}
enum grep_pat_token {
GREP_PATTERN,
GREP_AND,
GREP_OPEN_PAREN,
GREP_CLOSE_PAREN,
GREP_NOT,
GREP_OR,
};
struct grep_pat {
struct grep_pat *next;
const char *origin;
int no;
enum grep_pat_token token;
const char *pattern;
regex_t regexp;
};
enum grep_expr_node {
GREP_NODE_ATOM,
GREP_NODE_NOT,
GREP_NODE_AND,
GREP_NODE_OR,
};
struct grep_expr {
enum grep_expr_node node;
union {
struct grep_pat *atom;
struct grep_expr *unary;
struct {
struct grep_expr *left;
struct grep_expr *right;
} binary;
} u;
};
struct grep_opt {
struct grep_pat *pattern_list;
struct grep_pat **pattern_tail;
struct grep_expr *pattern_expression;
int prefix_length;
regex_t regexp;
unsigned linenum:1;
unsigned invert:1;
unsigned name_only:1;
unsigned unmatch_name_only:1;
unsigned count:1;
unsigned word_regexp:1;
unsigned fixed:1;
#define GREP_BINARY_DEFAULT 0
#define GREP_BINARY_NOMATCH 1
#define GREP_BINARY_TEXT 2
unsigned binary:2;
unsigned extended:1;
unsigned relative:1;
unsigned pathname:1;
int regflags;
unsigned pre_context;
unsigned post_context;
};
static void add_pattern(struct grep_opt *opt, const char *pat,
const char *origin, int no, enum grep_pat_token t)
{
struct grep_pat *p = xcalloc(1, sizeof(*p));
p->pattern = pat;
p->origin = origin;
p->no = no;
p->token = t;
*opt->pattern_tail = p;
opt->pattern_tail = &p->next;
p->next = NULL;
}
static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
{
int err = regcomp(&p->regexp, p->pattern, opt->regflags);
if (err) {
char errbuf[1024];
char where[1024];
if (p->no)
sprintf(where, "In '%s' at %d,
|