2 # (c) 2007, Joe Perches <joe@perches.com>
3 # created from checkpatch.pl
5 # Print selected MAINTAINERS information for
6 # the files modified in a patch or for a file
8 # usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
9 # perl scripts/get_maintainer.pl [OPTIONS] -f <file>
11 # Licensed under the terms of the GNU GPL License version 2
18 use Getopt
::Long
qw(:config no_auto_abbrev);
22 my $email_usename = 1;
23 my $email_maintainer = 1;
24 my $email_reviewer = 1;
26 my $email_subscriber_list = 0;
27 my $email_git_penguin_chiefs = 0;
29 my $email_git_all_signature_types = 0;
30 my $email_git_blame = 0;
31 my $email_git_blame_signatures = 1;
32 my $email_git_fallback = 1;
33 my $email_git_min_signatures = 1;
34 my $email_git_max_maintainers = 5;
35 my $email_git_min_percent = 5;
36 my $email_git_since = "1-year-ago";
37 my $email_hg_since = "-365";
39 my $email_remove_duplicates = 1;
40 my $email_use_mailmap = 1;
41 my $output_multiline = 1;
42 my $output_separator = ", ";
44 my $output_rolestats = 1;
45 my $output_section_maxlen = 50;
53 my $from_filename = 0;
54 my $pattern_depth = 0;
62 my %commit_author_hash;
63 my %commit_signer_hash;
65 my @penguin_chief = ();
66 push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
67 #Andrew wants in on most everything - 2009/01/14
68 #push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
70 my @penguin_chief_names = ();
71 foreach my $chief (@penguin_chief) {
72 if ($chief =~ m/^(.*):(.*)/) {
75 push(@penguin_chief_names, $chief_name);
78 my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
80 # Signature types of people who are either
81 # a) responsible for the code in question, or
82 # b) familiar enough with it to give relevant feedback
83 my @signature_tags = ();
84 push(@signature_tags, "Signed-off-by:");
85 push(@signature_tags, "Reviewed-by:");
86 push(@signature_tags, "Acked-by:");
88 my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
90 # rfc822 email address - preloaded methods go here.
91 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
92 my $rfc822_char = '[\\000-\\377]';
94 # VCS command support: class-like functions and strings
99 "execute_cmd" => \
&git_execute_cmd
,
100 "available" => '(which("git") ne "") && (-e ".git")',
101 "find_signers_cmd" =>
102 "git log --no-color --follow --since=\$email_git_since " .
103 '--numstat --no-merges ' .
104 '--format="GitCommit: %H%n' .
105 'GitAuthor: %an <%ae>%n' .
110 "find_commit_signers_cmd" =>
111 "git log --no-color " .
113 '--format="GitCommit: %H%n' .
114 'GitAuthor: %an <%ae>%n' .
119 "find_commit_author_cmd" =>
120 "git log --no-color " .
122 '--format="GitCommit: %H%n' .
123 'GitAuthor: %an <%ae>%n' .
125 'GitSubject: %s%n"' .
127 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
128 "blame_file_cmd" => "git blame -l \$file",
129 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
130 "blame_commit_pattern" => "^([0-9a-f]+) ",
131 "author_pattern" => "^GitAuthor: (.*)",
132 "subject_pattern" => "^GitSubject: (.*)",
133 "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
137 "execute_cmd" => \
&hg_execute_cmd
,
138 "available" => '(which("hg") ne "") && (-d ".hg")',
139 "find_signers_cmd" =>
140 "hg log --date=\$email_hg_since " .
141 "--template='HgCommit: {node}\\n" .
142 "HgAuthor: {author}\\n" .
143 "HgSubject: {desc}\\n'" .
145 "find_commit_signers_cmd" =>
147 "--template='HgSubject: {desc}\\n'" .
149 "find_commit_author_cmd" =>
151 "--template='HgCommit: {node}\\n" .
152 "HgAuthor: {author}\\n" .
153 "HgSubject: {desc|firstline}\\n'" .
155 "blame_range_cmd" => "", # not supported
156 "blame_file_cmd" => "hg blame -n \$file",
157 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
158 "blame_commit_pattern" => "^([ 0-9a-f]+):",
159 "author_pattern" => "^HgAuthor: (.*)",
160 "subject_pattern" => "^HgSubject: (.*)",
161 "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$",
164 my $conf = which_conf
(".get_maintainer.conf");
167 open(my $conffile, '<', "$conf")
168 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
170 while (<$conffile>) {
173 $line =~ s/\s*\n?$//g;
177 next if ($line =~ m/^\s*#/);
178 next if ($line =~ m/^\s*$/);
180 my @words = split(" ", $line);
181 foreach my $word (@words) {
182 last if ($word =~ m/^#/);
183 push (@conf_args, $word);
187 unshift(@ARGV, @conf_args) if @conf_args;
190 my @ignore_emails = ();
191 my $ignore_file = which_conf
(".get_maintainer.ignore");
192 if (-f
$ignore_file) {
193 open(my $ignore, '<', "$ignore_file")
194 or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n";
198 $line =~ s/\s*\n?$//;
203 next if ($line =~ m/^\s*$/);
204 if (rfc822_valid
($line)) {
205 push(@ignore_emails, $line);
213 'git!' => \
$email_git,
214 'git-all-signature-types!' => \
$email_git_all_signature_types,
215 'git-blame!' => \
$email_git_blame,
216 'git-blame-signatures!' => \
$email_git_blame_signatures,
217 'git-fallback!' => \
$email_git_fallback,
218 'git-chief-penguins!' => \
$email_git_penguin_chiefs,
219 'git-min-signatures=i' => \
$email_git_min_signatures,
220 'git-max-maintainers=i' => \
$email_git_max_maintainers,
221 'git-min-percent=i' => \
$email_git_min_percent,
222 'git-since=s' => \
$email_git_since,
223 'hg-since=s' => \
$email_hg_since,
224 'i|interactive!' => \
$interactive,
225 'remove-duplicates!' => \
$email_remove_duplicates,
226 'mailmap!' => \
$email_use_mailmap,
227 'm!' => \
$email_maintainer,
228 'r!' => \
$email_reviewer,
229 'n!' => \
$email_usename,
230 'l!' => \
$email_list,
231 's!' => \
$email_subscriber_list,
232 'multiline!' => \
$output_multiline,
233 'roles!' => \
$output_roles,
234 'rolestats!' => \
$output_rolestats,
235 'separator=s' => \
$output_separator,
236 'subsystem!' => \
$subsystem,
237 'status!' => \
$status,
240 'pattern-depth=i' => \
$pattern_depth,
241 'k|keywords!' => \
$keywords,
242 'sections!' => \
$sections,
243 'fe|file-emails!' => \
$file_emails,
244 'f|file' => \
$from_filename,
245 'v|version' => \
$version,
246 'h|help|usage' => \
$help,
248 die "$P: invalid argument - use --help if necessary\n";
257 print("${P} ${V}\n");
261 if (-t STDIN
&& !@ARGV) {
262 # We're talking to a terminal, but have no command line arguments.
263 die "$P: missing patchfile or -f file - use --help if necessary\n";
266 $output_multiline = 0 if ($output_separator ne ", ");
267 $output_rolestats = 1 if ($interactive);
268 $output_roles = 1 if ($output_rolestats);
280 my $selections = $email + $scm + $status + $subsystem + $web;
281 if ($selections == 0) {
282 die "$P: Missing required option: email, scm, status, subsystem or web\n";
287 ($email_maintainer + $email_reviewer +
288 $email_list + $email_subscriber_list +
289 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
290 die "$P: Please select at least 1 email option\n";
293 if (!top_of_kernel_tree
($lk_path)) {
294 die "$P: The current directory does not appear to be "
295 . "a linux kernel source tree.\n";
298 ## Read MAINTAINERS for type/value pairs
303 open (my $maint, '<', "${lk_path}MAINTAINERS")
304 or die "$P: Can't open MAINTAINERS: $!\n";
308 if ($line =~ m/^([A-Z]):\s*(.*)/) {
312 ##Filename pattern matching
313 if ($type eq "F" || $type eq "X") {
314 $value =~ s
@\.@\\\.@g; ##Convert . to \.
315 $value =~ s/\*/\.\*/g; ##Convert * to .*
316 $value =~ s/\?/\./g; ##Convert ? to .
317 ##if pattern is a directory and it lacks a trailing slash, add one
319 $value =~ s
@([^/])$@$1/@;
321 } elsif ($type eq "K") {
322 $keyword_hash{@typevalue} = $value;
324 push(@typevalue, "$type:$value");
325 } elsif (!/^(\s)*$/) {
327 push(@typevalue, $line);
334 # Read mail address map
347 return if (!$email_use_mailmap || !(-f
"${lk_path}.mailmap"));
349 open(my $mailmap_file, '<', "${lk_path}.mailmap")
350 or warn "$P: Can't open .mailmap: $!\n";
352 while (<$mailmap_file>) {
353 s/#.*$//; #strip comments
354 s/^\s+|\s+$//g; #trim
356 next if (/^\s*$/); #skip empty lines
357 #entries have one of the following formats:
360 # name1 <mail1> <mail2>
361 # name1 <mail1> name2 <mail2>
362 # (see man git-shortlog)
364 if (/^([^<]+)<([^>]+)>$/) {
368 $real_name =~ s/\s+$//;
369 ($real_name, $address) = parse_email
("$real_name <$address>");
370 $mailmap->{names
}->{$address} = $real_name;
372 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
373 my $real_address = $1;
374 my $wrong_address = $2;
376 $mailmap->{addresses
}->{$wrong_address} = $real_address;
378 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
380 my $real_address = $2;
381 my $wrong_address = $3;
383 $real_name =~ s/\s+$//;
384 ($real_name, $real_address) =
385 parse_email
("$real_name <$real_address>");
386 $mailmap->{names
}->{$wrong_address} = $real_name;
387 $mailmap->{addresses
}->{$wrong_address} = $real_address;
389 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
391 my $real_address = $2;
393 my $wrong_address = $4;
395 $real_name =~ s/\s+$//;
396 ($real_name, $real_address) =
397 parse_email
("$real_name <$real_address>");
399 $wrong_name =~ s/\s+$//;
400 ($wrong_name, $wrong_address) =
401 parse_email
("$wrong_name <$wrong_address>");
403 my $wrong_email = format_email
($wrong_name, $wrong_address, 1);
404 $mailmap->{names
}->{$wrong_email} = $real_name;
405 $mailmap->{addresses
}->{$wrong_email} = $real_address;
408 close($mailmap_file);
411 ## use the filenames on the command line or find the filenames in the patchfiles
415 my @keyword_tvi = ();
416 my @file_emails = ();
419 push(@ARGV, "&STDIN");
422 foreach my $file (@ARGV) {
423 if ($file ne "&STDIN") {
424 ##if $file is a directory and it lacks a trailing slash, add one
426 $file =~ s
@([^/])$@$1/@;
427 } elsif (!(-f
$file)) {
428 die "$P: file '${file}' not found\n";
431 if ($from_filename) {
433 if ($file ne "MAINTAINERS" && -f
$file && ($keywords || $file_emails)) {
434 open(my $f, '<', $file)
435 or die "$P: Can't open $file: $!\n";
436 my $text = do { local($/) ; <$f> };
439 foreach my $line (keys %keyword_hash) {
440 if ($text =~ m/$keyword_hash{$line}/x) {
441 push(@keyword_tvi, $line);
446 my @poss_addr = $text =~ m
$[A-Za-zÀ
-ÿ
\"\' \
,\
.\
+-]*\s
*[\
,]*\s
*[\
(\
<\
{]{0,1}[A-Za-z0-9_\
.\
+-]+\
@[A-Za-z0-9\
.-]+\
.[A-Za-z0-9
]+[\
)\
>\
}]{0,1}$g;
447 push(@file_emails, clean_file_emails
(@poss_addr));
451 my $file_cnt = @files;
454 open(my $patch, "< $file")
455 or die "$P: Can't open $file: $!\n";
457 # We can check arbitrary information before the patch
458 # like the commit message, mail headers, etc...
459 # This allows us to match arbitrary keywords against any part
460 # of a git format-patch generated file (subject tags, etc...)
462 my $patch_prefix = ""; #Parsing the intro
466 if (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
468 $filename =~ s
@^[^/]*/@@;
470 $lastfile = $filename;
471 push(@files, $filename);
472 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
473 } elsif (m/^\@\@ -(\d+),(\d+)/) {
474 if ($email_git_blame) {
475 push(@range, "$lastfile:$1:$2");
477 } elsif ($keywords) {
478 foreach my $line (keys %keyword_hash) {
479 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
480 push(@keyword_tvi, $line);
487 if ($file_cnt == @files) {
488 warn "$P: file '${file}' doesn't appear to be a patch. "
489 . "Add -f to options?\n";
491 @files = sort_and_uniq
(@files);
495 @file_emails = uniq
(@file_emails);
498 my %email_hash_address;
506 my %deduplicate_name_hash = ();
507 my %deduplicate_address_hash = ();
509 my @maintainers = get_maintainers
();
512 @maintainers = merge_email
(@maintainers);
513 output
(@maintainers);
522 @status = uniq
(@status);
527 @subsystem = uniq
(@subsystem);
538 sub ignore_email_address
{
541 foreach my $ignore (@ignore_emails) {
542 return 1 if ($ignore eq $address);
548 sub range_is_maintained
{
549 my ($start, $end) = @_;
551 for (my $i = $start; $i < $end; $i++) {
552 my $line = $typevalue[$i];
553 if ($line =~ m/^([A-Z]):\s*(.*)/) {
557 if ($value =~ /(maintain|support)/i) {
566 sub range_has_maintainer
{
567 my ($start, $end) = @_;
569 for (my $i = $start; $i < $end; $i++) {
570 my $line = $typevalue[$i];
571 if ($line =~ m/^([A-Z]):\s*(.*)/) {
582 sub get_maintainers
{
583 %email_hash_name = ();
584 %email_hash_address = ();
585 %commit_author_hash = ();
586 %commit_signer_hash = ();
594 %deduplicate_name_hash = ();
595 %deduplicate_address_hash = ();
596 if ($email_git_all_signature_types) {
597 $signature_pattern = "(.+?)[Bb][Yy]:";
599 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
602 # Find responsible parties
604 my %exact_pattern_match_hash = ();
606 foreach my $file (@files) {
609 my $tvi = find_first_section
();
610 while ($tvi < @typevalue) {
611 my $start = find_starting_index
($tvi);
612 my $end = find_ending_index
($tvi);
616 #Do not match excluded file patterns
618 for ($i = $start; $i < $end; $i++) {
619 my $line = $typevalue[$i];
620 if ($line =~ m/^([A-Z]):\s*(.*)/) {
624 if (file_match_pattern
($file, $value)) {
633 for ($i = $start; $i < $end; $i++) {
634 my $line = $typevalue[$i];
635 if ($line =~ m/^([A-Z]):\s*(.*)/) {
639 if (file_match_pattern
($file, $value)) {
640 my $value_pd = ($value =~ tr
@/@@);
641 my $file_pd = ($file =~ tr
@/@@);
642 $value_pd++ if (substr($value,-1,1) ne "/");
643 $value_pd = -1 if ($value =~ /^\.\*/);
644 if ($value_pd >= $file_pd &&
645 range_is_maintained
($start, $end) &&
646 range_has_maintainer
($start, $end)) {
647 $exact_pattern_match_hash{$file} = 1;
649 if ($pattern_depth == 0 ||
650 (($file_pd - $value_pd) < $pattern_depth)) {
651 $hash{$tvi} = $value_pd;
654 } elsif ($type eq 'N') {
655 if ($file =~ m/$value/x) {
665 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
666 add_categories
($line);
669 my $start = find_starting_index
($line);
670 my $end = find_ending_index
($line);
671 for ($i = $start; $i < $end; $i++) {
672 my $line = $typevalue[$i];
673 if ($line =~ /^[FX]:/) { ##Restore file patterns
674 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
675 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
676 $line =~ s/\\\./\./g; ##Convert \. to .
677 $line =~ s/\.\*/\*/g; ##Convert .* to *
679 $line =~ s/^([A-Z]):/$1:\t/g;
688 @keyword_tvi = sort_and_uniq
(@keyword_tvi);
689 foreach my $line (@keyword_tvi) {
690 add_categories
($line);
694 foreach my $email (@email_to, @list_to) {
695 $email->[0] = deduplicate_email
($email->[0]);
698 foreach my $file (@files) {
700 ($email_git || ($email_git_fallback &&
701 !$exact_pattern_match_hash{$file}))) {
702 vcs_file_signoffs
($file);
704 if ($email && $email_git_blame) {
705 vcs_file_blame
($file);
710 foreach my $chief (@penguin_chief) {
711 if ($chief =~ m/^(.*):(.*)/) {
714 $email_address = format_email
($1, $2, $email_usename);
715 if ($email_git_penguin_chiefs) {
716 push(@email_to, [$email_address, 'chief penguin']);
718 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
723 foreach my $email (@file_emails) {
724 my ($name, $address) = parse_email
($email);
726 my $tmp_email = format_email
($name, $address, $email_usename);
727 push_email_address
($tmp_email, '');
728 add_role
($tmp_email, 'in file');
733 if ($email || $email_list) {
735 @to = (@to, @email_to);
738 @to = (@to, @list_to);
743 @to = interactive_get_maintainers
(\
@to);
749 sub file_match_pattern
{
750 my ($file, $pattern) = @_;
751 if (substr($pattern, -1) eq "/") {
752 if ($file =~ m
@^$pattern@) {
756 if ($file =~ m
@^$pattern@) {
757 my $s1 = ($file =~ tr
@/@@);
758 my $s2 = ($pattern =~ tr
@/@@);
769 usage: $P [options] patchfile
770 $P [options] -f file|directory
773 MAINTAINER field selection options:
774 --email => print email address(es) if any
775 --git => include recent git \*-by: signers
776 --git-all-signature-types => include signers regardless of signature type
777 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
778 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
779 --git-chief-penguins => include ${penguin_chiefs}
780 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
781 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
782 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
783 --git-blame => use git blame to find modified commits for patch or file
784 --git-blame-signatures => when used with --git-blame, also include all commit signers
785 --git-since => git history to use (default: $email_git_since)
786 --hg-since => hg history to use (default: $email_hg_since)
787 --interactive => display a menu (mostly useful if used with the --git option)
788 --m => include maintainer(s) if any
789 --r => include reviewer(s) if any
790 --n => include name 'Full Name <addr\@domain.tld>'
791 --l => include list(s) if any
792 --s => include subscriber only list(s) if any
793 --remove-duplicates => minimize duplicate email names/addresses
794 --roles => show roles (status:subsystem, git-signer, list, etc...)
795 --rolestats => show roles and statistics (commits/total_commits, %)
796 --file-emails => add email addresses found in -f file (default: 0 (off))
797 --scm => print SCM tree(s) if any
798 --status => print status if any
799 --subsystem => print subsystem name if any
800 --web => print website(s) if any
803 --separator [, ] => separator for multiple entries on 1 line
804 using --separator also sets --nomultiline if --separator is not [, ]
805 --multiline => print 1 entry per line
808 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
809 --keywords => scan patch for keywords (default: $keywords)
810 --sections => print all of the subsystem sections with pattern matches
811 --mailmap => use .mailmap file (default: $email_use_mailmap)
812 --version => show version
813 --help => show this help information
816 [--email --nogit --git-fallback --m --n --l --multiline -pattern-depth=0
817 --remove-duplicates --rolestats]
820 Using "-f directory" may give unexpected results:
821 Used with "--git", git signators for _all_ files in and below
822 directory are examined as git recurses directories.
823 Any specified X: (exclude) pattern matches are _not_ ignored.
824 Used with "--nogit", directory is used as a pattern match,
825 no individual file within the directory or subdirectory
827 Used with "--git-blame", does not iterate all files in directory
828 Using "--git-blame" is slow and may add old committers and authors
829 that are no longer active maintainers to the output.
830 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
831 other automated tools that expect only ["name"] <email address>
832 may not work because of additional output after <email address>.
833 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
834 not the percentage of the entire file authored. # of commits is
835 not a good measure of amount of code authored. 1 major commit may
836 contain a thousand lines, 5 trivial commits may modify a single line.
837 If git is not installed, but mercurial (hg) is installed and an .hg
838 repository exists, the following options apply to mercurial:
840 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
842 Use --hg-since not --git-since to control date selection
843 File ".get_maintainer.conf", if it exists in the linux kernel source root
844 directory, can change whatever get_maintainer defaults are desired.
845 Entries in this file can be any command line argument.
846 This file is prepended to any additional command line arguments.
847 Multiple lines and # comments are allowed.
851 sub top_of_kernel_tree
{
854 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
857 if ( (-f
"${lk_path}COPYING")
858 && (-f
"${lk_path}CREDITS")
859 && (-f
"${lk_path}Kbuild")
860 && (-f
"${lk_path}MAINTAINERS")
861 && (-f
"${lk_path}Makefile")
862 && (-f
"${lk_path}README")
863 && (-d
"${lk_path}Documentation")
864 && (-d
"${lk_path}arch")
865 && (-d
"${lk_path}include")
866 && (-d
"${lk_path}drivers")
867 && (-d
"${lk_path}fs")
868 && (-d
"${lk_path}init")
869 && (-d
"${lk_path}ipc")
870 && (-d
"${lk_path}kernel")
871 && (-d
"${lk_path}lib")
872 && (-d
"${lk_path}scripts")) {
879 my ($formatted_email) = @_;
884 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
887 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
889 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
893 $name =~ s/^\s+|\s+$//g;
894 $name =~ s/^\"|\"$//g;
895 $address =~ s/^\s+|\s+$//g;
897 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
898 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
902 return ($name, $address);
906 my ($name, $address, $usename) = @_;
910 $name =~ s/^\s+|\s+$//g;
911 $name =~ s/^\"|\"$//g;
912 $address =~ s/^\s+|\s+$//g;
914 if ($name =~ /[^\w \-]/i) { ##has "must quote
" chars
915 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
921 $formatted_email = "$address";
923 $formatted_email = "$name <$address>";
926 $formatted_email = $address;
929 return $formatted_email;
932 sub find_first_section
{
935 while ($index < @typevalue) {
936 my $tv = $typevalue[$index];
937 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
946 sub find_starting_index
{
950 my $tv = $typevalue[$index];
951 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
960 sub find_ending_index
{
963 while ($index < @typevalue) {
964 my $tv = $typevalue[$index];
965 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
974 sub get_maintainer_role
{
978 my $start = find_starting_index
($index);
979 my $end = find_ending_index
($index);
981 my $role = "unknown";
982 my $subsystem = $typevalue[$start];
983 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
984 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
985 $subsystem =~ s/\s*$//;
986 $subsystem = $subsystem . "...";
989 for ($i = $start + 1; $i < $end; $i++) {
990 my $tv = $typevalue[$i];
991 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1001 if ($role eq "supported") {
1002 $role = "supporter";
1003 } elsif ($role eq "maintained") {
1004 $role = "maintainer";
1005 } elsif ($role eq "odd fixes") {
1006 $role = "odd fixer";
1007 } elsif ($role eq "orphan") {
1008 $role = "orphan minder";
1009 } elsif ($role eq "obsolete") {
1010 $role = "obsolete minder";
1011 } elsif ($role eq "buried alive in reporters") {
1012 $role = "chief penguin";
1015 return $role . ":" . $subsystem;
1022 my $start = find_starting_index
($index);
1023 my $end = find_ending_index
($index);
1025 my $subsystem = $typevalue[$start];
1026 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
1027 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
1028 $subsystem =~ s/\s*$//;
1029 $subsystem = $subsystem . "...";
1032 if ($subsystem eq "THE REST") {
1039 sub add_categories
{
1043 my $start = find_starting_index
($index);
1044 my $end = find_ending_index
($index);
1046 push(@subsystem, $typevalue[$start]);
1048 for ($i = $start + 1; $i < $end; $i++) {
1049 my $tv = $typevalue[$i];
1050 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1053 if ($ptype eq "L") {
1054 my $list_address = $pvalue;
1055 my $list_additional = "";
1056 my $list_role = get_list_role
($i);
1058 if ($list_role ne "") {
1059 $list_role = ":" . $list_role;
1061 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1063 $list_additional = $2;
1065 if ($list_additional =~ m/subscribers-only/) {
1066 if ($email_subscriber_list) {
1067 if (!$hash_list_to{lc($list_address)}) {
1068 $hash_list_to{lc($list_address)} = 1;
1069 push(@list_to, [$list_address,
1070 "subscriber list${list_role}"]);
1075 if (!$hash_list_to{lc($list_address)}) {
1076 $hash_list_to{lc($list_address)} = 1;
1077 if ($list_additional =~ m/moderated/) {
1078 push(@list_to, [$list_address,
1079 "moderated list${list_role}"]);
1081 push(@list_to, [$list_address,
1082 "open list${list_role}"]);
1087 } elsif ($ptype eq "M") {
1088 my ($name, $address) = parse_email
($pvalue);
1091 my $tv = $typevalue[$i - 1];
1092 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1095 $pvalue = format_email
($name, $address, $email_usename);
1100 if ($email_maintainer) {
1101 my $role = get_maintainer_role
($i);
1102 push_email_addresses
($pvalue, $role);
1104 } elsif ($ptype eq "R") {
1105 my ($name, $address) = parse_email
($pvalue);
1108 my $tv = $typevalue[$i - 1];
1109 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1112 $pvalue = format_email
($name, $address, $email_usename);
1117 if ($email_reviewer) {
1118 push_email_addresses
($pvalue, 'reviewer');
1120 } elsif ($ptype eq "T") {
1121 push(@scm, $pvalue);
1122 } elsif ($ptype eq "W") {
1123 push(@web, $pvalue);
1124 } elsif ($ptype eq "S") {
1125 push(@status, $pvalue);
1132 my ($name, $address) = @_;
1134 return 1 if (($name eq "") && ($address eq ""));
1135 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1136 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1141 sub push_email_address
{
1142 my ($line, $role) = @_;
1144 my ($name, $address) = parse_email
($line);
1146 if ($address eq "") {
1150 if (!$email_remove_duplicates) {
1151 push(@email_to, [format_email
($name, $address, $email_usename), $role]);
1152 } elsif (!email_inuse
($name, $address)) {
1153 push(@email_to, [format_email
($name, $address, $email_usename), $role]);
1154 $email_hash_name{lc($name)}++ if ($name ne "");
1155 $email_hash_address{lc($address)}++;
1161 sub push_email_addresses
{
1162 my ($address, $role) = @_;
1164 my @address_list = ();
1166 if (rfc822_valid
($address)) {
1167 push_email_address
($address, $role);
1168 } elsif (@address_list = rfc822_validlist
($address)) {
1169 my $array_count = shift(@address_list);
1170 while (my $entry = shift(@address_list)) {
1171 push_email_address
($entry, $role);
1174 if (!push_email_address
($address, $role)) {
1175 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1181 my ($line, $role) = @_;
1183 my ($name, $address) = parse_email
($line);
1184 my $email = format_email
($name, $address, $email_usename);
1186 foreach my $entry (@email_to) {
1187 if ($email_remove_duplicates) {
1188 my ($entry_name, $entry_address) = parse_email
($entry->[0]);
1189 if (($name eq $entry_name || $address eq $entry_address)
1190 && ($role eq "" || !($entry->[1] =~ m/$role/))
1192 if ($entry->[1] eq "") {
1193 $entry->[1] = "$role";
1195 $entry->[1] = "$entry->[1],$role";
1199 if ($email eq $entry->[0]
1200 && ($role eq "" || !($entry->[1] =~ m/$role/))
1202 if ($entry->[1] eq "") {
1203 $entry->[1] = "$role";
1205 $entry->[1] = "$entry->[1],$role";
1215 foreach my $path (split(/:/, $ENV{PATH
})) {
1216 if (-e
"$path/$bin") {
1217 return "$path/$bin";
1227 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1228 if (-e
"$path/$conf") {
1229 return "$path/$conf";
1239 my ($name, $address) = parse_email
($line);
1240 my $email = format_email
($name, $address, 1);
1241 my $real_name = $name;
1242 my $real_address = $address;
1244 if (exists $mailmap->{names
}->{$email} ||
1245 exists $mailmap->{addresses
}->{$email}) {
1246 if (exists $mailmap->{names
}->{$email}) {
1247 $real_name = $mailmap->{names
}->{$email};
1249 if (exists $mailmap->{addresses
}->{$email}) {
1250 $real_address = $mailmap->{addresses
}->{$email};
1253 if (exists $mailmap->{names
}->{$address}) {
1254 $real_name = $mailmap->{names
}->{$address};
1256 if (exists $mailmap->{addresses
}->{$address}) {
1257 $real_address = $mailmap->{addresses
}->{$address};
1260 return format_email
($real_name, $real_address, 1);
1264 my (@addresses) = @_;
1266 my @mapped_emails = ();
1267 foreach my $line (@addresses) {
1268 push(@mapped_emails, mailmap_email
($line));
1270 merge_by_realname
(@mapped_emails) if ($email_use_mailmap);
1271 return @mapped_emails;
1274 sub merge_by_realname
{
1278 foreach my $email (@emails) {
1279 my ($name, $address) = parse_email
($email);
1280 if (exists $address_map{$name}) {
1281 $address = $address_map{$name};
1282 $email = format_email
($name, $address, 1);
1284 $address_map{$name} = $address;
1289 sub git_execute_cmd
{
1293 my $output = `$cmd`;
1294 $output =~ s/^\s*//gm;
1295 @lines = split("\n", $output);
1300 sub hg_execute_cmd {
1304 my $output = `$cmd`;
1305 @lines = split("\n", $output);
1310 sub extract_formatted_signatures
{
1311 my (@signature_lines) = @_;
1313 my @type = @signature_lines;
1315 s/\s*(.*):.*/$1/ for (@type);
1318 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1320 ## Reformat email addresses (with names) to avoid badly written signatures
1322 foreach my $signer (@signature_lines) {
1323 $signer = deduplicate_email
($signer);
1326 return (\
@type, \
@signature_lines);
1329 sub vcs_find_signers
{
1330 my ($cmd, $file) = @_;
1333 my @signatures = ();
1337 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1339 my $pattern = $VCS_cmds{"commit_pattern"};
1340 my $author_pattern = $VCS_cmds{"author_pattern"};
1341 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1343 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
1345 $commits = grep(/$pattern/, @lines); # of commits
1347 @authors = grep(/$author_pattern/, @lines);
1348 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1349 @stats = grep(/$stat_pattern/, @lines);
1351 # print("stats: <@stats>\n");
1353 return (0, \
@signatures, \
@authors, \
@stats) if !@signatures;
1355 save_commits_by_author
(@lines) if ($interactive);
1356 save_commits_by_signer
(@lines) if ($interactive);
1358 if (!$email_git_penguin_chiefs) {
1359 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1362 my ($author_ref, $authors_ref) = extract_formatted_signatures
(@authors);
1363 my ($types_ref, $signers_ref) = extract_formatted_signatures
(@signatures);
1365 return ($commits, $signers_ref, $authors_ref, \
@stats);
1368 sub vcs_find_author
{
1372 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1374 if (!$email_git_penguin_chiefs) {
1375 @lines = grep(!/${penguin_chiefs}/i, @lines);
1378 return @lines if !@lines;
1381 foreach my $line (@lines) {
1382 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1384 my ($name, $address) = parse_email
($author);
1385 $author = format_email
($name, $address, 1);
1386 push(@authors, $author);
1390 save_commits_by_author
(@lines) if ($interactive);
1391 save_commits_by_signer
(@lines) if ($interactive);
1396 sub vcs_save_commits
{
1401 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1403 foreach my $line (@lines) {
1404 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1417 return @commits if (!(-f
$file));
1419 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1420 my @all_commits = ();
1422 $cmd = $VCS_cmds{"blame_file_cmd"};
1423 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1424 @all_commits = vcs_save_commits
($cmd);
1426 foreach my $file_range_diff (@range) {
1427 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1429 my $diff_start = $2;
1430 my $diff_length = $3;
1431 next if ("$file" ne "$diff_file");
1432 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1433 push(@commits, $all_commits[$i]);
1437 foreach my $file_range_diff (@range) {
1438 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1440 my $diff_start = $2;
1441 my $diff_length = $3;
1442 next if ("$file" ne "$diff_file");
1443 $cmd = $VCS_cmds{"blame_range_cmd"};
1444 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1445 push(@commits, vcs_save_commits
($cmd));
1448 $cmd = $VCS_cmds{"blame_file_cmd"};
1449 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1450 @commits = vcs_save_commits
($cmd);
1453 foreach my $commit (@commits) {
1454 $commit =~ s/^\^//g;
1460 my $printed_novcs = 0;
1462 %VCS_cmds = %VCS_cmds_git;
1463 return 1 if eval $VCS_cmds{"available"};
1464 %VCS_cmds = %VCS_cmds_hg;
1465 return 2 if eval $VCS_cmds{"available"};
1467 if (!$printed_novcs) {
1468 warn("$P: No supported VCS found. Add --nogit to options?\n");
1469 warn("Using a git repository produces better results.\n");
1470 warn("Try Linus Torvalds' latest git repository using:\n");
1471 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
1479 return $vcs_used == 1;
1483 return $vcs_used == 2;
1486 sub interactive_get_maintainers
{
1487 my ($list_ref) = @_;
1488 my @list = @$list_ref;
1497 foreach my $entry (@list) {
1498 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1499 $selected{$count} = 1;
1500 $authored{$count} = 0;
1501 $signed{$count} = 0;
1507 my $print_options = 0;
1512 printf STDERR
"\n%1s %2s %-65s",
1513 "*", "#", "email/list and role:stats";
1515 ($email_git_fallback && !$maintained) ||
1517 print STDERR
"auth sign";
1520 foreach my $entry (@list) {
1521 my $email = $entry->[0];
1522 my $role = $entry->[1];
1524 $sel = "*" if ($selected{$count});
1525 my $commit_author = $commit_author_hash{$email};
1526 my $commit_signer = $commit_signer_hash{$email};
1529 $authored++ for (@{$commit_author});
1530 $signed++ for (@{$commit_signer});
1531 printf STDERR
"%1s %2d %-65s", $sel, $count + 1, $email;
1532 printf STDERR
"%4d %4d", $authored, $signed
1533 if ($authored > 0 || $signed > 0);
1534 printf STDERR
"\n %s\n", $role;
1535 if ($authored{$count}) {
1536 my $commit_author = $commit_author_hash{$email};
1537 foreach my $ref (@{$commit_author}) {
1538 print STDERR
" Author: @{$ref}[1]\n";
1541 if ($signed{$count}) {
1542 my $commit_signer = $commit_signer_hash{$email};
1543 foreach my $ref (@{$commit_signer}) {
1544 print STDERR
" @{$ref}[2]: @{$ref}[1]\n";
1551 my $date_ref = \
$email_git_since;
1552 $date_ref = \
$email_hg_since if (vcs_is_hg
());
1553 if ($print_options) {
1558 Version Control options:
1559 g use git history [$email_git]
1560 gf use git-fallback [$email_git_fallback]
1561 b use git blame [$email_git_blame]
1562 bs use blame signatures [$email_git_blame_signatures]
1563 c# minimum commits [$email_git_min_signatures]
1564 %# min percent [$email_git_min_percent]
1565 d# history to use [$$date_ref]
1566 x# max maintainers [$email_git_max_maintainers]
1567 t all signature types [$email_git_all_signature_types]
1568 m use .mailmap [$email_use_mailmap]
1575 tm toggle maintainers
1576 tg toggle git entries
1577 tl toggle open list entries
1578 ts toggle subscriber list entries
1579 f emails in file [$file_emails]
1580 k keywords in file [$keywords]
1581 r remove duplicates [$email_remove_duplicates]
1582 p# pattern match depth [$pattern_depth]
1586 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1588 my $input = <STDIN
>;
1593 my @wish = split(/[, ]+/, $input);
1594 foreach my $nr (@wish) {
1596 my $sel = substr($nr, 0, 1);
1597 my $str = substr($nr, 1);
1599 $val = $1 if $str =~ /^(\d+)$/;
1604 $output_rolestats = 0;
1607 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1608 $selected{$nr - 1} = !$selected{$nr - 1};
1609 } elsif ($sel eq "*" || $sel eq '^') {
1611 $toggle = 1 if ($sel eq '*');
1612 for (my $i = 0; $i < $count; $i++) {
1613 $selected{$i} = $toggle;
1615 } elsif ($sel eq "0") {
1616 for (my $i = 0; $i < $count; $i++) {
1617 $selected{$i} = !$selected{$i};
1619 } elsif ($sel eq "t") {
1620 if (lc($str) eq "m") {
1621 for (my $i = 0; $i < $count; $i++) {
1622 $selected{$i} = !$selected{$i}
1623 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1625 } elsif (lc($str) eq "g") {
1626 for (my $i = 0; $i < $count; $i++) {
1627 $selected{$i} = !$selected{$i}
1628 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1630 } elsif (lc($str) eq "l") {
1631 for (my $i = 0; $i < $count; $i++) {
1632 $selected{$i} = !$selected{$i}
1633 if ($list[$i]->[1] =~ /^(open list)/i);
1635 } elsif (lc($str) eq "s") {
1636 for (my $i = 0; $i < $count; $i++) {
1637 $selected{$i} = !$selected{$i}
1638 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1641 } elsif ($sel eq "a") {
1642 if ($val > 0 && $val <= $count) {
1643 $authored{$val - 1} = !$authored{$val - 1};
1644 } elsif ($str eq '*' || $str eq '^') {
1646 $toggle = 1 if ($str eq '*');
1647 for (my $i = 0; $i < $count; $i++) {
1648 $authored{$i} = $toggle;
1651 } elsif ($sel eq "s") {
1652 if ($val > 0 && $val <= $count) {
1653 $signed{$val - 1} = !$signed{$val - 1};
1654 } elsif ($str eq '*' || $str eq '^') {
1656 $toggle = 1 if ($str eq '*');
1657 for (my $i = 0; $i < $count; $i++) {
1658 $signed{$i} = $toggle;
1661 } elsif ($sel eq "o") {
1664 } elsif ($sel eq "g") {
1666 bool_invert
(\
$email_git_fallback);
1668 bool_invert
(\
$email_git);
1671 } elsif ($sel eq "b") {
1673 bool_invert
(\
$email_git_blame_signatures);
1675 bool_invert
(\
$email_git_blame);
1678 } elsif ($sel eq "c") {
1680 $email_git_min_signatures = $val;
1683 } elsif ($sel eq "x") {
1685 $email_git_max_maintainers = $val;
1688 } elsif ($sel eq "%") {
1689 if ($str ne "" && $val >= 0) {
1690 $email_git_min_percent = $val;
1693 } elsif ($sel eq "d") {
1695 $email_git_since = $str;
1696 } elsif (vcs_is_hg
()) {
1697 $email_hg_since = $str;
1700 } elsif ($sel eq "t") {
1701 bool_invert
(\
$email_git_all_signature_types);
1703 } elsif ($sel eq "f") {
1704 bool_invert
(\
$file_emails);
1706 } elsif ($sel eq "r") {
1707 bool_invert
(\
$email_remove_duplicates);
1709 } elsif ($sel eq "m") {
1710 bool_invert
(\
$email_use_mailmap);
1713 } elsif ($sel eq "k") {
1714 bool_invert
(\
$keywords);
1716 } elsif ($sel eq "p") {
1717 if ($str ne "" && $val >= 0) {
1718 $pattern_depth = $val;
1721 } elsif ($sel eq "h" || $sel eq "?") {
1724 Interactive mode allows you to select the various maintainers, submitters,
1725 commit signers and mailing lists that could be CC'd on a patch.
1727 Any *'d entry is selected.
1729 If you have git or hg installed, you can choose to summarize the commit
1730 history of files in the patch. Also, each line of the current file can
1731 be matched to its commit author and that commits signers with blame.
1733 Various knobs exist to control the length of time for active commit
1734 tracking, the maximum number of commit authors and signers to add,
1737 Enter selections at the prompt until you are satisfied that the selected
1738 maintainers are appropriate. You may enter multiple selections separated
1739 by either commas or spaces.
1743 print STDERR
"invalid option: '$nr'\n";
1748 print STDERR
"git-blame can be very slow, please have patience..."
1749 if ($email_git_blame);
1750 goto &get_maintainers
;
1754 #drop not selected entries
1756 my @new_emailto = ();
1757 foreach my $entry (@list) {
1758 if ($selected{$count}) {
1759 push(@new_emailto, $list[$count]);
1763 return @new_emailto;
1767 my ($bool_ref) = @_;
1776 sub deduplicate_email
{
1780 my ($name, $address) = parse_email
($email);
1781 $email = format_email
($name, $address, 1);
1782 $email = mailmap_email
($email);
1784 return $email if (!$email_remove_duplicates);
1786 ($name, $address) = parse_email
($email);
1788 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1789 $name = $deduplicate_name_hash{lc($name)}->[0];
1790 $address = $deduplicate_name_hash{lc($name)}->[1];
1792 } elsif ($deduplicate_address_hash{lc($address)}) {
1793 $name = $deduplicate_address_hash{lc($address)}->[0];
1794 $address = $deduplicate_address_hash{lc($address)}->[1];
1798 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1799 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1801 $email = format_email
($name, $address, 1);
1802 $email = mailmap_email
($email);
1806 sub save_commits_by_author
{
1813 foreach my $line (@lines) {
1814 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1816 $author = deduplicate_email
($author);
1817 push(@authors, $author);
1819 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1820 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1823 for (my $i = 0; $i < @authors; $i++) {
1825 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1826 if (@{$ref}[0] eq $commits[$i] &&
1827 @{$ref}[1] eq $subjects[$i]) {
1833 push(@{$commit_author_hash{$authors[$i]}},
1834 [ ($commits[$i], $subjects[$i]) ]);
1839 sub save_commits_by_signer
{
1845 foreach my $line (@lines) {
1846 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1847 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1848 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1849 my @signatures = ($line);
1850 my ($types_ref, $signers_ref) = extract_formatted_signatures
(@signatures);
1851 my @types = @$types_ref;
1852 my @signers = @$signers_ref;
1854 my $type = $types[0];
1855 my $signer = $signers[0];
1857 $signer = deduplicate_email
($signer);
1860 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1861 if (@{$ref}[0] eq $commit &&
1862 @{$ref}[1] eq $subject &&
1863 @{$ref}[2] eq $type) {
1869 push(@{$commit_signer_hash{$signer}},
1870 [ ($commit, $subject, $type) ]);
1877 my ($role, $divisor, @lines) = @_;
1882 return if (@lines <= 0);
1884 if ($divisor <= 0) {
1885 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
1889 @lines = mailmap
(@lines);
1891 return if (@lines <= 0);
1893 @lines = sort(@lines);
1896 $hash{$_}++ for @lines;
1899 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
1900 my $sign_offs = $hash{$line};
1901 my $percent = $sign_offs * 100 / $divisor;
1903 $percent = 100 if ($percent > 100);
1904 next if (ignore_email_address
($line));
1906 last if ($sign_offs < $email_git_min_signatures ||
1907 $count > $email_git_max_maintainers ||
1908 $percent < $email_git_min_percent);
1909 push_email_address
($line, '');
1910 if ($output_rolestats) {
1911 my $fmt_percent = sprintf("%.0f", $percent);
1912 add_role
($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1914 add_role
($line, $role);
1919 sub vcs_file_signoffs
{
1930 $vcs_used = vcs_exists
();
1931 return if (!$vcs_used);
1933 my $cmd = $VCS_cmds{"find_signers_cmd"};
1934 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1936 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers
($cmd, $file);
1938 @signers = @{$signers_ref} if defined $signers_ref;
1939 @authors = @{$authors_ref} if defined $authors_ref;
1940 @stats = @{$stats_ref} if defined $stats_ref;
1942 # print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
1944 foreach my $signer (@signers) {
1945 $signer = deduplicate_email
($signer);
1948 vcs_assign
("commit_signer", $commits, @signers);
1949 vcs_assign
("authored", $commits, @authors);
1950 if ($#authors == $#stats) {
1951 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1952 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
1956 for (my $i = 0; $i <= $#stats; $i++) {
1957 if ($stats[$i] =~ /$stat_pattern/) {
1962 my @tmp_authors = uniq
(@authors);
1963 foreach my $author (@tmp_authors) {
1964 $author = deduplicate_email
($author);
1966 @tmp_authors = uniq
(@tmp_authors);
1967 my @list_added = ();
1968 my @list_deleted = ();
1969 foreach my $author (@tmp_authors) {
1971 my $auth_deleted = 0;
1972 for (my $i = 0; $i <= $#stats; $i++) {
1973 if ($author eq deduplicate_email
($authors[$i]) &&
1974 $stats[$i] =~ /$stat_pattern/) {
1976 $auth_deleted += $2;
1979 for (my $i = 0; $i < $auth_added; $i++) {
1980 push(@list_added, $author);
1982 for (my $i = 0; $i < $auth_deleted; $i++) {
1983 push(@list_deleted, $author);
1986 vcs_assign
("added_lines", $added, @list_added);
1987 vcs_assign
("removed_lines", $deleted, @list_deleted);
1991 sub vcs_file_blame
{
1995 my @all_commits = ();
2000 $vcs_used = vcs_exists
();
2001 return if (!$vcs_used);
2003 @all_commits = vcs_blame
($file);
2004 @commits = uniq
(@all_commits);
2005 $total_commits = @commits;
2006 $total_lines = @all_commits;
2008 if ($email_git_blame_signatures) {
2011 my $commit_authors_ref;
2012 my $commit_signers_ref;
2014 my @commit_authors = ();
2015 my @commit_signers = ();
2016 my $commit = join(" -r ", @commits);
2019 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2020 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2022 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers
($cmd, $file);
2023 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2024 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2026 push(@signers, @commit_signers);
2028 foreach my $commit (@commits) {
2030 my $commit_authors_ref;
2031 my $commit_signers_ref;
2033 my @commit_authors = ();
2034 my @commit_signers = ();
2037 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2038 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2040 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers
($cmd, $file);
2041 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2042 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2044 push(@signers, @commit_signers);
2049 if ($from_filename) {
2050 if ($output_rolestats) {
2052 if (vcs_is_hg
()) {{ # Double brace for last exit
2054 my @commit_signers = ();
2055 @commits = uniq
(@commits);
2056 @commits = sort(@commits);
2057 my $commit = join(" -r ", @commits);
2060 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2061 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2065 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2067 if (!$email_git_penguin_chiefs) {
2068 @lines = grep(!/${penguin_chiefs}/i, @lines);
2074 foreach my $line (@lines) {
2075 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2077 $author = deduplicate_email
($author);
2078 push(@authors, $author);
2082 save_commits_by_author
(@lines) if ($interactive);
2083 save_commits_by_signer
(@lines) if ($interactive);
2085 push(@signers, @authors);
2088 foreach my $commit (@commits) {
2090 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2091 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2092 my @author = vcs_find_author
($cmd);
2095 my $formatted_author = deduplicate_email
($author[0]);
2097 my $count = grep(/$commit/, @all_commits);
2098 for ($i = 0; $i < $count ; $i++) {
2099 push(@blame_signers, $formatted_author);
2103 if (@blame_signers) {
2104 vcs_assign
("authored lines", $total_lines, @blame_signers);
2107 foreach my $signer (@signers) {
2108 $signer = deduplicate_email
($signer);
2110 vcs_assign
("commits", $total_commits, @signers);
2112 foreach my $signer (@signers) {
2113 $signer = deduplicate_email
($signer);
2115 vcs_assign
("modified commits", $total_commits, @signers);
2123 @parms = grep(!$saw{$_}++, @parms);
2131 @parms = sort @parms;
2132 @parms = grep(!$saw{$_}++, @parms);
2136 sub clean_file_emails
{
2137 my (@file_emails) = @_;
2138 my @fmt_emails = ();
2140 foreach my $email (@file_emails) {
2141 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2142 my ($name, $address) = parse_email
($email);
2143 if ($name eq '"[,\.]"') {
2147 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2149 my $first = $nw[@nw - 3];
2150 my $middle = $nw[@nw - 2];
2151 my $last = $nw[@nw - 1];
2153 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2154 (length($first) == 2 && substr($first, -1) eq ".")) ||
2155 (length($middle) == 1 ||
2156 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2157 $name = "$first $middle $last";
2159 $name = "$middle $last";
2163 if (substr($name, -1) =~ /[,\.]/) {
2164 $name = substr($name, 0, length($name) - 1);
2165 } elsif (substr($name, -2) =~ /[,\.]"/) {
2166 $name = substr($name, 0, length($name) - 2) . '"';
2169 if (substr($name, 0, 1) =~ /[,\.]/) {
2170 $name = substr($name, 1, length($name) - 1);
2171 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2172 $name = '"' . substr($name, 2, length($name) - 2);
2175 my $fmt_email = format_email
($name, $address, $email_usename);
2176 push(@fmt_emails, $fmt_email);
2186 my ($address, $role) = @$_;
2187 if (!$saw{$address}) {
2188 if ($output_roles) {
2189 push(@lines, "$address ($role)");
2191 push(@lines, $address);
2203 if ($output_multiline) {
2204 foreach my $line (@parms) {
2208 print(join($output_separator, @parms));
2216 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2217 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2218 # This regexp will only work on addresses which have had comments stripped
2219 # and replaced with rfc822_lwsp.
2221 my $specials = '()<>@,;:\\\\".\\[\\]';
2222 my $controls = '\\000-\\037\\177';
2224 my $dtext = "[^\\[\\]\\r\\\\]";
2225 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2227 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2229 # Use zero-width assertion to spot the limit of an atom. A simple
2230 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2231 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2232 my $word = "(?:$atom|$quoted_string)";
2233 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2235 my $sub_domain = "(?:$atom|$domain_literal)";
2236 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2238 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2240 my $phrase = "$word*";
2241 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2242 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2243 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2245 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2246 my $address = "(?:$mailbox|$group)";
2248 return "$rfc822_lwsp*$address";
2251 sub rfc822_strip_comments
{
2253 # Recursively remove comments, and replace with a single space. The simpler
2254 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2255 # chars in atoms, for example.
2257 while ($s =~ s
/^((?
:[^"\\]|\\.)*
2258 (?:"(?
:[^"\\]|\\.)*"(?
:[^"\\]|\\.)*)*)
2259 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2263 # valid: returns true if the parameter is an RFC822 valid address
2266 my $s = rfc822_strip_comments(shift);
2269 $rfc822re = make_rfc822re();
2272 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2275 # validlist: In scalar context, returns true if the parameter is an RFC822
2276 # valid list of addresses.
2278 # In list context, returns an empty list on failure (an invalid
2279 # address was found); otherwise a list whose first element is the
2280 # number of addresses found and whose remaining elements are the
2281 # addresses. This is needed to disambiguate failure (invalid)
2282 # from success with no addresses found, because an empty string is
2285 sub rfc822_validlist {
2286 my $s = rfc822_strip_comments(shift);
2289 $rfc822re = make_rfc822re();
2291 # * null list items are valid according to the RFC
2292 # * the '1' business is to aid in distinguishing failure from no results
2295 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2296 $s =~ m/^$rfc822_char*$/) {
2297 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2300 return wantarray ? (scalar(@r), @r) : 1;
2302 return wantarray ? () : 0;