How to invert `git log --grep=<pattern>` or How to show git logs that don't match a pattern
Asked Answered
J

8

37

I want to use git log to show all commits that do not match a given pattern. I know I can use the following to show all commits that do match a pattern:

git log --grep=<pattern>

How do I invert the sense of matching?

I am trying to ignore commits that have "bumped to version ..." in the message.

EDIT: I want my final output to be pretty verbose. e.g. git log --pretty --stat. So output from git log --format=oneline won't work for me.

Justiceship answered 9/4, 2011 at 1:5 Comment(3)
Follow up question on mailing list groups.google.com/group/git-users/browse_thread/thread/…Wellbalanced
Git 2.3.1+ (Q1/Q2 2015) will introduce git log --invert-grep=<pattern>, which should answer your question. See my answer below.Musing
git log --grep=<string> should be released in Git 2.4 (Q2 2015): github.com/git/git/blob/…Musing
P
16

Generate a list of all commits, subtract those whose log messages contain the offending pattern, and feed the result to git log with your desired options. In the final stage, a couple of options to git log are handy:

--stdin
In addition to the commit listed on the command line, read them from the standard input.

--no-walk
Only show the given revs, but do not traverse their ancestors.

You can do it with a single pipeline and process substitution.

#! /bin/bash

if (( $# < 1 )); then
  echo >&2 "Usage: $0 pattern [<since>..<until>]"
  exit 1
fi

pattern=$1
shift

git log --format=%H $@ |
  grep -v -f <(git log --format=%H "--grep=$pattern" $@) |
  git log --pretty --stat --stdin --no-walk

If you don't want to use bash, you could do it with Perl.

#! /usr/bin/env perl

use strict;
use warnings;
no warnings "exec";

sub usage { "Usage: $0 pattern\n" }

sub commits_to_omit {
  my($pattern) = @_;

  open my $fh, "-|", "git", "log", "--grep=$pattern", "--format=%H", @ARGV
    or die "$0: exec: $!";
  my %omit = map +($_ => 1), <$fh>;
  %omit;
}

die usage unless @ARGV >= 1;
my $pattern = shift;

my %omit = commits_to_omit $pattern;

open my $all, "-|", "git", "log", "--format=%H", @ARGV
  or die "$0: exec: $!";

open my $out, "|-", "git", "log", "--pretty", "--stat", "--stdin", "--no-walk"
  or die "$0: exec: $!";

while (<$all>) {
  print $out $_ unless $omit{$_};
}

Assuming one of the above is in your PATH as git-log-vgrep and with a history of the form

$ git lola
* b0f2a28 (tmp, feature1) D
* 68f87b0 C
* d311c65 B
* a092126 A
| * 83052e6 (HEAD, origin/master, master) Z
| * 90c3d28 Y
| * 4165a42 X
| * 37844cb W
|/  
* f8ba9ea V

we could say

$ git log-vgrep X

to get Z, Y, W, and V.

You can also log other branches, so

$ git log-vgrep A tmp

gives D, C, B, and V; and

$ git log-vgrep C tmp~2..tmp

yields just D.

One limitation of the above implementations is if you use a pattern that matches all commits, e.g., . or ^, then you'll get HEAD. This is how git log works:

$ git log --stdin --no-walk --pretty=oneline </dev/null
83052e62f0dc1c6ddfc1aff3463504a4bf23e3c4 Z
Palomo answered 9/4, 2011 at 17:2 Comment(0)
M
36

This will be possible with Git 2.4+ (Q2 2015): see commit 22dfa8a by Christoph Junghans (junghans):

log: teach --invert-grep option

"git log --grep=<string>" shows only commits with messages that match the given string, but sometimes it is useful to be able to show only commits that do not have certain messages (e.g. "show me ones that are not FIXUP commits").

Originally, we had the invert-grep flag in grep_opt, but because "git grep --invert-grep" does not make sense except in conjunction with "--files-with-matches", which is already covered by "--files-without-matches", it was moved it to revisions structure.
To have the flag there expresses the function to the feature better.

When the newly inserted two tests run, the history would have commits with messages "initial", "second", "third", "fourth", "fifth", "sixth" and "Second", committed in this order.
The commits that does not match either "th" or "Sec" is "second" and "initial". For the case insensitive case only "initial" matches.

--invert-grep

Limit the commits output to ones with log message that do not match the pattern specified with --grep=<pattern>.

Example:

I first grep message with "sequencer" in them:

vonc@voncm C:\Users\vonc\prog\git\git

> git log -2 --pretty="tformat:%s" --grep=sequencer
Merge branch 'js/sequencer-wo-die'
sequencer: ensure to release the lock when we could not read the index

If I want messages with no sequencer:

> git log -2 --pretty="tformat:%s" --grep=sequencer --invert-grep
Second batch for 2.11
Merge branch 'js/git-gui-commit-gpgsign'

Warning: this inverts only patterns specified by --grep.
It will not invert anymore header matches, like --author, --committer, starting Git 2.35+ (Q1 2022).
See an example in "equivalence of: git log --exclude-author?".


Note that git -P log -1 --invert-grep could segfault:

Giving "--invert-grep" and "--all-match" without "--grep" to the "git log"(man) command resulted in an attempt to access grep pattern expression structure that has not been allocated, which has been corrected with Git 2.39 (Q4 2022).

See commit db84376 (11 Oct 2022) by Ævar Arnfjörð Bjarmason (avar).
(Merged by Junio C Hamano -- gitster -- in commit 91d3d7e, 21 Oct 2022)

grep.c: remove "extended" in favor of "pattern_expression", fix segfault

Reported-by: orygaw
Signed-off-by: Ævar Arnfjörð Bjarmason

Since 79d3696 ("git-grep: boolean expression on pattern matching.", 2006-06-30, Git v1.4.2-rc1 -- merge) the "pattern_expression" member has been used for complex queries (AND/OR...), with "pattern_list" being used for the simple OR queries.
Since then we've used both "pattern_expression" and its associated boolean "extended" member to see if we have a complex expression.

Since f41fb66 (revisions API: have release_revisions() release , 2022-04-13, Git v2.37.0-rc0 -- merge listed in batch #8) (revisions API: have release_revisions() release "grep_filter", 2022-04-13) we've had a subtle bug relating to that: If we supplied options that were only used for "complex queries", but didn't supply the query itself we'd set "opt->extended", but would have a NULL "pattern_expression".
As a result these would segfault as we tried to call "free_grep_patterns()" from "release_revisions()":

git -P log -1 --invert-grep
git -P log -1 --all-match

The root cause of this is that we were conflating the state management we needed in "compile_grep_patterns()" itself with whether or not we had an "opt->pattern_expression" later on.

In this cases as we're going through "compile_grep_patterns()" we have no "opt->pattern_list" but have "opt->no_body_match" or "opt->all_match".
So we'd set "opt->extended = 1", but not "return" on "opt->extended" as that's an "else if" in the same "if" statement.

That behavior is intentional and required, as the common case is that we have an "opt->pattern_list" that we're about to parse into the "opt->pattern_expression".

But we don't need to keep track of this "extended" flag beyond the state management in compile_grep_patterns() itself.
It needs it, but once we're out of that function we can rely on "opt->pattern_expression" being non-NULL instead for using these extended patterns.

Musing answered 15/2, 2015 at 4:47 Comment(4)
Hmmm, not working for me: "unrecognized argument: --invert-grep. This is with git 2.9.2.windows.1.Obligatory
@KentBoogaart What command did you type? I just used successfully (windows, git 2.10.1) git log -5 --pretty="tformat:%s" --grep=sequencer in the git/git cloned repo: it work just fine.Musing
@Kent I have edited the answer to illustrate the invert-grep works just fine as well. Again, what exact command did you use?Musing
@Musing ah, yes, I didn't realise it was a switch. I was doing --invert-grep=foo. That works for me now, though it's less useful than I'd hoped. Being able to specify "contains this AND doesn't contain this" in one command would be nice. Thanks!Obligatory
P
16

Generate a list of all commits, subtract those whose log messages contain the offending pattern, and feed the result to git log with your desired options. In the final stage, a couple of options to git log are handy:

--stdin
In addition to the commit listed on the command line, read them from the standard input.

--no-walk
Only show the given revs, but do not traverse their ancestors.

You can do it with a single pipeline and process substitution.

#! /bin/bash

if (( $# < 1 )); then
  echo >&2 "Usage: $0 pattern [<since>..<until>]"
  exit 1
fi

pattern=$1
shift

git log --format=%H $@ |
  grep -v -f <(git log --format=%H "--grep=$pattern" $@) |
  git log --pretty --stat --stdin --no-walk

If you don't want to use bash, you could do it with Perl.

#! /usr/bin/env perl

use strict;
use warnings;
no warnings "exec";

sub usage { "Usage: $0 pattern\n" }

sub commits_to_omit {
  my($pattern) = @_;

  open my $fh, "-|", "git", "log", "--grep=$pattern", "--format=%H", @ARGV
    or die "$0: exec: $!";
  my %omit = map +($_ => 1), <$fh>;
  %omit;
}

die usage unless @ARGV >= 1;
my $pattern = shift;

my %omit = commits_to_omit $pattern;

open my $all, "-|", "git", "log", "--format=%H", @ARGV
  or die "$0: exec: $!";

open my $out, "|-", "git", "log", "--pretty", "--stat", "--stdin", "--no-walk"
  or die "$0: exec: $!";

while (<$all>) {
  print $out $_ unless $omit{$_};
}

Assuming one of the above is in your PATH as git-log-vgrep and with a history of the form

$ git lola
* b0f2a28 (tmp, feature1) D
* 68f87b0 C
* d311c65 B
* a092126 A
| * 83052e6 (HEAD, origin/master, master) Z
| * 90c3d28 Y
| * 4165a42 X
| * 37844cb W
|/  
* f8ba9ea V

we could say

$ git log-vgrep X

to get Z, Y, W, and V.

You can also log other branches, so

$ git log-vgrep A tmp

gives D, C, B, and V; and

$ git log-vgrep C tmp~2..tmp

yields just D.

One limitation of the above implementations is if you use a pattern that matches all commits, e.g., . or ^, then you'll get HEAD. This is how git log works:

$ git log --stdin --no-walk --pretty=oneline </dev/null
83052e62f0dc1c6ddfc1aff3463504a4bf23e3c4 Z
Palomo answered 9/4, 2011 at 17:2 Comment(0)
H
8

A relatively simple method with a lot of flexibility is to use git log with the -z option piped to awk. The -z option adds nulls between commit records, and so makes it easy parse with awk:

git log --color=always -z | awk -v RS=\\0

(color=always is required to keep coloring when the output is a pipe). Then, its simple to add any boolean expression you want that works on each field. For example, this will print all entries where the author email is not from fugly.com, and the day of the commit was Sunday:

git log --color=always -z | awk -v RS=\\0 '!/Author:.*fugly.com>/ && /Date:.* Sun /'

Another nice thing is its you can add in any formatting option or revision range to the git log, and it still works.

One last thing, if you want to paginate it, use "less -r" to keep the colors.

EDIT: changed to use -v in awk to make it a little simpler.

Horseshit answered 19/10, 2012 at 18:44 Comment(1)
with perl: git log -z . |perl -ln0e 'print unless /regex/'Riorsson
K
1

get a list of all commits containing your result, then filter out their hashes.

git log --format=oneline | grep -v `git log --grep="bumped to version" --format="%h"`
Kana answered 9/4, 2011 at 1:26 Comment(0)
P
1

As with thebriguy's answer, grep also has a -z option to enable it to work with null terminated strings rather than lines. This would then be as simple as inverting the match:

git log -z --color | grep -vz "bumped to version"

For safety you may want to match within the commit message only. To do this with grep, you need to use pearl expressions to match newlines within the null terminated strings. To skip the header:

git log -z | grep -Pvz '^commit.*\nAuthor:.*\nDate:.*\n[\S\s]*bumped to version'

Or with colour:

git log -z --color | \
  grep -Pvz '^.....commit.*\nAuthor:.*\nDate:.*\n[\S\s]*bumped to version'

Finally, if using --stat, you could also match the beginning of this output to avoid matching file names containing the commit string. So a full answer to the question would look like:

log -z --color --pretty --stat | \
  grep -Pvz '^.....commit.*\nAuthor:.*\nDate:.*\n[\S\s]*?bumped to version[\S\s]*?\n [^ ]'

Note that grep -P is described as 'highly experimental' in my man page. It may be better to use pcregrep instead which uses libpcre, see How to give a pattern for new line in grep?. Although grep -P works fine for me and I have no idea if pcregrep has a -z option or equivalent.

Portion answered 21/9, 2013 at 11:10 Comment(3)
grep -z on OSX means to read zip compressed input, not handle null terminators.Sandoval
Right, -z and -P are not POSIX. So likely this will only work with GNU grep.Portion
Install gnu core utils on OS X if you want the -z option in grep. I prefer this answer over the accepted one since you can specify any extra options for git log: pattern=$1; shift; git log -z --color "$@" | grep -vz "$pattern" | tr '\0' '\n' | less -rMiscarriage
A
0

As far as I can tell, this is not possible to do directly with a single command line; you'd have to do something like Justin Lilly suggests and then run 'git log' on the resulting list of hashes, e.g.,

git log --format="%h" | grep -v `git log -1 --grep="bumped to version" --format="%h"` > good-hashes
for h in `cat good-hashes`; do
    PAGER=cat git log -1 --pretty --stat $h
done

should do the trick.

Alduino answered 9/4, 2011 at 2:45 Comment(2)
That's too bad. I was hoping there was some option I missed or a new version I should upgrade to. ebneter: I tried your solution, but it did not work for me.Justiceship
Hmmm. Worked for me, although I was filtering a different string, of course. I'm curious about what didn't work?Alduino
M
0

As mentioned by VonC the best option is if you can update to Git 2.4.0 (which is currently on RC2). But even if you can't do that there is no reason for elaborate scripts. A (gnu) awk one-liner should do it. git log has the useful -z option to separate commits by a NUL-character which makes it easy to parse them:

git log -z --pretty --stat | awk 'BEGIN{ RS="\0"; FS="\n\n" } !match($2, /<pattern>/)'

If you don't have gnu awk, you probably should at least install that. Or port this script to your specific awk version, which I leave as an exercise for the reader ;-).

Mycology answered 21/4, 2015 at 9:11 Comment(0)
G
-1
git log --pretty --stat | grep -v "bumped to version"
Gimmick answered 9/4, 2011 at 1:39 Comment(1)
That won't work since --pretty --stat will produce multiple lines per commit.Alduino

© 2022 - 2024 — McMap. All rights reserved.