5 use open ':std', OUT => ':utf8';
8 use Getopt::Long qw(:config bundling);
16 'min|min-count|unique|u:i',
17 'max|max-count|show|n:i',
18 'version|V' => sub { Getopt::Long::VersionMessage() },
19 'usage|h' => sub { Getopt::Long::HelpMessage() },
20 'help|man|?' => sub { Getopt::Long::HelpMessage(-verbose => 2) },
26 my $HEADERMATCH = qr/ [a-z]+ (?: (?:-\w+)+ | \ by ) | cc | reference /ix;
28 my (%headercount, @headercache);
31 s/^([0-9a-f]{4,40})\n//m and
34 # strip commit seperator
36 # skip expensive checks without potential identifier
38 # try to parse as UTF-8
39 eval { $_ = decode(utf8 => $_, Encode::FB_CROAK()) };
40 # if invalid, assume it's latin1
41 $_ = decode(cp1252 => $_) if $@;
46 for (reverse split /\n\n/) {
64 push @header, $_ if defined $opt{max};
70 state $BY = qr{ (?: -? b[yu] )? \Z }ix;
71 s{^ si (?:ge?n|n?g) (?:e?[dt])? -? (?:of+)? $BY}{Signed-off-by}ix;
72 s{^ ack (?:ed|de)? $BY}{Acked-by}ix;
73 s{^ review (?:e?d)? $BY}{Reviewed-by}ix;
74 s{^ teste[dt] $BY}{Tested-by}ix;
78 given ($opt{simplify} // 'none') {
79 when (['email', 'authors']) {
83 < [^@>]+ (?: @ | \h?\W? at \W?\h? ) [a-z0-9.-]+ >
87 when (['var', 'vars', '']) {
88 when ($header[0] =~ /[ _-] (?: by | to ) $ | ^cc$/imsx) {
92 s{\b (https?)://\S+ }{[$1]}gmsx; # url
93 s{(?: < | \A ) [^@>\s]+ @ [^>]+ (?: > | \Z )}{<...>}igmsx; # address
94 s{\b [0-9]+ \b}{[num]}gmsx; # number
95 s{\b [Ig]? [0-9a-f]{ 40} \b}{[sha1]}gmsx; # hash
96 s{\b [Ig]? [0-9a-f]{6,40} \b}{[hash]}gmsx; # abbrev
99 when (['all', 'contents']) {
102 when (['none', 'no', '0']) {
105 die "Unknown simplify option: '$_'\n";
109 if ($opt{'ignore-case'}) {
110 $_ = lc for $header[0], $header[1] // ();
113 pop @header if not defined $header[-1];
115 push @headers, \@header;
118 next BLOCK if not @headers;
120 if ($opt{debug} and $prefix) {
121 say sprintf ': invalid lines in %s (%s)', $hash // 'block', $prefix;
125 my $line = $_->[2] // join(': ', @$_);
126 $line =~ s/^/$hash / if defined $hash;
128 if (defined $opt{min} or $opt{max} or $opt{count}) {
129 my $counter = \$headercount{ $_->[0] }->{ $_->[1] // '' };
130 my $excess = $$counter++ - ($opt{min} // 0);
131 next if $excess >= ($opt{max} || 1);
134 push @headercache, [ $line, $excess ? \undef : $counter ];
146 say ${$_->[1]} // '', "\t", $_->[0];
153 git-grep-footer - Find custom header lines in commit messages
157 F<git> log --pretty=%b%x00 | F<git-grep-footer> [OPTIONS]
161 Filters out header sections near the end of a commit body,
162 a common convention to list custom metadata such as
163 C<Signed-off-by> and C<Acked-by>.
165 Sections are identified by at least one leading keyword containing a dash
166 (or exceptionally recognised)
173 =item -i, --ignore-case
175 Lowercases everything.
177 =item -s, --simplify[=<rule>]
179 Modifies values to hide specific details.
180 Several different rules are supported:
184 =item I<var> (default)
186 Replaces highly variable contents such as numbers, hashes, and addresses,
187 leaving only exceptional annotations as distinct text.
188 Attributes ending in I<-to> or I<-by> are assumed variable author names
189 and omitted entirely,
190 unless they contain a colon indicating possible attribute exceptions.
194 Filters out author lines following the git signoff convention,
195 i.e. an <email address> optionally preceded by a name.
199 Values will be hidden entirely, so only attribute names remain.
203 =item -u, --unique[=<threshold>]
205 Each match is only shown once,
206 optionally after it has already occurred a given amount of times.
208 =item -n, --show[=<limit>]
210 The original line is given for each match,
211 but simplifications still apply for duplicate determination.
212 Additional samples are optionally given upto the given maximum.
216 Prefixes (unique) lines by the number of occurrences.
217 Causes output to be buffered until all input has been read (obviously).
223 Mischa POSLAWSKY <perl@shiar.org>
227 Copyright. All rights reserved.