log: teach --invert-grep option

"git log --grep=<string>" shows only commits with messages that match the given string, but sometimes it is useful to be able to show only commits that do *not* have certain messages (e.g. "show me ones that are not FIXUP commits"). Originally, we had the invert-grep flag in grep_opt, but because "git grep --invert-grep" does not make sense except in conjunction with "--files-with-matches", which is already covered by "--files-without-matches", it was moved it to revisions structure. To have the flag there expresses the function to the feature better. When the newly inserted two tests run, the history would have commits with messages "initial", "second", "third", "fourth", "fifth", "sixth" and "Second", committed in this order. The commits that does not match either "th" or "Sec" is "second" and "initial". For the case insensitive case only "initial" matches. Signed-off-by: Christoph Junghans <ottxor@gentoo.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Christoph Junghans committed Jan 12, 2015 at 18:33 UTC 22dfa8a23de4bbb274027736edd3bd311dda2981
5 files changed +25 -2
Documentation/rev-list-options.txt
+4
@@ -66,6 +66,10 @@ if it is part of the log message.
66 Limit the commits output to ones that match all given `--grep`,
67 instead of ones that match at least one.
68
69 +--invert-grep::
70 + Limit the commits output to ones with log message that do not
71 + match the pattern specified with `--grep=<pattern>`.
72 +
73 -i::
74 --regexp-ignore-case::
75 Match the regular expression limiting patterns without regard to letter
contrib/completion/git-completion.bash
+1 -1
@@ -1428,7 +1428,7 @@ __git_log_gitk_options="
1428 # Options that go well for log and shortlog (not gitk)
1429 __git_log_shortlog_options="
1430 --author= --committer= --grep=
1431 - --all-match
1431 + --all-match --invert-grep
1432 "
1433
1434 __git_log_pretty_formats="oneline short medium full fuller email raw format:"
revision.c
+3 -1
@@ -1952,6 +1952,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
1952 grep_set_pattern_type_option(GREP_PATTERN_TYPE_PCRE, &revs->grep_filter);
1953 } else if (!strcmp(arg, "--all-match")) {
1954 revs->grep_filter.all_match = 1;
1955 + } else if (!strcmp(arg, "--invert-grep")) {
1956 + revs->invert_grep = 1;
1957 } else if ((argcount = parse_long_opt("encoding", argv, &optarg))) {
1958 if (strcmp(optarg, "none"))
1959 git_log_output_encoding = xstrdup(optarg);
@@ -2848,7 +2850,7 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
2850 (char *)message, strlen(message));
2851 strbuf_release(&buf);
2852 unuse_commit_buffer(commit, message);
2851 - return retval;
2853 + return opt->invert_grep ? !retval : retval;
2854 }
2855
2856 static inline int want_ancestry(const struct rev_info *revs)
revision.h
+2
@@ -168,6 +168,8 @@ struct rev_info {
168
169 /* Filter by commit log message */
170 struct grep_opt grep_filter;
171 + /* Negate the match of grep_filter */
172 + int invert_grep;
173
174 /* Display history graph */
175 struct git_graph *graph;
t/t4202-log.sh
+15
@@ -212,6 +212,21 @@ test_expect_success 'log --grep' '
212 test_cmp expect actual
213 '
214
215 +cat > expect << EOF
216 +second
217 +initial
218 +EOF
219 +test_expect_success 'log --invert-grep --grep' '
220 + git log --pretty="tformat:%s" --invert-grep --grep=th --grep=Sec >actual &&
221 + test_cmp expect actual
222 +'
223 +
224 +test_expect_success 'log --invert-grep --grep -i' '
225 + echo initial >expect &&
226 + git log --pretty="tformat:%s" --invert-grep -i --grep=th --grep=Sec >actual &&
227 + test_cmp expect actual
228 +'
229 +
230 test_expect_success 'log --grep option parsing' '
231 echo second >expect &&
232 git log -1 --pretty="tformat:%s" --grep sec >actual &&