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 --r --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.
848 Most options have both positive and negative forms.
849 The negative forms for --<foo> are --no<foo> and --no-<foo>.
854 sub top_of_kernel_tree
{
857 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
860 if ( (-f
"${lk_path}COPYING")
861 && (-f
"${lk_path}CREDITS")
862 && (-f
"${lk_path}Kbuild")
863 && (-f
"${lk_path}MAINTAINERS")
864 && (-f
"${lk_path}Makefile")
865 && (-f
"${lk_path}README")
866 && (-d
"${lk_path}Documentation")
867 && (-d
"${lk_path}arch")
868 && (-d
"${lk_path}include")
869 && (-d
"${lk_path}drivers")
870 && (-d
"${lk_path}fs")
871 && (-d
"${lk_path}init")
872 && (-d
"${lk_path}ipc")
873 && (-d
"${lk_path}kernel")
874 && (-d
"${lk_path}lib")
875 && (-d
"${lk_path}scripts")) {
882 my ($formatted_email) = @_;
887 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
890 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
892 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
896 $name =~ s/^\s+|\s+$//g;
897 $name =~ s/^\"|\"$//g;
898 $address =~ s/^\s+|\s+$//g;
900 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
901 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
905 return ($name, $address);
909 my ($name, $address, $usename) = @_;
913 $name =~ s/^\s+|\s+$//g;
914 $name =~ s/^\"|\"$//g;
915 $address =~ s/^\s+|\s+$//g;
917 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
918 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
924 $formatted_email = "$address";
926 $formatted_email = "$name <$address>";
929 $formatted_email = $address;
932 return $formatted_email;
935 sub find_first_section
{
938 while ($index < @typevalue) {
939 my $tv = $typevalue[$index];
940 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
949 sub find_starting_index
{
953 my $tv = $typevalue[$index];
954 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
963 sub find_ending_index
{
966 while ($index < @typevalue) {
967 my $tv = $typevalue[$index];
968 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
977 sub get_subsystem_name
{
980 my $start = find_starting_index
($index);
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 . "...";
991 sub get_maintainer_role
{
995 my $start = find_starting_index
($index);
996 my $end = find_ending_index
($index);
998 my $role = "unknown";
999 my $subsystem = get_subsystem_name
($index);
1001 for ($i = $start + 1; $i < $end; $i++) {
1002 my $tv = $typevalue[$i];
1003 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1006 if ($ptype eq "S") {
1013 if ($role eq "supported") {
1014 $role = "supporter";
1015 } elsif ($role eq "maintained") {
1016 $role = "maintainer";
1017 } elsif ($role eq "odd fixes") {
1018 $role = "odd fixer";
1019 } elsif ($role eq "orphan") {
1020 $role = "orphan minder";
1021 } elsif ($role eq "obsolete") {
1022 $role = "obsolete minder";
1023 } elsif ($role eq "buried alive in reporters") {
1024 $role = "chief penguin";
1027 return $role . ":" . $subsystem;
1033 my $subsystem = get_subsystem_name
($index);
1035 if ($subsystem eq "THE REST") {
1042 sub add_categories
{
1046 my $start = find_starting_index
($index);
1047 my $end = find_ending_index
($index);
1049 push(@subsystem, $typevalue[$start]);
1051 for ($i = $start + 1; $i < $end; $i++) {
1052 my $tv = $typevalue[$i];
1053 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1056 if ($ptype eq "L") {
1057 my $list_address = $pvalue;
1058 my $list_additional = "";
1059 my $list_role = get_list_role
($i);
1061 if ($list_role ne "") {
1062 $list_role = ":" . $list_role;
1064 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1066 $list_additional = $2;
1068 if ($list_additional =~ m/subscribers-only/) {
1069 if ($email_subscriber_list) {
1070 if (!$hash_list_to{lc($list_address)}) {
1071 $hash_list_to{lc($list_address)} = 1;
1072 push(@list_to, [$list_address,
1073 "subscriber list${list_role}"]);
1078 if (!$hash_list_to{lc($list_address)}) {
1079 $hash_list_to{lc($list_address)} = 1;
1080 if ($list_additional =~ m/moderated/) {
1081 push(@list_to, [$list_address,
1082 "moderated list${list_role}"]);
1084 push(@list_to, [$list_address,
1085 "open list${list_role}"]);
1090 } elsif ($ptype eq "M") {
1091 my ($name, $address) = parse_email
($pvalue);
1094 my $tv = $typevalue[$i - 1];
1095 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1098 $pvalue = format_email
($name, $address, $email_usename);
1103 if ($email_maintainer) {
1104 my $role = get_maintainer_role
($i);
1105 push_email_addresses
($pvalue, $role);
1107 } elsif ($ptype eq "R") {
1108 my ($name, $address) = parse_email
($pvalue);
1111 my $tv = $typevalue[$i - 1];
1112 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1115 $pvalue = format_email
($name, $address, $email_usename);
1120 if ($email_reviewer) {
1121 my $subsystem = get_subsystem_name
($i);
1122 push_email_addresses
($pvalue, "reviewer:$subsystem");
1124 } elsif ($ptype eq "T") {
1125 push(@scm, $pvalue);
1126 } elsif ($ptype eq "W") {
1127 push(@web, $pvalue);
1128 } elsif ($ptype eq "S") {
1129 push(@status, $pvalue);
1136 my ($name, $address) = @_;
1138 return 1 if (($name eq "") && ($address eq ""));
1139 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1140 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1145 sub push_email_address
{
1146 my ($line, $role) = @_;
1148 my ($name, $address) = parse_email
($line);
1150 if ($address eq "") {
1154 if (!$email_remove_duplicates) {
1155 push(@email_to, [format_email
($name, $address, $email_usename), $role]);
1156 } elsif (!email_inuse
($name, $address)) {
1157 push(@email_to, [format_email
($name, $address, $email_usename), $role]);
1158 $email_hash_name{lc($name)}++ if ($name ne "");
1159 $email_hash_address{lc($address)}++;
1165 sub push_email_addresses
{
1166 my ($address, $role) = @_;
1168 my @address_list = ();
1170 if (rfc822_valid
($address)) {
1171 push_email_address
($address, $role);
1172 } elsif (@address_list = rfc822_validlist
($address)) {
1173 my $array_count = shift(@address_list);
1174 while (my $entry = shift(@address_list)) {
1175 push_email_address
($entry, $role);
1178 if (!push_email_address
($address, $role)) {
1179 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1185 my ($line, $role) = @_;
1187 my ($name, $address) = parse_email
($line);
1188 my $email = format_email
($name, $address, $email_usename);
1190 foreach my $entry (@email_to) {
1191 if ($email_remove_duplicates) {
1192 my ($entry_name, $entry_address) = parse_email
($entry->[0]);
1193 if (($name eq $entry_name || $address eq $entry_address)
1194 && ($role eq "" || !($entry->[1] =~ m/$role/))
1196 if ($entry->[1] eq "") {
1197 $entry->[1] = "$role";
1199 $entry->[1] = "$entry->[1],$role";
1203 if ($email eq $entry->[0]
1204 && ($role eq "" || !($entry->[1] =~ m/$role/))
1206 if ($entry->[1] eq "") {
1207 $entry->[1] = "$role";
1209 $entry->[1] = "$entry->[1],$role";
1219 foreach my $path (split(/:/, $ENV{PATH
})) {
1220 if (-e
"$path/$bin") {
1221 return "$path/$bin";
1231 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1232 if (-e
"$path/$conf") {
1233 return "$path/$conf";
1243 my ($name, $address) = parse_email
($line);
1244 my $email = format_email
($name, $address, 1);
1245 my $real_name = $name;
1246 my $real_address = $address;
1248 if (exists $mailmap->{names
}->{$email} ||
1249 exists $mailmap->{addresses
}->{$email}) {
1250 if (exists $mailmap->{names
}->{$email}) {
1251 $real_name = $mailmap->{names
}->{$email};
1253 if (exists $mailmap->{addresses
}->{$email}) {
1254 $real_address = $mailmap->{addresses
}->{$email};
1257 if (exists $mailmap->{names
}->{$address}) {
1258 $real_name = $mailmap->{names
}->{$address};
1260 if (exists $mailmap->{addresses
}->{$address}) {
1261 $real_address = $mailmap->{addresses
}->{$address};
1264 return format_email
($real_name, $real_address, 1);
1268 my (@addresses) = @_;
1270 my @mapped_emails = ();
1271 foreach my $line (@addresses) {
1272 push(@mapped_emails, mailmap_email
($line));
1274 merge_by_realname
(@mapped_emails) if ($email_use_mailmap);
1275 return @mapped_emails;
1278 sub merge_by_realname
{
1282 foreach my $email (@emails) {
1283 my ($name, $address) = parse_email
($email);
1284 if (exists $address_map{$name}) {
1285 $address = $address_map{$name};
1286 $email = format_email
($name, $address, 1);
1288 $address_map{$name} = $address;
1293 sub git_execute_cmd
{
1297 my $output = `$cmd`;
1298 $output =~ s/^\s*//gm;
1299 @lines = split("\n", $output);
1304 sub hg_execute_cmd
{
1308 my $output = `$cmd`;
1309 @lines = split("\n", $output);
1314 sub extract_formatted_signatures
{
1315 my (@signature_lines) = @_;
1317 my @type = @signature_lines;
1319 s/\s*(.*):.*/$1/ for (@type);
1322 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1324 ## Reformat email addresses (with names) to avoid badly written signatures
1326 foreach my $signer (@signature_lines) {
1327 $signer = deduplicate_email
($signer);
1330 return (\
@type, \
@signature_lines);
1333 sub vcs_find_signers
{
1334 my ($cmd, $file) = @_;
1337 my @signatures = ();
1341 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1343 my $pattern = $VCS_cmds{"commit_pattern"};
1344 my $author_pattern = $VCS_cmds{"author_pattern"};
1345 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1347 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
1349 $commits = grep(/$pattern/, @lines); # of commits
1351 @authors = grep(/$author_pattern/, @lines);
1352 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1353 @stats = grep(/$stat_pattern/, @lines);
1355 # print("stats: <@stats>\n");
1357 return (0, \
@signatures, \
@authors, \
@stats) if !@signatures;
1359 save_commits_by_author
(@lines) if ($interactive);
1360 save_commits_by_signer
(@lines) if ($interactive);
1362 if (!$email_git_penguin_chiefs) {
1363 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1366 my ($author_ref, $authors_ref) = extract_formatted_signatures
(@authors);
1367 my ($types_ref, $signers_ref) = extract_formatted_signatures
(@signatures);
1369 return ($commits, $signers_ref, $authors_ref, \
@stats);
1372 sub vcs_find_author
{
1376 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1378 if (!$email_git_penguin_chiefs) {
1379 @lines = grep(!/${penguin_chiefs}/i, @lines);
1382 return @lines if !@lines;
1385 foreach my $line (@lines) {
1386 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1388 my ($name, $address) = parse_email
($author);
1389 $author = format_email
($name, $address, 1);
1390 push(@authors, $author);
1394 save_commits_by_author
(@lines) if ($interactive);
1395 save_commits_by_signer
(@lines) if ($interactive);
1400 sub vcs_save_commits
{
1405 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1407 foreach my $line (@lines) {
1408 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1421 return @commits if (!(-f
$file));
1423 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1424 my @all_commits = ();
1426 $cmd = $VCS_cmds{"blame_file_cmd"};
1427 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1428 @all_commits = vcs_save_commits
($cmd);
1430 foreach my $file_range_diff (@range) {
1431 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1433 my $diff_start = $2;
1434 my $diff_length = $3;
1435 next if ("$file" ne "$diff_file");
1436 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1437 push(@commits, $all_commits[$i]);
1441 foreach my $file_range_diff (@range) {
1442 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1444 my $diff_start = $2;
1445 my $diff_length = $3;
1446 next if ("$file" ne "$diff_file");
1447 $cmd = $VCS_cmds{"blame_range_cmd"};
1448 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1449 push(@commits, vcs_save_commits
($cmd));
1452 $cmd = $VCS_cmds{"blame_file_cmd"};
1453 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1454 @commits = vcs_save_commits
($cmd);
1457 foreach my $commit (@commits) {
1458 $commit =~ s/^\^//g;
1464 my $printed_novcs = 0;
1466 %VCS_cmds = %VCS_cmds_git;
1467 return 1 if eval $VCS_cmds{"available"};
1468 %VCS_cmds = %VCS_cmds_hg;
1469 return 2 if eval $VCS_cmds{"available"};
1471 if (!$printed_novcs) {
1472 warn("$P: No supported VCS found. Add --nogit to options?\n");
1473 warn("Using a git repository produces better results.\n");
1474 warn("Try Linus Torvalds' latest git repository using:\n");
1475 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
1483 return $vcs_used == 1;
1487 return $vcs_used == 2;
1490 sub interactive_get_maintainers
{
1491 my ($list_ref) = @_;
1492 my @list = @
$list_ref;
1501 foreach my $entry (@list) {
1502 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1503 $selected{$count} = 1;
1504 $authored{$count} = 0;
1505 $signed{$count} = 0;
1511 my $print_options = 0;
1516 printf STDERR
"\n%1s %2s %-65s",
1517 "*", "#", "email/list and role:stats";
1519 ($email_git_fallback && !$maintained) ||
1521 print STDERR
"auth sign";
1524 foreach my $entry (@list) {
1525 my $email = $entry->[0];
1526 my $role = $entry->[1];
1528 $sel = "*" if ($selected{$count});
1529 my $commit_author = $commit_author_hash{$email};
1530 my $commit_signer = $commit_signer_hash{$email};
1533 $authored++ for (@
{$commit_author});
1534 $signed++ for (@
{$commit_signer});
1535 printf STDERR
"%1s %2d %-65s", $sel, $count + 1, $email;
1536 printf STDERR
"%4d %4d", $authored, $signed
1537 if ($authored > 0 || $signed > 0);
1538 printf STDERR
"\n %s\n", $role;
1539 if ($authored{$count}) {
1540 my $commit_author = $commit_author_hash{$email};
1541 foreach my $ref (@
{$commit_author}) {
1542 print STDERR
" Author: @{$ref}[1]\n";
1545 if ($signed{$count}) {
1546 my $commit_signer = $commit_signer_hash{$email};
1547 foreach my $ref (@
{$commit_signer}) {
1548 print STDERR
" @{$ref}[2]: @{$ref}[1]\n";
1555 my $date_ref = \
$email_git_since;
1556 $date_ref = \
$email_hg_since if (vcs_is_hg
());
1557 if ($print_options) {
1562 Version Control options:
1563 g use git history [$email_git]
1564 gf use git-fallback [$email_git_fallback]
1565 b use git blame [$email_git_blame]
1566 bs use blame signatures [$email_git_blame_signatures]
1567 c# minimum commits [$email_git_min_signatures]
1568 %# min percent [$email_git_min_percent]
1569 d# history to use [$$date_ref]
1570 x# max maintainers [$email_git_max_maintainers]
1571 t all signature types [$email_git_all_signature_types]
1572 m use .mailmap [$email_use_mailmap]
1579 tm toggle maintainers
1580 tg toggle git entries
1581 tl toggle open list entries
1582 ts toggle subscriber list entries
1583 f emails in file [$file_emails]
1584 k keywords in file [$keywords]
1585 r remove duplicates [$email_remove_duplicates]
1586 p# pattern match depth [$pattern_depth]
1590 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1592 my $input = <STDIN
>;
1597 my @wish = split(/[, ]+/, $input);
1598 foreach my $nr (@wish) {
1600 my $sel = substr($nr, 0, 1);
1601 my $str = substr($nr, 1);
1603 $val = $1 if $str =~ /^(\d+)$/;
1608 $output_rolestats = 0;
1611 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1612 $selected{$nr - 1} = !$selected{$nr - 1};
1613 } elsif ($sel eq "*" || $sel eq '^') {
1615 $toggle = 1 if ($sel eq '*');
1616 for (my $i = 0; $i < $count; $i++) {
1617 $selected{$i} = $toggle;
1619 } elsif ($sel eq "0") {
1620 for (my $i = 0; $i < $count; $i++) {
1621 $selected{$i} = !$selected{$i};
1623 } elsif ($sel eq "t") {
1624 if (lc($str) eq "m") {
1625 for (my $i = 0; $i < $count; $i++) {
1626 $selected{$i} = !$selected{$i}
1627 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1629 } elsif (lc($str) eq "g") {
1630 for (my $i = 0; $i < $count; $i++) {
1631 $selected{$i} = !$selected{$i}
1632 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1634 } elsif (lc($str) eq "l") {
1635 for (my $i = 0; $i < $count; $i++) {
1636 $selected{$i} = !$selected{$i}
1637 if ($list[$i]->[1] =~ /^(open list)/i);
1639 } elsif (lc($str) eq "s") {
1640 for (my $i = 0; $i < $count; $i++) {
1641 $selected{$i} = !$selected{$i}
1642 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1645 } elsif ($sel eq "a") {
1646 if ($val > 0 && $val <= $count) {
1647 $authored{$val - 1} = !$authored{$val - 1};
1648 } elsif ($str eq '*' || $str eq '^') {
1650 $toggle = 1 if ($str eq '*');
1651 for (my $i = 0; $i < $count; $i++) {
1652 $authored{$i} = $toggle;
1655 } elsif ($sel eq "s") {
1656 if ($val > 0 && $val <= $count) {
1657 $signed{$val - 1} = !$signed{$val - 1};
1658 } elsif ($str eq '*' || $str eq '^') {
1660 $toggle = 1 if ($str eq '*');
1661 for (my $i = 0; $i < $count; $i++) {
1662 $signed{$i} = $toggle;
1665 } elsif ($sel eq "o") {
1668 } elsif ($sel eq "g") {
1670 bool_invert
(\
$email_git_fallback);
1672 bool_invert
(\
$email_git);
1675 } elsif ($sel eq "b") {
1677 bool_invert
(\
$email_git_blame_signatures);
1679 bool_invert
(\
$email_git_blame);
1682 } elsif ($sel eq "c") {
1684 $email_git_min_signatures = $val;
1687 } elsif ($sel eq "x") {
1689 $email_git_max_maintainers = $val;
1692 } elsif ($sel eq "%") {
1693 if ($str ne "" && $val >= 0) {
1694 $email_git_min_percent = $val;
1697 } elsif ($sel eq "d") {
1699 $email_git_since = $str;
1700 } elsif (vcs_is_hg
()) {
1701 $email_hg_since = $str;
1704 } elsif ($sel eq "t") {
1705 bool_invert
(\
$email_git_all_signature_types);
1707 } elsif ($sel eq "f") {
1708 bool_invert
(\
$file_emails);
1710 } elsif ($sel eq "r") {
1711 bool_invert
(\
$email_remove_duplicates);
1713 } elsif ($sel eq "m") {
1714 bool_invert
(\
$email_use_mailmap);
1717 } elsif ($sel eq "k") {
1718 bool_invert
(\
$keywords);
1720 } elsif ($sel eq "p") {
1721 if ($str ne "" && $val >= 0) {
1722 $pattern_depth = $val;
1725 } elsif ($sel eq "h" || $sel eq "?") {
1728 Interactive mode allows you to select the various maintainers, submitters,
1729 commit signers and mailing lists that could be CC'd on a patch.
1731 Any *'d entry is selected.
1733 If you have git or hg installed, you can choose to summarize the commit
1734 history of files in the patch. Also, each line of the current file can
1735 be matched to its commit author and that commits signers with blame.
1737 Various knobs exist to control the length of time for active commit
1738 tracking, the maximum number of commit authors and signers to add,
1741 Enter selections at the prompt until you are satisfied that the selected
1742 maintainers are appropriate. You may enter multiple selections separated
1743 by either commas or spaces.
1747 print STDERR
"invalid option: '$nr'\n";
1752 print STDERR
"git-blame can be very slow, please have patience..."
1753 if ($email_git_blame);
1754 goto &get_maintainers
;
1758 #drop not selected entries
1760 my @new_emailto = ();
1761 foreach my $entry (@list) {
1762 if ($selected{$count}) {
1763 push(@new_emailto, $list[$count]);
1767 return @new_emailto;
1771 my ($bool_ref) = @_;
1780 sub deduplicate_email
{
1784 my ($name, $address) = parse_email
($email);
1785 $email = format_email
($name, $address, 1);
1786 $email = mailmap_email
($email);
1788 return $email if (!$email_remove_duplicates);
1790 ($name, $address) = parse_email
($email);
1792 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1793 $name = $deduplicate_name_hash{lc($name)}->[0];
1794 $address = $deduplicate_name_hash{lc($name)}->[1];
1796 } elsif ($deduplicate_address_hash{lc($address)}) {
1797 $name = $deduplicate_address_hash{lc($address)}->[0];
1798 $address = $deduplicate_address_hash{lc($address)}->[1];
1802 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1803 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1805 $email = format_email
($name, $address, 1);
1806 $email = mailmap_email
($email);
1810 sub save_commits_by_author
{
1817 foreach my $line (@lines) {
1818 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1820 $author = deduplicate_email
($author);
1821 push(@authors, $author);
1823 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1824 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1827 for (my $i = 0; $i < @authors; $i++) {
1829 foreach my $ref(@
{$commit_author_hash{$authors[$i]}}) {
1830 if (@
{$ref}[0] eq $commits[$i] &&
1831 @
{$ref}[1] eq $subjects[$i]) {
1837 push(@
{$commit_author_hash{$authors[$i]}},
1838 [ ($commits[$i], $subjects[$i]) ]);
1843 sub save_commits_by_signer
{
1849 foreach my $line (@lines) {
1850 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1851 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1852 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1853 my @signatures = ($line);
1854 my ($types_ref, $signers_ref) = extract_formatted_signatures
(@signatures);
1855 my @types = @
$types_ref;
1856 my @signers = @
$signers_ref;
1858 my $type = $types[0];
1859 my $signer = $signers[0];
1861 $signer = deduplicate_email
($signer);
1864 foreach my $ref(@
{$commit_signer_hash{$signer}}) {
1865 if (@
{$ref}[0] eq $commit &&
1866 @
{$ref}[1] eq $subject &&
1867 @
{$ref}[2] eq $type) {
1873 push(@
{$commit_signer_hash{$signer}},
1874 [ ($commit, $subject, $type) ]);
1881 my ($role, $divisor, @lines) = @_;
1886 return if (@lines <= 0);
1888 if ($divisor <= 0) {
1889 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
1893 @lines = mailmap
(@lines);
1895 return if (@lines <= 0);
1897 @lines = sort(@lines);
1900 $hash{$_}++ for @lines;
1903 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
1904 my $sign_offs = $hash{$line};
1905 my $percent = $sign_offs * 100 / $divisor;
1907 $percent = 100 if ($percent > 100);
1908 next if (ignore_email_address
($line));
1910 last if ($sign_offs < $email_git_min_signatures ||
1911 $count > $email_git_max_maintainers ||
1912 $percent < $email_git_min_percent);
1913 push_email_address
($line, '');
1914 if ($output_rolestats) {
1915 my $fmt_percent = sprintf("%.0f", $percent);
1916 add_role
($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1918 add_role
($line, $role);
1923 sub vcs_file_signoffs
{
1934 $vcs_used = vcs_exists
();
1935 return if (!$vcs_used);
1937 my $cmd = $VCS_cmds{"find_signers_cmd"};
1938 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1940 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers
($cmd, $file);
1942 @signers = @
{$signers_ref} if defined $signers_ref;
1943 @authors = @
{$authors_ref} if defined $authors_ref;
1944 @stats = @
{$stats_ref} if defined $stats_ref;
1946 # print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
1948 foreach my $signer (@signers) {
1949 $signer = deduplicate_email
($signer);
1952 vcs_assign
("commit_signer", $commits, @signers);
1953 vcs_assign
("authored", $commits, @authors);
1954 if ($#authors == $#stats) {
1955 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1956 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
1960 for (my $i = 0; $i <= $#stats; $i++) {
1961 if ($stats[$i] =~ /$stat_pattern/) {
1966 my @tmp_authors = uniq
(@authors);
1967 foreach my $author (@tmp_authors) {
1968 $author = deduplicate_email
($author);
1970 @tmp_authors = uniq
(@tmp_authors);
1971 my @list_added = ();
1972 my @list_deleted = ();
1973 foreach my $author (@tmp_authors) {
1975 my $auth_deleted = 0;
1976 for (my $i = 0; $i <= $#stats; $i++) {
1977 if ($author eq deduplicate_email
($authors[$i]) &&
1978 $stats[$i] =~ /$stat_pattern/) {
1980 $auth_deleted += $2;
1983 for (my $i = 0; $i < $auth_added; $i++) {
1984 push(@list_added, $author);
1986 for (my $i = 0; $i < $auth_deleted; $i++) {
1987 push(@list_deleted, $author);
1990 vcs_assign
("added_lines", $added, @list_added);
1991 vcs_assign
("removed_lines", $deleted, @list_deleted);
1995 sub vcs_file_blame
{
1999 my @all_commits = ();
2004 $vcs_used = vcs_exists
();
2005 return if (!$vcs_used);
2007 @all_commits = vcs_blame
($file);
2008 @commits = uniq
(@all_commits);
2009 $total_commits = @commits;
2010 $total_lines = @all_commits;
2012 if ($email_git_blame_signatures) {
2015 my $commit_authors_ref;
2016 my $commit_signers_ref;
2018 my @commit_authors = ();
2019 my @commit_signers = ();
2020 my $commit = join(" -r ", @commits);
2023 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2024 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2026 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers
($cmd, $file);
2027 @commit_authors = @
{$commit_authors_ref} if defined $commit_authors_ref;
2028 @commit_signers = @
{$commit_signers_ref} if defined $commit_signers_ref;
2030 push(@signers, @commit_signers);
2032 foreach my $commit (@commits) {
2034 my $commit_authors_ref;
2035 my $commit_signers_ref;
2037 my @commit_authors = ();
2038 my @commit_signers = ();
2041 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2042 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2044 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers
($cmd, $file);
2045 @commit_authors = @
{$commit_authors_ref} if defined $commit_authors_ref;
2046 @commit_signers = @
{$commit_signers_ref} if defined $commit_signers_ref;
2048 push(@signers, @commit_signers);
2053 if ($from_filename) {
2054 if ($output_rolestats) {
2056 if (vcs_is_hg
()) {{ # Double brace for last exit
2058 my @commit_signers = ();
2059 @commits = uniq
(@commits);
2060 @commits = sort(@commits);
2061 my $commit = join(" -r ", @commits);
2064 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2065 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2069 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2071 if (!$email_git_penguin_chiefs) {
2072 @lines = grep(!/${penguin_chiefs}/i, @lines);
2078 foreach my $line (@lines) {
2079 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2081 $author = deduplicate_email
($author);
2082 push(@authors, $author);
2086 save_commits_by_author
(@lines) if ($interactive);
2087 save_commits_by_signer
(@lines) if ($interactive);
2089 push(@signers, @authors);
2092 foreach my $commit (@commits) {
2094 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2095 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2096 my @author = vcs_find_author
($cmd);
2099 my $formatted_author = deduplicate_email
($author[0]);
2101 my $count = grep(/$commit/, @all_commits);
2102 for ($i = 0; $i < $count ; $i++) {
2103 push(@blame_signers, $formatted_author);
2107 if (@blame_signers) {
2108 vcs_assign
("authored lines", $total_lines, @blame_signers);
2111 foreach my $signer (@signers) {
2112 $signer = deduplicate_email
($signer);
2114 vcs_assign
("commits", $total_commits, @signers);
2116 foreach my $signer (@signers) {
2117 $signer = deduplicate_email
($signer);
2119 vcs_assign
("modified commits", $total_commits, @signers);
2127 @parms = grep(!$saw{$_}++, @parms);
2135 @parms = sort @parms;
2136 @parms = grep(!$saw{$_}++, @parms);
2140 sub clean_file_emails
{
2141 my (@file_emails) = @_;
2142 my @fmt_emails = ();
2144 foreach my $email (@file_emails) {
2145 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2146 my ($name, $address) = parse_email
($email);
2147 if ($name eq '"[,\.]"') {
2151 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2153 my $first = $nw[@nw - 3];
2154 my $middle = $nw[@nw - 2];
2155 my $last = $nw[@nw - 1];
2157 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2158 (length($first) == 2 && substr($first, -1) eq ".")) ||
2159 (length($middle) == 1 ||
2160 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2161 $name = "$first $middle $last";
2163 $name = "$middle $last";
2167 if (substr($name, -1) =~ /[,\.]/) {
2168 $name = substr($name, 0, length($name) - 1);
2169 } elsif (substr($name, -2) =~ /[,\.]"/) {
2170 $name = substr($name, 0, length($name) - 2) . '"';
2173 if (substr($name, 0, 1) =~ /[,\.]/) {
2174 $name = substr($name, 1, length($name) - 1);
2175 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2176 $name = '"' . substr($name, 2, length($name) - 2);
2179 my $fmt_email = format_email
($name, $address, $email_usename);
2180 push(@fmt_emails, $fmt_email);
2190 my ($address, $role) = @
$_;
2191 if (!$saw{$address}) {
2192 if ($output_roles) {
2193 push(@lines, "$address ($role)");
2195 push(@lines, $address);
2207 if ($output_multiline) {
2208 foreach my $line (@parms) {
2212 print(join($output_separator, @parms));
2220 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2221 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2222 # This regexp will only work on addresses which have had comments stripped
2223 # and replaced with rfc822_lwsp.
2225 my $specials = '()<>@,;:\\\\".\\[\\]';
2226 my $controls = '\\000-\\037\\177';
2228 my $dtext = "[^\\[\\]\\r\\\\]";
2229 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2231 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2233 # Use zero-width assertion to spot the limit of an atom. A simple
2234 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2235 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2236 my $word = "(?:$atom|$quoted_string)";
2237 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2239 my $sub_domain = "(?:$atom|$domain_literal)";
2240 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2242 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2244 my $phrase = "$word*";
2245 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2246 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2247 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2249 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2250 my $address = "(?:$mailbox|$group)";
2252 return "$rfc822_lwsp*$address";
2255 sub rfc822_strip_comments
{
2257 # Recursively remove comments, and replace with a single space. The simpler
2258 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2259 # chars in atoms, for example.
2261 while ($s =~ s
/^((?
:[^"\\]|\\.)*
2262 (?:"(?
:[^"\\]|\\.)*"(?
:[^"\\]|\\.)*)*)
2263 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2267 # valid: returns true if the parameter is an RFC822 valid address
2270 my $s = rfc822_strip_comments(shift);
2273 $rfc822re = make_rfc822re();
2276 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2279 # validlist: In scalar context, returns true if the parameter is an RFC822
2280 # valid list of addresses.
2282 # In list context, returns an empty list on failure (an invalid
2283 # address was found); otherwise a list whose first element is the
2284 # number of addresses found and whose remaining elements are the
2285 # addresses. This is needed to disambiguate failure (invalid)
2286 # from success with no addresses found, because an empty string is
2289 sub rfc822_validlist {
2290 my $s = rfc822_strip_comments(shift);
2293 $rfc822re = make_rfc822re();
2295 # * null list items are valid according to the RFC
2296 # * the '1' business is to aid in distinguishing failure from no results
2299 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2300 $s =~ m/^$rfc822_char*$/) {
2301 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2304 return wantarray ? (scalar(@r), @r) : 1;
2306 return wantarray ? () : 0;