5 use open ':std', OUT => ':utf8';
8 use Getopt::Long qw(:config bundling);
18 'min|min-count|unique|u:i',
19 'max|max-count|show|n:i',
20 'version|V' => sub { Getopt::Long::VersionMessage() },
21 'usage|h' => sub { Getopt::Long::HelpMessage() },
22 'help|man|?' => sub { Getopt::Long::HelpMessage(-verbose => 2) },
25 my $inputstream = $opt{''} ? \*ARGV : eval {
27 Git::command_output_pipe('log', '-z', '--pretty=format:%b', @ARGV);
28 } || die "Automatic git log failed: $@";
33 my $HEADERMATCH = qr/ [a-z]+ (?: (?:-\w+)+ | \ by ) | cc | reference /ix;
35 my (%headercount, @headercache);
37 while (readline $inputstream) {
38 s/^([0-9a-f]{4,40})\n//m and
41 # strip commit seperator
43 # skip expensive checks without potential identifier
45 # try to parse as UTF-8
46 eval { $_ = decode(utf8 => $_, Encode::FB_CROAK()) };
47 # if invalid, assume it's latin1
48 $_ = decode(cp1252 => $_) if $@;
53 for (reverse split /\n\n/) {
71 push @header, $_ if defined $opt{max};
77 state $BY = qr{ (?: -? b[yu] )? \Z }ix;
78 s{^ si (?:ge?n|n?g) (?:e?[dt])? -? (?:of+)? $BY}{Signed-off-by}ix;
79 s{^ ack (?:ed|de)? $BY}{Acked-by}ix;
80 s{^ review (?:e?d)? $BY}{Reviewed-by}ix;
81 s{^ teste[dt] $BY}{Tested-by}ix;
85 if (defined $opt{grep}) {
86 $_ ~~ qr/$opt{grep}/i or next LINE;
89 given ($opt{simplify} // 'none') {
90 when (['email', 'authors']) {
94 < [^@>]+ (?: @ | \h?\W? at \W?\h? ) [a-z0-9.-]+ >
98 when (['var', 'vars', '']) {
99 when ($header[0] =~ /[ _-] (?: by | to ) $ | ^cc$/imsx) {
103 s{\b (https?)://\S+ }{[$1]}gmsx; # url
104 s{(?: < | \A ) [^@>\s]+ @ [^>]+ (?: > | \Z )}{<...>}igmsx; # address
105 s{\b [0-9]+ \b}{[num]}gmsx; # number
106 s{\b [Ig]? [0-9a-f]{ 40} \b}{[sha1]}gmsx; # hash
107 s{\b [Ig]? [0-9a-f]{6,40} \b}{[hash]}gmsx; # abbrev
110 when (['all', 'contents']) {
113 when (['none', 'no', '0']) {
116 die "Unknown simplify option: '$_'\n";
120 if ($opt{'ignore-case'}) {
121 $_ = lc for $header[0], $header[1] // ();
124 pop @header if not defined $header[-1];
126 push @headers, \@header;
129 next BLOCK if not @headers;
131 if ($opt{debug} and $prefix) {
132 say sprintf ': invalid lines in %s (%s)', $hash // 'block', $prefix;
136 my $line = $_->[2] // join(': ', @$_);
137 $line =~ s/^/$hash / if defined $hash;
139 if (defined $opt{min} or $opt{max} or $opt{count}) {
140 my $counter = \$headercount{ $_->[0] }->{ $_->[1] // '' };
141 my $excess = $$counter++ - ($opt{min} // 0);
142 next if $excess >= ($opt{max} || 1);
145 push @headercache, [ $line, $excess ? \undef : $counter ];
157 say ${$_->[1]} // '', "\t", $_->[0];
164 git-grep-footer - Find custom header lines in commit messages
168 F<git-grep-footer> [OPTIONS] [-- <git log options>]
170 F<git> log -z --pretty=format:%b | F<git-grep-footer> [OPTIONS] -
174 Filters out header sections near the end of a commit body,
175 a common convention to list custom metadata such as
176 C<Signed-off-by> and C<Acked-by>.
178 Sections are identified by at least one leading keyword containing a dash
179 (or exceptionally recognised)
186 =item -i, --ignore-case
188 Lowercases everything.
190 =item -s, --simplify[=<rule>]
192 Modifies values to hide specific details.
193 Several different rules are supported:
197 =item I<var> (default)
199 Replaces highly variable contents such as numbers, hashes, and addresses,
200 leaving only exceptional annotations as distinct text.
201 Attributes ending in I<-to> or I<-by> are assumed variable author names
202 and omitted entirely,
203 unless they contain a colon indicating possible attribute exceptions.
207 Filters out author lines following the git signoff convention,
208 i.e. an <email address> optionally preceded by a name.
212 Values will be hidden entirely, so only attribute names remain.
216 =item --grep=<pattern>
218 Only include lines matching the specified regular expression.
219 Case insensitivity can be disabled by prepending C<(?-i)>.
221 =item -u, --unique[=<threshold>]
223 Each match is only shown once,
224 optionally after it has already occurred a given amount of times.
226 =item -n, --show[=<limit>]
228 The original line is given for each match,
229 but simplifications still apply for duplicate determination.
230 Additional samples are optionally given upto the given maximum.
234 Prefixes (unique) lines by the number of occurrences.
235 Causes output to be buffered until all input has been read (obviously).
243 =item git-grep-footer --grep=^ack v2.6.32..v2.6.33
245 Search for I<Acked-by> lines for version I<v2.6.33>.
246 Append C<-uin> to skip reoccurrences.
248 =item git-grep-footer -u --grep=junio
250 Show distinct lines mentioning a specific author.
252 =item git-grep-footer -c --simplify --grep=^si
254 Compare various capitalisations and (mis)spellings of signoffs.
256 =item git-grep-footer -c --simplify=all -i | sort -n -r | head -n10
258 List the ten most frequently used attribute names.
260 =item git-grep-footer -n2 -i -s -- --reverse
262 The earliest two usages of each distinct identifier.
268 Mischa POSLAWSKY <perl@shiar.org>
272 This software is free software;
273 you can redistribute and/or modify it under the terms of the GNU GPL