Skip to content

Commit 3d7535e

Browse files
committed
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
2 parents 06e211a + 39f2e01 commit 3d7535e

File tree

6 files changed

+215
-23
lines changed

6 files changed

+215
-23
lines changed

Documentation/rev-list-options.txt

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,15 @@ Commit Limiting
33

44
Besides specifying a range of commits that should be listed using the
55
special notations explained in the description, additional commit
6-
limiting may be applied. Note that they are applied before commit
7-
ordering and formatting options, such as '--reverse'.
6+
limiting may be applied.
7+
8+
Using more options generally further limits the output (e.g.
9+
`--since=<date1>` limits to commits newer than `<date1>`, and using it
10+
with `--grep=<pattern>` further limits to commits whose log message
11+
has a line that matches `<pattern>`), unless otherwise noted.
12+
13+
Note that these are applied before commit
14+
ordering and formatting options, such as `--reverse`.
815

916
--
1017

@@ -39,16 +46,22 @@ endif::git-rev-list[]
3946
--committer=<pattern>::
4047

4148
Limit the commits output to ones with author/committer
42-
header lines that match the specified pattern (regular expression).
49+
header lines that match the specified pattern (regular
50+
expression). With more than one `--author=<pattern>`,
51+
commits whose author matches any of the given patterns are
52+
chosen (similarly for multiple `--committer=<pattern>`).
4353

4454
--grep=<pattern>::
4555

4656
Limit the commits output to ones with log message that
47-
matches the specified pattern (regular expression).
57+
matches the specified pattern (regular expression). With
58+
more than one `--grep=<pattern>`, commits whose message
59+
matches any of the given patterns are chosen (but see
60+
`--all-match`).
4861

4962
--all-match::
5063
Limit the commits output to ones that match all given --grep,
51-
--author and --committer instead of ones that match at least one.
64+
instead of ones that match at least one.
5265

5366
-i::
5467
--regexp-ignore-case::

builtin/grep.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ static void start_threads(struct grep_opt *opt)
209209
int err;
210210
struct grep_opt *o = grep_opt_dup(opt);
211211
o->output = strbuf_out;
212+
o->debug = 0;
212213
compile_grep_patterns(o);
213214
err = pthread_create(&threads[i], NULL, run, o);
214215

@@ -817,6 +818,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
817818
N_("indicate hit with exit status without output")),
818819
OPT_BOOLEAN(0, "all-match", &opt.all_match,
819820
N_("show only matches from files that match all patterns")),
821+
{ OPTION_SET_INT, 0, "debug", &opt.debug, NULL,
822+
N_("show parse tree for grep expression"),
823+
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1 },
820824
OPT_GROUP(""),
821825
{ OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager,
822826
N_("pager"), N_("show matching files in the pager"),

grep.c

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
#include "userdiff.h"
44
#include "xdiff-interface.h"
55

6+
static int grep_source_load(struct grep_source *gs);
7+
static int grep_source_is_binary(struct grep_source *gs);
8+
9+
610
static struct grep_pat *create_grep_pat(const char *pat, size_t patlen,
711
const char *origin, int no,
812
enum grep_pat_token t,
@@ -332,6 +336,87 @@ static struct grep_expr *compile_pattern_expr(struct grep_pat **list)
332336
return compile_pattern_or(list);
333337
}
334338

339+
static void indent(int in)
340+
{
341+
while (in-- > 0)
342+
fputc(' ', stderr);
343+
}
344+
345+
static void dump_grep_pat(struct grep_pat *p)
346+
{
347+
switch (p->token) {
348+
case GREP_AND: fprintf(stderr, "*and*"); break;
349+
case GREP_OPEN_PAREN: fprintf(stderr, "*(*"); break;
350+
case GREP_CLOSE_PAREN: fprintf(stderr, "*)*"); break;
351+
case GREP_NOT: fprintf(stderr, "*not*"); break;
352+
case GREP_OR: fprintf(stderr, "*or*"); break;
353+
354+
case GREP_PATTERN: fprintf(stderr, "pattern"); break;
355+
case GREP_PATTERN_HEAD: fprintf(stderr, "pattern_head"); break;
356+
case GREP_PATTERN_BODY: fprintf(stderr, "pattern_body"); break;
357+
}
358+
359+
switch (p->token) {
360+
default: break;
361+
case GREP_PATTERN_HEAD:
362+
fprintf(stderr, "<head %d>", p->field); break;
363+
case GREP_PATTERN_BODY:
364+
fprintf(stderr, "<body>"); break;
365+
}
366+
switch (p->token) {
367+
default: break;
368+
case GREP_PATTERN_HEAD:
369+
case GREP_PATTERN_BODY:
370+
case GREP_PATTERN:
371+
fprintf(stderr, "%.*s", (int)p->patternlen, p->pattern);
372+
break;
373+
}
374+
fputc('\n', stderr);
375+
}
376+
377+
static void dump_grep_expression_1(struct grep_expr *x, int in)
378+
{
379+
indent(in);
380+
switch (x->node) {
381+
case GREP_NODE_TRUE:
382+
fprintf(stderr, "true\n");
383+
break;
384+
case GREP_NODE_ATOM:
385+
dump_grep_pat(x->u.atom);
386+
break;
387+
case GREP_NODE_NOT:
388+
fprintf(stderr, "(not\n");
389+
dump_grep_expression_1(x->u.unary, in+1);
390+
indent(in);
391+
fprintf(stderr, ")\n");
392+
break;
393+
case GREP_NODE_AND:
394+
fprintf(stderr, "(and\n");
395+
dump_grep_expression_1(x->u.binary.left, in+1);
396+
dump_grep_expression_1(x->u.binary.right, in+1);
397+
indent(in);
398+
fprintf(stderr, ")\n");
399+
break;
400+
case GREP_NODE_OR:
401+
fprintf(stderr, "(or\n");
402+
dump_grep_expression_1(x->u.binary.left, in+1);
403+
dump_grep_expression_1(x->u.binary.right, in+1);
404+
indent(in);
405+
fprintf(stderr, ")\n");
406+
break;
407+
}
408+
}
409+
410+
static void dump_grep_expression(struct grep_opt *opt)
411+
{
412+
struct grep_expr *x = opt->pattern_expression;
413+
414+
if (opt->all_match)
415+
fprintf(stderr, "[all-match]\n");
416+
dump_grep_expression_1(x, 0);
417+
fflush(NULL);
418+
}
419+
335420
static struct grep_expr *grep_true_expr(void)
336421
{
337422
struct grep_expr *z = xcalloc(1, sizeof(*z));
@@ -395,7 +480,23 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
395480
return header_expr;
396481
}
397482

398-
void compile_grep_patterns(struct grep_opt *opt)
483+
static struct grep_expr *grep_splice_or(struct grep_expr *x, struct grep_expr *y)
484+
{
485+
struct grep_expr *z = x;
486+
487+
while (x) {
488+
assert(x->node == GREP_NODE_OR);
489+
if (x->u.binary.right &&
490+
x->u.binary.right->node == GREP_NODE_TRUE) {
491+
x->u.binary.right = y;
492+
break;
493+
}
494+
x = x->u.binary.right;
495+
}
496+
return z;
497+
}
498+
499+
static void compile_grep_patterns_real(struct grep_opt *opt)
399500
{
400501
struct grep_pat *p;
401502
struct grep_expr *header_expr = prep_header_patterns(opt);
@@ -415,7 +516,7 @@ void compile_grep_patterns(struct grep_opt *opt)
415516

416517
if (opt->all_match || header_expr)
417518
opt->extended = 1;
418-
else if (!opt->extended)
519+
else if (!opt->extended && !opt->debug)
419520
return;
420521

421522
p = opt->pattern_list;
@@ -429,12 +530,22 @@ void compile_grep_patterns(struct grep_opt *opt)
429530

430531
if (!opt->pattern_expression)
431532
opt->pattern_expression = header_expr;
533+
else if (opt->all_match)
534+
opt->pattern_expression = grep_splice_or(header_expr,
535+
opt->pattern_expression);
432536
else
433537
opt->pattern_expression = grep_or_expr(opt->pattern_expression,
434538
header_expr);
435539
opt->all_match = 1;
436540
}
437541

542+
void compile_grep_patterns(struct grep_opt *opt)
543+
{
544+
compile_grep_patterns_real(opt);
545+
if (opt->debug)
546+
dump_grep_expression(opt);
547+
}
548+
438549
static void free_pattern_expr(struct grep_expr *x)
439550
{
440551
switch (x->node) {

grep.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ struct grep_opt {
9898
int word_regexp;
9999
int fixed;
100100
int all_match;
101+
int debug;
101102
#define GREP_BINARY_DEFAULT 0
102103
#define GREP_BINARY_NOMATCH 1
103104
#define GREP_BINARY_TEXT 2
@@ -158,11 +159,10 @@ struct grep_source {
158159

159160
void grep_source_init(struct grep_source *gs, enum grep_source_type type,
160161
const char *name, const void *identifier);
161-
int grep_source_load(struct grep_source *gs);
162162
void grep_source_clear_data(struct grep_source *gs);
163163
void grep_source_clear(struct grep_source *gs);
164164
void grep_source_load_driver(struct grep_source *gs);
165-
int grep_source_is_binary(struct grep_source *gs);
165+
166166

167167
int grep_source(struct grep_opt *opt, struct grep_source *gs);
168168

revision.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,6 +1598,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
15981598
} else if ((argcount = parse_long_opt("grep", argv, &optarg))) {
15991599
add_message_grep(revs, optarg);
16001600
return argcount;
1601+
} else if (!strcmp(arg, "--grep-debug")) {
1602+
revs->grep_filter.debug = 1;
16011603
} else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
16021604
revs->grep_filter.regflags |= REG_EXTENDED;
16031605
} else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {

t/t7810-grep.sh

Lines changed: 76 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -502,31 +502,41 @@ test_expect_success 'log grep setup' '
502502

503503
test_expect_success 'log grep (1)' '
504504
git log --author=author --pretty=tformat:%s >actual &&
505-
( echo third ; echo initial ) >expect &&
505+
{
506+
echo third && echo initial
507+
} >expect &&
506508
test_cmp expect actual
507509
'
508510

509511
test_expect_success 'log grep (2)' '
510512
git log --author=" * " -F --pretty=tformat:%s >actual &&
511-
( echo second ) >expect &&
513+
{
514+
echo second
515+
} >expect &&
512516
test_cmp expect actual
513517
'
514518

515519
test_expect_success 'log grep (3)' '
516520
git log --author="^A U" --pretty=tformat:%s >actual &&
517-
( echo third ; echo initial ) >expect &&
521+
{
522+
echo third && echo initial
523+
} >expect &&
518524
test_cmp expect actual
519525
'
520526

521527
test_expect_success 'log grep (4)' '
522528
git log --author="frotz\.com>$" --pretty=tformat:%s >actual &&
523-
( echo second ) >expect &&
529+
{
530+
echo second
531+
} >expect &&
524532
test_cmp expect actual
525533
'
526534

527535
test_expect_success 'log grep (5)' '
528536
git log --author=Thor -F --pretty=tformat:%s >actual &&
529-
( echo third ; echo initial ) >expect &&
537+
{
538+
echo third && echo initial
539+
} >expect &&
530540
test_cmp expect actual
531541
'
532542

@@ -536,11 +546,19 @@ test_expect_success 'log grep (6)' '
536546
test_cmp expect actual
537547
'
538548

539-
test_expect_success 'log --grep --author implicitly uses all-match' '
540-
# grep matches initial and second but not third
541-
# author matches only initial and third
542-
git log --author="A U Thor" --grep=s --grep=l --format=%s >actual &&
543-
echo initial >expect &&
549+
test_expect_success 'log with multiple --grep uses union' '
550+
git log --grep=i --grep=r --format=%s >actual &&
551+
{
552+
echo fourth && echo third && echo initial
553+
} >expect &&
554+
test_cmp expect actual
555+
'
556+
557+
test_expect_success 'log --all-match with multiple --grep uses intersection' '
558+
git log --all-match --grep=i --grep=r --format=%s >actual &&
559+
{
560+
echo third
561+
} >expect &&
544562
test_cmp expect actual
545563
'
546564

@@ -552,17 +570,61 @@ test_expect_success 'log with multiple --author uses union' '
552570
test_cmp expect actual
553571
'
554572

555-
test_expect_success 'log with --grep and multiple --author uses all-match' '
573+
test_expect_success 'log --all-match with multiple --author still uses union' '
574+
git log --all-match --author="Thor" --author="Aster" --format=%s >actual &&
575+
{
576+
echo third && echo second && echo initial
577+
} >expect &&
578+
test_cmp expect actual
579+
'
580+
581+
test_expect_success 'log --grep --author uses intersection' '
582+
# grep matches only third and fourth
583+
# author matches only initial and third
584+
git log --author="A U Thor" --grep=r --format=%s >actual &&
585+
{
586+
echo third
587+
} >expect &&
588+
test_cmp expect actual
589+
'
590+
591+
test_expect_success 'log --grep --grep --author takes union of greps and intersects with author' '
592+
# grep matches initial and second but not third
593+
# author matches only initial and third
594+
git log --author="A U Thor" --grep=s --grep=l --format=%s >actual &&
595+
{
596+
echo initial
597+
} >expect &&
598+
test_cmp expect actual
599+
'
600+
601+
test_expect_success 'log ---all-match -grep --author --author still takes union of authors and intersects with grep' '
602+
# grep matches only initial and third
603+
# author matches all but second
604+
git log --all-match --author="Thor" --author="Night" --grep=i --format=%s >actual &&
605+
{
606+
echo third && echo initial
607+
} >expect &&
608+
test_cmp expect actual
609+
'
610+
611+
test_expect_success 'log --grep --author --author takes union of authors and intersects with grep' '
612+
# grep matches only initial and third
613+
# author matches all but second
556614
git log --author="Thor" --author="Night" --grep=i --format=%s >actual &&
557615
{
558616
echo third && echo initial
559617
} >expect &&
560618
test_cmp expect actual
561619
'
562620

563-
test_expect_success 'log with --grep and multiple --author uses all-match' '
564-
git log --author="Thor" --author="Night" --grep=q --format=%s >actual &&
565-
>expect &&
621+
test_expect_success 'log --all-match --grep --grep --author takes intersection' '
622+
# grep matches only third
623+
# author matches only initial and third
624+
git log --all-match --author="A U Thor" --grep=i --grep=r --format=%s >actual &&
625+
{
626+
echo third
627+
} >expect &&
566628
test_cmp expect actual
567629
'
568630

0 commit comments

Comments
 (0)