log: let --invert-grep only invert --grep

The option --invert-grep is documented to filter out commits whose messages match the --grep filters. However, it also affects the header matches (--author, --committer), which is not intended. Move the handling of that option to grep.c, as only the code there can distinguish between matches in the header from those in the message body. If --invert-grep is given then enable extended expressions (not the regex type, we just need git grep's --not to work), negate the body patterns and check if any of them match by piggy-backing on the collect_hits mechanism of grep_source_1(). Collecting the matches in struct grep_opt is a bit iffy, but with "last_shown" we have a precedent for writing state information to that struct. Reported-by: Dotan Cohen <dotancohen@gmail.com> Signed-off-by: René Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed Dec 17, 2021 at 17:48 UTC 794c000267b7bd29024b56e282509a82b31e6fc8
5 files changed +42 -7
grep.c
+19 -3
@@ -699,6 +699,14 @@ static struct grep_expr *compile_pattern_expr(struct grep_pat **list)
699 return compile_pattern_or(list);
700 }
701
702 +static struct grep_expr *grep_not_expr(struct grep_expr *expr)
703 +{
704 + struct grep_expr *z = xcalloc(1, sizeof(*z));
705 + z->node = GREP_NODE_NOT;
706 + z->u.unary = expr;
707 + return z;
708 +}
709 +
710 static struct grep_expr *grep_true_expr(void)
711 {
712 struct grep_expr *z = xcalloc(1, sizeof(*z));
@@ -797,7 +805,7 @@ void compile_grep_patterns(struct grep_opt *opt)
805 }
806 }
807
800 - if (opt->all_match || header_expr)
808 + if (opt->all_match || opt->no_body_match || header_expr)
809 opt->extended = 1;
810 else if (!opt->extended)
811 return;
@@ -808,6 +816,9 @@ void compile_grep_patterns(struct grep_opt *opt)
816 if (p)
817 die("incomplete pattern expression: %s", p->pattern);
818
819 + if (opt->no_body_match && opt->pattern_expression)
820 + opt->pattern_expression = grep_not_expr(opt->pattern_expression);
821 +
822 if (!header_expr)
823 return;
824
@@ -1057,6 +1068,8 @@ static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x,
1068 if (h && (*col < 0 || tmp.rm_so < *col))
1069 *col = tmp.rm_so;
1070 }
1071 + if (x->u.atom->token == GREP_PATTERN_BODY)
1072 + opt->body_hit |= h;
1073 break;
1074 case GREP_NODE_NOT:
1075 /*
@@ -1825,16 +1838,19 @@ int grep_source(struct grep_opt *opt, struct grep_source *gs)
1838 * we do not have to do the two-pass grep when we do not check
1839 * buffer-wide "all-match".
1840 */
1828 - if (!opt->all_match)
1841 + if (!opt->all_match && !opt->no_body_match)
1842 return grep_source_1(opt, gs, 0);
1843
1844 /* Otherwise the toplevel "or" terms hit a bit differently.
1845 * We first clear hit markers from them.
1846 */
1847 clr_hit_marker(opt->pattern_expression);
1848 + opt->body_hit = 0;
1849 grep_source_1(opt, gs, 1);
1850
1837 - if (!chk_hit_marker(opt->pattern_expression))
1851 + if (opt->all_match && !chk_hit_marker(opt->pattern_expression))
1852 + return 0;
1853 + if (opt->no_body_match && opt->body_hit)
1854 return 0;
1855
1856 return grep_source_1(opt, gs, 0);
grep.h
+2
@@ -148,6 +148,8 @@ struct grep_opt {
148 int word_regexp;
149 int fixed;
150 int all_match;
151 + int no_body_match;
152 + int body_hit;
153 #define GREP_BINARY_DEFAULT 0
154 #define GREP_BINARY_NOMATCH 1
155 #define GREP_BINARY_TEXT 2
revision.c
+2 -2
@@ -2493,7 +2493,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
2493 } else if (!strcmp(arg, "--all-match")) {
2494 revs->grep_filter.all_match = 1;
2495 } else if (!strcmp(arg, "--invert-grep")) {
2496 - revs->invert_grep = 1;
2496 + revs->grep_filter.no_body_match = 1;
2497 } else if ((argcount = parse_long_opt("encoding", argv, &optarg))) {
2498 if (strcmp(optarg, "none"))
2499 git_log_output_encoding = xstrdup(optarg);
@@ -3778,7 +3778,7 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
3778 (char *)message, strlen(message));
3779 strbuf_release(&buf);
3780 unuse_commit_buffer(commit, message);
3781 - return opt->invert_grep ? !retval : retval;
3781 + return retval;
3782 }
3783
3784 static inline int want_ancestry(const struct rev_info *revs)
revision.h
-2
@@ -246,8 +246,6 @@ struct rev_info {
246
247 /* Filter by commit log message */
248 struct grep_opt grep_filter;
249 - /* Negate the match of grep_filter */
250 - int invert_grep;
249
250 /* Display history graph */
251 struct git_graph *graph;
t/t4202-log.sh
+19
@@ -2010,4 +2010,23 @@ test_expect_success 'log --end-of-options' '
2010 test_cmp expect actual
2011 '
2012
2013 +test_expect_success 'set up commits with different authors' '
2014 + git checkout --orphan authors &&
2015 + test_commit --author "Jim <jim@example.com>" jim_1 &&
2016 + test_commit --author "Val <val@example.com>" val_1 &&
2017 + test_commit --author "Val <val@example.com>" val_2 &&
2018 + test_commit --author "Jim <jim@example.com>" jim_2 &&
2019 + test_commit --author "Val <val@example.com>" val_3 &&
2020 + test_commit --author "Jim <jim@example.com>" jim_3
2021 +'
2022 +
2023 +test_expect_success 'log --invert-grep --grep --author' '
2024 + cat >expect <<-\EOF &&
2025 + val_3
2026 + val_1
2027 + EOF
2028 + git log --format=%s --author=Val --grep 2 --invert-grep >actual &&
2029 + test_cmp expect actual
2030 +'
2031 +
2032 test_done