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;
25 my $email_subscriber_list = 0;
26 my $email_git_penguin_chiefs = 0;
28 my $email_git_all_signature_types = 0;
29 my $email_git_blame = 0;
30 my $email_git_blame_signatures = 1;
31 my $email_git_fallback = 1;
32 my $email_git_min_signatures = 1;
33 my $email_git_max_maintainers = 5;
34 my $email_git_min_percent = 5;
35 my $email_git_since = "1-year-ago";
36 my $email_hg_since = "-365";
38 my $email_remove_duplicates = 1;
39 my $email_use_mailmap = 1;
40 my $output_multiline = 1;
41 my $output_separator = ", ";
43 my $output_rolestats = 0;
51 my $from_filename = 0;
52 my $pattern_depth = 0;
60 my %commit_author_hash;
61 my %commit_signer_hash;
63 my @penguin_chief = ();
64 push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
65 #Andrew wants in on most everything - 2009/01/14
66 #push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
68 my @penguin_chief_names = ();
69 foreach my $chief (@penguin_chief) {
70 if ($chief =~ m/^(.*):(.*)/) {
73 push(@penguin_chief_names, $chief_name);
76 my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
78 # Signature types of people who are either
79 # a) responsible for the code in question, or
80 # b) familiar enough with it to give relevant feedback
81 my @signature_tags = ();
82 push(@signature_tags, "Signed-off-by:");
83 push(@signature_tags, "Reviewed-by:");
84 push(@signature_tags, "Acked-by:");
86 # rfc822 email address - preloaded methods go here.
87 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
88 my $rfc822_char = '[\\000-\\377]';
90 # VCS command support: class-like functions and strings
95 "execute_cmd" => \
&git_execute_cmd
,
96 "available" => '(which("git") ne "") && (-d ".git")',
98 "git log --no-color --since=\$email_git_since " .
99 '--format="GitCommit: %H%n' .
100 'GitAuthor: %an <%ae>%n' .
105 "find_commit_signers_cmd" =>
106 "git log --no-color " .
107 '--format="GitCommit: %H%n' .
108 'GitAuthor: %an <%ae>%n' .
113 "find_commit_author_cmd" =>
114 "git log --no-color " .
115 '--format="GitCommit: %H%n' .
116 'GitAuthor: %an <%ae>%n' .
118 'GitSubject: %s%n"' .
120 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
121 "blame_file_cmd" => "git blame -l \$file",
122 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
123 "blame_commit_pattern" => "^([0-9a-f]+) ",
124 "author_pattern" => "^GitAuthor: (.*)",
125 "subject_pattern" => "^GitSubject: (.*)",
129 "execute_cmd" => \
&hg_execute_cmd
,
130 "available" => '(which("hg") ne "") && (-d ".hg")',
131 "find_signers_cmd" =>
132 "hg log --date=\$email_hg_since " .
133 "--template='HgCommit: {node}\\n" .
134 "HgAuthor: {author}\\n" .
135 "HgSubject: {desc}\\n'" .
137 "find_commit_signers_cmd" =>
139 "--template='HgSubject: {desc}\\n'" .
141 "find_commit_author_cmd" =>
143 "--template='HgCommit: {node}\\n" .
144 "HgAuthor: {author}\\n" .
145 "HgSubject: {desc|firstline}\\n'" .
147 "blame_range_cmd" => "", # not supported
148 "blame_file_cmd" => "hg blame -n \$file",
149 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
150 "blame_commit_pattern" => "^([ 0-9a-f]+):",
151 "author_pattern" => "^HgAuthor: (.*)",
152 "subject_pattern" => "^HgSubject: (.*)",
155 my $conf = which_conf
(".get_maintainer.conf");
158 open(my $conffile, '<', "$conf")
159 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
161 while (<$conffile>) {
164 $line =~ s/\s*\n?$//g;
168 next if ($line =~ m/^\s*#/);
169 next if ($line =~ m/^\s*$/);
171 my @words = split(" ", $line);
172 foreach my $word (@words) {
173 last if ($word =~ m/^#/);
174 push (@conf_args, $word);
178 unshift(@ARGV, @conf_args) if @conf_args;
183 'git!' => \
$email_git,
184 'git-all-signature-types!' => \
$email_git_all_signature_types,
185 'git-blame!' => \
$email_git_blame,
186 'git-blame-signatures!' => \
$email_git_blame_signatures,
187 'git-fallback!' => \
$email_git_fallback,
188 'git-chief-penguins!' => \
$email_git_penguin_chiefs,
189 'git-min-signatures=i' => \
$email_git_min_signatures,
190 'git-max-maintainers=i' => \
$email_git_max_maintainers,
191 'git-min-percent=i' => \
$email_git_min_percent,
192 'git-since=s' => \
$email_git_since,
193 'hg-since=s' => \
$email_hg_since,
194 'i|interactive!' => \
$interactive,
195 'remove-duplicates!' => \
$email_remove_duplicates,
196 'mailmap!' => \
$email_use_mailmap,
197 'm!' => \
$email_maintainer,
198 'n!' => \
$email_usename,
199 'l!' => \
$email_list,
200 's!' => \
$email_subscriber_list,
201 'multiline!' => \
$output_multiline,
202 'roles!' => \
$output_roles,
203 'rolestats!' => \
$output_rolestats,
204 'separator=s' => \
$output_separator,
205 'subsystem!' => \
$subsystem,
206 'status!' => \
$status,
209 'pattern-depth=i' => \
$pattern_depth,
210 'k|keywords!' => \
$keywords,
211 'sections!' => \
$sections,
212 'fe|file-emails!' => \
$file_emails,
213 'f|file' => \
$from_filename,
214 'v|version' => \
$version,
215 'h|help|usage' => \
$help,
217 die "$P: invalid argument - use --help if necessary\n";
226 print("${P} ${V}\n");
230 if (-t STDIN
&& !@ARGV) {
231 # We're talking to a terminal, but have no command line arguments.
232 die "$P: missing patchfile or -f file - use --help if necessary\n";
235 $output_multiline = 0 if ($output_separator ne ", ");
236 $output_rolestats = 1 if ($interactive);
237 $output_roles = 1 if ($output_rolestats);
249 my $selections = $email + $scm + $status + $subsystem + $web;
250 if ($selections == 0) {
251 die "$P: Missing required option: email, scm, status, subsystem or web\n";
256 ($email_maintainer + $email_list + $email_subscriber_list +
257 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
258 die "$P: Please select at least 1 email option\n";
261 if (!top_of_kernel_tree
($lk_path)) {
262 die "$P: The current directory does not appear to be "
263 . "a linux kernel source tree.\n";
266 ## Read MAINTAINERS for type/value pairs
271 open (my $maint, '<', "${lk_path}MAINTAINERS")
272 or die "$P: Can't open MAINTAINERS: $!\n";
276 if ($line =~ m/^(\C):\s*(.*)/) {
280 ##Filename pattern matching
281 if ($type eq "F" || $type eq "X") {
282 $value =~ s@\
.@
\\\
.@g; ##Convert . to \.
283 $value =~ s/\*/\.\*/g; ##Convert * to .*
284 $value =~ s/\?/\./g; ##Convert ? to .
285 ##if pattern is a directory and it lacks a trailing slash, add one
287 $value =~ s@
([^/])$@$1/@
;
289 } elsif ($type eq "K") {
290 $keyword_hash{@typevalue} = $value;
292 push(@typevalue, "$type:$value");
293 } elsif (!/^(\s)*$/) {
295 push(@typevalue, $line);
302 # Read mail address map
315 return if (!$email_use_mailmap || !(-f
"${lk_path}.mailmap"));
317 open(my $mailmap_file, '<', "${lk_path}.mailmap")
318 or warn "$P: Can't open .mailmap: $!\n";
320 while (<$mailmap_file>) {
321 s/#.*$//; #strip comments
322 s/^\s+|\s+$//g; #trim
324 next if (/^\s*$/); #skip empty lines
325 #entries have one of the following formats:
328 # name1 <mail1> <mail2>
329 # name1 <mail1> name2 <mail2>
330 # (see man git-shortlog)
331 if (/^(.+)<(.+)>$/) {
335 $real_name =~ s/\s+$//;
336 ($real_name, $address) = parse_email
("$real_name <$address>");
337 $mailmap->{names
}->{$address} = $real_name;
339 } elsif (/^<([^\s]+)>\s*<([^\s]+)>$/) {
340 my $real_address = $1;
341 my $wrong_address = $2;
343 $mailmap->{addresses
}->{$wrong_address} = $real_address;
345 } elsif (/^(.+)<([^\s]+)>\s*<([^\s]+)>$/) {
347 my $real_address = $2;
348 my $wrong_address = $3;
350 $real_name =~ s/\s+$//;
351 ($real_name, $real_address) =
352 parse_email
("$real_name <$real_address>");
353 $mailmap->{names
}->{$wrong_address} = $real_name;
354 $mailmap->{addresses
}->{$wrong_address} = $real_address;
356 } elsif (/^(.+)<([^\s]+)>\s*([^\s].*)<([^\s]+)>$/) {
358 my $real_address = $2;
360 my $wrong_address = $4;
362 $real_name =~ s/\s+$//;
363 ($real_name, $real_address) =
364 parse_email
("$real_name <$real_address>");
366 $wrong_name =~ s/\s+$//;
367 ($wrong_name, $wrong_address) =
368 parse_email
("$wrong_name <$wrong_address>");
370 my $wrong_email = format_email
($wrong_name, $wrong_address, 1);
371 $mailmap->{names
}->{$wrong_email} = $real_name;
372 $mailmap->{addresses
}->{$wrong_email} = $real_address;
375 close($mailmap_file);
378 ## use the filenames on the command line or find the filenames in the patchfiles
382 my @keyword_tvi = ();
383 my @file_emails = ();
386 push(@ARGV, "&STDIN");
389 foreach my $file (@ARGV) {
390 if ($file ne "&STDIN") {
391 ##if $file is a directory and it lacks a trailing slash, add one
393 $file =~ s@
([^/])$@$1/@
;
394 } elsif (!(-f
$file)) {
395 die "$P: file '${file}' not found\n";
398 if ($from_filename) {
400 if ($file ne "MAINTAINERS" && -f
$file && ($keywords || $file_emails)) {
401 open(my $f, '<', $file)
402 or die "$P: Can't open $file: $!\n";
403 my $text = do { local($/) ; <$f> };
406 foreach my $line (keys %keyword_hash) {
407 if ($text =~ m/$keyword_hash{$line}/x) {
408 push(@keyword_tvi, $line);
413 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;
414 push(@file_emails, clean_file_emails
(@poss_addr));
418 my $file_cnt = @files;
421 open(my $patch, "< $file")
422 or die "$P: Can't open $file: $!\n";
425 if (m/^\+\+\+\s+(\S+)/) {
427 $filename =~ s@
^[^/]*/@@
;
429 $lastfile = $filename;
430 push(@files, $filename);
431 } elsif (m/^\@\@ -(\d+),(\d+)/) {
432 if ($email_git_blame) {
433 push(@range, "$lastfile:$1:$2");
435 } elsif ($keywords) {
436 foreach my $line (keys %keyword_hash) {
437 if ($patch_line =~ m/^[+-].*$keyword_hash{$line}/x) {
438 push(@keyword_tvi, $line);
445 if ($file_cnt == @files) {
446 warn "$P: file '${file}' doesn't appear to be a patch. "
447 . "Add -f to options?\n";
449 @files = sort_and_uniq
(@files);
453 @file_emails = uniq
(@file_emails);
456 my %email_hash_address;
464 my %deduplicate_name_hash = ();
465 my %deduplicate_address_hash = ();
466 my $signature_pattern;
468 my @maintainers = get_maintainers
();
471 @maintainers = merge_email
(@maintainers);
472 output
(@maintainers);
481 @status = uniq
(@status);
486 @subsystem = uniq
(@subsystem);
497 sub get_maintainers
{
498 %email_hash_name = ();
499 %email_hash_address = ();
500 %commit_author_hash = ();
501 %commit_signer_hash = ();
509 %deduplicate_name_hash = ();
510 %deduplicate_address_hash = ();
511 if ($email_git_all_signature_types) {
512 $signature_pattern = "(.+?)[Bb][Yy]:";
514 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
517 # Find responsible parties
519 my %exact_pattern_match_hash = ();
521 foreach my $file (@files) {
524 my $tvi = find_first_section
();
525 while ($tvi < @typevalue) {
526 my $start = find_starting_index
($tvi);
527 my $end = find_ending_index
($tvi);
531 #Do not match excluded file patterns
533 for ($i = $start; $i < $end; $i++) {
534 my $line = $typevalue[$i];
535 if ($line =~ m/^(\C):\s*(.*)/) {
539 if (file_match_pattern
($file, $value)) {
548 for ($i = $start; $i < $end; $i++) {
549 my $line = $typevalue[$i];
550 if ($line =~ m/^(\C):\s*(.*)/) {
554 if (file_match_pattern
($file, $value)) {
555 my $value_pd = ($value =~ tr@
/@@
);
556 my $file_pd = ($file =~ tr@
/@@
);
557 $value_pd++ if (substr($value,-1,1) ne "/");
558 $value_pd = -1 if ($value =~ /^\.\*/);
559 if ($value_pd >= $file_pd) {
560 $exact_pattern_match_hash{$file} = 1;
562 if ($pattern_depth == 0 ||
563 (($file_pd - $value_pd) < $pattern_depth)) {
564 $hash{$tvi} = $value_pd;
574 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
575 add_categories
($line);
578 my $start = find_starting_index
($line);
579 my $end = find_ending_index
($line);
580 for ($i = $start; $i < $end; $i++) {
581 my $line = $typevalue[$i];
582 if ($line =~ /^[FX]:/) { ##Restore file patterns
583 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
584 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
585 $line =~ s/\\\./\./g; ##Convert \. to .
586 $line =~ s/\.\*/\*/g; ##Convert .* to *
588 $line =~ s/^([A-Z]):/$1:\t/g;
597 @keyword_tvi = sort_and_uniq
(@keyword_tvi);
598 foreach my $line (@keyword_tvi) {
599 add_categories
($line);
603 foreach my $email (@email_to, @list_to) {
604 $email->[0] = deduplicate_email
($email->[0]);
607 foreach my $file (@files) {
609 ($email_git || ($email_git_fallback &&
610 !$exact_pattern_match_hash{$file}))) {
611 vcs_file_signoffs
($file);
613 if ($email && $email_git_blame) {
614 vcs_file_blame
($file);
619 foreach my $chief (@penguin_chief) {
620 if ($chief =~ m/^(.*):(.*)/) {
623 $email_address = format_email
($1, $2, $email_usename);
624 if ($email_git_penguin_chiefs) {
625 push(@email_to, [$email_address, 'chief penguin']);
627 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
632 foreach my $email (@file_emails) {
633 my ($name, $address) = parse_email
($email);
635 my $tmp_email = format_email
($name, $address, $email_usename);
636 push_email_address
($tmp_email, '');
637 add_role
($tmp_email, 'in file');
642 if ($email || $email_list) {
644 @to = (@to, @email_to);
647 @to = (@to, @list_to);
652 @to = interactive_get_maintainers
(\
@to);
658 sub file_match_pattern
{
659 my ($file, $pattern) = @_;
660 if (substr($pattern, -1) eq "/") {
661 if ($file =~ m@
^$pattern@
) {
665 if ($file =~ m@
^$pattern@
) {
666 my $s1 = ($file =~ tr@
/@@
);
667 my $s2 = ($pattern =~ tr@
/@@
);
678 usage: $P [options] patchfile
679 $P [options] -f file|directory
682 MAINTAINER field selection options:
683 --email => print email address(es) if any
684 --git => include recent git \*-by: signers
685 --git-all-signature-types => include signers regardless of signature type
686 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
687 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
688 --git-chief-penguins => include ${penguin_chiefs}
689 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
690 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
691 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
692 --git-blame => use git blame to find modified commits for patch or file
693 --git-since => git history to use (default: $email_git_since)
694 --hg-since => hg history to use (default: $email_hg_since)
695 --interactive => display a menu (mostly useful if used with the --git option)
696 --m => include maintainer(s) if any
697 --n => include name 'Full Name <addr\@domain.tld>'
698 --l => include list(s) if any
699 --s => include subscriber only list(s) if any
700 --remove-duplicates => minimize duplicate email names/addresses
701 --roles => show roles (status:subsystem, git-signer, list, etc...)
702 --rolestats => show roles and statistics (commits/total_commits, %)
703 --file-emails => add email addresses found in -f file (default: 0 (off))
704 --scm => print SCM tree(s) if any
705 --status => print status if any
706 --subsystem => print subsystem name if any
707 --web => print website(s) if any
710 --separator [, ] => separator for multiple entries on 1 line
711 using --separator also sets --nomultiline if --separator is not [, ]
712 --multiline => print 1 entry per line
715 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
716 --keywords => scan patch for keywords (default: $keywords)
717 --sections => print all of the subsystem sections with pattern matches
718 --mailmap => use .mailmap file (default: $email_use_mailmap)
719 --version => show version
720 --help => show this help information
723 [--email --git --m --n --l --multiline --pattern-depth=0 --remove-duplicates]
726 Using "-f directory" may give unexpected results:
727 Used with "--git", git signators for _all_ files in and below
728 directory are examined as git recurses directories.
729 Any specified X: (exclude) pattern matches are _not_ ignored.
730 Used with "--nogit", directory is used as a pattern match,
731 no individual file within the directory or subdirectory
733 Used with "--git-blame", does not iterate all files in directory
734 Using "--git-blame" is slow and may add old committers and authors
735 that are no longer active maintainers to the output.
736 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
737 other automated tools that expect only ["name"] <email address>
738 may not work because of additional output after <email address>.
739 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
740 not the percentage of the entire file authored. # of commits is
741 not a good measure of amount of code authored. 1 major commit may
742 contain a thousand lines, 5 trivial commits may modify a single line.
743 If git is not installed, but mercurial (hg) is installed and an .hg
744 repository exists, the following options apply to mercurial:
746 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
748 Use --hg-since not --git-since to control date selection
749 File ".get_maintainer.conf", if it exists in the linux kernel source root
750 directory, can change whatever get_maintainer defaults are desired.
751 Entries in this file can be any command line argument.
752 This file is prepended to any additional command line arguments.
753 Multiple lines and # comments are allowed.
757 sub top_of_kernel_tree
{
760 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
763 if ( (-f
"${lk_path}COPYING")
764 && (-f
"${lk_path}CREDITS")
765 && (-f
"${lk_path}Kbuild")
766 && (-f
"${lk_path}MAINTAINERS")
767 && (-f
"${lk_path}Makefile")
768 && (-f
"${lk_path}README")
769 && (-d
"${lk_path}Documentation")
770 && (-d
"${lk_path}arch")
771 && (-d
"${lk_path}include")
772 && (-d
"${lk_path}drivers")
773 && (-d
"${lk_path}fs")
774 && (-d
"${lk_path}init")
775 && (-d
"${lk_path}ipc")
776 && (-d
"${lk_path}kernel")
777 && (-d
"${lk_path}lib")
778 && (-d
"${lk_path}scripts")) {
785 my ($formatted_email) = @_;
790 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
793 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
795 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
799 $name =~ s/^\s+|\s+$//g;
800 $name =~ s/^\"|\"$//g;
801 $address =~ s/^\s+|\s+$//g;
803 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
804 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
808 return ($name, $address);
812 my ($name, $address, $usename) = @_;
816 $name =~ s/^\s+|\s+$//g;
817 $name =~ s/^\"|\"$//g;
818 $address =~ s/^\s+|\s+$//g;
820 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
821 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
827 $formatted_email = "$address";
829 $formatted_email = "$name <$address>";
832 $formatted_email = $address;
835 return $formatted_email;
838 sub find_first_section
{
841 while ($index < @typevalue) {
842 my $tv = $typevalue[$index];
843 if (($tv =~ m/^(\C):\s*(.*)/)) {
852 sub find_starting_index
{
856 my $tv = $typevalue[$index];
857 if (!($tv =~ m/^(\C):\s*(.*)/)) {
866 sub find_ending_index
{
869 while ($index < @typevalue) {
870 my $tv = $typevalue[$index];
871 if (!($tv =~ m/^(\C):\s*(.*)/)) {
880 sub get_maintainer_role
{
884 my $start = find_starting_index
($index);
885 my $end = find_ending_index
($index);
888 my $subsystem = $typevalue[$start];
889 if (length($subsystem) > 20) {
890 $subsystem = substr($subsystem, 0, 17);
891 $subsystem =~ s/\s*$//;
892 $subsystem = $subsystem . "...";
895 for ($i = $start + 1; $i < $end; $i++) {
896 my $tv = $typevalue[$i];
897 if ($tv =~ m/^(\C):\s*(.*)/) {
907 if ($role eq "supported") {
909 } elsif ($role eq "maintained") {
910 $role = "maintainer";
911 } elsif ($role eq "odd fixes") {
913 } elsif ($role eq "orphan") {
914 $role = "orphan minder";
915 } elsif ($role eq "obsolete") {
916 $role = "obsolete minder";
917 } elsif ($role eq "buried alive in reporters") {
918 $role = "chief penguin";
921 return $role . ":" . $subsystem;
928 my $start = find_starting_index
($index);
929 my $end = find_ending_index
($index);
931 my $subsystem = $typevalue[$start];
932 if (length($subsystem) > 20) {
933 $subsystem = substr($subsystem, 0, 17);
934 $subsystem =~ s/\s*$//;
935 $subsystem = $subsystem . "...";
938 if ($subsystem eq "THE REST") {
949 my $start = find_starting_index
($index);
950 my $end = find_ending_index
($index);
952 push(@subsystem, $typevalue[$start]);
954 for ($i = $start + 1; $i < $end; $i++) {
955 my $tv = $typevalue[$i];
956 if ($tv =~ m/^(\C):\s*(.*)/) {
960 my $list_address = $pvalue;
961 my $list_additional = "";
962 my $list_role = get_list_role
($i);
964 if ($list_role ne "") {
965 $list_role = ":" . $list_role;
967 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
969 $list_additional = $2;
971 if ($list_additional =~ m/subscribers-only/) {
972 if ($email_subscriber_list) {
973 if (!$hash_list_to{lc($list_address)}) {
974 $hash_list_to{lc($list_address)} = 1;
975 push(@list_to, [$list_address,
976 "subscriber list${list_role}"]);
981 if (!$hash_list_to{lc($list_address)}) {
982 $hash_list_to{lc($list_address)} = 1;
983 push(@list_to, [$list_address,
984 "open list${list_role}"]);
988 } elsif ($ptype eq "M") {
989 my ($name, $address) = parse_email
($pvalue);
992 my $tv = $typevalue[$i - 1];
993 if ($tv =~ m/^(\C):\s*(.*)/) {
996 $pvalue = format_email
($name, $address, $email_usename);
1001 if ($email_maintainer) {
1002 my $role = get_maintainer_role
($i);
1003 push_email_addresses
($pvalue, $role);
1005 } elsif ($ptype eq "T") {
1006 push(@scm, $pvalue);
1007 } elsif ($ptype eq "W") {
1008 push(@web, $pvalue);
1009 } elsif ($ptype eq "S") {
1010 push(@status, $pvalue);
1017 my ($name, $address) = @_;
1019 return 1 if (($name eq "") && ($address eq ""));
1020 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1021 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1026 sub push_email_address
{
1027 my ($line, $role) = @_;
1029 my ($name, $address) = parse_email
($line);
1031 if ($address eq "") {
1035 if (!$email_remove_duplicates) {
1036 push(@email_to, [format_email
($name, $address, $email_usename), $role]);
1037 } elsif (!email_inuse
($name, $address)) {
1038 push(@email_to, [format_email
($name, $address, $email_usename), $role]);
1039 $email_hash_name{lc($name)}++ if ($name ne "");
1040 $email_hash_address{lc($address)}++;
1046 sub push_email_addresses
{
1047 my ($address, $role) = @_;
1049 my @address_list = ();
1051 if (rfc822_valid
($address)) {
1052 push_email_address
($address, $role);
1053 } elsif (@address_list = rfc822_validlist
($address)) {
1054 my $array_count = shift(@address_list);
1055 while (my $entry = shift(@address_list)) {
1056 push_email_address
($entry, $role);
1059 if (!push_email_address
($address, $role)) {
1060 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1066 my ($line, $role) = @_;
1068 my ($name, $address) = parse_email
($line);
1069 my $email = format_email
($name, $address, $email_usename);
1071 foreach my $entry (@email_to) {
1072 if ($email_remove_duplicates) {
1073 my ($entry_name, $entry_address) = parse_email
($entry->[0]);
1074 if (($name eq $entry_name || $address eq $entry_address)
1075 && ($role eq "" || !($entry->[1] =~ m/$role/))
1077 if ($entry->[1] eq "") {
1078 $entry->[1] = "$role";
1080 $entry->[1] = "$entry->[1],$role";
1084 if ($email eq $entry->[0]
1085 && ($role eq "" || !($entry->[1] =~ m/$role/))
1087 if ($entry->[1] eq "") {
1088 $entry->[1] = "$role";
1090 $entry->[1] = "$entry->[1],$role";
1100 foreach my $path (split(/:/, $ENV{PATH
})) {
1101 if (-e
"$path/$bin") {
1102 return "$path/$bin";
1112 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1113 if (-e
"$path/$conf") {
1114 return "$path/$conf";
1124 my ($name, $address) = parse_email
($line);
1125 my $email = format_email
($name, $address, 1);
1126 my $real_name = $name;
1127 my $real_address = $address;
1129 if (exists $mailmap->{names
}->{$email} ||
1130 exists $mailmap->{addresses
}->{$email}) {
1131 if (exists $mailmap->{names
}->{$email}) {
1132 $real_name = $mailmap->{names
}->{$email};
1134 if (exists $mailmap->{addresses
}->{$email}) {
1135 $real_address = $mailmap->{addresses
}->{$email};
1138 if (exists $mailmap->{names
}->{$address}) {
1139 $real_name = $mailmap->{names
}->{$address};
1141 if (exists $mailmap->{addresses
}->{$address}) {
1142 $real_address = $mailmap->{addresses
}->{$address};
1145 return format_email
($real_name, $real_address, 1);
1149 my (@addresses) = @_;
1151 my @mapped_emails = ();
1152 foreach my $line (@addresses) {
1153 push(@mapped_emails, mailmap_email
($line));
1155 merge_by_realname
(@mapped_emails) if ($email_use_mailmap);
1156 return @mapped_emails;
1159 sub merge_by_realname
{
1163 foreach my $email (@emails) {
1164 my ($name, $address) = parse_email
($email);
1165 if (exists $address_map{$name}) {
1166 $address = $address_map{$name};
1167 $email = format_email
($name, $address, 1);
1169 $address_map{$name} = $address;
1174 sub git_execute_cmd
{
1178 my $output = `$cmd`;
1179 $output =~ s/^\s*//gm;
1180 @lines = split("\n", $output);
1185 sub hg_execute_cmd
{
1189 my $output = `$cmd`;
1190 @lines = split("\n", $output);
1195 sub extract_formatted_signatures
{
1196 my (@signature_lines) = @_;
1198 my @type = @signature_lines;
1200 s/\s*(.*):.*/$1/ for (@type);
1203 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1205 ## Reformat email addresses (with names) to avoid badly written signatures
1207 foreach my $signer (@signature_lines) {
1208 $signer = deduplicate_email
($signer);
1211 return (\
@type, \
@signature_lines);
1214 sub vcs_find_signers
{
1218 my @signatures = ();
1220 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1222 my $pattern = $VCS_cmds{"commit_pattern"};
1224 $commits = grep(/$pattern/, @lines); # of commits
1226 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1228 return (0, @signatures) if !@signatures;
1230 save_commits_by_author
(@lines) if ($interactive);
1231 save_commits_by_signer
(@lines) if ($interactive);
1233 if (!$email_git_penguin_chiefs) {
1234 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1237 my ($types_ref, $signers_ref) = extract_formatted_signatures
(@signatures);
1239 return ($commits, @
$signers_ref);
1242 sub vcs_find_author
{
1246 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1248 if (!$email_git_penguin_chiefs) {
1249 @lines = grep(!/${penguin_chiefs}/i, @lines);
1252 return @lines if !@lines;
1255 foreach my $line (@lines) {
1256 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1258 my ($name, $address) = parse_email
($author);
1259 $author = format_email
($name, $address, 1);
1260 push(@authors, $author);
1264 save_commits_by_author
(@lines) if ($interactive);
1265 save_commits_by_signer
(@lines) if ($interactive);
1270 sub vcs_save_commits
{
1275 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1277 foreach my $line (@lines) {
1278 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1291 return @commits if (!(-f
$file));
1293 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1294 my @all_commits = ();
1296 $cmd = $VCS_cmds{"blame_file_cmd"};
1297 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1298 @all_commits = vcs_save_commits
($cmd);
1300 foreach my $file_range_diff (@range) {
1301 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1303 my $diff_start = $2;
1304 my $diff_length = $3;
1305 next if ("$file" ne "$diff_file");
1306 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1307 push(@commits, $all_commits[$i]);
1311 foreach my $file_range_diff (@range) {
1312 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1314 my $diff_start = $2;
1315 my $diff_length = $3;
1316 next if ("$file" ne "$diff_file");
1317 $cmd = $VCS_cmds{"blame_range_cmd"};
1318 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1319 push(@commits, vcs_save_commits
($cmd));
1322 $cmd = $VCS_cmds{"blame_file_cmd"};
1323 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1324 @commits = vcs_save_commits
($cmd);
1327 foreach my $commit (@commits) {
1328 $commit =~ s/^\^//g;
1334 my $printed_novcs = 0;
1336 %VCS_cmds = %VCS_cmds_git;
1337 return 1 if eval $VCS_cmds{"available"};
1338 %VCS_cmds = %VCS_cmds_hg;
1339 return 2 if eval $VCS_cmds{"available"};
1341 if (!$printed_novcs) {
1342 warn("$P: No supported VCS found. Add --nogit to options?\n");
1343 warn("Using a git repository produces better results.\n");
1344 warn("Try Linus Torvalds' latest git repository using:\n");
1345 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
1353 return $vcs_used == 1;
1357 return $vcs_used == 2;
1360 sub interactive_get_maintainers
{
1361 my ($list_ref) = @_;
1362 my @list = @
$list_ref;
1371 foreach my $entry (@list) {
1372 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1373 $selected{$count} = 1;
1374 $authored{$count} = 0;
1375 $signed{$count} = 0;
1381 my $print_options = 0;
1386 printf STDERR
"\n%1s %2s %-65s",
1387 "*", "#", "email/list and role:stats";
1389 ($email_git_fallback && !$maintained) ||
1391 print STDERR
"auth sign";
1394 foreach my $entry (@list) {
1395 my $email = $entry->[0];
1396 my $role = $entry->[1];
1398 $sel = "*" if ($selected{$count});
1399 my $commit_author = $commit_author_hash{$email};
1400 my $commit_signer = $commit_signer_hash{$email};
1403 $authored++ for (@
{$commit_author});
1404 $signed++ for (@
{$commit_signer});
1405 printf STDERR
"%1s %2d %-65s", $sel, $count + 1, $email;
1406 printf STDERR
"%4d %4d", $authored, $signed
1407 if ($authored > 0 || $signed > 0);
1408 printf STDERR
"\n %s\n", $role;
1409 if ($authored{$count}) {
1410 my $commit_author = $commit_author_hash{$email};
1411 foreach my $ref (@
{$commit_author}) {
1412 print STDERR
" Author: @{$ref}[1]\n";
1415 if ($signed{$count}) {
1416 my $commit_signer = $commit_signer_hash{$email};
1417 foreach my $ref (@
{$commit_signer}) {
1418 print STDERR
" @{$ref}[2]: @{$ref}[1]\n";
1425 my $date_ref = \
$email_git_since;
1426 $date_ref = \
$email_hg_since if (vcs_is_hg
());
1427 if ($print_options) {
1432 Version Control options:
1433 g use git history [$email_git]
1434 gf use git-fallback [$email_git_fallback]
1435 b use git blame [$email_git_blame]
1436 bs use blame signatures [$email_git_blame_signatures]
1437 c# minimum commits [$email_git_min_signatures]
1438 %# min percent [$email_git_min_percent]
1439 d# history to use [$$date_ref]
1440 x# max maintainers [$email_git_max_maintainers]
1441 t all signature types [$email_git_all_signature_types]
1442 m use .mailmap [$email_use_mailmap]
1449 tm toggle maintainers
1450 tg toggle git entries
1451 tl toggle open list entries
1452 ts toggle subscriber list entries
1453 f emails in file [$file_emails]
1454 k keywords in file [$keywords]
1455 r remove duplicates [$email_remove_duplicates]
1456 p# pattern match depth [$pattern_depth]
1460 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1462 my $input = <STDIN
>;
1467 my @wish = split(/[, ]+/, $input);
1468 foreach my $nr (@wish) {
1470 my $sel = substr($nr, 0, 1);
1471 my $str = substr($nr, 1);
1473 $val = $1 if $str =~ /^(\d+)$/;
1478 $output_rolestats = 0;
1481 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1482 $selected{$nr - 1} = !$selected{$nr - 1};
1483 } elsif ($sel eq "*" || $sel eq '^') {
1485 $toggle = 1 if ($sel eq '*');
1486 for (my $i = 0; $i < $count; $i++) {
1487 $selected{$i} = $toggle;
1489 } elsif ($sel eq "0") {
1490 for (my $i = 0; $i < $count; $i++) {
1491 $selected{$i} = !$selected{$i};
1493 } elsif ($sel eq "t") {
1494 if (lc($str) eq "m") {
1495 for (my $i = 0; $i < $count; $i++) {
1496 $selected{$i} = !$selected{$i}
1497 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1499 } elsif (lc($str) eq "g") {
1500 for (my $i = 0; $i < $count; $i++) {
1501 $selected{$i} = !$selected{$i}
1502 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1504 } elsif (lc($str) eq "l") {
1505 for (my $i = 0; $i < $count; $i++) {
1506 $selected{$i} = !$selected{$i}
1507 if ($list[$i]->[1] =~ /^(open list)/i);
1509 } elsif (lc($str) eq "s") {
1510 for (my $i = 0; $i < $count; $i++) {
1511 $selected{$i} = !$selected{$i}
1512 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1515 } elsif ($sel eq "a") {
1516 if ($val > 0 && $val <= $count) {
1517 $authored{$val - 1} = !$authored{$val - 1};
1518 } elsif ($str eq '*' || $str eq '^') {
1520 $toggle = 1 if ($str eq '*');
1521 for (my $i = 0; $i < $count; $i++) {
1522 $authored{$i} = $toggle;
1525 } elsif ($sel eq "s") {
1526 if ($val > 0 && $val <= $count) {
1527 $signed{$val - 1} = !$signed{$val - 1};
1528 } elsif ($str eq '*' || $str eq '^') {
1530 $toggle = 1 if ($str eq '*');
1531 for (my $i = 0; $i < $count; $i++) {
1532 $signed{$i} = $toggle;
1535 } elsif ($sel eq "o") {
1538 } elsif ($sel eq "g") {
1540 bool_invert
(\
$email_git_fallback);
1542 bool_invert
(\
$email_git);
1545 } elsif ($sel eq "b") {
1547 bool_invert
(\
$email_git_blame_signatures);
1549 bool_invert
(\
$email_git_blame);
1552 } elsif ($sel eq "c") {
1554 $email_git_min_signatures = $val;
1557 } elsif ($sel eq "x") {
1559 $email_git_max_maintainers = $val;
1562 } elsif ($sel eq "%") {
1563 if ($str ne "" && $val >= 0) {
1564 $email_git_min_percent = $val;
1567 } elsif ($sel eq "d") {
1569 $email_git_since = $str;
1570 } elsif (vcs_is_hg
()) {
1571 $email_hg_since = $str;
1574 } elsif ($sel eq "t") {
1575 bool_invert
(\
$email_git_all_signature_types);
1577 } elsif ($sel eq "f") {
1578 bool_invert
(\
$file_emails);
1580 } elsif ($sel eq "r") {
1581 bool_invert
(\
$email_remove_duplicates);
1583 } elsif ($sel eq "m") {
1584 bool_invert
(\
$email_use_mailmap);
1587 } elsif ($sel eq "k") {
1588 bool_invert
(\
$keywords);
1590 } elsif ($sel eq "p") {
1591 if ($str ne "" && $val >= 0) {
1592 $pattern_depth = $val;
1595 } elsif ($sel eq "h" || $sel eq "?") {
1598 Interactive mode allows you to select the various maintainers, submitters,
1599 commit signers and mailing lists that could be CC'd on a patch.
1601 Any *'d entry is selected.
1603 If you have git or hg installed, you can choose to summarize the commit
1604 history of files in the patch. Also, each line of the current file can
1605 be matched to its commit author and that commits signers with blame.
1607 Various knobs exist to control the length of time for active commit
1608 tracking, the maximum number of commit authors and signers to add,
1611 Enter selections at the prompt until you are satisfied that the selected
1612 maintainers are appropriate. You may enter multiple selections separated
1613 by either commas or spaces.
1617 print STDERR
"invalid option: '$nr'\n";
1622 print STDERR
"git-blame can be very slow, please have patience..."
1623 if ($email_git_blame);
1624 goto &get_maintainers
;
1628 #drop not selected entries
1630 my @new_emailto = ();
1631 foreach my $entry (@list) {
1632 if ($selected{$count}) {
1633 push(@new_emailto, $list[$count]);
1637 return @new_emailto;
1641 my ($bool_ref) = @_;
1650 sub deduplicate_email
{
1654 my ($name, $address) = parse_email
($email);
1655 $email = format_email
($name, $address, 1);
1656 $email = mailmap_email
($email);
1658 return $email if (!$email_remove_duplicates);
1660 ($name, $address) = parse_email
($email);
1662 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1663 $name = $deduplicate_name_hash{lc($name)}->[0];
1664 $address = $deduplicate_name_hash{lc($name)}->[1];
1666 } elsif ($deduplicate_address_hash{lc($address)}) {
1667 $name = $deduplicate_address_hash{lc($address)}->[0];
1668 $address = $deduplicate_address_hash{lc($address)}->[1];
1672 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1673 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1675 $email = format_email
($name, $address, 1);
1676 $email = mailmap_email
($email);
1680 sub save_commits_by_author
{
1687 foreach my $line (@lines) {
1688 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1690 $author = deduplicate_email
($author);
1691 push(@authors, $author);
1693 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1694 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1697 for (my $i = 0; $i < @authors; $i++) {
1699 foreach my $ref(@
{$commit_author_hash{$authors[$i]}}) {
1700 if (@
{$ref}[0] eq $commits[$i] &&
1701 @
{$ref}[1] eq $subjects[$i]) {
1707 push(@
{$commit_author_hash{$authors[$i]}},
1708 [ ($commits[$i], $subjects[$i]) ]);
1713 sub save_commits_by_signer
{
1719 foreach my $line (@lines) {
1720 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1721 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1722 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1723 my @signatures = ($line);
1724 my ($types_ref, $signers_ref) = extract_formatted_signatures
(@signatures);
1725 my @types = @
$types_ref;
1726 my @signers = @
$signers_ref;
1728 my $type = $types[0];
1729 my $signer = $signers[0];
1731 $signer = deduplicate_email
($signer);
1734 foreach my $ref(@
{$commit_signer_hash{$signer}}) {
1735 if (@
{$ref}[0] eq $commit &&
1736 @
{$ref}[1] eq $subject &&
1737 @
{$ref}[2] eq $type) {
1743 push(@
{$commit_signer_hash{$signer}},
1744 [ ($commit, $subject, $type) ]);
1751 my ($role, $divisor, @lines) = @_;
1756 return if (@lines <= 0);
1758 if ($divisor <= 0) {
1759 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
1763 @lines = mailmap
(@lines);
1765 return if (@lines <= 0);
1767 @lines = sort(@lines);
1770 $hash{$_}++ for @lines;
1773 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
1774 my $sign_offs = $hash{$line};
1775 my $percent = $sign_offs * 100 / $divisor;
1777 $percent = 100 if ($percent > 100);
1779 last if ($sign_offs < $email_git_min_signatures ||
1780 $count > $email_git_max_maintainers ||
1781 $percent < $email_git_min_percent);
1782 push_email_address
($line, '');
1783 if ($output_rolestats) {
1784 my $fmt_percent = sprintf("%.0f", $percent);
1785 add_role
($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1787 add_role
($line, $role);
1792 sub vcs_file_signoffs
{
1798 $vcs_used = vcs_exists
();
1799 return if (!$vcs_used);
1801 my $cmd = $VCS_cmds{"find_signers_cmd"};
1802 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1804 ($commits, @signers) = vcs_find_signers
($cmd);
1806 foreach my $signer (@signers) {
1807 $signer = deduplicate_email
($signer);
1810 vcs_assign
("commit_signer", $commits, @signers);
1813 sub vcs_file_blame
{
1817 my @all_commits = ();
1822 $vcs_used = vcs_exists
();
1823 return if (!$vcs_used);
1825 @all_commits = vcs_blame
($file);
1826 @commits = uniq
(@all_commits);
1827 $total_commits = @commits;
1828 $total_lines = @all_commits;
1830 if ($email_git_blame_signatures) {
1833 my @commit_signers = ();
1834 my $commit = join(" -r ", @commits);
1837 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1838 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1840 ($commit_count, @commit_signers) = vcs_find_signers
($cmd);
1842 push(@signers, @commit_signers);
1844 foreach my $commit (@commits) {
1846 my @commit_signers = ();
1849 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1850 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1852 ($commit_count, @commit_signers) = vcs_find_signers
($cmd);
1854 push(@signers, @commit_signers);
1859 if ($from_filename) {
1860 if ($output_rolestats) {
1862 if (vcs_is_hg
()) {{ # Double brace for last exit
1864 my @commit_signers = ();
1865 @commits = uniq
(@commits);
1866 @commits = sort(@commits);
1867 my $commit = join(" -r ", @commits);
1870 $cmd = $VCS_cmds{"find_commit_author_cmd"};
1871 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1875 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1877 if (!$email_git_penguin_chiefs) {
1878 @lines = grep(!/${penguin_chiefs}/i, @lines);
1884 foreach my $line (@lines) {
1885 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1887 $author = deduplicate_email
($author);
1888 push(@authors, $author);
1892 save_commits_by_author
(@lines) if ($interactive);
1893 save_commits_by_signer
(@lines) if ($interactive);
1895 push(@signers, @authors);
1898 foreach my $commit (@commits) {
1900 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1901 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1902 my @author = vcs_find_author
($cmd);
1905 my $formatted_author = deduplicate_email
($author[0]);
1907 my $count = grep(/$commit/, @all_commits);
1908 for ($i = 0; $i < $count ; $i++) {
1909 push(@blame_signers, $formatted_author);
1913 if (@blame_signers) {
1914 vcs_assign
("authored lines", $total_lines, @blame_signers);
1917 foreach my $signer (@signers) {
1918 $signer = deduplicate_email
($signer);
1920 vcs_assign
("commits", $total_commits, @signers);
1922 foreach my $signer (@signers) {
1923 $signer = deduplicate_email
($signer);
1925 vcs_assign
("modified commits", $total_commits, @signers);
1933 @parms = grep(!$saw{$_}++, @parms);
1941 @parms = sort @parms;
1942 @parms = grep(!$saw{$_}++, @parms);
1946 sub clean_file_emails
{
1947 my (@file_emails) = @_;
1948 my @fmt_emails = ();
1950 foreach my $email (@file_emails) {
1951 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
1952 my ($name, $address) = parse_email
($email);
1953 if ($name eq '"[,\.]"') {
1957 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
1959 my $first = $nw[@nw - 3];
1960 my $middle = $nw[@nw - 2];
1961 my $last = $nw[@nw - 1];
1963 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
1964 (length($first) == 2 && substr($first, -1) eq ".")) ||
1965 (length($middle) == 1 ||
1966 (length($middle) == 2 && substr($middle, -1) eq "."))) {
1967 $name = "$first $middle $last";
1969 $name = "$middle $last";
1973 if (substr($name, -1) =~ /[,\.]/) {
1974 $name = substr($name, 0, length($name) - 1);
1975 } elsif (substr($name, -2) =~ /[,\.]"/) {
1976 $name = substr($name, 0, length($name) - 2) . '"';
1979 if (substr($name, 0, 1) =~ /[,\.]/) {
1980 $name = substr($name, 1, length($name) - 1);
1981 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
1982 $name = '"' . substr($name, 2, length($name) - 2);
1985 my $fmt_email = format_email
($name, $address, $email_usename);
1986 push(@fmt_emails, $fmt_email);
1996 my ($address, $role) = @
$_;
1997 if (!$saw{$address}) {
1998 if ($output_roles) {
1999 push(@lines, "$address ($role)");
2001 push(@lines, $address);
2013 if ($output_multiline) {
2014 foreach my $line (@parms) {
2018 print(join($output_separator, @parms));
2026 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2027 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2028 # This regexp will only work on addresses which have had comments stripped
2029 # and replaced with rfc822_lwsp.
2031 my $specials = '()<>@,;:\\\\".\\[\\]';
2032 my $controls = '\\000-\\037\\177';
2034 my $dtext = "[^\\[\\]\\r\\\\]";
2035 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2037 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2039 # Use zero-width assertion to spot the limit of an atom. A simple
2040 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2041 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2042 my $word = "(?:$atom|$quoted_string)";
2043 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2045 my $sub_domain = "(?:$atom|$domain_literal)";
2046 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2048 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2050 my $phrase = "$word*";
2051 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2052 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2053 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2055 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2056 my $address = "(?:$mailbox|$group)";
2058 return "$rfc822_lwsp*$address";
2061 sub rfc822_strip_comments
{
2063 # Recursively remove comments, and replace with a single space. The simpler
2064 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2065 # chars in atoms, for example.
2067 while ($s =~ s
/^((?
:[^"\\]|\\.)*
2068 (?:"(?
:[^"\\]|\\.)*"(?
:[^"\\]|\\.)*)*)
2069 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2073 # valid: returns true if the parameter is an RFC822 valid address
2076 my $s = rfc822_strip_comments(shift);
2079 $rfc822re = make_rfc822re();
2082 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2085 # validlist: In scalar context, returns true if the parameter is an RFC822
2086 # valid list of addresses.
2088 # In list context, returns an empty list on failure (an invalid
2089 # address was found); otherwise a list whose first element is the
2090 # number of addresses found and whose remaining elements are the
2091 # addresses. This is needed to disambiguate failure (invalid)
2092 # from success with no addresses found, because an empty string is
2095 sub rfc822_validlist {
2096 my $s = rfc822_strip_comments(shift);
2099 $rfc822re = make_rfc822re();
2101 # * null list items are valid according to the RFC
2102 # * the '1' business is to aid in distinguishing failure from no results
2105 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2106 $s =~ m/^$rfc822_char*$/) {
2107 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2110 return wantarray ? (scalar(@r), @r) : 1;
2112 return wantarray ? () : 0;