Re: [PATCH] git-log: added --invert-grep option

4 messages, 2 authors, 2016-06-15 · open the first message on its own page

Re: [PATCH] git-log: added --invert-grep option

From: Junio C Hamano <hidden>
Date: 2016-06-15 23:03:18

Christoph Junghans [off-list ref] writes:
Implements a inverted match for "git log", like in the case of
"git grep -v", which is useful from time to time to e.g. filter
FIXUP message out of "git log".

Internally, a new bol 'global_invert' has been introduces as
revs->grep_filter.invert inverts the match line-wise, which cannot
work as i.e. empty line always not match the pattern given.
While I am very sympathetic to those who feel the pain, i.e. the
need for something like this, I do not think this patch takes the
right approach.

The pain is that "git log --grep=..." can express only a limited
subset of what "git grep" can express.  The latter supports a very
rich set of logical operations, with operators like --and and --not,
and grouping, e.g. "git grep \( -e foo --or -e bar \) --and --not -e
baz" (i.e. "has either foo or bar but not baz").  It also can turn
the list of top-level predicates into "all of these predicates must
match somewhere in the entire file" with --all-match option.  None
of this richness is available to "git log --grep=...".

The root cause of the pain comes from the fact that it needs to
share the command line parsing with the revision list commands to
drive the underlying "grep" machinery, so you cannot say

    git log --not --grep=FIXUP

because "--not" is taken as "commits reachable from revs listed after
this point are to be excluded from the result" and not passed to the
underlying grep machinery.

The right way to do this is to somehow find a way to allow you to
express the full "grep" logical operations to the command line
parser that is used by the "log" family of commands.  That would
allow you to express something like "Show only commits that has
either foo or bar and does not have baz".  I am not going to
advocate this exact syntax, but to illustrate the idea, if you had
something like this supported:

    git log --grep-begin \
    	\( -e foo --or -e bar \) --and --not -e baz \
        --grep-end

by stopping the revision.c parser between --grep-{begin,end} and
instead feeding the arguments to the grep expression builder, we may
be able to get the full expressiveness of the logical operations
offered by the grep machinery.

And you shouldn't need to add any new field to grep_opt for that.
All you need is a design of a new syntax and a tweak to the revision
argument parser to understand the new syntax to redirect some
arguments to the grep command line parser.

A new option that allows you to _only_ negate without allowing you
to enable other richer logical operations of the underlying grep
machinery is going in a wrong direction, isn't it?
quoted hunk
Signed-off-by: Christoph Junghans <redacted>
---
 Documentation/rev-list-options.txt     | 4 ++++
 contrib/completion/git-completion.bash | 2 +-
 grep.h                                 | 3 ++-
 revision.c                             | 4 +++-
 4 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index afccfdc..6d4671f 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 2fece98..914c317 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1425,7 +1425,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/grep.h b/grep.h
index 95f197a..c137103 100644
--- a/grep.h
+++ b/grep.h
@@ -93,7 +93,8 @@ struct grep_opt {
 	int prefix_length;
 	regex_t regexp;
 	int linenum;
-	int invert;
+	int invert; /** line-wise invert match */
+	int global_invert; /** final global invert match */
 	int ignore_case;
 	int status_only;
 	int name_only;
diff --git a/revision.c b/revision.c
index 75dda92..c8d4c49 100644
--- a/revision.c
+++ b/revision.c
@@ -2011,6 +2011,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->grep_filter.global_invert = 1;
 	} else if ((argcount = parse_long_opt("encoding", argv, &optarg))) {
 		if (strcmp(optarg, "none"))
 			git_log_output_encoding = xstrdup(optarg);
@@ -2909,7 +2911,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->grep_filter.global_invert ? !retval : retval;
 }
 
 static inline int want_ancestry(const struct rev_info *revs)

Re: [PATCH] git-log: added --invert-grep option

From: Christoph Junghans <hidden>
Date: 2016-06-15 23:03:21

Ok, I drafted a first version of the suggest --grep-begin ...
--grep-end syntax.

However, I could not find a good ways to invert the match on a commit
basis instead of the normal line-wise version. Any suggestions?

[PATCH] git-log: added --grep-begin .. --grep-end syntax

From: Christoph Junghans <hidden>
Date: 2016-06-15 23:03:21

This is useful to specify more complicated pattern as with '--grep'.

Signed-off-by: Christoph Junghans <redacted>
---
 builtin/grep.c | 73 +++++-----------------------------------------------------
 grep.c         | 62 +++++++++++++++++++++++++++++++++++++++++++++++++
 grep.h         | 10 ++++++++
 revision.c     | 56 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 134 insertions(+), 67 deletions(-)
diff --git a/builtin/grep.c b/builtin/grep.c
index 4063882..0127fa0 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -551,67 +551,6 @@ static int context_callback(const struct option *opt, const char *arg,
 	return 0;
 }
 
-static int file_callback(const struct option *opt, const char *arg, int unset)
-{
-	struct grep_opt *grep_opt = opt->value;
-	int from_stdin = !strcmp(arg, "-");
-	FILE *patterns;
-	int lno = 0;
-	struct strbuf sb = STRBUF_INIT;
-
-	patterns = from_stdin ? stdin : fopen(arg, "r");
-	if (!patterns)
-		die_errno(_("cannot open '%s'"), arg);
-	while (strbuf_getline(&sb, patterns, '\n') == 0) {
-		/* ignore empty line like grep does */
-		if (sb.len == 0)
-			continue;
-
-		append_grep_pat(grep_opt, sb.buf, sb.len, arg, ++lno,
-				GREP_PATTERN);
-	}
-	if (!from_stdin)
-		fclose(patterns);
-	strbuf_release(&sb);
-	return 0;
-}
-
-static int not_callback(const struct option *opt, const char *arg, int unset)
-{
-	struct grep_opt *grep_opt = opt->value;
-	append_grep_pattern(grep_opt, "--not", "command line", 0, GREP_NOT);
-	return 0;
-}
-
-static int and_callback(const struct option *opt, const char *arg, int unset)
-{
-	struct grep_opt *grep_opt = opt->value;
-	append_grep_pattern(grep_opt, "--and", "command line", 0, GREP_AND);
-	return 0;
-}
-
-static int open_callback(const struct option *opt, const char *arg, int unset)
-{
-	struct grep_opt *grep_opt = opt->value;
-	append_grep_pattern(grep_opt, "(", "command line", 0, GREP_OPEN_PAREN);
-	return 0;
-}
-
-static int close_callback(const struct option *opt, const char *arg, int unset)
-{
-	struct grep_opt *grep_opt = opt->value;
-	append_grep_pattern(grep_opt, ")", "command line", 0, GREP_CLOSE_PAREN);
-	return 0;
-}
-
-static int pattern_callback(const struct option *opt, const char *arg,
-			    int unset)
-{
-	struct grep_opt *grep_opt = opt->value;
-	append_grep_pattern(grep_opt, arg, "-e option", 0, GREP_PATTERN);
-	return 0;
-}
-
 static int help_callback(const struct option *opt, const char *arg, int unset)
 {
 	return -1;
@@ -710,21 +649,21 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
 			N_("show the surrounding function")),
 		OPT_GROUP(""),
 		OPT_CALLBACK('f', NULL, &opt, N_("file"),
-			N_("read patterns from file"), file_callback),
+			N_("read patterns from file"), grep_file_callback),
 		{ OPTION_CALLBACK, 'e', NULL, &opt, N_("pattern"),
-			N_("match <pattern>"), PARSE_OPT_NONEG, pattern_callback },
+			N_("match <pattern>"), PARSE_OPT_NONEG, grep_pattern_callback },
 		{ OPTION_CALLBACK, 0, "and", &opt, NULL,
 		  N_("combine patterns specified with -e"),
-		  PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback },
+		  PARSE_OPT_NOARG | PARSE_OPT_NONEG, grep_and_callback },
 		OPT_BOOL(0, "or", &dummy, ""),
 		{ OPTION_CALLBACK, 0, "not", &opt, NULL, "",
-		  PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback },
+		  PARSE_OPT_NOARG | PARSE_OPT_NONEG, grep_not_callback },
 		{ OPTION_CALLBACK, '(', NULL, &opt, NULL, "",
 		  PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
-		  open_callback },
+		  grep_open_callback },
 		{ OPTION_CALLBACK, ')', NULL, &opt, NULL, "",
 		  PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
-		  close_callback },
+		  grep_close_callback },
 		OPT__QUIET(&opt.status_only,
 			   N_("indicate hit with exit status without output")),
 		OPT_BOOL(0, "all-match", &opt.all_match,
diff --git a/grep.c b/grep.c
index 6e085f8..0c9a977 100644
--- a/grep.c
+++ b/grep.c
@@ -1796,3 +1796,65 @@ static int grep_source_is_binary(struct grep_source *gs)
 
 	return 0;
 }
+
+int grep_file_callback(const struct option *opt, const char *arg, int unset)
+{
+	struct grep_opt *grep_opt = opt->value;
+	int from_stdin = !strcmp(arg, "-");
+	FILE *patterns;
+	int lno = 0;
+	struct strbuf sb = STRBUF_INIT;
+
+	patterns = from_stdin ? stdin : fopen(arg, "r");
+	if (!patterns)
+		die_errno(_("cannot open '%s'"), arg);
+	while (strbuf_getline(&sb, patterns, '\n') == 0) {
+		/* ignore empty line like grep does */
+		if (sb.len == 0)
+			continue;
+
+		append_grep_pat(grep_opt, sb.buf, sb.len, arg, ++lno,
+				GREP_PATTERN);
+	}
+	if (!from_stdin)
+		fclose(patterns);
+	strbuf_release(&sb);
+	return 0;
+}
+
+int grep_not_callback(const struct option *opt, const char *arg, int unset)
+{
+	struct grep_opt *grep_opt = opt->value;
+	append_grep_pattern(grep_opt, "--not", "command line", 0, GREP_NOT);
+	return 0;
+}
+
+int grep_and_callback(const struct option *opt, const char *arg, int unset)
+{
+	struct grep_opt *grep_opt = opt->value;
+	append_grep_pattern(grep_opt, "--and", "command line", 0, GREP_AND);
+	return 0;
+}
+
+int grep_open_callback(const struct option *opt, const char *arg, int unset)
+{
+	struct grep_opt *grep_opt = opt->value;
+	append_grep_pattern(grep_opt, "(", "command line", 0, GREP_OPEN_PAREN);
+	return 0;
+}
+
+int grep_close_callback(const struct option *opt, const char *arg, int unset)
+{
+	struct grep_opt *grep_opt = opt->value;
+	append_grep_pattern(grep_opt, ")", "command line", 0, GREP_CLOSE_PAREN);
+	return 0;
+}
+
+int grep_pattern_callback(const struct option *opt, const char *arg,
+			    int unset)
+{
+	struct grep_opt *grep_opt = opt->value;
+	append_grep_pattern(grep_opt, arg, "-e option", 0, GREP_PATTERN);
+	return 0;
+}
+
diff --git a/grep.h b/grep.h
index 95f197a..d85fdb4 100644
--- a/grep.h
+++ b/grep.h
@@ -10,6 +10,7 @@ typedef int pcre_extra;
 #include "kwset.h"
 #include "thread-utils.h"
 #include "userdiff.h"
+#include "parse-options.h"
 
 enum grep_pat_token {
 	GREP_PATTERN,
@@ -181,6 +182,15 @@ void grep_source_load_driver(struct grep_source *gs);
 
 int grep_source(struct grep_opt *opt, struct grep_source *gs);
 
+
+int grep_file_callback(const struct option *opt, const char *arg, int unset);
+int grep_not_callback(const struct option *opt, const char *arg, int unset);
+int grep_and_callback(const struct option *opt, const char *arg, int unset);
+int grep_open_callback(const struct option *opt, const char *arg, int unset);
+int grep_close_callback(const struct option *opt, const char *arg, int unset);
+int grep_pattern_callback(const struct option *opt, const char *arg, int unset);
+
+
 extern struct grep_opt *grep_opt_dup(const struct grep_opt *opt);
 extern int grep_threads_ok(const struct grep_opt *opt);
 
diff --git a/revision.c b/revision.c
index 75dda92..4fe9085 100644
--- a/revision.c
+++ b/revision.c
@@ -1998,6 +1998,62 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 		return argcount;
 	} else if (!strcmp(arg, "--grep-debug")) {
 		revs->grep_filter.debug = 1;
+	} else if (!strcmp(arg, "--grep-begin")) {
+		if (argc <= 1)
+			return error("--grep-begin requires an argument");
+		int dummy;
+		struct option options[] = {
+			OPT_CALLBACK('f', NULL, &revs->grep_filter, N_("file"),
+				N_("read patterns from file"), grep_file_callback),
+			{ OPTION_CALLBACK, 'e', NULL, &revs->grep_filter, N_("pattern"),
+				N_("match <pattern>"), PARSE_OPT_NONEG, grep_pattern_callback },
+			{ OPTION_CALLBACK, 0, "and", &revs->grep_filter, NULL,
+			  N_("combine patterns specified with -e"),
+			  PARSE_OPT_NOARG | PARSE_OPT_NONEG, grep_and_callback },
+			OPT_BOOL(0, "or", &dummy, ""),
+			{ OPTION_CALLBACK, 0, "not", &revs->grep_filter, NULL, "",
+			  PARSE_OPT_NOARG | PARSE_OPT_NONEG, grep_not_callback },
+			{ OPTION_CALLBACK, '(', NULL, &revs->grep_filter, NULL, "",
+			  PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
+			  grep_open_callback },
+			{ OPTION_CALLBACK, ')', NULL, &revs->grep_filter, NULL, "",
+			  PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
+			  grep_close_callback },
+			OPT_END()
+		};
+		char const * const grep_usage[] = {
+			N_("git log [log-options] --begin-grep [grep-options] --end-grep ..."), 
+			NULL 
+		};
+		struct parse_opt_ctx_t ctx;
+
+		parse_options_start(&ctx, argc, argv, revs->prefix, options,
+				PARSE_OPT_STOP_AT_NON_OPTION | PARSE_OPT_NO_INTERNAL_HELP);
+		if (parse_options_step(&ctx, options, grep_usage) != PARSE_OPT_UNKNOWN) {
+			error("--grep-end expected");
+			usage_with_options(grep_usage, options);
+		}
+		if(strcmp(ctx.argv[0], "--grep-end")) {
+			if (ctx.argv[0][1] == '-') {
+			error("unknown option `%s'", ctx.argv[0] + 2);
+			} else if (isascii(*ctx.opt)) {
+				error("unknown switch `%c'", *ctx.opt);
+			} else {
+			error("unknown non-ascii option in string: `%s'",
+			      ctx.argv[0]);
+			}	
+			usage_with_options(grep_usage, options);
+		}
+
+		precompose_argv(argc, argv);
+		argcount = argc + 1 - parse_options_end(&ctx);
+		if (argcount == 2 ) {
+			return error("There should be options between --grep-begin and --grep-end ;-)");
+		}
+
+		grep_commit_pattern_type(GREP_PATTERN_TYPE_UNSPECIFIED, &revs->grep_filter);
+
+		return argcount;
 	} else if (!strcmp(arg, "--basic-regexp")) {
 		grep_set_pattern_type_option(GREP_PATTERN_TYPE_BRE, &revs->grep_filter);
 	} else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
-- 
2.0.5

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

From: Christoph Junghans <hidden>
Date: 2016-06-15 23:03:23

Implements a inverted match for "git log", like in the case of
"git grep -v", which is useful from time to time to e.g. filter
FIXUP message out of "git log".

Internally, a new bol 'none_match' has been introduces as
revs->grep_filter.invert inverts the match line-wise, which cannot
work as i.e. empty line always not match the pattern given.

Signed-off-by: Christoph Junghans <redacted>
---
 Documentation/rev-list-options.txt     | 4 ++++
 contrib/completion/git-completion.bash | 2 +-
 grep.c                                 | 2 ++
 grep.h                                 | 1 +
 revision.c                             | 4 ++++
 5 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index afccfdc..08e4ed8 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.
 
+--none-match::
+	Limit the commits output to ones that do not match any of the 
+	given `--grep`, instead of ones that match at least one.
+
 -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 23988ec..b0720e9 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1425,7 +1425,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 --none-match
 "
 
 __git_log_pretty_formats="oneline short medium full fuller email raw format:"
diff --git a/grep.c b/grep.c
index 6e085f8..eadf8d9 100644
--- a/grep.c
+++ b/grep.c
@@ -1622,6 +1622,8 @@ static int chk_hit_marker(struct grep_expr *x)
 
 int grep_source(struct grep_opt *opt, struct grep_source *gs)
 {
+  	if(opt->none_match)
+		return !grep_source_1(opt, gs, 0);	
 	/*
 	 * we do not have to do the two-pass grep when we do not check
 	 * buffer-wide "all-match".
diff --git a/grep.h b/grep.h
index 95f197a..8e50c95 100644
--- a/grep.h
+++ b/grep.h
@@ -102,6 +102,7 @@ struct grep_opt {
 	int word_regexp;
 	int fixed;
 	int all_match;
+	int none_match;
 	int debug;
 #define GREP_BINARY_DEFAULT	0
 #define GREP_BINARY_NOMATCH	1
diff --git a/revision.c b/revision.c
index 75dda92..d43779e 100644
--- a/revision.c
+++ b/revision.c
@@ -2011,6 +2011,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, "--none-match")) {
+		revs->grep_filter.none_match = 1;
 	} else if ((argcount = parse_long_opt("encoding", argv, &optarg))) {
 		if (strcmp(optarg, "none"))
 			git_log_output_encoding = xstrdup(optarg);
@@ -2333,6 +2335,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 		die("cannot combine --walk-reflogs with --graph");
 	if (!revs->reflog_info && revs->grep_filter.use_reflog_filter)
 		die("cannot use --grep-reflog without --walk-reflogs");
+	if (revs->grep_filter.all_match && revs->grep_filter.none_match)
+		die("cannot combine --all-match with --none-match");
 
 	return left;
 }
-- 
2.0.5
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help