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 ) /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 ) $/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}) {
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
172 =item -i, --ignore-case
174 Lowercases everything.
176 =item -s, --simplify[=<rule>]
178 Modifies values to hide specific details.
179 Several different rules are supported:
183 =item I<var> (default)
185 Replaces highly variable contents such as numbers, hashes, and addresses,
186 leaving only exceptional annotations as distinct text.
187 Attributes ending in I<-to> or I<-by> are assumed variable author names
188 and omitted entirely,
189 unless they contain a colon indicating possible attribute exceptions.
193 Filters out author lines following the git signoff convention,
194 i.e. an <email address> optionally preceded by a name.
198 Values will be hidden entirely, so only attribute names remain.
202 =item -u, --unique[=<threshold>]
204 Each match is only shown once,
205 optionally after it has already occurred a given amount of times.
207 =item -n, --show[=<limit>]
209 The original line is given for each match,
210 but simplifications still apply for duplicate determination.
211 Additional samples are optionally given upto the given maximum.
217 Mischa POSLAWSKY <perl@shiar.org>
221 Copyright. All rights reserved.