{"thread":{"id":"8915","subject":"[PATCH] revision: allow selection of commits that do not match a pattern","startedAt":"2007-07-07T15:30:01Z","lastAt":"2007-07-11T17:42:39Z","messageCount":11,"participants":["Sven Verdoolaege","Johannes Schindelin","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"46690","messageId":"20070707153001.GA10408MdfPADPa@greensroom.kotnet.org","threadId":"8915","inReplyTo":null,"subject":"[PATCH] revision: allow selection of commits that do not match a pattern","fromName":"Sven Verdoolaege","fromEmail":"skimo@kotnet.org","sentAt":"2007-07-07T15:30:01Z","receivedAt":"2007-07-07T15:30:01Z","isPatch":true,"sender":{"key":"skimo@kotnet.org","avatar":null},"body":"We do this by maintaining two lists of patterns, one for\nthose that should match and one for those that should not match.\n\nSigned-off-by: Sven Verdoolaege <skimo@kotnet.org>\n---\n Documentation/git-rev-list.txt |    7 ++++-\n revision.c                     |   57 ++++++++++++++++++++++++++++-----------\n revision.h                     |    1 +\n 3 files changed, 48 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt\nindex 20dcac6..22bfd60 100644\n--- a/Documentation/git-rev-list.txt\n+++ b/Documentation/git-rev-list.txt\n@@ -214,11 +214,15 @@ limiting may be applied.\n \n \tLimit the commits output to ones with author/committer\n \theader lines that match the specified pattern (regular expression).\n+\tIf this option is preceded by an odd number of --not options,\n+\tthen only commits that do not match will be shown.\n \n --grep='pattern'::\n \n \tLimit the commits output to ones with log message that\n \tmatches the specified pattern (regular expression).\n+\tIf this option is preceded by an odd number of --not options,\n+\tthen only commits that do not match will be shown.\n \n --regexp-ignore-case::\n \n@@ -248,7 +252,8 @@ limiting may be applied.\n --not::\n \n \tReverses the meaning of the '{caret}' prefix (or lack thereof)\n-\tfor all following revision specifiers, up to the next '--not'.\n+\tfor all following revision specifiers as well as the result\n+\tof matching a pattern, up to the next '--not'.\n \n --all::\n \ndiff --git a/revision.c b/revision.c\nindex 5184716..4bf9a0b 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -821,20 +821,30 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,\n \treturn 0;\n }\n \n-static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)\n+static void add_grep_to_filter(struct grep_opt **filter, const char *ptn,\n+\t\t\t\tenum grep_pat_token what)\n {\n-\tif (!revs->grep_filter) {\n+\tif (!*filter) {\n \t\tstruct grep_opt *opt = xcalloc(1, sizeof(*opt));\n \t\topt->status_only = 1;\n \t\topt->pattern_tail = &(opt->pattern_list);\n \t\topt->regflags = REG_NEWLINE;\n-\t\trevs->grep_filter = opt;\n+\t\t*filter = opt;\n \t}\n-\tappend_grep_pattern(revs->grep_filter, ptn,\n-\t\t\t    \"command line\", 0, what);\n+\tappend_grep_pattern(*filter, ptn, \"command line\", 0, what);\n }\n \n-static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern)\n+static void add_grep(struct rev_info *revs, const char *ptn,\n+\t\t    enum grep_pat_token what, int not)\n+{\n+\tif (not)\n+\t\tadd_grep_to_filter(&revs->grep_neg_filter, ptn, what);\n+\telse\n+\t\tadd_grep_to_filter(&revs->grep_filter, ptn, what);\n+}\n+\n+static void add_header_grep(struct rev_info *revs, const char *field,\n+\t\t\t    const char *pattern, int not)\n {\n \tchar *pat;\n \tconst char *prefix;\n@@ -849,12 +859,14 @@ static void add_header_grep(struct rev_info *revs, const char *field, const char\n \t\tpattern++;\n \t}\n \tsprintf(pat, \"^%s %s%s\", field, prefix, pattern);\n-\tadd_grep(revs, pat, GREP_PATTERN_HEAD);\n+\tfprintf(stderr, \"not: %d\\n\", not);\n+\tadd_grep(revs, pat, GREP_PATTERN_HEAD, not);\n }\n \n-static void add_message_grep(struct rev_info *revs, const char *pattern)\n+static void add_message_grep(struct rev_info *revs, const char *pattern,\n+\t\t\t\tint not)\n {\n-\tadd_grep(revs, pattern, GREP_PATTERN_BODY);\n+\tadd_grep(revs, pattern, GREP_PATTERN_BODY, not);\n }\n \n static void add_ignore_packed(struct rev_info *revs, const char *name)\n@@ -1141,15 +1153,18 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n \t\t\t * Grepping the commit log\n \t\t\t */\n \t\t\tif (!prefixcmp(arg, \"--author=\")) {\n-\t\t\t\tadd_header_grep(revs, \"author\", arg+9);\n+\t\t\t\tadd_header_grep(revs, \"author\", arg+9,\n+\t\t\t\t\t\tflags & UNINTERESTING);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!prefixcmp(arg, \"--committer=\")) {\n-\t\t\t\tadd_header_grep(revs, \"committer\", arg+12);\n+\t\t\t\tadd_header_grep(revs, \"committer\", arg+12,\n+\t\t\t\t\t\tflags & UNINTERESTING);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!prefixcmp(arg, \"--grep=\")) {\n-\t\t\t\tadd_message_grep(revs, arg+7);\n+\t\t\t\tadd_message_grep(revs, arg+7,\n+\t\t\t\t\t\tflags & UNINTERESTING);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!prefixcmp(arg, \"--extended-regexp\")) {\n@@ -1212,6 +1227,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n \tif (revs->grep_filter)\n \t\trevs->grep_filter->regflags |= regflags;\n \n+\tif (revs->grep_neg_filter)\n+\t\trevs->grep_neg_filter->regflags |= regflags;\n+\n \tif (show_merge)\n \t\tprepare_show_merge(revs);\n \tif (def && !revs->pending.nr) {\n@@ -1249,6 +1267,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n \t\tcompile_grep_patterns(revs->grep_filter);\n \t}\n \n+\tif (revs->grep_neg_filter) {\n+\t\tcompile_grep_patterns(revs->grep_neg_filter);\n+\t}\n+\n \treturn left;\n }\n \n@@ -1329,11 +1351,14 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit)\n \n static int commit_match(struct commit *commit, struct rev_info *opt)\n {\n-\tif (!opt->grep_filter)\n-\t\treturn 1;\n-\treturn grep_buffer(opt->grep_filter,\n+\treturn (!opt->grep_filter ||\n+\t\tgrep_buffer(opt->grep_filter,\n+\t\t\t   NULL, /* we say nothing, not even filename */\n+\t\t\t   commit->buffer, strlen(commit->buffer))) &&\n+\t       (!opt->grep_neg_filter ||\n+\t\t!grep_buffer(opt->grep_neg_filter,\n \t\t\t   NULL, /* we say nothing, not even filename */\n-\t\t\t   commit->buffer, strlen(commit->buffer));\n+\t\t\t   commit->buffer, strlen(commit->buffer)));\n }\n \n static struct commit *get_revision_1(struct rev_info *revs)\ndiff --git a/revision.h b/revision.h\nindex f46b4d5..9728d4c 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -84,6 +84,7 @@ struct rev_info {\n \n \t/* Filter by commit log message */\n \tstruct grep_opt\t*grep_filter;\n+\tstruct grep_opt\t*grep_neg_filter;\n \n \t/* special limits */\n \tint skip_count;\n-- \n1.5.3.rc0.40.g46ad-dirty\n"},{"id":"46693","messageId":"Pine.LNX.4.64.0707071724410.4093@racer.site","threadId":"8915","inReplyTo":"20070707153001.GA10408MdfPADPa@greensroom.kotnet.org","subject":"Re: [PATCH] revision: allow selection of commits that do not match a pattern","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-07-07T16:27:23Z","receivedAt":"2007-07-07T16:27:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 7 Jul 2007, Sven Verdoolaege wrote:\n\n> We do this by maintaining two lists of patterns, one for\n> those that should match and one for those that should not match.\n\nI suspect that with this patch,\n\n\tgit rev-list --not --grep bugfix HEAD\n\ndoes not work as expected. Why? Because the --not is heeded when \ninterpreting \"HEAD\". And that is confusing, because you use --not for two \ncompletely unrelated things.\n\nWhy not make \"git rev-list --grep '!bugfix' HEAD\" work?\n\nYes, you would have to have a special exception that the prefix \"!!\" \nactually matches an exclamation mark, but I'd be willing to live with \nthat.\n\nCiao,\nDscho\n"},{"id":"46695","messageId":"20070707165208.GC1528MdfPADPa@greensroom.kotnet.org","threadId":"8915","inReplyTo":"Pine.LNX.4.64.0707071724410.4093@racer.site","subject":"Re: [PATCH] revision: allow selection of commits that do not match a pattern","fromName":"Sven Verdoolaege","fromEmail":"skimo@kotnet.org","sentAt":"2007-07-07T16:52:08Z","receivedAt":"2007-07-07T16:52:08Z","isPatch":true,"sender":{"key":"skimo@kotnet.org","avatar":null},"body":"On Sat, Jul 07, 2007 at 05:27:23PM +0100, Johannes Schindelin wrote:\n> I suspect that with this patch,\n> \n> \tgit rev-list --not --grep bugfix HEAD\n> \n> does not work as expected. Why?\n\nWell... I guess that depends on what you expect...\n\n> Why not make \"git rev-list --grep '!bugfix' HEAD\" work?\n> \n> Yes, you would have to have a special exception that the prefix \"!!\" \n> actually matches an exclamation mark, but I'd be willing to live with \n> that.\n\nHmm... what if you want to (not) match anything starting with\none or more '!' ?\nHow about I add a '--invert-match' option that would\napply to all following match options?\nOr we could escape the '!' with backslash.\n\nskimo\n"},{"id":"46702","messageId":"Pine.LNX.4.64.0707071831300.4093@racer.site","threadId":"8915","inReplyTo":"20070707165208.GC1528MdfPADPa@greensroom.kotnet.org","subject":"Re: [PATCH] revision: allow selection of commits that do not match a pattern","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-07-07T17:33:06Z","receivedAt":"2007-07-07T17:33:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 7 Jul 2007, Sven Verdoolaege wrote:\n\n> On Sat, Jul 07, 2007 at 05:27:23PM +0100, Johannes Schindelin wrote:\n> > I suspect that with this patch,\n> > \n> > \tgit rev-list --not --grep bugfix HEAD\n> > \n> > does not work as expected. Why?\n> \n> Well... I guess that depends on what you expect...\n\nWell, at least you hopefully that it is confusing. To use --not for grep \npatterns _as well_ as for revision arguments.\n\n> > Why not make \"git rev-list --grep '!bugfix' HEAD\" work?\n> > \n> > Yes, you would have to have a special exception that the prefix \"!!\" \n> > actually matches an exclamation mark, but I'd be willing to live with \n> > that.\n> \n> Hmm... what if you want to (not) match anything starting with\n> one or more '!' ?\n\nYeah, that would not work.\n\n> How about I add a '--invert-match' option that would\n> apply to all following match options?\n> Or we could escape the '!' with backslash.\n\nIf we want to match it at the beginning. Yes, that sounds more reasonable \nto me.\n\nCiao,\nDscho\n"},{"id":"46705","messageId":"20070707184224.GE1528MdfPADPa@greensroom.kotnet.org","threadId":"8915","inReplyTo":"Pine.LNX.4.64.0707071831300.4093@racer.site","subject":"[PATCH] revision: allow selection of commits that do not match a pattern","fromName":"Sven Verdoolaege","fromEmail":"skimo@kotnet.org","sentAt":"2007-07-07T18:42:24Z","receivedAt":"2007-07-07T18:42:24Z","isPatch":true,"sender":{"key":"skimo@kotnet.org","avatar":null},"body":"We do this by maintaining two lists of patterns, one for\nthose that should match and one for those that should not match.\n\nSigned-off-by: Sven Verdoolaege <skimo@kotnet.org>\n---\n Documentation/git-rev-list.txt |    8 +++++\n revision.c                     |   59 +++++++++++++++++++++++++++++++--------\n revision.h                     |    1 +\n 3 files changed, 56 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt\nindex 20dcac6..a27e4dd 100644\n--- a/Documentation/git-rev-list.txt\n+++ b/Documentation/git-rev-list.txt\n@@ -214,11 +214,19 @@ limiting may be applied.\n \n \tLimit the commits output to ones with author/committer\n \theader lines that match the specified pattern (regular expression).\n+\tA pattern starting with a '!' will show only commits that do\n+\tnot match the remainder of the pattern.\n+\tTo match lines starting with '!', escape the initial '!'\n+\twith a backslash.\n \n --grep='pattern'::\n \n \tLimit the commits output to ones with log message that\n \tmatches the specified pattern (regular expression).\n+\tA pattern starting with a '!' will show only commits that do\n+\tnot match the remainder of the pattern.\n+\tTo match lines starting with '!', escape the initial '!'\n+\twith a backslash.\n \n --regexp-ignore-case::\n \ndiff --git a/revision.c b/revision.c\nindex 5184716..4b00ada 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -821,40 +821,65 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,\n \treturn 0;\n }\n \n-static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)\n+static void add_grep_to_filter(struct grep_opt **filter, const char *ptn,\n+\t\t\t\tenum grep_pat_token what)\n {\n-\tif (!revs->grep_filter) {\n+\tif (!*filter) {\n \t\tstruct grep_opt *opt = xcalloc(1, sizeof(*opt));\n \t\topt->status_only = 1;\n \t\topt->pattern_tail = &(opt->pattern_list);\n \t\topt->regflags = REG_NEWLINE;\n-\t\trevs->grep_filter = opt;\n+\t\t*filter = opt;\n \t}\n-\tappend_grep_pattern(revs->grep_filter, ptn,\n-\t\t\t    \"command line\", 0, what);\n+\tappend_grep_pattern(*filter, ptn, \"command line\", 0, what);\n }\n \n-static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern)\n+static void add_grep(struct rev_info *revs, const char *ptn,\n+\t\t    enum grep_pat_token what, int negated)\n+{\n+\tif (negated)\n+\t\tadd_grep_to_filter(&revs->grep_neg_filter, ptn, what);\n+\telse\n+\t\tadd_grep_to_filter(&revs->grep_filter, ptn, what);\n+}\n+\n+static void add_header_grep(struct rev_info *revs, const char *field,\n+\t\t\t    const char *pattern)\n {\n \tchar *pat;\n \tconst char *prefix;\n \tint patlen, fldlen;\n+\tint negated = 0;\n \n \tfldlen = strlen(field);\n \tpatlen = strlen(pattern);\n \tpat = xmalloc(patlen + fldlen + 10);\n \tprefix = \".*\";\n+\tif (*pattern == '!') {\n+\t\tnegated = 1;\n+\t\tpattern++;\n+\t}\n+\tif (pattern[0] == '\\\\' && pattern[1] == '!')\n+\t\tpattern++;\n \tif (*pattern == '^') {\n \t\tprefix = \"\";\n \t\tpattern++;\n \t}\n \tsprintf(pat, \"^%s %s%s\", field, prefix, pattern);\n-\tadd_grep(revs, pat, GREP_PATTERN_HEAD);\n+\tadd_grep(revs, pat, GREP_PATTERN_HEAD, negated);\n }\n \n static void add_message_grep(struct rev_info *revs, const char *pattern)\n {\n-\tadd_grep(revs, pattern, GREP_PATTERN_BODY);\n+\tint negated = 0;\n+\n+\tif (*pattern == '!') {\n+\t\tnegated = 1;\n+\t\tpattern++;\n+\t}\n+\tif (pattern[0] == '\\\\' && pattern[1] == '!')\n+\t\tpattern++;\n+\tadd_grep(revs, pattern, GREP_PATTERN_BODY, negated);\n }\n \n static void add_ignore_packed(struct rev_info *revs, const char *name)\n@@ -1212,6 +1237,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n \tif (revs->grep_filter)\n \t\trevs->grep_filter->regflags |= regflags;\n \n+\tif (revs->grep_neg_filter)\n+\t\trevs->grep_neg_filter->regflags |= regflags;\n+\n \tif (show_merge)\n \t\tprepare_show_merge(revs);\n \tif (def && !revs->pending.nr) {\n@@ -1249,6 +1277,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n \t\tcompile_grep_patterns(revs->grep_filter);\n \t}\n \n+\tif (revs->grep_neg_filter) {\n+\t\tcompile_grep_patterns(revs->grep_neg_filter);\n+\t}\n+\n \treturn left;\n }\n \n@@ -1329,11 +1361,14 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit)\n \n static int commit_match(struct commit *commit, struct rev_info *opt)\n {\n-\tif (!opt->grep_filter)\n-\t\treturn 1;\n-\treturn grep_buffer(opt->grep_filter,\n+\treturn (!opt->grep_filter ||\n+\t\tgrep_buffer(opt->grep_filter,\n+\t\t\t   NULL, /* we say nothing, not even filename */\n+\t\t\t   commit->buffer, strlen(commit->buffer))) &&\n+\t       (!opt->grep_neg_filter ||\n+\t\t!grep_buffer(opt->grep_neg_filter,\n \t\t\t   NULL, /* we say nothing, not even filename */\n-\t\t\t   commit->buffer, strlen(commit->buffer));\n+\t\t\t   commit->buffer, strlen(commit->buffer)));\n }\n \n static struct commit *get_revision_1(struct rev_info *revs)\ndiff --git a/revision.h b/revision.h\nindex f46b4d5..9728d4c 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -84,6 +84,7 @@ struct rev_info {\n \n \t/* Filter by commit log message */\n \tstruct grep_opt\t*grep_filter;\n+\tstruct grep_opt\t*grep_neg_filter;\n \n \t/* special limits */\n \tint skip_count;\n-- \n1.5.3.rc0.41.ge7772\n"},{"id":"46709","messageId":"Pine.LNX.4.64.0707071957370.4093@racer.site","threadId":"8915","inReplyTo":"20070707184224.GE1528MdfPADPa@greensroom.kotnet.org","subject":"Re: [PATCH] revision: allow selection of commits that do not match a pattern","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-07-07T19:35:35Z","receivedAt":"2007-07-07T19:35:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sat, 7 Jul 2007, Sven Verdoolaege wrote:\n\n> We do this by maintaining two lists of patterns, one for\n> those that should match and one for those that should not match.\n\nI would at least give one example in the commit message\n\n> diff --git a/revision.c b/revision.c\n> index 5184716..4b00ada 100644\n> --- a/revision.c\n> +++ b/revision.c\n> @@ -821,40 +821,65 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,\n>  \treturn 0;\n>  }\n>  \n> -static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)\n> +static void add_grep_to_filter(struct grep_opt **filter, const char *ptn,\n> +\t\t\t\tenum grep_pat_token what)\n>  {\n> -\tif (!revs->grep_filter) {\n> +\tif (!*filter) {\n\nWhy not keep it \"add_grep\", and do a\n\n\tstruct grep_opt **filter = negated ? \n\t\t&revs->grep_neg_filter : &revs->grep_filter;\n\nHm? You avoid an extra function that way.\n\n> +static void add_header_grep(struct rev_info *revs, const char *field,\n> +\t\t\t    const char *pattern)\n>  {\n>  \tchar *pat;\n>  \tconst char *prefix;\n>  \tint patlen, fldlen;\n> +\tint negated = 0;\n>  \n>  \tfldlen = strlen(field);\n>  \tpatlen = strlen(pattern);\n>  \tpat = xmalloc(patlen + fldlen + 10);\n>  \tprefix = \".*\";\n> +\tif (*pattern == '!') {\n> +\t\tnegated = 1;\n> +\t\tpattern++;\n> +\t}\n> +\tif (pattern[0] == '\\\\' && pattern[1] == '!')\n> +\t\tpattern++;\n>  \tif (*pattern == '^') {\n>  \t\tprefix = \"\";\n>  \t\tpattern++;\n>  \t}\n>  \tsprintf(pat, \"^%s %s%s\", field, prefix, pattern);\n> -\tadd_grep(revs, pat, GREP_PATTERN_HEAD);\n> +\tadd_grep(revs, pat, GREP_PATTERN_HEAD, negated);\n>  }\n\nThe parsing for \"!\" is again duplicated in add_message_grep(). Why not put \nit into add_grep(), and do\n\n\tnegated = *pattern == '!';\n\tsprintf(pat, \"%s^%s %s%s\", negated ? \"!\" : \"\", field, prefix, \n\t\tpattern + negated);\n\ninstead? No need to change the signature of add_grep(), and all callers \nget the '!' feature for free.\n\n> @@ -1249,6 +1277,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n>  \t\tcompile_grep_patterns(revs->grep_filter);\n>  \t}\n>  \n> +\tif (revs->grep_neg_filter) {\n> +\t\tcompile_grep_patterns(revs->grep_neg_filter);\n> +\t}\n> +\n\nPlease lose the \"{\" and \"}\".\n\n> @@ -1329,11 +1361,14 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit)\n>  \n>  static int commit_match(struct commit *commit, struct rev_info *opt)\n>  {\n> -\tif (!opt->grep_filter)\n> -\t\treturn 1;\n> -\treturn grep_buffer(opt->grep_filter,\n> +\treturn (!opt->grep_filter ||\n> +\t\tgrep_buffer(opt->grep_filter,\n> +\t\t\t   NULL, /* we say nothing, not even filename */\n> +\t\t\t   commit->buffer, strlen(commit->buffer))) &&\n> +\t       (!opt->grep_neg_filter ||\n> +\t\t!grep_buffer(opt->grep_neg_filter,\n>  \t\t\t   NULL, /* we say nothing, not even filename */\n> -\t\t\t   commit->buffer, strlen(commit->buffer));\n> +\t\t\t   commit->buffer, strlen(commit->buffer)));\n>  }\n\nUrgh! That's not nice on my eyes.\n\nAlso, I suspect that the semantics are not yet clear, what should happen \nif all_match is unset.\n\nBTW I suspect that a better way than having two filter lists is \ndemonstrated in builtin-grep.c.\n\nCiao,\nDscho\n"},{"id":"46712","messageId":"20070707202200.GF1528MdfPADPa@greensroom.kotnet.org","threadId":"8915","inReplyTo":"Pine.LNX.4.64.0707071957370.4093@racer.site","subject":"Re: [PATCH] revision: allow selection of commits that do not match a pattern","fromName":"Sven Verdoolaege","fromEmail":"skimo@kotnet.org","sentAt":"2007-07-07T20:22:00Z","receivedAt":"2007-07-07T20:22:00Z","isPatch":true,"sender":{"key":"skimo@kotnet.org","avatar":null},"body":"On Sat, Jul 07, 2007 at 08:35:35PM +0100, Johannes Schindelin wrote:\n> Why not keep it \"add_grep\", and do a\n> \n> \tstruct grep_opt **filter = negated ? \n> \t\t&revs->grep_neg_filter : &revs->grep_filter;\n> \n> Hm? You avoid an extra function that way.\n\n[..]\n> \n> The parsing for \"!\" is again duplicated in add_message_grep(). Why not put \n> it into add_grep(), and do\n> \n> \tnegated = *pattern == '!';\n> \tsprintf(pat, \"%s^%s %s%s\", negated ? \"!\" : \"\", field, prefix, \n> \t\tpattern + negated);\n> \n> instead? No need to change the signature of add_grep(), and all callers \n> get the '!' feature for free.\n\nI can do these things, but they don't exactly improve readability, IMHO.\n\n> > @@ -1249,6 +1277,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n> >  \t\tcompile_grep_patterns(revs->grep_filter);\n> >  \t}\n> >  \n> > +\tif (revs->grep_neg_filter) {\n> > +\t\tcompile_grep_patterns(revs->grep_neg_filter);\n> > +\t}\n> > +\n> \n> Please lose the \"{\" and \"}\".\n\nI may still need them for doing something with all_match...\n\n> > @@ -1329,11 +1361,14 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit)\n> >  \n> >  static int commit_match(struct commit *commit, struct rev_info *opt)\n> >  {\n> > -\tif (!opt->grep_filter)\n> > -\t\treturn 1;\n> > -\treturn grep_buffer(opt->grep_filter,\n> > +\treturn (!opt->grep_filter ||\n> > +\t\tgrep_buffer(opt->grep_filter,\n> > +\t\t\t   NULL, /* we say nothing, not even filename */\n> > +\t\t\t   commit->buffer, strlen(commit->buffer))) &&\n> > +\t       (!opt->grep_neg_filter ||\n> > +\t\t!grep_buffer(opt->grep_neg_filter,\n> >  \t\t\t   NULL, /* we say nothing, not even filename */\n> > -\t\t\t   commit->buffer, strlen(commit->buffer));\n> > +\t\t\t   commit->buffer, strlen(commit->buffer)));\n> >  }\n> \n> Urgh! That's not nice on my eyes.\n\nYou prefer\n\n\tif (opt->grep_filter && !grep_buffer(opt->grep_filter,\n\t\t\t   NULL, /* we say nothing, not even filename */\n\t\t\t   commit->buffer, strlen(commit->buffer)))\n\t       return 0;\n\tif (opt->grep_neg_filter && grep_buffer(opt->grep_neg_filter,\n\t\t\t   NULL, /* we say nothing, not even filename */\n\t\t\t   commit->buffer, strlen(commit->buffer)));\n\t       return 0;\n       return 1;\n\n?\n\n> Also, I suspect that the semantics are not yet clear, what should happen \n> if all_match is unset.\n\nSo what are the semantics of all_match without negated matches?\nIt doesn't seem to be documented in git-rev-list.txt.\n\n> BTW I suspect that a better way than having two filter lists is \n> demonstrated in builtin-grep.c.\n\nCould you be a bit more specific?\nIf you're talking about the GREP_NOT thing, then AFAICS that is line based\nand I want these things to be commit based.  That is I want to select\ncommits with either a or no lines that match a given pattern and not\ncommits that have a line that matches some patterns and not some others.\n\nskimo\n"},{"id":"46746","messageId":"20070708105719.GH1528MdfPADPa@greensroom.kotnet.org","threadId":"8915","inReplyTo":"Pine.LNX.4.64.0707071957370.4093@racer.site","subject":"[PATCH v3] revision: allow selection of commits that do not match a pattern","fromName":"Sven Verdoolaege","fromEmail":"skimo@kotnet.org","sentAt":"2007-07-08T10:57:20Z","receivedAt":"2007-07-08T10:57:20Z","isPatch":true,"sender":{"key":"skimo@kotnet.org","avatar":null},"body":"We do this by maintaining two lists of patterns, one for\nthose that should match and one for those that should not match.\n\nA negative pattern is specified by putting a '!' in front.\nFor example, to show the commits of Jakub Narebski that\nare not about gitweb, you'd do a\n\n\tgit log --author='Narebski' --grep='!gitweb' --all-match\n\nAs an added bonus, this patch also documents --all-match.\n\nSigned-off-by: Sven Verdoolaege <skimo@kotnet.org>\n---\n Documentation/git-rev-list.txt |   17 ++++++++++\n revision.c                     |   64 +++++++++++++++++++++++++++++++++------\n revision.h                     |    1 +\n 3 files changed, 72 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt\nindex 20dcac6..c462f5d 100644\n--- a/Documentation/git-rev-list.txt\n+++ b/Documentation/git-rev-list.txt\n@@ -214,11 +214,19 @@ limiting may be applied.\n \n \tLimit the commits output to ones with author/committer\n \theader lines that match the specified pattern (regular expression).\n+\tA pattern starting with a '!' will show only commits that do\n+\tnot match the remainder of the pattern.\n+\tTo match lines starting with '!', escape the initial '!'\n+\twith a backslash.\n \n --grep='pattern'::\n \n \tLimit the commits output to ones with log message that\n \tmatches the specified pattern (regular expression).\n+\tA pattern starting with a '!' will show only commits that do\n+\tnot match the remainder of the pattern.\n+\tTo match lines starting with '!', escape the initial '!'\n+\twith a backslash.\n \n --regexp-ignore-case::\n \n@@ -229,6 +237,15 @@ limiting may be applied.\n \tConsider the limiting patterns to be extended regular expressions\n \tinstead of the default basic regular expressions.\n \n+--all-match::\n+\n+\tWithout this option, a commit is shown if any of the\n+\t(positive or negative) patterns matches, i.e., there\n+\tis at least one positive match or not all of the negative\n+\tpatterns match.  With this options, a commit is only\n+\tshown if all of the patterns match, i.e., all positive\n+\tpatterns match and no negative pattern matches.\n+\n --remove-empty::\n \n \tStop when a given path disappears from the tree.\ndiff --git a/revision.c b/revision.c\nindex 5184716..0035d40 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -821,34 +821,50 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,\n \treturn 0;\n }\n \n-static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)\n+static void add_grep(struct rev_info *revs, const char *ptn,\n+\t\t    enum grep_pat_token what)\n {\n-\tif (!revs->grep_filter) {\n+\tint negated = 0;\n+\tstruct grep_opt **filter;\n+\n+\tif (ptn[0] == '\\\\' && ptn[1] == '!')\n+\t\tptn++;\n+\tif (*ptn == '!') {\n+\t\tnegated = 1;\n+\t\tptn++;\n+\t}\n+\tfilter = negated ? &revs->grep_neg_filter : &revs->grep_filter;\n+\tif (!*filter) {\n \t\tstruct grep_opt *opt = xcalloc(1, sizeof(*opt));\n \t\topt->status_only = 1;\n \t\topt->pattern_tail = &(opt->pattern_list);\n \t\topt->regflags = REG_NEWLINE;\n-\t\trevs->grep_filter = opt;\n+\t\t*filter = opt;\n \t}\n-\tappend_grep_pattern(revs->grep_filter, ptn,\n-\t\t\t    \"command line\", 0, what);\n+\tappend_grep_pattern(*filter, ptn, \"command line\", 0, what);\n }\n \n-static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern)\n+static void add_header_grep(struct rev_info *revs, const char *field,\n+\t\t\t    const char *pattern)\n {\n \tchar *pat;\n-\tconst char *prefix;\n+\tconst char *prefix, *negated;\n \tint patlen, fldlen;\n \n \tfldlen = strlen(field);\n \tpatlen = strlen(pattern);\n \tpat = xmalloc(patlen + fldlen + 10);\n+\tnegated = \"\";\n+\tif (*pattern == '!') {\n+\t\tnegated = \"!\";\n+\t\tpattern++;\n+\t}\n \tprefix = \".*\";\n \tif (*pattern == '^') {\n \t\tprefix = \"\";\n \t\tpattern++;\n \t}\n-\tsprintf(pat, \"^%s %s%s\", field, prefix, pattern);\n+\tsprintf(pat, \"%s^%s %s%s\", negated, field, prefix, pattern);\n \tadd_grep(revs, pat, GREP_PATTERN_HEAD);\n }\n \n@@ -1212,6 +1228,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n \tif (revs->grep_filter)\n \t\trevs->grep_filter->regflags |= regflags;\n \n+\tif (revs->grep_neg_filter)\n+\t\trevs->grep_neg_filter->regflags |= regflags;\n+\n \tif (show_merge)\n \t\tprepare_show_merge(revs);\n \tif (def && !revs->pending.nr) {\n@@ -1249,6 +1268,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n \t\tcompile_grep_patterns(revs->grep_filter);\n \t}\n \n+\tif (revs->grep_neg_filter) {\n+\t\trevs->grep_neg_filter->all_match = !all_match;\n+\t\tcompile_grep_patterns(revs->grep_neg_filter);\n+\t}\n+\n \treturn left;\n }\n \n@@ -1327,11 +1351,31 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit)\n \treturn 0;\n }\n \n+/*\n+ * If all_match is set, then a commit matches if all the positive\n+ * patterns match and not one of the negative patterns matches.\n+ * If all_match is not set, then a commit matches if at least one\n+ * of the positive patterns matches or not all of the negative\n+ * patterns match.\n+ */\n static int commit_match(struct commit *commit, struct rev_info *opt)\n {\n-\tif (!opt->grep_filter)\n+\tint pos_match, all_match;\n+\n+\tpos_match = !opt->grep_filter ||\n+\t\t    grep_buffer(opt->grep_filter,\n+\t\t\t   NULL, /* we say nothing, not even filename */\n+\t\t\t   commit->buffer, strlen(commit->buffer));\n+\tif (!opt->grep_neg_filter)\n+\t\treturn pos_match;\n+\n+\tall_match = !opt->grep_neg_filter->all_match;\n+\tif (!all_match && opt->grep_filter && pos_match)\n \t\treturn 1;\n-\treturn grep_buffer(opt->grep_filter,\n+\tif (all_match && !pos_match)\n+\t\treturn 0;\n+\n+\treturn !grep_buffer(opt->grep_neg_filter,\n \t\t\t   NULL, /* we say nothing, not even filename */\n \t\t\t   commit->buffer, strlen(commit->buffer));\n }\ndiff --git a/revision.h b/revision.h\nindex f46b4d5..9728d4c 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -84,6 +84,7 @@ struct rev_info {\n \n \t/* Filter by commit log message */\n \tstruct grep_opt\t*grep_filter;\n+\tstruct grep_opt\t*grep_neg_filter;\n \n \t/* special limits */\n \tint skip_count;\n-- \n1.5.3.rc0.65.ge75d-dirty\n"},{"id":"46757","messageId":"Pine.LNX.4.64.0707081519230.4248@racer.site","threadId":"8915","inReplyTo":"20070708105719.GH1528MdfPADPa@greensroom.kotnet.org","subject":"Re: [PATCH v3] revision: allow selection of commits that do not match a pattern","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-07-08T14:22:06Z","receivedAt":"2007-07-08T14:22:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\njust to give you an impression of what I had in mind, here is a WIP.  It \nis not completely thought through, for example I did not make up my mind \nhow to handle something like \"--not --not-at-all <pattern>\".  Oh, and the \ncode for non-status_only is not there.  And builtin-grep does not see any \nof this, yet. But you'll get the idea:\n\n---\n\n grep.c     |   19 +++++++++++++++----\n grep.h     |    3 +++\n revision.c |   13 ++++++++++++-\n 3 files changed, 30 insertions(+), 5 deletions(-)\n\ndiff --git a/grep.c b/grep.c\nindex f67d671..40a2620 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -66,20 +66,24 @@ static struct grep_expr *compile_pattern_atom(struct grep_pat **list)\n \n static struct grep_expr *compile_pattern_not(struct grep_pat **list)\n {\n+\tconst char *what;\n \tstruct grep_pat *p;\n \tstruct grep_expr *x;\n \n \tp = *list;\n \tswitch (p->token) {\n \tcase GREP_NOT:\n+\tcase GREP_NOT_AT_ALL:\n+\t\twhat = p->token == GREP_NOT ? \"--not\" : \"--not-at-all\";\n \t\tif (!p->next)\n-\t\t\tdie(\"--not not followed by pattern expression\");\n+\t\t\tdie(\"%s not followed by pattern expression\", what);\n \t\t*list = p->next;\n \t\tx = xcalloc(1, sizeof (struct grep_expr));\n-\t\tx->node = GREP_NODE_NOT;\n+\t\tx->node = p->token == GREP_NOT ?\n+\t\t\tGREP_NODE_NOT : GREP_NODE_NOT_AT_ALL;\n \t\tx->u.unary = compile_pattern_not(list);\n \t\tif (!x->u.unary)\n-\t\t\tdie(\"--not followed by non pattern expression\");\n+\t\t\tdie(\"%s followed by non pattern expression\", what);\n \t\treturn x;\n \tdefault:\n \t\treturn compile_pattern_atom(list);\n@@ -173,6 +177,7 @@ static void free_pattern_expr(struct grep_expr *x)\n \tcase GREP_NODE_ATOM:\n \t\tbreak;\n \tcase GREP_NODE_NOT:\n+\tcase GREP_NODE_NOT_AT_ALL:\n \t\tfree_pattern_expr(x->u.unary);\n \t\tbreak;\n \tcase GREP_NODE_AND:\n@@ -316,6 +321,10 @@ static int match_expr_eval(struct grep_opt *o,\n \tcase GREP_NODE_NOT:\n \t\th = !match_expr_eval(o, x->u.unary, bol, eol, ctx, 0);\n \t\tbreak;\n+\tcase GREP_NODE_NOT_AT_ALL:\n+\t\tif (match_expr_eval(o, x->u.unary, bol, eol, ctx, 0))\n+\t\t\to->not_at_all = 1;\n+\t\tbreak;\n \tcase GREP_NODE_AND:\n \t\tif (!collect_hits)\n \t\t\treturn (match_expr_eval(o, x->u.binary.left,\n@@ -382,6 +391,8 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,\n \tunsigned count = 0;\n \tenum grep_context ctx = GREP_CONTEXT_HEAD;\n \n+\topt->not_at_all = 0;\n+\n \tif (buffer_is_binary(buf, size)) {\n \t\tswitch (opt->binary) {\n \t\tcase GREP_BINARY_DEFAULT:\n@@ -500,7 +511,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,\n \t\treturn 0;\n \n \tif (opt->status_only)\n-\t\treturn 0;\n+\t\treturn !opt->not_at_all;\n \tif (opt->unmatch_name_only) {\n \t\t/* We did not see any hit, so we want to show this */\n \t\tprintf(\"%s\\n\", name);\ndiff --git a/grep.h b/grep.h\nindex d252dd2..f80a1c2 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -10,6 +10,7 @@ enum grep_pat_token {\n \tGREP_CLOSE_PAREN,\n \tGREP_NOT,\n \tGREP_OR,\n+\tGREP_NOT_AT_ALL,\n };\n \n enum grep_context {\n@@ -31,6 +32,7 @@ enum grep_expr_node {\n \tGREP_NODE_NOT,\n \tGREP_NODE_AND,\n \tGREP_NODE_OR,\n+\tGREP_NODE_NOT_AT_ALL,\n };\n \n struct grep_expr {\n@@ -68,6 +70,7 @@ struct grep_opt {\n \tunsigned extended:1;\n \tunsigned relative:1;\n \tunsigned pathname:1;\n+\tunsigned not_at_all:1; /* is set if the pattern was seen */\n \tint regflags;\n \tunsigned pre_context;\n \tunsigned post_context;\ndiff --git a/revision.c b/revision.c\nindex 5184716..3df8a57 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -823,6 +823,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,\n \n static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)\n {\n+\tint negate = *ptn == '!';\n \tif (!revs->grep_filter) {\n \t\tstruct grep_opt *opt = xcalloc(1, sizeof(*opt));\n \t\topt->status_only = 1;\n@@ -830,6 +831,13 @@ static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token\n \t\topt->regflags = REG_NEWLINE;\n \t\trevs->grep_filter = opt;\n \t}\n+\tif (negate) {\n+\t\trevs->grep_filter->extended = 1;\n+\t\tappend_grep_pattern(revs->grep_filter, ptn,\n+\t\t\t\t\"command line\", 0, GREP_NOT_AT_ALL);\n+\t}\n+\tif (negate || ( *ptn == '\\\\' && ptn[1] == '!'))\n+\t\tptn++;\n \tappend_grep_pattern(revs->grep_filter, ptn,\n \t\t\t    \"command line\", 0, what);\n }\n@@ -839,7 +847,10 @@ static void add_header_grep(struct rev_info *revs, const char *field, const char\n \tchar *pat;\n \tconst char *prefix;\n \tint patlen, fldlen;\n+\tint negate = *pattern == '!';\n \n+\tif (negate || (*pattern == '\\\\' && pattern[1] == '!'))\n+\t\tpattern++;\n \tfldlen = strlen(field);\n \tpatlen = strlen(pattern);\n \tpat = xmalloc(patlen + fldlen + 10);\n@@ -848,7 +859,7 @@ static void add_header_grep(struct rev_info *revs, const char *field, const char\n \t\tprefix = \"\";\n \t\tpattern++;\n \t}\n-\tsprintf(pat, \"^%s %s%s\", field, prefix, pattern);\n+\tsprintf(pat, \"%s^%s %s%s\", negate ? \"!\" : \"\", field, prefix, pattern);\n \tadd_grep(revs, pat, GREP_PATTERN_HEAD);\n }\n \n"},{"id":"46760","messageId":"20070708145746.GJ1528MdfPADPa@greensroom.kotnet.org","threadId":"8915","inReplyTo":"Pine.LNX.4.64.0707081519230.4248@racer.site","subject":"Re: [PATCH v3] revision: allow selection of commits that do not match a pattern","fromName":"Sven Verdoolaege","fromEmail":"skimo@kotnet.org","sentAt":"2007-07-08T14:57:46Z","receivedAt":"2007-07-08T14:57:46Z","isPatch":true,"sender":{"key":"skimo@kotnet.org","avatar":null},"body":"On Sun, Jul 08, 2007 at 03:22:06PM +0100, Johannes Schindelin wrote:\n> @@ -382,6 +391,8 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,\n>  \tunsigned count = 0;\n>  \tenum grep_context ctx = GREP_CONTEXT_HEAD;\n>  \n> +\topt->not_at_all = 0;\n> +\n>  \tif (buffer_is_binary(buf, size)) {\n>  \t\tswitch (opt->binary) {\n>  \t\tcase GREP_BINARY_DEFAULT:\n> @@ -500,7 +511,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,\n>  \t\treturn 0;\n>  \n>  \tif (opt->status_only)\n> -\t\treturn 0;\n> +\t\treturn !opt->not_at_all;\n>  \tif (opt->unmatch_name_only) {\n>  \t\t/* We did not see any hit, so we want to show this */\n>  \t\tprintf(\"%s\\n\", name);\n\nI don't understand this part.\nAren't you changing the return value from 0 to 1 here if there is no NOT_AT_ALL node?\n\n> @@ -68,6 +70,7 @@ struct grep_opt {\n>  \tunsigned extended:1;\n>  \tunsigned relative:1;\n>  \tunsigned pathname:1;\n> +\tunsigned not_at_all:1; /* is set if the pattern was seen */\n>  \tint regflags;\n>  \tunsigned pre_context;\n>  \tunsigned post_context;\n\nThe name for this field is also a bit confusing.\nWouldn't \"matched_some_line\" or some such by more appropriate?\n\nskimo\n"},{"id":"47051","messageId":"20070711174238.GA23748@coredump.intra.peff.net","threadId":"8915","inReplyTo":"20070708105719.GH1528MdfPADPa@greensroom.kotnet.org","subject":"Re: [PATCH v3] revision: allow selection of commits that do not match a pattern","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2007-07-11T17:42:39Z","receivedAt":"2007-07-11T17:42:39Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Jul 08, 2007 at 12:57:20PM +0200, Sven Verdoolaege wrote:\n\n> We do this by maintaining two lists of patterns, one for\n> those that should match and one for those that should not match.\n> \n> A negative pattern is specified by putting a '!' in front.\n> For example, to show the commits of Jakub Narebski that\n> are not about gitweb, you'd do a\n> \n> \tgit log --author='Narebski' --grep='!gitweb' --all-match\n\nI wondered if the usage might be more natural if we could grep in\nseparate processes, and then build filtering pipelines (like we would\nwith regular grep).  For example, something like:\n\ngit-rev-list HEAD |\n  git-revgrep --author=Narebski |\n  git-revgrep -v gitweb |\n  git-log -\n\nHowever, my concern was that things might get a lot slower. And they do:\n\n$ time git-rev-list --grep=Jeff.King HEAD >/dev/null\nreal    0m0.553s\nuser    0m0.532s\nsys     0m0.020s\n\n$ time git-rev-list HEAD | git-revgrep Jeff.King >/dev/null\nreal    0m0.662s\nuser    0m1.072s\nsys     0m0.036s\n\n(note the user time -- it's a dual-CPU box, so the wall clock time is\ndeceptive).\n\nSo it really does double your time (or triple, if you have a second\nrevgrep, and so forth (although as you filter out commits the cost of\neach successive revgrep goes down)).  So it's probably not worth\npursuing this approach, but I thought I would throw it out there to\ndocument my dead-end.\n\nQuick and dirty git-revgrep code is below.\n\n-Peff\n\n---\ndiff --git a/Makefile b/Makefile\nindex d7541b4..baaf4f1 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -384,7 +384,8 @@ BUILTIN_OBJS = \\\n \tbuiltin-verify-pack.o \\\n \tbuiltin-write-tree.o \\\n \tbuiltin-show-ref.o \\\n-\tbuiltin-pack-refs.o\n+\tbuiltin-pack-refs.o \\\n+\tbuiltin-revgrep.o\n \n GITLIBS = $(LIB_FILE) $(XDIFF_LIB)\n EXTLIBS = -lz\ndiff --git a/builtin-revgrep.c b/builtin-revgrep.c\nnew file mode 100644\nindex 0000000..35993c8\n--- /dev/null\n+++ b/builtin-revgrep.c\n@@ -0,0 +1,68 @@\n+#include \"cache.h\"\n+#include \"commit.h\"\n+#include \"grep.h\"\n+\n+static int revgrep_one(const struct commit *commit, struct grep_opt *opt)\n+{\n+\treturn grep_buffer(opt, NULL, commit->buffer, strlen(commit->buffer));\n+}\n+\n+static void revgrep_filter(struct grep_opt *opt)\n+{\n+\tchar line[1000];\n+\tunsigned char sha1[20];\n+\tstruct commit *commit;\n+\n+\twhile (fgets(line, sizeof(line), stdin) != NULL) {\n+\t\tint len = strlen(line);\n+\t\tif (line[len - 1] == '\\n')\n+\t\t\tline[--len] = 0;\n+\t\tif (!len)\n+\t\t\tbreak;\n+\t\tget_sha1_hex(line, sha1);\n+\t\tcommit = lookup_commit_reference(sha1);\n+\t\tif (!commit)\n+\t\t\tcontinue;\n+\t\tif (revgrep_one(commit, opt))\n+\t\t\tprintf(\"%s\\n\", line);\n+\t}\n+}\n+\n+int cmd_revgrep(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct grep_opt opt;\n+\n+\tmemset(&opt, 0, sizeof(opt));\n+\topt.pattern_tail = &opt.pattern_list;\n+\topt.status_only = 1;\n+\n+\twhile (1 < argc) {\n+\t\tconst char *arg = argv[1];\n+\t\targc--; argv++;\n+\n+\t\tif (!strcmp(arg, \"-i\")) {\n+\t\t\topt.regflags |= REG_ICASE;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-v\")) {\n+\t\t\topt.invert = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(arg, \"-E\")) {\n+\t\t\topt.regflags |= REG_EXTENDED;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tappend_grep_pattern(&opt, arg, \"command line\", 0,\n+\t\t\t\tGREP_PATTERN);\n+\t}\n+\n+\tif (!opt.pattern_list)\n+\t\tdie(\"no pattern given\");\n+\n+\tcompile_grep_patterns(&opt);\n+\trevgrep_filter(&opt);\n+\tfree_grep_patterns(&opt);\n+\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex 661a92f..d185528 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -82,5 +82,6 @@ extern int cmd_write_tree(int argc, const char **argv, const char *prefix);\n extern int cmd_verify_pack(int argc, const char **argv, const char *prefix);\n extern int cmd_show_ref(int argc, const char **argv, const char *prefix);\n extern int cmd_pack_refs(int argc, const char **argv, const char *prefix);\n+extern int cmd_revgrep(int argc, const char **argv, const char *prefix);\n \n #endif\ndiff --git a/git.c b/git.c\nindex a647f9c..d9ec129 100644\n--- a/git.c\n+++ b/git.c\n@@ -374,6 +374,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"verify-pack\", cmd_verify_pack },\n \t\t{ \"show-ref\", cmd_show_ref, RUN_SETUP },\n \t\t{ \"pack-refs\", cmd_pack_refs, RUN_SETUP },\n+\t\t{ \"revgrep\", cmd_revgrep, RUN_SETUP },\n \t};\n \tint i;\n \n\n\n\n\n\n\n> \n> As an added bonus, this patch also documents --all-match.\n> \n> Signed-off-by: Sven Verdoolaege <skimo@kotnet.org>\n> ---\n>  Documentation/git-rev-list.txt |   17 ++++++++++\n>  revision.c                     |   64 +++++++++++++++++++++++++++++++++------\n>  revision.h                     |    1 +\n>  3 files changed, 72 insertions(+), 10 deletions(-)\n> \n> diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt\n> index 20dcac6..c462f5d 100644\n> --- a/Documentation/git-rev-list.txt\n> +++ b/Documentation/git-rev-list.txt\n> @@ -214,11 +214,19 @@ limiting may be applied.\n>  \n>  \tLimit the commits output to ones with author/committer\n>  \theader lines that match the specified pattern (regular expression).\n> +\tA pattern starting with a '!' will show only commits that do\n> +\tnot match the remainder of the pattern.\n> +\tTo match lines starting with '!', escape the initial '!'\n> +\twith a backslash.\n>  \n>  --grep='pattern'::\n>  \n>  \tLimit the commits output to ones with log message that\n>  \tmatches the specified pattern (regular expression).\n> +\tA pattern starting with a '!' will show only commits that do\n> +\tnot match the remainder of the pattern.\n> +\tTo match lines starting with '!', escape the initial '!'\n> +\twith a backslash.\n>  \n>  --regexp-ignore-case::\n>  \n> @@ -229,6 +237,15 @@ limiting may be applied.\n>  \tConsider the limiting patterns to be extended regular expressions\n>  \tinstead of the default basic regular expressions.\n>  \n> +--all-match::\n> +\n> +\tWithout this option, a commit is shown if any of the\n> +\t(positive or negative) patterns matches, i.e., there\n> +\tis at least one positive match or not all of the negative\n> +\tpatterns match.  With this options, a commit is only\n> +\tshown if all of the patterns match, i.e., all positive\n> +\tpatterns match and no negative pattern matches.\n> +\n>  --remove-empty::\n>  \n>  \tStop when a given path disappears from the tree.\n> diff --git a/revision.c b/revision.c\n> index 5184716..0035d40 100644\n> --- a/revision.c\n> +++ b/revision.c\n> @@ -821,34 +821,50 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,\n>  \treturn 0;\n>  }\n>  \n> -static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)\n> +static void add_grep(struct rev_info *revs, const char *ptn,\n> +\t\t    enum grep_pat_token what)\n>  {\n> -\tif (!revs->grep_filter) {\n> +\tint negated = 0;\n> +\tstruct grep_opt **filter;\n> +\n> +\tif (ptn[0] == '\\\\' && ptn[1] == '!')\n> +\t\tptn++;\n> +\tif (*ptn == '!') {\n> +\t\tnegated = 1;\n> +\t\tptn++;\n> +\t}\n> +\tfilter = negated ? &revs->grep_neg_filter : &revs->grep_filter;\n> +\tif (!*filter) {\n>  \t\tstruct grep_opt *opt = xcalloc(1, sizeof(*opt));\n>  \t\topt->status_only = 1;\n>  \t\topt->pattern_tail = &(opt->pattern_list);\n>  \t\topt->regflags = REG_NEWLINE;\n> -\t\trevs->grep_filter = opt;\n> +\t\t*filter = opt;\n>  \t}\n> -\tappend_grep_pattern(revs->grep_filter, ptn,\n> -\t\t\t    \"command line\", 0, what);\n> +\tappend_grep_pattern(*filter, ptn, \"command line\", 0, what);\n>  }\n>  \n> -static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern)\n> +static void add_header_grep(struct rev_info *revs, const char *field,\n> +\t\t\t    const char *pattern)\n>  {\n>  \tchar *pat;\n> -\tconst char *prefix;\n> +\tconst char *prefix, *negated;\n>  \tint patlen, fldlen;\n>  \n>  \tfldlen = strlen(field);\n>  \tpatlen = strlen(pattern);\n>  \tpat = xmalloc(patlen + fldlen + 10);\n> +\tnegated = \"\";\n> +\tif (*pattern == '!') {\n> +\t\tnegated = \"!\";\n> +\t\tpattern++;\n> +\t}\n>  \tprefix = \".*\";\n>  \tif (*pattern == '^') {\n>  \t\tprefix = \"\";\n>  \t\tpattern++;\n>  \t}\n> -\tsprintf(pat, \"^%s %s%s\", field, prefix, pattern);\n> +\tsprintf(pat, \"%s^%s %s%s\", negated, field, prefix, pattern);\n>  \tadd_grep(revs, pat, GREP_PATTERN_HEAD);\n>  }\n>  \n> @@ -1212,6 +1228,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n>  \tif (revs->grep_filter)\n>  \t\trevs->grep_filter->regflags |= regflags;\n>  \n> +\tif (revs->grep_neg_filter)\n> +\t\trevs->grep_neg_filter->regflags |= regflags;\n> +\n>  \tif (show_merge)\n>  \t\tprepare_show_merge(revs);\n>  \tif (def && !revs->pending.nr) {\n> @@ -1249,6 +1268,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n>  \t\tcompile_grep_patterns(revs->grep_filter);\n>  \t}\n>  \n> +\tif (revs->grep_neg_filter) {\n> +\t\trevs->grep_neg_filter->all_match = !all_match;\n> +\t\tcompile_grep_patterns(revs->grep_neg_filter);\n> +\t}\n> +\n>  \treturn left;\n>  }\n>  \n> @@ -1327,11 +1351,31 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit)\n>  \treturn 0;\n>  }\n>  \n> +/*\n> + * If all_match is set, then a commit matches if all the positive\n> + * patterns match and not one of the negative patterns matches.\n> + * If all_match is not set, then a commit matches if at least one\n> + * of the positive patterns matches or not all of the negative\n> + * patterns match.\n> + */\n>  static int commit_match(struct commit *commit, struct rev_info *opt)\n>  {\n> -\tif (!opt->grep_filter)\n> +\tint pos_match, all_match;\n> +\n> +\tpos_match = !opt->grep_filter ||\n> +\t\t    grep_buffer(opt->grep_filter,\n> +\t\t\t   NULL, /* we say nothing, not even filename */\n> +\t\t\t   commit->buffer, strlen(commit->buffer));\n> +\tif (!opt->grep_neg_filter)\n> +\t\treturn pos_match;\n> +\n> +\tall_match = !opt->grep_neg_filter->all_match;\n> +\tif (!all_match && opt->grep_filter && pos_match)\n>  \t\treturn 1;\n> -\treturn grep_buffer(opt->grep_filter,\n> +\tif (all_match && !pos_match)\n> +\t\treturn 0;\n> +\n> +\treturn !grep_buffer(opt->grep_neg_filter,\n>  \t\t\t   NULL, /* we say nothing, not even filename */\n>  \t\t\t   commit->buffer, strlen(commit->buffer));\n>  }\n> diff --git a/revision.h b/revision.h\n> index f46b4d5..9728d4c 100644\n> --- a/revision.h\n> +++ b/revision.h\n> @@ -84,6 +84,7 @@ struct rev_info {\n>  \n>  \t/* Filter by commit log message */\n>  \tstruct grep_opt\t*grep_filter;\n> +\tstruct grep_opt\t*grep_neg_filter;\n>  \n>  \t/* special limits */\n>  \tint skip_count;\n> -- \n> 1.5.3.rc0.65.ge75d-dirty\n> \n> -\n> To unsubscribe from this list: send the line \"unsubscribe git\" in\n> the body of a message to majordomo@vger.kernel.org\n> More majordomo info at  http://vger.kernel.org/majordomo-info.html\n"}]}