saltycrane saltycrane - 1 month ago 27
Git Question

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

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.


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:

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

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


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>;

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