Subject: Re: [PATCH] git-log: added --none-match option

From: Junio C Hamano <[email-masked]>
Date: Mon, 12 Jan 2015 12:51:38 -0800

Junio C Hamano <[email-masked]> writes:

> Christoph Junghans <[email-masked]> writes:
>
>> The only useful thing I could image is using it in conjunction with
>> --files-with-matches, but that is what --files-without-match is for.
>
> Yes, "-l" was exactly what I had in mind and I was hoping that "git
> grep -l --no-match -e WIP -e TODO -e FIXME -e NEEDSWORK" may be a
> way to find perfect files without needing any work.
>
> You can say "git grep -L -e WIP -e TODO -e FIXME -e NEEDSWORK"
> instead.  I missed that "-L" option.

Thanks for your patience.  I should have realized that this not only
can be "log-only" but _should_ be "log-only".  As you pointed out,
when we already have "-L", trying to extend it to "grep" does not
make much sense.

Continuing this line of thought, as we determined that it is
pointless to have this at "grep" level and it is needed only in the
"log" family of commands, I would very much prefer the approach
taken by your original "log --invert-grep" patch.  I would further
say that I prefer not to touch grep_opt at all.

The new "global-invert bit" is about "we'd run the usual grep thing
on the log message, and instead of filtering to only show the
commits with matching message, we only show the ones with messages
that do not match".  That logically belongs to the revision struct
that is used to interpret what the underlying grep machinery figured
out, and should not have to affect the way how underlying grep
machinery works.

We would want a few test to make sure that we do not break the
feature in the future changes.  Here is an attempt.  The patch would
apply any commit your original "--invert-grep" option would have
applied.

Thanks again.

-- >8 --
Subject: 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").

Signed-off-by: Christoph Junghans <[email-masked]>
Signed-off-by: Junio C Hamano <[email-masked]>
---

 jc: Christoph originally 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", I moved it to revisions
     structure.  I think it expresses what the feature is about
     better to have the flag there.

     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
     first commit that does not match either "th" or "Sec" is
     "second", and "initial" is the one that does not match either
     "th" or "Sec" case insensitively.

 Documentation/rev-list-options.txt     |  4 ++++
 contrib/completion/git-completion.bash |  2 +-
 revision.c                             |  4 +++-
 revision.h                             |  2 ++
 t/t4202-log.sh                         | 12 ++++++++++++
 5 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index deb8cca..05aa997 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -66,6 +66,10 @@ if it is part of the log message.
 	Limit the commits output to ones that match all given `--grep`,
 	instead of ones that match at least one.
 
+--invert-grep::
+	Limit the commits output to ones with log message that do not
+	match the pattern specified with `--grep=<pattern>`.
+
 -i::
 --regexp-ignore-case::
 	Match the regular expression limiting patterns without regard to letter
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 06bf262..53857f0 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1428,7 +1428,7 @@ __git_log_gitk_options="
 # Options that go well for log and shortlog (not gitk)
 __git_log_shortlog_options="
 	--author= --committer= --grep=
-	--all-match
+	--all-match --invert-grep
 "
 
 __git_log_pretty_formats="oneline short medium full fuller email raw format:"
diff --git a/revision.c b/revision.c
index 615535c..84b33a3 100644
--- a/revision.c
+++ b/revision.c
@@ -1952,6 +1952,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 		grep_set_pattern_type_option(GREP_PATTERN_TYPE_PCRE, &revs->grep_filter);
 	} else if (!strcmp(arg, "--all-match")) {
 		revs->grep_filter.all_match = 1;
+	} else if (!strcmp(arg, "--invert-grep")) {
+		revs->invert_grep = 1;
 	} else if ((argcount = parse_long_opt("encoding", argv, &optarg))) {
 		if (strcmp(optarg, "none"))
 			git_log_output_encoding = xstrdup(optarg);
@@ -2848,7 +2850,7 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
 				     (char *)message, strlen(message));
 	strbuf_release(&buf);
 	unuse_commit_buffer(commit, message);
-	return retval;
+	return opt->invert_grep ? !retval : retval;
 }
 
 static inline int want_ancestry(const struct rev_info *revs)
diff --git a/revision.h b/revision.h
index a620530..b0b82e7 100644
--- a/revision.h
+++ b/revision.h
@@ -168,6 +168,8 @@ struct rev_info {
 
 	/* Filter by commit log message */
 	struct grep_opt	grep_filter;
+	/* Negate the match of grep_filter */
+	int invert_grep;
 
 	/* Display history graph */
 	struct git_graph *graph;
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 99ab7ca..1c9934e 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -212,6 +212,18 @@ test_expect_success 'log --grep' '
 	test_cmp expect actual
 '
 
+test_expect_success 'log --invert-grep --grep' '
+	echo second >expect &&
+	git log -1 --pretty="tformat:%s" --invert-grep --grep=th --grep=Sec >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'log --invert-grep --grep -i' '
+	echo initial >expect &&
+	git log -1 --pretty="tformat:%s" --invert-grep -i --grep=th --grep=Sec >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'log --grep option parsing' '
 	echo second >expect &&
 	git log -1 --pretty="tformat:%s" --grep sec >actual &&