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',
21 'version|V' => sub { Getopt::Long::VersionMessage() },
22 'usage|h' => sub { Getopt::Long::HelpMessage() },
23 'help|man|?' => sub { Getopt::Long::HelpMessage(-verbose => 2) },
26 my $inputstream = $opt{''} ? \*ARGV : eval {
28 Git::command_output_pipe('log', '-z', '--pretty=format:%h%n%b', @ARGV);
29 } || die "Automatic git log failed: $@";
34 my $HEADERMATCH = qr/ [a-z]+ (?: (?:-\w+)+ | \ by ) | cc | reference /imsx;
36 my (%headercount, @headercache);
38 while (readline $inputstream) {
39 s/^ ([0-9a-f]{4,40}) \n//msx;
40 my $hash = $opt{hash} ? $1 : undef;
42 # strip commit seperator
44 # skip expensive checks without potential identifier
46 # try to parse as UTF-8
47 eval { $_ = decode(utf8 => $_, Encode::FB_CROAK()); return 1 }
48 # if invalid, assume it's latin1
49 or $_ = decode(cp1252 => $_);
52 for (reverse split /\n\n/) {
70 push @header, $_ if defined $opt{max};
76 state $BY = qr{ (?: -? b[yu] )? \Z }imsx;
77 s{\A si (?:ge?n|n?g) (?:e?[dt])? -? (?:of+)? $BY}{Signed-off-by}imsx;
78 s{\A ack (?:ed|de)? $BY}{Acked-by}imsx;
79 s{\A review (?:e?d)? $BY}{Reviewed-by}imsx;
80 s{\A teste[dt] $BY}{Tested-by}imsx;
84 if (defined $opt{grep}) {
85 $_ ~~ qr/$opt{grep}/im or next LINE;
88 given ($opt{simplify} // 'none') {
89 when (['email', 'authors']) {
93 < [^@>]+ (?: @ | \h?\W? at \W?\h? ) [a-z0-9.-]+ >
97 when (['var', 'vars', '']) {
98 when ($header[0] =~ m/[ _-] (?: by | to ) $ | ^cc$/imsx) {
102 s{\b (https?)://\S+ }{[$1]}gmsx; # url
103 s{(?: < | \A ) [^@>\s]+ @ [^>]+ (?: > | \Z )}{<...>}igmsx; # address
104 s{\b [0-9]+ \b}{[num]}gmsx; # number
105 s{\b [Ig]? [0-9a-f]{ 40} \b}{[sha1]}gmsx; # hash
106 s{\b [Ig]? [0-9a-f]{6,40} \b}{[hash]}gmsx; # abbrev
109 when (['all', 'contents']) {
112 when (['none', 'no', '0']) {
115 die "Unknown simplify option: '$_'\n";
119 if ($opt{'ignore-case'}) {
120 $_ = lc for $header[0], $header[1] // ();
123 pop @header if not defined $header[-1];
125 push @headers, \@header;
128 next BLOCK if not @headers;
130 if ($opt{debug} and $prefix) {
131 say sprintf ': invalid lines in %s (%s)', $hash // 'block', $prefix;
135 my $line = $_->[2] // join(': ', @$_);
136 $line =~ s/\A/$hash /msx if defined $hash;
138 if (defined $opt{min} or $opt{max} or $opt{count}) {
139 my $counter = \$headercount{ $_->[0] }->{ $_->[1] // '' };
140 my $excess = ${$counter}++ - ($opt{min} // 0);
141 next if $excess >= ($opt{max} || 1);
144 push @headercache, [ $line, $excess ? \undef : $counter ];
156 say ${$_->[1]} // '', "\t", $_->[0];
163 git-grep-footer - Find custom header lines in commit messages
167 F<git-grep-footer> [OPTIONS] [-- <git log options>]
169 F<git> log -z --pretty=format:%b | F<git-grep-footer> [OPTIONS] -
173 Filters out header sections near the end of a commit body,
174 a common convention to list custom metadata such as
175 C<Signed-off-by> and C<Acked-by>.
177 Sections are identified by at least one leading keyword containing a dash
178 (or exceptionally recognised)
185 =item -i, --ignore-case
187 Lowercases everything.
189 =item -s, --simplify[=<rule>]
191 Modifies values to hide specific details.
192 Several different rules are supported:
196 =item I<var> (default)
198 Replaces highly variable contents such as numbers, hashes, and addresses,
199 leaving only exceptional annotations as distinct text.
200 Attributes ending in I<-to> or I<-by> are assumed variable author names
201 and omitted entirely,
202 unless they contain a colon indicating possible attribute exceptions.
206 Filters out author lines following the git signoff convention,
207 i.e. an <email address> optionally preceded by a name.
211 Values will be hidden entirely, so only attribute names remain.
215 =item --grep=<pattern>
217 Only include lines matching the specified regular expression.
218 Case insensitivity can be disabled by prepending C<(?-i)>.
220 =item -u, --unique[=<threshold>]
222 Each match is only shown once,
223 optionally after it has already occurred a given amount of times.
225 =item -n, --show[=<limit>]
227 The original line is given for each match,
228 but simplifications still apply for duplicate determination.
229 Additional samples are optionally given upto the given maximum.
233 Prefixes (unique) lines by the number of occurrences.
234 Causes output to be buffered until all input has been read (obviously).
238 Prefixes the SHA1 hash of the (or a) matching commit.
246 =item git-grep-footer --grep=^ack v2.6.32..v2.6.33
248 Search for I<Acked-by> lines for version I<v2.6.33>.
249 Append C<-uin> to skip reoccurrences.
251 =item git-grep-footer -u --grep=junio
253 Show distinct lines mentioning a specific author.
255 =item git-grep-footer -c --simplify --grep=^si
257 Compare various capitalisations and (mis)spellings of signoffs.
259 =item git-grep-footer -c --simplify=all -i | sort -n -r | head -n10
261 List the ten most frequently used attribute names.
263 =item git-grep-footer -n2 -i -s --hash -- --reverse
265 The earliest two usages of each distinct identifier.
271 Mischa POSLAWSKY <perl@shiar.org>
275 This software is free software;
276 you can redistribute and/or modify it under the terms of the GNU GPL