1
0
Fork 0
mirror of https://github.com/git/git.git synced 2024-05-28 23:16:11 +02:00

Merge branch 'jc/maint-log-grep-all-match'

Fix a long-standing bug in "git log --grep" when multiple "--grep"
are used together with "--all-match" and "--author" or "--committer".

* jc/maint-log-grep-all-match:
  t7810-grep: test --all-match with multiple --grep and --author options
  t7810-grep: test interaction of multiple --grep and --author options
  t7810-grep: test multiple --author with --all-match
  t7810-grep: test multiple --grep with and without --all-match
  t7810-grep: bring log --grep tests in common form
  grep.c: mark private file-scope symbols as static
  log: document use of multiple commit limiting options
  log --grep/--author: honor --all-match honored for multiple --grep patterns
  grep: show --debug output only once
  grep: teach --debug option to dump the parse tree
This commit is contained in:
Junio C Hamano 2012-09-18 14:37:53 -07:00
commit 3d7535e424
6 changed files with 215 additions and 23 deletions

View File

@ -3,8 +3,15 @@ Commit Limiting
Besides specifying a range of commits that should be listed using the
special notations explained in the description, additional commit
limiting may be applied. Note that they are applied before commit
ordering and formatting options, such as '--reverse'.
limiting may be applied.
Using more options generally further limits the output (e.g.
`--since=<date1>` limits to commits newer than `<date1>`, and using it
with `--grep=<pattern>` further limits to commits whose log message
has a line that matches `<pattern>`), unless otherwise noted.
Note that these are applied before commit
ordering and formatting options, such as `--reverse`.
--
@ -39,16 +46,22 @@ endif::git-rev-list[]
--committer=<pattern>::
Limit the commits output to ones with author/committer
header lines that match the specified pattern (regular expression).
header lines that match the specified pattern (regular
expression). With more than one `--author=<pattern>`,
commits whose author matches any of the given patterns are
chosen (similarly for multiple `--committer=<pattern>`).
--grep=<pattern>::
Limit the commits output to ones with log message that
matches the specified pattern (regular expression).
matches the specified pattern (regular expression). With
more than one `--grep=<pattern>`, commits whose message
matches any of the given patterns are chosen (but see
`--all-match`).
--all-match::
Limit the commits output to ones that match all given --grep,
--author and --committer instead of ones that match at least one.
instead of ones that match at least one.
-i::
--regexp-ignore-case::

View File

@ -209,6 +209,7 @@ static void start_threads(struct grep_opt *opt)
int err;
struct grep_opt *o = grep_opt_dup(opt);
o->output = strbuf_out;
o->debug = 0;
compile_grep_patterns(o);
err = pthread_create(&threads[i], NULL, run, o);
@ -817,6 +818,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
N_("indicate hit with exit status without output")),
OPT_BOOLEAN(0, "all-match", &opt.all_match,
N_("show only matches from files that match all patterns")),
{ OPTION_SET_INT, 0, "debug", &opt.debug, NULL,
N_("show parse tree for grep expression"),
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1 },
OPT_GROUP(""),
{ OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager,
N_("pager"), N_("show matching files in the pager"),

115
grep.c
View File

@ -3,6 +3,10 @@
#include "userdiff.h"
#include "xdiff-interface.h"
static int grep_source_load(struct grep_source *gs);
static int grep_source_is_binary(struct grep_source *gs);
static struct grep_pat *create_grep_pat(const char *pat, size_t patlen,
const char *origin, int no,
enum grep_pat_token t,
@ -332,6 +336,87 @@ 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));
@ -395,7 +480,23 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
return header_expr;
}
void compile_grep_patterns(struct grep_opt *opt)
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);
@ -415,7 +516,7 @@ void compile_grep_patterns(struct grep_opt *opt)
if (opt->all_match || header_expr)
opt->extended = 1;
else if (!opt->extended)
else if (!opt->extended && !opt->debug)
return;
p = opt->pattern_list;
@ -429,12 +530,22 @@ void compile_grep_patterns(struct grep_opt *opt)
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) {

4
grep.h
View File

@ -98,6 +98,7 @@ struct grep_opt {
int word_regexp;
int fixed;
int all_match;
int debug;
#define GREP_BINARY_DEFAULT 0
#define GREP_BINARY_NOMATCH 1
#define GREP_BINARY_TEXT 2
@ -158,11 +159,10 @@ struct grep_source {
void grep_source_init(struct grep_source *gs, enum grep_source_type type,
const char *name, const void *identifier);
int grep_source_load(struct grep_source *gs);
void grep_source_clear_data(struct grep_source *gs);
void grep_source_clear(struct grep_source *gs);
void grep_source_load_driver(struct grep_source *gs);
int grep_source_is_binary(struct grep_source *gs);
int grep_source(struct grep_opt *opt, struct grep_source *gs);

View File

@ -1598,6 +1598,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if ((argcount = parse_long_opt("grep", argv, &optarg))) {
add_message_grep(revs, optarg);
return argcount;
} else if (!strcmp(arg, "--grep-debug")) {
revs->grep_filter.debug = 1;
} else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
revs->grep_filter.regflags |= REG_EXTENDED;
} else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {

View File

@ -502,31 +502,41 @@ test_expect_success 'log grep setup' '
test_expect_success 'log grep (1)' '
git log --author=author --pretty=tformat:%s >actual &&
( echo third ; echo initial ) >expect &&
{
echo third && echo initial
} >expect &&
test_cmp expect actual
'
test_expect_success 'log grep (2)' '
git log --author=" * " -F --pretty=tformat:%s >actual &&
( echo second ) >expect &&
{
echo second
} >expect &&
test_cmp expect actual
'
test_expect_success 'log grep (3)' '
git log --author="^A U" --pretty=tformat:%s >actual &&
( echo third ; echo initial ) >expect &&
{
echo third && echo initial
} >expect &&
test_cmp expect actual
'
test_expect_success 'log grep (4)' '
git log --author="frotz\.com>$" --pretty=tformat:%s >actual &&
( echo second ) >expect &&
{
echo second
} >expect &&
test_cmp expect actual
'
test_expect_success 'log grep (5)' '
git log --author=Thor -F --pretty=tformat:%s >actual &&
( echo third ; echo initial ) >expect &&
{
echo third && echo initial
} >expect &&
test_cmp expect actual
'
@ -536,11 +546,19 @@ test_expect_success 'log grep (6)' '
test_cmp expect actual
'
test_expect_success 'log --grep --author implicitly uses all-match' '
# grep matches initial and second but not third
# author matches only initial and third
git log --author="A U Thor" --grep=s --grep=l --format=%s >actual &&
echo initial >expect &&
test_expect_success 'log with multiple --grep uses union' '
git log --grep=i --grep=r --format=%s >actual &&
{
echo fourth && echo third && echo initial
} >expect &&
test_cmp expect actual
'
test_expect_success 'log --all-match with multiple --grep uses intersection' '
git log --all-match --grep=i --grep=r --format=%s >actual &&
{
echo third
} >expect &&
test_cmp expect actual
'
@ -552,7 +570,47 @@ test_expect_success 'log with multiple --author uses union' '
test_cmp expect actual
'
test_expect_success 'log with --grep and multiple --author uses all-match' '
test_expect_success 'log --all-match with multiple --author still uses union' '
git log --all-match --author="Thor" --author="Aster" --format=%s >actual &&
{
echo third && echo second && echo initial
} >expect &&
test_cmp expect actual
'
test_expect_success 'log --grep --author uses intersection' '
# grep matches only third and fourth
# author matches only initial and third
git log --author="A U Thor" --grep=r --format=%s >actual &&
{
echo third
} >expect &&
test_cmp expect actual
'
test_expect_success 'log --grep --grep --author takes union of greps and intersects with author' '
# grep matches initial and second but not third
# author matches only initial and third
git log --author="A U Thor" --grep=s --grep=l --format=%s >actual &&
{
echo initial
} >expect &&
test_cmp expect actual
'
test_expect_success 'log ---all-match -grep --author --author still takes union of authors and intersects with grep' '
# grep matches only initial and third
# author matches all but second
git log --all-match --author="Thor" --author="Night" --grep=i --format=%s >actual &&
{
echo third && echo initial
} >expect &&
test_cmp expect actual
'
test_expect_success 'log --grep --author --author takes union of authors and intersects with grep' '
# grep matches only initial and third
# author matches all but second
git log --author="Thor" --author="Night" --grep=i --format=%s >actual &&
{
echo third && echo initial
@ -560,9 +618,13 @@ test_expect_success 'log with --grep and multiple --author uses all-match' '
test_cmp expect actual
'
test_expect_success 'log with --grep and multiple --author uses all-match' '
git log --author="Thor" --author="Night" --grep=q --format=%s >actual &&
>expect &&
test_expect_success 'log --all-match --grep --grep --author takes intersection' '
# grep matches only third
# author matches only initial and third
git log --all-match --author="A U Thor" --grep=i --grep=r --format=%s >actual &&
{
echo third
} >expect &&
test_cmp expect actual
'