public release
[git-grep-footer.git] / git-grep-footer
index 6f20fac6f180c281612f0689b7f9b95b86813586..97fbca7748b374a295e1758f43887ce6f14de8ef 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/perl
+#!/usr/bin/env perl
 use 5.010;
 use strict;
 use warnings;
 use 5.010;
 use strict;
 use warnings;
@@ -7,8 +7,11 @@ use Encode 'decode';
 use Data::Dump 'pp';
 use Getopt::Long qw(:config bundling);
 
 use Data::Dump 'pp';
 use Getopt::Long qw(:config bundling);
 
+our $VERSION = '1.00';
+
 GetOptions(\my %opt,
        'debug!',
 GetOptions(\my %opt,
        'debug!',
+       '',  # stdin
        'count|c!',
        'simplify|s:s',
        'ignore-case|i!',
        'count|c!',
        'simplify|s:s',
        'ignore-case|i!',
@@ -16,32 +19,36 @@ GetOptions(\my %opt,
        'grep|S=s',
        'min|min-count|unique|u:i',
        'max|max-count|show|n:i',
        'grep|S=s',
        'min|min-count|unique|u:i',
        'max|max-count|show|n:i',
+       'hash|H!',
        'version|V'  => sub { Getopt::Long::VersionMessage() },
        'usage|h'    => sub { Getopt::Long::HelpMessage() },
        'help|man|?' => sub { Getopt::Long::HelpMessage(-verbose => 2) },
 ) or exit 129;
 
        'version|V'  => sub { Getopt::Long::VersionMessage() },
        'usage|h'    => sub { Getopt::Long::HelpMessage() },
        'help|man|?' => sub { Getopt::Long::HelpMessage(-verbose => 2) },
 ) or exit 129;
 
+my $inputstream = $opt{''} ? \*ARGV : eval {
+       require Git;
+       Git::command_output_pipe('log', '-z', '--pretty=format:%h%n%b', @ARGV);
+} || die "Automatic git log failed: $@";
+
 local $| = 1;
 local $/ = "\0";
 
 local $| = 1;
 local $/ = "\0";
 
-my $HEADERMATCH = qr/ [a-z]+ (?: (?:-\w+)+ | \ by ) | cc | reference /ix;
+my $HEADERMATCH = qr/ [a-z]+ (?: (?:-\w+)+ | \ by ) | cc | reference /imsx;
 
 my (%headercount, @headercache);
 
 
 my (%headercount, @headercache);
 
-while (readline) {
-       s/^([0-9a-f]{4,40})\n//m and
-       my $hash = $1;
+while (readline $inputstream) {
+       s/^ ([0-9a-f]{4,40}) \n//msx;
+       my $hash = $opt{hash} ? $1 : undef;
 
        # strip commit seperator
        chomp;
        # skip expensive checks without potential identifier
        m/:/ or next;
        # try to parse as UTF-8
 
        # strip commit seperator
        chomp;
        # skip expensive checks without potential identifier
        m/:/ or next;
        # try to parse as UTF-8
-       eval { $_ = decode(utf8   => $_, Encode::FB_CROAK()) };
+       eval { $_ = decode(utf8   => $_, Encode::FB_CROAK()); return 1 }
        # if invalid, assume it's latin1
        # if invalid, assume it's latin1
-              $_ = decode(cp1252 => $_) if $@;
-
-       my %attr;
+           or $_ = decode(cp1252 => $_);
 
        BLOCK:
        for (reverse split /\n\n/) {
 
        BLOCK:
        for (reverse split /\n\n/) {
@@ -50,14 +57,14 @@ while (readline) {
 
                LINE:
                for (split /\n/) {
 
                LINE:
                for (split /\n/) {
-                       next if not /\S/;
+                       next if not m/\S/;
                        my @header = m{
                                ^
                                (?<key> $HEADERMATCH)
                                : \s*
                        my @header = m{
                                ^
                                (?<key> $HEADERMATCH)
                                : \s*
-                               (?<val> \S .+)
+                               (?<val> \S [^\n]+)
                                $
                                $
-                       }imx or do {
+                       }imsx or do {
                                $prefix++;
                                next LINE;
                        };
                                $prefix++;
                                next LINE;
                        };
@@ -68,16 +75,16 @@ while (readline) {
                                for ($header[0]) {
                                        tr/ _/-/;
 
                                for ($header[0]) {
                                        tr/ _/-/;
 
-                                       state $BY = qr{ (?: -? b[yu] )? \Z }ix;
-                                       s{^ si (?:ge?n|n?g) (?:e?[dt])? -? (?:of+)? $BY}{Signed-off-by}ix;
-                                       s{^ ack (?:ed|de)?  $BY}{Acked-by}ix;
-                                       s{^ review (?:e?d)? $BY}{Reviewed-by}ix;
-                                       s{^ teste[dt]       $BY}{Tested-by}ix;
+                                       state $BY = qr{ (?: -? b[yu] )? \Z }imsx;
+                                       s{\A si (?:ge?n|n?g) (?:e?[dt])? -? (?:of+)? $BY}{Signed-off-by}imsx;
+                                       s{\A ack (?:ed|de)?  $BY}{Acked-by}imsx;
+                                       s{\A review (?:e?d)? $BY}{Reviewed-by}imsx;
+                                       s{\A teste[dt]       $BY}{Tested-by}imsx;
                                }
                        }
 
                        if (defined $opt{grep}) {
                                }
                        }
 
                        if (defined $opt{grep}) {
-                               $_ ~~ qr/$opt{grep}/i or next LINE;
+                               $_ ~~ qr/$opt{grep}/im or next LINE;
                        }
 
                        given ($opt{simplify} // 'none') {
                        }
 
                        given ($opt{simplify} // 'none') {
@@ -90,7 +97,7 @@ while (readline) {
                                        }{<...>}imsx;
                                }
                                when (['var', 'vars', '']) {
                                        }{<...>}imsx;
                                }
                                when (['var', 'vars', '']) {
-                                       when ($header[0] =~ /[ _-] (?: by | to ) $ | ^cc$/imsx) {
+                                       when ($header[0] =~ m/[ _-] (?: by | to ) $ | ^cc$/imsx) {
                                                $header[1] = undef;
                                        }
                                        for ($header[1]) {
                                                $header[1] = undef;
                                        }
                                        for ($header[1]) {
@@ -128,11 +135,11 @@ while (readline) {
 
                for (@headers) {
                        my $line = $_->[2] // join(': ', @$_);
 
                for (@headers) {
                        my $line = $_->[2] // join(': ', @$_);
-                       $line =~ s/^/$hash / if defined $hash;
+                       $line =~ s/\A/$hash /msx if defined $hash;
 
                        if (defined $opt{min} or $opt{max} or $opt{count}) {
                                my $counter = \$headercount{ $_->[0] }->{ $_->[1] // '' };
 
                        if (defined $opt{min} or $opt{max} or $opt{count}) {
                                my $counter = \$headercount{ $_->[0] }->{ $_->[1] // '' };
-                               my $excess = $$counter++ - ($opt{min} // 0);
+                               my $excess = ${$counter}++ - ($opt{min} // 0);
                                next if $excess >= ($opt{max} || 1);
                                next if $excess <  0;
                                if ($opt{count}) {
                                next if $excess >= ($opt{max} || 1);
                                next if $excess <  0;
                                if ($opt{count}) {
@@ -159,7 +166,9 @@ git-grep-footer - Find custom header lines in commit messages
 
 =head1 SYNOPSIS
 
 
 =head1 SYNOPSIS
 
-F<git> log --pretty=%b%x00 | F<git-grep-footer> [OPTIONS]
+F<git-grep-footer> [OPTIONS] [-- <git log options>]
+
+F<git> log -z --pretty=format:%b | F<git-grep-footer> [OPTIONS] -
 
 =head1 DESCRIPTION
 
 
 =head1 DESCRIPTION
 
@@ -226,15 +235,19 @@ Additional samples are optionally given upto the given maximum.
 Prefixes (unique) lines by the number of occurrences.
 Causes output to be buffered until all input has been read (obviously).
 
 Prefixes (unique) lines by the number of occurrences.
 Causes output to be buffered until all input has been read (obviously).
 
+=item -H, --hash
+
+Prefixes the SHA1 hash of the (or a) matching commit.
+
 =back
 
 =head1 EXAMPLES
 
 =over
 
 =back
 
 =head1 EXAMPLES
 
 =over
 
-=item git-grep-footer --grep=^ack
+=item git-grep-footer --grep=^ack v2.6.32..v2.6.33
 
 
-Search for Acked-by lines.
+Search for I<Acked-by> lines for version I<v2.6.33>.
 Append C<-uin> to skip reoccurrences.
 
 =item git-grep-footer -u --grep=junio
 Append C<-uin> to skip reoccurrences.
 
 =item git-grep-footer -u --grep=junio
@@ -249,9 +262,9 @@ Compare various capitalisations and (mis)spellings of signoffs.
 
 List the ten most frequently used attribute names.
 
 
 List the ten most frequently used attribute names.
 
-=item git-grep-footer -n2 -i -s
+=item git-grep-footer -n2 -i -s --hash -- --reverse
 
 
-Upto two examples for each distinct identifier.
+The earliest two usages of each distinct identifier.
 
 =back
 
 
 =back
 
@@ -261,5 +274,7 @@ Mischa POSLAWSKY <perl@shiar.org>
 
 =head1 LICENSE
 
 
 =head1 LICENSE
 
-Copyright. All rights reserved.
+This software is free software;
+you can redistribute and/or modify it under the terms of the GNU GPL
+version 2 or later.