{"thread":{"id":"9946","subject":"[PATCH] Add git-rev-list --invert-match","startedAt":"2007-09-19T20:26:15Z","lastAt":"2007-09-22T01:38:34Z","messageCount":13,"participants":["Bart Trojanowski","Junio C Hamano","Johannes Schindelin","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"53601","messageId":"20070919202615.GK3076@jukie.net","threadId":"9946","inReplyTo":null,"subject":"[PATCH] Add git-rev-list --invert-match","fromName":"Bart Trojanowski","fromEmail":"bart@jukie.net","sentAt":"2007-09-19T20:26:15Z","receivedAt":"2007-09-19T20:26:15Z","isPatch":true,"sender":{"key":"bart@jukie.net","avatar":"https://avatars.githubusercontent.com/u/6721?v=4"},"body":"Example usage:\n\n  git log --invert-match --grep=\"uninteresting\"\n\n  This command will prune out all commits that match the grep pattern.\n\nHow it works:\n\n  The --invert-match flag sets invert_match, in rev_info.  This boolean\n  is later checked in commit_match() and if set it inverts the result of\n  grep_buffer().\n---\n revision.c |   11 ++++++++++-\n revision.h |    3 ++-\n 2 files changed, 12 insertions(+), 2 deletions(-)\n\ndiff --git a/revision.c b/revision.c\nindex 33d092c..57b2d0f 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1182,6 +1182,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n \t\t\t\tregflags |= REG_ICASE;\n \t\t\t\tcontinue;\n \t\t\t}\n+\t\t\tif (!strcmp(arg, \"--invert-match\")) {\n+\t\t\t\trevs->invert_match = 1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n \t\t\tif (!strcmp(arg, \"--all-match\")) {\n \t\t\t\tall_match = 1;\n \t\t\t\tcontinue;\n@@ -1383,11 +1387,16 @@ 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+\tint result;\n+\n \tif (!opt->grep_filter)\n \t\treturn 1;\n-\treturn grep_buffer(opt->grep_filter,\n+\n+\tresult = 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+\n+\treturn opt->invert_match ? !result : result;\n }\n \n static struct commit *get_revision_1(struct rev_info *revs)\ndiff --git a/revision.h b/revision.h\nindex 98a0a8f..ead04a7 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -48,7 +48,8 @@ struct rev_info {\n \t\t\tparents:1,\n \t\t\treverse:1,\n \t\t\tcherry_pick:1,\n-\t\t\tfirst_parent_only:1;\n+\t\t\tfirst_parent_only:1,\n+\t\t\tinvert_match:1;\n \n \t/* Diff flags */\n \tunsigned int\tdiff:1,\n-- \n1.5.3.1.154.g734e65\n"},{"id":"53619","messageId":"20070920025225.GA12076@jukie.net","threadId":"9946","inReplyTo":"20070919202615.GK3076@jukie.net","subject":"[PATCH] Add git-rev-list --invert-match","fromName":"Bart Trojanowski","fromEmail":"bart@jukie.net","sentAt":"2007-09-20T02:52:25Z","receivedAt":"2007-09-20T02:52:25Z","isPatch":true,"sender":{"key":"bart@jukie.net","avatar":"https://avatars.githubusercontent.com/u/6721?v=4"},"body":"Example usage:\n\n  git log --invert-match --grep=\"uninteresting\"\n\n  This command will prune out all commits that match the grep pattern.\n\nHow it works:\n\n  The --invert-match flag sets invert_match, in rev_info.  This boolean\n  is later checked in commit_match() and if set it inverts the result of\n  grep_buffer().\n\nSigned-off-by: Bart Trojanowski <bart@jukie.net>\n---\n\nThis patch includes the Documentation update I missed in the first\npatch.\n\n Documentation/git-rev-list.txt |    6 ++++++\n revision.c                     |   11 ++++++++++-\n revision.h                     |    3 ++-\n 3 files changed, 18 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt\nindex 7cd0e89..f7a4891 100644\n--- a/Documentation/git-rev-list.txt\n+++ b/Documentation/git-rev-list.txt\n@@ -29,6 +29,7 @@ SYNOPSIS\n \t     [ \\--(author|committer|grep)=<pattern> ]\n \t     [ \\--regexp-ignore-case | \\-i ]\n \t     [ \\--extended-regexp | \\-E ]\n+\t     [ \\--invert-match ]\n \t     [ \\--date={local|relative|default|iso|rfc|short} ]\n \t     [ [\\--objects | \\--objects-edge] [ \\--unpacked ] ]\n \t     [ \\--pretty | \\--header ]\n@@ -238,6 +239,11 @@ limiting may be applied.\n \tConsider the limiting patterns to be extended regular expressions\n \tinstead of the default basic regular expressions.\n \n+--invert-match::\n+\n+\tShow those parts of history that do not match any of the regular\n+        expression patterns.\n+\n --remove-empty::\n \n \tStop when a given path disappears from the tree.\ndiff --git a/revision.c b/revision.c\nindex 33d092c..57b2d0f 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1182,6 +1182,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n \t\t\t\tregflags |= REG_ICASE;\n \t\t\t\tcontinue;\n \t\t\t}\n+\t\t\tif (!strcmp(arg, \"--invert-match\")) {\n+\t\t\t\trevs->invert_match = 1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n \t\t\tif (!strcmp(arg, \"--all-match\")) {\n \t\t\t\tall_match = 1;\n \t\t\t\tcontinue;\n@@ -1383,11 +1387,16 @@ 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+\tint result;\n+\n \tif (!opt->grep_filter)\n \t\treturn 1;\n-\treturn grep_buffer(opt->grep_filter,\n+\n+\tresult = 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+\n+\treturn opt->invert_match ? !result : result;\n }\n \n static struct commit *get_revision_1(struct rev_info *revs)\ndiff --git a/revision.h b/revision.h\nindex 98a0a8f..ead04a7 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -48,7 +48,8 @@ struct rev_info {\n \t\t\tparents:1,\n \t\t\treverse:1,\n \t\t\tcherry_pick:1,\n-\t\t\tfirst_parent_only:1;\n+\t\t\tfirst_parent_only:1,\n+\t\t\tinvert_match:1;\n \n \t/* Diff flags */\n \tunsigned int\tdiff:1,\n-- \n1.5.3.1.154.g734e65\n"},{"id":"53620","messageId":"7vbqbym15x.fsf@gitster.siamese.dyndns.org","threadId":"9946","inReplyTo":"20070920025225.GA12076@jukie.net","subject":"Re: [PATCH] Add git-rev-list --invert-match","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-09-20T04:05:46Z","receivedAt":"2007-09-20T04:05:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"> diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt\n> index 7cd0e89..f7a4891 100644\n> --- a/Documentation/git-rev-list.txt\n> +++ b/Documentation/git-rev-list.txt\n> @@ -29,6 +29,7 @@ SYNOPSIS\n>  \t     [ \\--(author|committer|grep)=<pattern> ]\n>  \t     [ \\--regexp-ignore-case | \\-i ]\n>  \t     [ \\--extended-regexp | \\-E ]\n> +\t     [ \\--invert-match ]\n>  \t     [ \\--date={local|relative|default|iso|rfc|short} ]\n>  \t     [ [\\--objects | \\--objects-edge] [ \\--unpacked ] ]\n>  \t     [ \\--pretty | \\--header ]\n> @@ -238,6 +239,11 @@ limiting may be applied.\n>  \tConsider the limiting patterns to be extended regular expressions\n>  \tinstead of the default basic regular expressions.\n>  \n> +--invert-match::\n> +\n> +\tShow those parts of history that do not match any of the regular\n> +        expression patterns.\n> +\n\nTabs and spaces?\n\nI am afraid people may not understand what \"any of the regular\nexpression patterns\" means.  How about being a bit more\nexplicit, like this?\n\n\tWhen filtering the commits based on log message (`--grep`)\n\tor people (`--author` and `--committer`), show commits\n\tthat do not match the criteria.\n"},{"id":"53636","messageId":"Pine.LNX.4.64.0709201132381.28395@racer.site","threadId":"9946","inReplyTo":"20070919202615.GK3076@jukie.net","subject":"Re: [PATCH] Add git-rev-list --invert-match","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-20T10:32:51Z","receivedAt":"2007-09-20T10:32:51Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Wed, 19 Sep 2007, Bart Trojanowski wrote:\n\n>   git log --invert-match --grep=\"uninteresting\"\n\nIMHO this is only half a solution.  Some of us want to say \"git log \n--grep=this --but-not-(in-the-whole-message) --grep=that\".\n\nYour code would be obsoleted by such a method (since it cannot be \nextended), and further would also benefit git-grep, not only git-log.\n\nCiao,\nDscho\n"},{"id":"53640","messageId":"20070920121806.GC12076@jukie.net","threadId":"9946","inReplyTo":"7vbqbym15x.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] Add git-rev-list --invert-match","fromName":"Bart Trojanowski","fromEmail":"bart@jukie.net","sentAt":"2007-09-20T12:18:06Z","receivedAt":"2007-09-20T12:18:06Z","isPatch":true,"sender":{"key":"bart@jukie.net","avatar":"https://avatars.githubusercontent.com/u/6721?v=4"},"body":"* Junio C Hamano <gitster@pobox.com> [070920 00:05]:\n> > +--invert-match::\n> > +\n> > +\tShow those parts of history that do not match any of the regular\n> > +        expression patterns.\n> > +\n> \n> Tabs and spaces?\n\nNoted.  Will fix after I address Johannes' request.\n\n> I am afraid people may not understand what \"any of the regular\n> expression patterns\" means.  How about being a bit more\n> explicit, like this?\n> \n> \tWhen filtering the commits based on log message (`--grep`)\n> \tor people (`--author` and `--committer`), show commits\n> \tthat do not match the criteria.\n\nYes, that sounds better.\n\nThanks,\n\n-Bart\n\n-- \n\t\t\t\tWebSig: http://www.jukie.net/~bart/sig/\n"},{"id":"53641","messageId":"20070920123849.GD12076@jukie.net","threadId":"9946","inReplyTo":"Pine.LNX.4.64.0709201132381.28395@racer.site","subject":"Re: [PATCH] Add git-rev-list --invert-match","fromName":"Bart Trojanowski","fromEmail":"bart@jukie.net","sentAt":"2007-09-20T12:38:49Z","receivedAt":"2007-09-20T12:38:49Z","isPatch":true,"sender":{"key":"bart@jukie.net","avatar":"https://avatars.githubusercontent.com/u/6721?v=4"},"body":"* Johannes Schindelin <Johannes.Schindelin@gmx.de> [070920 06:34]:\n> On Wed, 19 Sep 2007, Bart Trojanowski wrote:\n> >   git log --invert-match --grep=\"uninteresting\"\n> \n> IMHO this is only half a solution.  Some of us want to say \"git log \n> --grep=this --but-not-(in-the-whole-message) --grep=that\".\n\nI have noticed that unique negation flags are getting scarce... we\nalready have --reverse, --inverse, and --not  mean something elsewhere.\n--but-not maybe be good.\n\nI also agree that git-grep could use this extension.\n\nAnyways, I can see four solutions for adding \"show me this but not that\"\nfunctionality to git-rev-list:\n\n 1) adding a --but-not flag, as you suggested.  It separates positive\n    matches that precede it with negative matches that follow.\n\n  Example:\n    git log --grep=this --but-not --grep=that --committer=\"${MY_EMAIL}\"\n\n 2) Adding --not-grep, --not-author, --not-committer which add negative\n    matches.  Maybe even --grep!=PATTERN, --author!=PATTERN, ...\n\n  Example:\n    git log --grep=this --not-grep=that --committer!=\"${MY_EMAIL}\"\n\n 3) Extending the PATTERN we accept in --grep, --author, and --committer,\n    such that a prefix in the pattern tells us how to use the match:\n    --grep=!PATTERN\n\n  Example:\n    git log --grep=this --grep='!that' --committer=\"!${MY_EMAIL}\"\n\n 4) (going on a limb here) Can this kind of match be done with perl\n    regular expressions?  Maybe we could use --perl-regexp\n\n  Example:\n    I've got nothing :)\n\nPersonally I am currently in favour of 2.  It seems intuitive, and\nunlike option 3, the '!' cannot clash with the pattern.  Although 3 has\nthe bonus of allowing other flags like extended regexp, perl regexp,\ncase insensitive, negation, etc.\n\nComments?\n\n-Bart\n\n-- \n\t\t\t\tWebSig: http://www.jukie.net/~bart/sig/\n"},{"id":"53646","messageId":"Pine.LNX.4.64.0709201403540.28395@racer.site","threadId":"9946","inReplyTo":"20070920123849.GD12076@jukie.net","subject":"Re: [PATCH] Add git-rev-list --invert-match","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-20T13:12:54Z","receivedAt":"2007-09-20T13:12:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 20 Sep 2007, Bart Trojanowski wrote:\n\n> * Johannes Schindelin <Johannes.Schindelin@gmx.de> [070920 06:34]:\n> > On Wed, 19 Sep 2007, Bart Trojanowski wrote:\n> > >   git log --invert-match --grep=\"uninteresting\"\n> > \n> > IMHO this is only half a solution.  Some of us want to say \"git log \n> > --grep=this --but-not-(in-the-whole-message) --grep=that\".\n> \n> I have noticed that unique negation flags are getting scarce... we\n> already have --reverse, --inverse, and --not  mean something elsewhere.\n> --but-not maybe be good.\n> \n> I also agree that git-grep could use this extension.\n> \n> Anyways, I can see four solutions for adding \"show me this but not that\"\n> functionality to git-rev-list:\n> \n>  1) adding a --but-not flag, as you suggested.  It separates positive\n>     matches that precede it with negative matches that follow.\n> \n>   Example:\n>     git log --grep=this --but-not --grep=that --committer=\"${MY_EMAIL}\"\n\nThat is basically the approach taken by\n\nhttp://article.gmane.org/gmane.comp.version-control.git/51874\n\n(Yes, since you refused to search in the mailing list archives, I did it \nfor you... this time!)\n\n>  2) Adding --not-grep, --not-author, --not-committer which add negative\n>     matches.  Maybe even --grep!=PATTERN, --author!=PATTERN, ...\n> \n>   Example:\n>     git log --grep=this --not-grep=that --committer!=\"${MY_EMAIL}\"\n\nThat sounds sensible.\n\n>  3) Extending the PATTERN we accept in --grep, --author, and --committer,\n>     such that a prefix in the pattern tells us how to use the match:\n>     --grep=!PATTERN\n> \n>   Example:\n>     git log --grep=this --grep='!that' --committer=\"!${MY_EMAIL}\"\n\nNow you can no longer avoid defining clean semantics: what does that mean?  \nDoes it mean that there is _one_ line that does not have \"that\" in it, or \nis it the complete message?\n\nFurther, it probably makes sense to have the option to say _both_: \"Find \nme a commit that contains Bart in one line, but not Simpson, and that \ndoes not contain the word \"Sverdoolaege\" at all.\"\n\n>  4) (going on a limb here) Can this kind of match be done with perl\n>     regular expressions?  Maybe we could use --perl-regexp\n> \n>   Example:\n>     I've got nothing :)\n\nThis time you'll have to find the thread yourself.  Hint: search for pcre.\n\nCiao,\nDscho\n"},{"id":"53680","messageId":"7vtzppj9d1.fsf@gitster.siamese.dyndns.org","threadId":"9946","inReplyTo":"Pine.LNX.4.64.0709201403540.28395@racer.site","subject":"Re: [PATCH] Add git-rev-list --invert-match","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-09-20T21:49:14Z","receivedAt":"2007-09-20T21:49:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Further, it probably makes sense to have the option to say _both_: \"Find \n> me a commit that contains Bart in one line, but not Simpson, and that \n> does not contain the word \"Sverdoolaege\" at all.\"\n\nYeah.  And I think we would also want to have that at the\nunderlying \"grep\" layer wouldn't we?\n"},{"id":"53682","messageId":"Pine.LNX.4.64.0709202252570.28395@racer.site","threadId":"9946","inReplyTo":"7vtzppj9d1.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] Add git-rev-list --invert-match","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-20T21:54:46Z","receivedAt":"2007-09-20T21:54:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 20 Sep 2007, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > Further, it probably makes sense to have the option to say _both_: \n> > \"Find me a commit that contains Bart in one line, but not Simpson, and \n> > that does not contain the word \"Sverdoolaege\" at all.\"\n> \n> Yeah.  And I think we would also want to have that at the\n> underlying \"grep\" layer wouldn't we?\n\nThat's what I tried to get at...  Already on IRC, I tried to direct the \nattention to that thread on the list...  There I had an ugly patch, which \nwas incomplete, mainly because I did not sort out the semantics first.\n\nBut the idea was to put this into grep.c so that both git-log and git-grep \ncould benefit from that feature.\n\nCiao,\nDscho\n"},{"id":"53694","messageId":"20070921041821.GA28245@coredump.intra.peff.net","threadId":"9946","inReplyTo":"Pine.LNX.4.64.0709201403540.28395@racer.site","subject":"Re: [PATCH] Add git-rev-list --invert-match","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2007-09-21T04:18:21Z","receivedAt":"2007-09-21T04:18:21Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Sep 20, 2007 at 02:12:54PM +0100, Johannes Schindelin wrote:\n\n> Further, it probably makes sense to have the option to say _both_: \"Find \n> me a commit that contains Bart in one line, but not Simpson, and that \n> does not contain the word \"Sverdoolaege\" at all.\"\n\nThis is perhaps a little hack-ish compared to better grep support in the\ncore, but I find complex logic through command line options to be\nsomewhat unreadable. I prefer something more Perl-ish like this:\n\n  git-revgrep 'message =~ /bart/i\n               && message !~ /Simpson/\n               && author_name !~ /Sverdoolaege/'\n\nor if you want to get complex:\n\n  git-revgrep '\n    return 0 if message =~ /Sverdoolaege/;\n    while(my $line = message =~ /^(.*bart.*)/gmi) {\n      return 1 if $line !~ /Simpson/;\n    }\n    return 0;'\n\nwhere revgrep is the script below:\n\n-- >8 --\n#!/usr/bin/perl\nuse strict;\n\nmy $matcher = shift || '';\nmy $matcher_sub = eval \"sub { $matcher }\";\ndie $@ if $@;\n\nmy $input = do {\n  if(@ARGV == 1 && $ARGV[0] eq '-') {\n    \\*STDIN;\n  }\n  else {\n    open(my $fh, '-|', qw(git log --pretty=raw), @ARGV)\n      or die \"unable to open pipe to git log: $!\";\n    $fh;\n  }\n};\n\nour $commit;\nwhile(<$input>) {\n  if(/^commit /) {\n    try_match() if $commit;\n    $commit = $_;\n  }\n  else {\n    $commit .= $_;\n  }\n}\ntry_match() if $commit;\nexit 0;\n\nsub try_match {\n  if($matcher_sub->()) {\n    print STDOUT $commit;\n  }\n}\n\nsub parse_person { $_[0] =~ /([^<]*) <([^>]*)> (.*)/ }\nsub parse_author { return parse_person($commit =~ /^author (.*)/m) }\nsub parse_committer { return parse_person($commit =~ /^committer (.*)/m) }\nsub author_name { return (parse_author)[0] }\nsub author_email { return (parse_author)[1] }\nsub author_time { return (parse_author)[2] }\nsub committer_name { return (parse_committer)[0] }\nsub committer_email { return (parse_committer)[1] }\nsub committer_time { return (parse_committer)[2] }\nsub message { return ($commit =~ /^( +.*?^$)/ms)[0] }\n"},{"id":"53705","messageId":"Pine.LNX.4.64.0709210904210.28395@racer.site","threadId":"9946","inReplyTo":"20070921041821.GA28245@coredump.intra.peff.net","subject":"Re: [PATCH] Add git-rev-list --invert-match","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-09-21T09:10:12Z","receivedAt":"2007-09-21T09:10:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Fri, 21 Sep 2007, Jeff King wrote:\n\n> On Thu, Sep 20, 2007 at 02:12:54PM +0100, Johannes Schindelin wrote:\n> \n> > Further, it probably makes sense to have the option to say _both_: \"Find \n> > me a commit that contains Bart in one line, but not Simpson, and that \n> > does not contain the word \"Sverdoolaege\" at all.\"\n> \n> This is perhaps a little hack-ish compared to better grep support in the\n> core, but I find complex logic through command line options to be\n> somewhat unreadable. I prefer something more Perl-ish like this:\n> \n>   git-revgrep 'message =~ /bart/i\n>                && message !~ /Simpson/\n>                && author_name !~ /Sverdoolaege/'\n\nIndeed, it has a certain elegance to it.  However, I cannot specify any \nrev-list options with your method, not even \"--stat\" or \n\"--pretty=format:%H(%s)\".\n\nSo while your script is a good interim solution, I'd like to see a \ngeneric grep support for this feature.\n\nCiao,\nDscho\n"},{"id":"53706","messageId":"20070921091902.GA1870@coredump.intra.peff.net","threadId":"9946","inReplyTo":"Pine.LNX.4.64.0709210904210.28395@racer.site","subject":"Re: [PATCH] Add git-rev-list --invert-match","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2007-09-21T09:19:03Z","receivedAt":"2007-09-21T09:19:03Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Sep 21, 2007 at 10:10:12AM +0100, Johannes Schindelin wrote:\n\n> Indeed, it has a certain elegance to it.  However, I cannot specify any \n> rev-list options with your method, not even \"--stat\" or \n> \"--pretty=format:%H(%s)\".\n\nActually, you can pass --stat, but it erroneously is counted as part of\nthe message due to a parsing bug in the script. However you are pointing\nout a larger issue, which is that the format must be parseable by the\nscript. As a final step, the script could turn the output into a list of\ncommits, and pipe them into a git command which pretty-printed in the\ndesired format.\n\nBut that is doubly hack-ish, and slow to boot.\n\n> So while your script is a good interim solution, I'd like to see a \n> generic grep support for this feature.\n\nYes, I don't seriously propose it for mainline git. However, I quite\nlike the interface; unfortunately, to get it to work smoothly (and\nefficiently!) the perl interpreter would need to be embedded inside\ngit-log. And I think I know what you will say about that... :)\n\n-Peff\n"},{"id":"53779","messageId":"20070922013834.GU12076@jukie.net","threadId":"9946","inReplyTo":"Pine.LNX.4.64.0709202252570.28395@racer.site","subject":"[RFC] Add git-rev-list --not-(author|committer|grep)!=pattern","fromName":"Bart Trojanowski","fromEmail":"bart@jukie.net","sentAt":"2007-09-22T01:38:34Z","receivedAt":"2007-09-22T01:38:34Z","isPatch":false,"sender":{"key":"bart@jukie.net","avatar":"https://avatars.githubusercontent.com/u/6721?v=4"},"body":"Hi Johannes, Junio,\n\nI have worked out what I would like to see for git-log and friends.  It\naddresses one of the requirements which was raised by Johannes.\n\n* Johannes Schindelin <Johannes.Schindelin@gmx.de> [070920 06:34]:\n> IMHO this is only half a solution.  Some of us want to say \"git log \n> --grep=this --but-not-(in-the-whole-message) --grep=that\".\n\nThe way in which you get that information is:\n\n        git-log --all-match \\\n                --grep=Merge \\\n                --not-committer=gitster \\\n                --author!=Shawn\n\nI have tested it with and without --all-match and seems to work as I\nwould expect.  git-grep -v is still broken, but I will submit that in\nanother patch.\n\nI plan on removing opt->invert_final_result unless you guys thing it can\nbe used for something else.\n\nThis is my first crack at it, and I would like to get some feedback on\nthe direction I've taken.  I plan on adding test cases before I submit\nthe final patch.\n\nThanks for reviewing.\n\n-Bart\n\n   ----8<----\n\n>From e584ed4399e24bcd21991d1a9edc5e33f22a4f0c Mon Sep 17 00:00:00 2001\nFrom: Bart Trojanowski <bart@jukie.net>\nDate: Fri, 21 Sep 2007 11:24:37 -0400\nSubject: [PATCH] Add git-rev-list --not-(author|committer|grep)!=pattern\n\nThis patch adds the concept of negative grep patterns, which only\ngenerate a grepping hit when the pattern did not match any lines\nin the whole file (or buffer).\n\nThis makes it possible to run\n\n        git-rev-list --grep=foo --not-committer=bar\n        git-rev-list --all-match --grep=foo --not-author=bar\n\nAs with regular git-rev-list filtering, the default is an inclusive\nfilter.  So the first line matches those revisions that either have\nfoo in the message, or do not have bar as the comitter.  The second\nform locates all revisions that contain foo in the message, but not\nbar as the author.\n\nThe --not-* options have short forms:\n\n        git-rev-list --grep!=bar\n        git-rev-list --committer!=bar\n        git-rev-list --author!=bar\n\n...which is provided by a invertible_prefix_cmp() util function which\ncould be used for other --not-*=<arg> options in the future.\n\nSigned-off-by: Bart Trojanowski <bart@jukie.net>\n---\n Documentation/git-rev-list.txt |   20 ++++++++++++\n builtin-grep.c                 |   16 +++++-----\n git-compat-util.h              |   42 +++++++++++++++++++++++++\n grep.c                         |   65 +++++++++++++++++++++++++++++++++++----\n grep.h                         |   10 +++++-\n revision.c                     |   36 +++++++++++++++-------\n 6 files changed, 160 insertions(+), 29 deletions(-)\n\ndiff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt\nindex 7cd0e89..326495d 100644\n--- a/Documentation/git-rev-list.txt\n+++ b/Documentation/git-rev-list.txt\n@@ -27,6 +27,8 @@ SYNOPSIS\n \t     [ \\--cherry-pick ]\n \t     [ \\--encoding[=<encoding>] ]\n \t     [ \\--(author|committer|grep)=<pattern> ]\n+\t     [ \\--not-(author|committer|grep)=<pattern> ]\n+\t     [ \\--all-match ]\n \t     [ \\--regexp-ignore-case | \\-i ]\n \t     [ \\--extended-regexp | \\-E ]\n \t     [ \\--date={local|relative|default|iso|rfc|short} ]\n@@ -229,6 +231,18 @@ limiting may be applied.\n \tLimit the commits output to ones with log message that\n \tmatches the specified pattern (regular expression).\n \n+--not-author='pattern', --not-committer='pattern', --not-grep='pattern'::\n+\n+\tWhen filtering the commits based on log message (`--not-grep`)\n+\tor people (`--not-author` and `--not-committer`), show commits that\n+\tdo not match the criteria specified.\n+\n+--all-match::\n+\n+\tDisplay commits which match all criteria; default is to show\n+\tcommits that match at least one of `--grep`, `--committer`, and\n+\t`--author`.\n+\n -i, --regexp-ignore-case::\n \n \tMatch the regexp limiting patterns without regard to letters case.\n@@ -238,6 +252,12 @@ limiting may be applied.\n \tConsider the limiting patterns to be extended regular expressions\n \tinstead of the default basic regular expressions.\n \n+--invert-match::\n+\n+\tWhen filtering the commits based on log message (`--grep`) or people\n+\t(`--author` and `--committer`), show commits that do not match the\n+\tcriteria.\n+\n --remove-empty::\n \n \tStop when a given path disappears from the tree.\ndiff --git a/builtin-grep.c b/builtin-grep.c\nindex c7b45c4..90bc6f8 100644\n--- a/builtin-grep.c\n+++ b/builtin-grep.c\n@@ -547,7 +547,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t}\n \t\tif (!strcmp(\"-v\", arg) ||\n \t\t    !strcmp(\"--invert-match\", arg)) {\n-\t\t\topt.invert = 1;\n+\t\t\topt.invert_line_match = 1;\n \t\t\tcontinue;\n \t\t}\n \t\tif (!strcmp(\"-E\", arg) ||\n@@ -651,7 +651,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t\t\t\tcontinue;\n \t\t\t\tappend_grep_pattern(&opt, xstrdup(buf),\n \t\t\t\t\t\t    argv[1], ++lno,\n-\t\t\t\t\t\t    GREP_PATTERN);\n+\t\t\t\t\t\t    GREP_PATTERN, 0);\n \t\t\t}\n \t\t\tfclose(patterns);\n \t\t\targv++;\n@@ -660,24 +660,24 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t}\n \t\tif (!strcmp(\"--not\", arg)) {\n \t\t\tappend_grep_pattern(&opt, arg, \"command line\", 0,\n-\t\t\t\t\t    GREP_NOT);\n+\t\t\t\t\t    GREP_NOT, 0);\n \t\t\tcontinue;\n \t\t}\n \t\tif (!strcmp(\"--and\", arg)) {\n \t\t\tappend_grep_pattern(&opt, arg, \"command line\", 0,\n-\t\t\t\t\t    GREP_AND);\n+\t\t\t\t\t    GREP_AND, 0);\n \t\t\tcontinue;\n \t\t}\n \t\tif (!strcmp(\"--or\", arg))\n \t\t\tcontinue; /* no-op */\n \t\tif (!strcmp(\"(\", arg)) {\n \t\t\tappend_grep_pattern(&opt, arg, \"command line\", 0,\n-\t\t\t\t\t    GREP_OPEN_PAREN);\n+\t\t\t\t\t    GREP_OPEN_PAREN, 0);\n \t\t\tcontinue;\n \t\t}\n \t\tif (!strcmp(\")\", arg)) {\n \t\t\tappend_grep_pattern(&opt, arg, \"command line\", 0,\n-\t\t\t\t\t    GREP_CLOSE_PAREN);\n+\t\t\t\t\t    GREP_CLOSE_PAREN, 0);\n \t\t\tcontinue;\n \t\t}\n \t\tif (!strcmp(\"--all-match\", arg)) {\n@@ -688,7 +688,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t\tif (1 < argc) {\n \t\t\t\tappend_grep_pattern(&opt, argv[1],\n \t\t\t\t\t\t    \"-e option\", 0,\n-\t\t\t\t\t\t    GREP_PATTERN);\n+\t\t\t\t\t\t    GREP_PATTERN, 0);\n \t\t\t\targv++;\n \t\t\t\targc--;\n \t\t\t\tcontinue;\n@@ -711,7 +711,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t/* First unrecognized non-option token */\n \t\tif (!opt.pattern_list) {\n \t\t\tappend_grep_pattern(&opt, arg, \"command line\", 0,\n-\t\t\t\t\t    GREP_PATTERN);\n+\t\t\t\t\t    GREP_PATTERN, 0);\n \t\t\tbreak;\n \t\t}\n \t\telse {\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 1bfbdeb..db1c9d4 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -362,6 +362,48 @@ static inline int prefixcmp(const char *str, const char *prefix)\n \treturn strncmp(str, prefix, strlen(prefix));\n }\n \n+static inline int invertible_prefix_cmp(const char *str, const char *prefix,\n+\t\tconst char **pattern, int *inverted)\n+{\n+\tint prefix_len = strlen(prefix);\n+\tint rc;\n+\n+\t*inverted = 0;\n+\t*pattern = str + prefix_len;\n+\n+\trc = strncmp(str, prefix, prefix_len-1);\n+\tif (rc)\n+\t\tgoto try_alternate_form;\n+\n+\tif (prefix[prefix_len-1] != '=')\n+\t\treturn strncmp(str, prefix, strlen(prefix));\n+\n+\tif (str[prefix_len-1] == '=')\n+\t\treturn 0;\n+\n+\tif (str[prefix_len-1] != '!'\n+\t\t\t|| str[prefix_len] != '=')\n+\t\treturn 1;\n+\n+\t*inverted = 1;\n+\t(*pattern) ++;\n+\treturn 0;\n+\n+try_alternate_form:\n+\tif (prefix[0]!='-' || prefix[1]!='-')\n+\t\treturn rc;\n+\n+\tif (strncmp (str, \"--not-\", 6))\n+\t\treturn rc;\n+\n+\tif (strncmp (str+6, prefix+2, prefix_len-2))\n+\t\treturn rc;\n+\n+\t*inverted = 1;\n+\t(*pattern) += 4;\n+\treturn 0;\n+}\n+\n static inline int strtoul_ui(char const *s, int base, unsigned int *result)\n {\n \tunsigned long ul;\ndiff --git a/grep.c b/grep.c\nindex f67d671..877f0ed 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -3,12 +3,16 @@\n #include \"xdiff-interface.h\"\n \n void append_grep_pattern(struct grep_opt *opt, const char *pat,\n-\t\t\t const char *origin, int no, enum grep_pat_token t)\n+\t\t\t const char *origin, int no, enum grep_pat_token t,\n+\t\t\t int is_negative)\n {\n \tstruct grep_pat *p = xcalloc(1, sizeof(*p));\n \tp->pattern = pat;\n \tp->origin = origin;\n \tp->no = no;\n+\tp->is_negative = !!is_negative;\n+\tif (p->is_negative)\n+\t\topt->extended = 1;\n \tp->token = t;\n \t*opt->pattern_tail = p;\n \topt->pattern_tail = &p->next;\n@@ -253,10 +257,30 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol\n \tint at_true_bol = 1;\n \tregmatch_t pmatch[10];\n \n+\tif (ctx == GREP_CONTEXT_FINALIZE)\n+\t\treturn p->is_negative\n+\t\t\t? !p->did_match\n+\t\t\t: p->did_match;\n+\n+\tif (ctx == GREP_CONTEXT_INITIALIZE) {\n+\t\tp->did_match = 0;\n+\t\treturn 0;\n+\t}\n+\n \tif ((p->token != GREP_PATTERN) &&\n \t    ((p->token == GREP_PATTERN_HEAD) != (ctx == GREP_CONTEXT_HEAD)))\n \t\treturn 0;\n \n+\t/* Shallow searches can stop matching lines early and just reuse the\n+\t * previous match result because we are only interested if there was\n+\t * at least one match.  For positive matches, we return 1 because once\n+\t * we match the result will always be successful; conversely, for\n+\t * negative matches once the base patter was found the result will\n+\t * always be 0.\n+\t */\n+\tif (opt->shallow && p->did_match)\n+\t\treturn !p->is_negative;\n+\n  again:\n \tif (!opt->fixed) {\n \t\tregex_t *exp = &p->regexp;\n@@ -298,6 +322,15 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol\n \t\t\tgoto again;\n \t\t}\n \t}\n+\n+\tif (hit)\n+\t\tp->did_match = 1;\n+\n+\t/* a negative pattern cannot match until the very end when we know\n+\t * there have been no matches on any of the lines */\n+\tif (p->is_negative)\n+\t\treturn 0;\n+\n \treturn hit;\n }\n \n@@ -382,6 +415,10 @@ 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+\t/* Reset flags in all grep_pat objects\n+\t */\n+\tmatch_line (opt, NULL, NULL, GREP_CONTEXT_INITIALIZE, collect_hits);\n+\n \tif (buffer_is_binary(buf, size)) {\n \t\tswitch (opt->binary) {\n \t\tcase GREP_BINARY_DEFAULT:\n@@ -414,6 +451,14 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,\n \t\thit = match_line(opt, bol, eol, ctx, collect_hits);\n \t\t*eol = ch;\n \n+\t\t/* on the last line we can determine the hit of negative\n+\t\t * patterns\n+\t\t */\n+\t\tif (left < 2) {\n+\t\t\thit = match_line (opt, NULL, NULL, GREP_CONTEXT_FINALIZE, collect_hits);\n+\t\t}\n+\n+\n \t\tif (collect_hits)\n \t\t\tgoto next_line;\n \n@@ -421,7 +466,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,\n \t\t * that do not have either, so inversion should\n \t\t * be done outside.\n \t\t */\n-\t\tif (opt->invert)\n+\t\tif (opt->invert_line_match)\n \t\t\thit = !hit;\n \t\tif (opt->unmatch_name_only) {\n \t\t\tif (hit)\n@@ -545,12 +590,16 @@ static int chk_hit_marker(struct grep_expr *x)\n \n int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size)\n {\n+\tint result;\n+\n \t/*\n \t * we do not have to do the two-pass grep when we do not check\n \t * buffer-wide \"all-match\".\n \t */\n-\tif (!opt->all_match)\n-\t\treturn grep_buffer_1(opt, name, buf, size, 0);\n+\tif (!opt->all_match) {\n+\t\tresult = grep_buffer_1(opt, name, buf, size, 0);\n+\t\tgoto done;\n+\t}\n \n \t/* Otherwise the toplevel \"or\" terms hit a bit differently.\n \t * We first clear hit markers from them.\n@@ -558,8 +607,10 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long\n \tclr_hit_marker(opt->pattern_expression);\n \tgrep_buffer_1(opt, name, buf, size, 1);\n \n-\tif (!chk_hit_marker(opt->pattern_expression))\n-\t\treturn 0;\n+\tresult = 0;\n+\tif (chk_hit_marker(opt->pattern_expression))\n+\t\tresult = grep_buffer_1(opt, name, buf, size, 0);\n \n-\treturn grep_buffer_1(opt, name, buf, size, 0);\n+done:\n+\treturn opt->invert_final_result ? !result : result;\n }\ndiff --git a/grep.h b/grep.h\nindex d252dd2..adb5b94 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -13,14 +13,18 @@ enum grep_pat_token {\n };\n \n enum grep_context {\n+\tGREP_CONTEXT_INITIALIZE,\n \tGREP_CONTEXT_HEAD,\n \tGREP_CONTEXT_BODY,\n+\tGREP_CONTEXT_FINALIZE,\n };\n \n struct grep_pat {\n \tstruct grep_pat *next;\n \tconst char *origin;\n \tint no;\n+\tunsigned is_negative:1;\n+\tunsigned did_match:1;\n \tenum grep_pat_token token;\n \tconst char *pattern;\n \tregex_t regexp;\n@@ -53,8 +57,10 @@ struct grep_opt {\n \tint prefix_length;\n \tregex_t regexp;\n \tunsigned linenum:1;\n-\tunsigned invert:1;\n+\tunsigned invert_line_match:1;\n+\tunsigned invert_final_result:1;\n \tunsigned status_only:1;\n+\tunsigned shallow:1;\t\t// stop testing patterns after hit\n \tunsigned name_only:1;\n \tunsigned unmatch_name_only:1;\n \tunsigned count:1;\n@@ -73,7 +79,7 @@ struct grep_opt {\n \tunsigned post_context;\n };\n \n-extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t);\n+extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t, int is_negative);\n extern void compile_grep_patterns(struct grep_opt *opt);\n extern void free_grep_patterns(struct grep_opt *opt);\n extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size);\ndiff --git a/revision.c b/revision.c\nindex 33d092c..721ef52 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -827,20 +827,23 @@ 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, int is_negative,\n+\t\tenum grep_pat_token what)\n {\n \tif (!revs->grep_filter) {\n \t\tstruct grep_opt *opt = xcalloc(1, sizeof(*opt));\n \t\topt->status_only = 1;\n+\t\topt->shallow = 1;\n \t\topt->pattern_tail = &(opt->pattern_list);\n \t\topt->regflags = REG_NEWLINE;\n \t\trevs->grep_filter = opt;\n \t}\n \tappend_grep_pattern(revs->grep_filter, ptn,\n-\t\t\t    \"command line\", 0, what);\n+\t\t\t    \"command line\", 0, what, is_negative);\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\tconst char *pattern, int is_negative)\n {\n \tchar *pat;\n \tconst char *prefix;\n@@ -855,12 +858,13 @@ 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+\tadd_grep(revs, pat, is_negative, GREP_PATTERN_HEAD);\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\tint is_negative)\n {\n-\tadd_grep(revs, pattern, GREP_PATTERN_BODY);\n+\tadd_grep(revs, pattern, is_negative, GREP_PATTERN_BODY);\n }\n \n static void add_ignore_packed(struct rev_info *revs, const char *name)\n@@ -887,6 +891,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n \tint left = 1;\n \tint all_match = 0;\n \tint regflags = 0;\n+\tint inverted;\n+\tconst char *pattern;\n \n \t/* First, search for \"--\" */\n \tseen_dashdash = 0;\n@@ -1160,16 +1166,21 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n \t\t\t/*\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\tif (!invertible_prefix_cmp(arg, \"--author=\",\n+\t\t\t\t\t\t&pattern, &inverted)) {\n+\t\t\t\tadd_header_grep(revs, \"author\", pattern,\n+\t\t\t\t\t\tinverted);\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\tif (!invertible_prefix_cmp(arg, \"--committer=\",\n+\t\t\t\t\t\t&pattern, &inverted)) {\n+\t\t\t\tadd_header_grep(revs, \"committer\", pattern,\n+\t\t\t\t\t\tinverted);\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\tif (!invertible_prefix_cmp (arg, \"--grep=\",\n+\t\t\t\t\t\t&pattern, &inverted)) {\n+\t\t\t\tadd_message_grep(revs, pattern, inverted);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--extended-regexp\") ||\n@@ -1385,6 +1396,7 @@ static int commit_match(struct commit *commit, struct rev_info *opt)\n {\n \tif (!opt->grep_filter)\n \t\treturn 1;\n+\n \treturn 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-- \n1.5.3.1.154.g734e65\n"}]}