5 use open ':std', OUT => ':utf8';
8 use Getopt::Long qw(:config bundling);
17 'min|min-count|unique|u:i',
18 'max|max-count|show|n:i',
19 'version|V' => sub { Getopt::Long::VersionMessage() },
20 'usage|h' => sub { Getopt::Long::HelpMessage() },
21 'help|man|?' => sub { Getopt::Long::HelpMessage(-verbose => 2) },
27 my $HEADERMATCH = qr/ [a-z]+ (?: (?:-\w+)+ | \ by ) | cc | reference /ix;
29 my (%headercount, @headercache);
32 s/^([0-9a-f]{4,40})\n//m and
35 # strip commit seperator
37 # skip expensive checks without potential identifier
39 # try to parse as UTF-8
40 eval { $_ = decode(utf8 => $_, Encode::FB_CROAK()) };
41 # if invalid, assume it's latin1
42 $_ = decode(cp1252 => $_) if $@;
47 for (reverse split /\n\n/) {
65 push @header, $_ if defined $opt{max};
71 state $BY = qr{ (?: -? b[yu] )? \Z }ix;
72 s{^ si (?:ge?n|n?g) (?:e?[dt])? -? (?:of+)? $BY}{Signed-off-by}ix;
73 s{^ ack (?:ed|de)? $BY}{Acked-by}ix;
74 s{^ review (?:e?d)? $BY}{Reviewed-by}ix;
75 s{^ teste[dt] $BY}{Tested-by}ix;
79 if (defined $opt{grep}) {
80 $_ ~~ qr/$opt{grep}/i or next LINE;
83 given ($opt{simplify} // 'none') {
84 when (['email', 'authors']) {
88 < [^@>]+ (?: @ | \h?\W? at \W?\h? ) [a-z0-9.-]+ >
92 when (['var', 'vars', '']) {
93 when ($header[0] =~ /[ _-] (?: by | to ) $ | ^cc$/imsx) {
97 s{\b (https?)://\S+ }{[$1]}gmsx; # url
98 s{(?: < | \A ) [^@>\s]+ @ [^>]+ (?: > | \Z )}{<...>}igmsx; # address
99 s{\b [0-9]+ \b}{[num]}gmsx; # number
100 s{\b [Ig]? [0-9a-f]{ 40} \b}{[sha1]}gmsx; # hash
101 s{\b [Ig]? [0-9a-f]{6,40} \b}{[hash]}gmsx; # abbrev
104 when (['all', 'contents']) {
107 when (['none', 'no', '0']) {
110 die "Unknown simplify option: '$_'\n";
114 if ($opt{'ignore-case'}) {
115 $_ = lc for $header[0], $header[1] // ();
118 pop @header if not defined $header[-1];
120 push @headers, \@header;
123 next BLOCK if not @headers;
125 if ($opt{debug} and $prefix) {
126 say sprintf ': invalid lines in %s (%s)', $hash // 'block', $prefix;
130 my $line = $_->[2] // join(': ', @$_);
131 $line =~ s/^/$hash / if defined $hash;
133 if (defined $opt{min} or $opt{max} or $opt{count}) {
134 my $counter = \$headercount{ $_->[0] }->{ $_->[1] // '' };
135 my $excess = $$counter++ - ($opt{min} // 0);
136 next if $excess >= ($opt{max} || 1);
139 push @headercache, [ $line, $excess ? \undef : $counter ];
151 say ${$_->[1]} // '', "\t", $_->[0];
158 git-grep-footer - Find custom header lines in commit messages
162 F<git> log --pretty=%b%x00 | F<git-grep-footer> [OPTIONS]
166 Filters out header sections near the end of a commit body,
167 a common convention to list custom metadata such as
168 C<Signed-off-by> and C<Acked-by>.
170 Sections are identified by at least one leading keyword containing a dash
171 (or exceptionally recognised)
178 =item -i, --ignore-case
180 Lowercases everything.
182 =item -s, --simplify[=<rule>]
184 Modifies values to hide specific details.
185 Several different rules are supported:
189 =item I<var> (default)
191 Replaces highly variable contents such as numbers, hashes, and addresses,
192 leaving only exceptional annotations as distinct text.
193 Attributes ending in I<-to> or I<-by> are assumed variable author names
194 and omitted entirely,
195 unless they contain a colon indicating possible attribute exceptions.
199 Filters out author lines following the git signoff convention,
200 i.e. an <email address> optionally preceded by a name.
204 Values will be hidden entirely, so only attribute names remain.
208 =item --grep=<pattern>
210 Only include lines matching the specified regular expression.
211 Case insensitivity can be disabled by prepending C<(?-i)>.
213 =item -u, --unique[=<threshold>]
215 Each match is only shown once,
216 optionally after it has already occurred a given amount of times.
218 =item -n, --show[=<limit>]
220 The original line is given for each match,
221 but simplifications still apply for duplicate determination.
222 Additional samples are optionally given upto the given maximum.
226 Prefixes (unique) lines by the number of occurrences.
227 Causes output to be buffered until all input has been read (obviously).
235 =item git-grep-footer --grep=^ack
237 Search for Acked-by lines.
238 Append C<-uin> to skip reoccurrences.
240 =item git-grep-footer -u --grep=junio
242 Show distinct lines mentioning a specific author.
244 =item git-grep-footer -c --simplify --grep=^si
246 Compare various capitalisations and (mis)spellings of signoffs.
248 =item git-grep-footer -c --simplify=all -i | sort -n -r | head -n10
250 List the ten most frequently used attribute names.
252 =item git-grep-footer -n2 -i -s
254 Upto two examples for each distinct identifier.
260 Mischa POSLAWSKY <perl@shiar.org>
264 Copyright. All rights reserved.