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);
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 $@;
47 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 "infix junk in commit $hash";
125 my $line = $_->[2] // join(': ', @$_);
126 if (defined $opt{min} or $opt{max}) {
127 my $counter = \$headercount{ $_->[0] }->{ $_->[1] // '' };
128 my $excess = $$counter++ - ($opt{min} // 0);
129 next if $excess >= ($opt{max} || 1);
132 push @headercache, [ $line, $excess ? \undef : $counter ];
144 say ${$_->[1]} // '', "\t", $_->[0];
151 git-grep-footer - Find custom header lines in commit messages
155 F<git> log --pretty=%b%x00 | F<git-grep-footer> [OPTIONS]
159 Filters out header sections near the end of a commit body,
160 a common convention to list custom metadata such as
161 C<Signed-off-by> and C<Acked-by>.
163 Sections are identified by at least one leading keyword containing a dash
170 =item -i, --ignore-case
172 Lowercases everything.
174 =item -s, --simplify[=<rule>]
176 Modifies values to hide specific details.
177 Several different rules are supported:
181 =item I<var> (default)
183 Replaces highly variable contents such as numbers, hashes, and addresses,
184 leaving only exceptional annotations as distinct text.
185 Attributes ending in I<-to> or I<-by> are assumed variable author names
186 and omitted entirely,
187 unless they contain a colon indicating possible attribute exceptions.
191 Filters out author lines following the git signoff convention,
192 i.e. an <email address> optionally preceded by a name.
196 Values will be hidden entirely, so only attribute names remain.
200 =item -u, --unique[=<threshold>]
202 Each match is only shown once,
203 optionally after it has already occurred a given amount of times.
205 =item -n, --show[=<limit>]
207 The original line is given for each match,
208 but simplifications still apply for duplicate determination.
209 Additional samples are optionally given upto the given maximum.
215 Mischa POSLAWSKY <perl@shiar.org>
219 Copyright. All rights reserved.