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;
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 = 1;
51 my $from_filename = 0;
52 my $pattern_depth = 0;
60 my %commit_author_hash;
61 my %commit_signer_hash;
63 # Signature types of people who are either
64 # a) responsible for the code in question, or
65 # b) familiar enough with it to give relevant feedback
66 my @signature_tags = ();
67 push(@signature_tags, "Signed-off-by:");
68 push(@signature_tags, "Reviewed-by:");
69 push(@signature_tags, "Acked-by:");
71 my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
73 # rfc822 email address - preloaded methods go here.
74 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
75 my $rfc822_char = '[\\000-\\377]';
77 # VCS command support: class-like functions and strings
82 "execute_cmd" => \
&git_execute_cmd
,
83 "available" => '(which("git") ne "") && (-d ".git")',
85 "git log --no-color --follow --since=\$email_git_since " .
86 '--format="GitCommit: %H%n' .
87 'GitAuthor: %an <%ae>%n' .
92 "find_commit_signers_cmd" =>
93 "git log --no-color " .
94 '--format="GitCommit: %H%n' .
95 'GitAuthor: %an <%ae>%n' .
100 "find_commit_author_cmd" =>
101 "git log --no-color " .
102 '--format="GitCommit: %H%n' .
103 'GitAuthor: %an <%ae>%n' .
105 'GitSubject: %s%n"' .
107 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
108 "blame_file_cmd" => "git blame -l \$file",
109 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
110 "blame_commit_pattern" => "^([0-9a-f]+) ",
111 "author_pattern" => "^GitAuthor: (.*)",
112 "subject_pattern" => "^GitSubject: (.*)",
116 "execute_cmd" => \
&hg_execute_cmd
,
117 "available" => '(which("hg") ne "") && (-d ".hg")',
118 "find_signers_cmd" =>
119 "hg log --date=\$email_hg_since " .
120 "--template='HgCommit: {node}\\n" .
121 "HgAuthor: {author}\\n" .
122 "HgSubject: {desc}\\n'" .
124 "find_commit_signers_cmd" =>
126 "--template='HgSubject: {desc}\\n'" .
128 "find_commit_author_cmd" =>
130 "--template='HgCommit: {node}\\n" .
131 "HgAuthor: {author}\\n" .
132 "HgSubject: {desc|firstline}\\n'" .
134 "blame_range_cmd" => "", # not supported
135 "blame_file_cmd" => "hg blame -n \$file",
136 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
137 "blame_commit_pattern" => "^([ 0-9a-f]+):",
138 "author_pattern" => "^HgAuthor: (.*)",
139 "subject_pattern" => "^HgSubject: (.*)",
142 my $conf = which_conf
(".get_maintainer.conf");
145 open(my $conffile, '<', "$conf")
146 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
148 while (<$conffile>) {
151 $line =~ s/\s*\n?$//g;
155 next if ($line =~ m/^\s*#/);
156 next if ($line =~ m/^\s*$/);
158 my @words = split(" ", $line);
159 foreach my $word (@words) {
160 last if ($word =~ m/^#/);
161 push (@conf_args, $word);
165 unshift(@ARGV, @conf_args) if @conf_args;
170 'git!' => \
$email_git,
171 'git-all-signature-types!' => \
$email_git_all_signature_types,
172 'git-blame!' => \
$email_git_blame,
173 'git-blame-signatures!' => \
$email_git_blame_signatures,
174 'git-fallback!' => \
$email_git_fallback,
175 'git-min-signatures=i' => \
$email_git_min_signatures,
176 'git-max-maintainers=i' => \
$email_git_max_maintainers,
177 'git-min-percent=i' => \
$email_git_min_percent,
178 'git-since=s' => \
$email_git_since,
179 'hg-since=s' => \
$email_hg_since,
180 'i|interactive!' => \
$interactive,
181 'remove-duplicates!' => \
$email_remove_duplicates,
182 'mailmap!' => \
$email_use_mailmap,
183 'm!' => \
$email_maintainer,
184 'r!' => \
$email_reviewer,
185 'n!' => \
$email_usename,
186 'l!' => \
$email_list,
187 's!' => \
$email_subscriber_list,
188 'multiline!' => \
$output_multiline,
189 'roles!' => \
$output_roles,
190 'rolestats!' => \
$output_rolestats,
191 'separator=s' => \
$output_separator,
192 'subsystem!' => \
$subsystem,
193 'status!' => \
$status,
196 'pattern-depth=i' => \
$pattern_depth,
197 'k|keywords!' => \
$keywords,
198 'sections!' => \
$sections,
199 'fe|file-emails!' => \
$file_emails,
200 'f|file' => \
$from_filename,
201 'v|version' => \
$version,
202 'h|help|usage' => \
$help,
204 die "$P: invalid argument - use --help if necessary\n";
213 print("${P} ${V}\n");
217 if (-t STDIN
&& !@ARGV) {
218 # We're talking to a terminal, but have no command line arguments.
219 die "$P: missing patchfile or -f file - use --help if necessary\n";
222 $output_multiline = 0 if ($output_separator ne ", ");
223 $output_rolestats = 1 if ($interactive);
224 $output_roles = 1 if ($output_rolestats);
236 my $selections = $email + $scm + $status + $subsystem + $web;
237 if ($selections == 0) {
238 die "$P: Missing required option: email, scm, status, subsystem or web\n";
243 ($email_maintainer + $email_reviewer +
244 $email_list + $email_subscriber_list +
245 $email_git + $email_git_blame) == 0) {
246 die "$P: Please select at least 1 email option\n";
249 if (!top_of_tree
($lk_path)) {
250 die "$P: The current directory does not appear to be "
251 . "a QEMU source tree.\n";
254 ## Read MAINTAINERS for type/value pairs
259 open (my $maint, '<', "${lk_path}MAINTAINERS")
260 or die "$P: Can't open MAINTAINERS: $!\n";
264 if ($line =~ m/^(.):\s*(.*)/) {
268 ##Filename pattern matching
269 if ($type eq "F" || $type eq "X") {
270 $value =~ s
@\.@\\\.@g; ##Convert . to \.
271 $value =~ s/\*/\.\*/g; ##Convert * to .*
272 $value =~ s/\?/\./g; ##Convert ? to .
273 ##if pattern is a directory and it lacks a trailing slash, add one
275 $value =~ s
@([^/])$@$1/@;
277 } elsif ($type eq "K") {
278 $keyword_hash{@typevalue} = $value;
280 push(@typevalue, "$type:$value");
281 } elsif (!/^(\s)*$/) {
283 push(@typevalue, $line);
290 # Read mail address map
303 return if (!$email_use_mailmap || !(-f
"${lk_path}.mailmap"));
305 open(my $mailmap_file, '<', "${lk_path}.mailmap")
306 or warn "$P: Can't open .mailmap: $!\n";
308 while (<$mailmap_file>) {
309 s/#.*$//; #strip comments
310 s/^\s+|\s+$//g; #trim
312 next if (/^\s*$/); #skip empty lines
313 #entries have one of the following formats:
316 # name1 <mail1> <mail2>
317 # name1 <mail1> name2 <mail2>
318 # (see man git-shortlog)
320 if (/^([^<]+)<([^>]+)>$/) {
324 $real_name =~ s/\s+$//;
325 ($real_name, $address) = parse_email
("$real_name <$address>");
326 $mailmap->{names
}->{$address} = $real_name;
328 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
329 my $real_address = $1;
330 my $wrong_address = $2;
332 $mailmap->{addresses
}->{$wrong_address} = $real_address;
334 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
336 my $real_address = $2;
337 my $wrong_address = $3;
339 $real_name =~ s/\s+$//;
340 ($real_name, $real_address) =
341 parse_email
("$real_name <$real_address>");
342 $mailmap->{names
}->{$wrong_address} = $real_name;
343 $mailmap->{addresses
}->{$wrong_address} = $real_address;
345 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
347 my $real_address = $2;
349 my $wrong_address = $4;
351 $real_name =~ s/\s+$//;
352 ($real_name, $real_address) =
353 parse_email
("$real_name <$real_address>");
355 $wrong_name =~ s/\s+$//;
356 ($wrong_name, $wrong_address) =
357 parse_email
("$wrong_name <$wrong_address>");
359 my $wrong_email = format_email
($wrong_name, $wrong_address, 1);
360 $mailmap->{names
}->{$wrong_email} = $real_name;
361 $mailmap->{addresses
}->{$wrong_email} = $real_address;
364 close($mailmap_file);
367 ## use the filenames on the command line or find the filenames in the patchfiles
371 my @keyword_tvi = ();
372 my @file_emails = ();
375 push(@ARGV, "&STDIN");
378 foreach my $file (@ARGV) {
379 if ($file ne "&STDIN") {
380 ##if $file is a directory and it lacks a trailing slash, add one
382 $file =~ s
@([^/])$@$1/@;
383 } elsif (!(-f
$file)) {
384 die "$P: file '${file}' not found\n";
387 if ($from_filename) {
389 if ($file ne "MAINTAINERS" && -f
$file && ($keywords || $file_emails)) {
390 open(my $f, '<', $file)
391 or die "$P: Can't open $file: $!\n";
392 my $text = do { local($/) ; <$f> };
395 foreach my $line (keys %keyword_hash) {
396 if ($text =~ m/$keyword_hash{$line}/x) {
397 push(@keyword_tvi, $line);
402 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;
403 push(@file_emails, clean_file_emails
(@poss_addr));
407 my $file_cnt = @files;
410 open(my $patch, "< $file")
411 or die "$P: Can't open $file: $!\n";
413 # We can check arbitrary information before the patch
414 # like the commit message, mail headers, etc...
415 # This allows us to match arbitrary keywords against any part
416 # of a git format-patch generated file (subject tags, etc...)
418 my $patch_prefix = ""; #Parsing the intro
422 if (m/^\+\+\+\s+(\S+)/) {
424 $filename =~ s
@^[^/]*/@@;
426 $lastfile = $filename;
427 push(@files, $filename);
428 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
429 } elsif (m/^\@\@ -(\d+),(\d+)/) {
430 if ($email_git_blame) {
431 push(@range, "$lastfile:$1:$2");
433 } elsif ($keywords) {
434 foreach my $line (keys %keyword_hash) {
435 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
436 push(@keyword_tvi, $line);
443 if ($file_cnt == @files) {
444 warn "$P: file '${file}' doesn't appear to be a patch. "
445 . "Add -f to options?\n";
447 @files = sort_and_uniq
(@files);
451 @file_emails = uniq
(@file_emails);
454 my %email_hash_address;
462 my %deduplicate_name_hash = ();
463 my %deduplicate_address_hash = ();
465 my @maintainers = get_maintainers
();
468 @maintainers = merge_email
(@maintainers);
469 output
(@maintainers);
478 @status = uniq
(@status);
483 @subsystem = uniq
(@subsystem);
494 sub range_is_maintained
{
495 my ($start, $end) = @_;
497 for (my $i = $start; $i < $end; $i++) {
498 my $line = $typevalue[$i];
499 if ($line =~ m/^(.):\s*(.*)/) {
503 if ($value =~ /(maintain|support)/i) {
512 sub range_has_maintainer
{
513 my ($start, $end) = @_;
515 for (my $i = $start; $i < $end; $i++) {
516 my $line = $typevalue[$i];
517 if ($line =~ m/^(.):\s*(.*)/) {
528 sub get_maintainers
{
529 %email_hash_name = ();
530 %email_hash_address = ();
531 %commit_author_hash = ();
532 %commit_signer_hash = ();
540 %deduplicate_name_hash = ();
541 %deduplicate_address_hash = ();
542 if ($email_git_all_signature_types) {
543 $signature_pattern = "(.+?)[Bb][Yy]:";
545 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
548 # Find responsible parties
550 my %exact_pattern_match_hash = ();
552 foreach my $file (@files) {
555 my $tvi = find_first_section
();
556 while ($tvi < @typevalue) {
557 my $start = find_starting_index
($tvi);
558 my $end = find_ending_index
($tvi);
562 #Do not match excluded file patterns
564 for ($i = $start; $i < $end; $i++) {
565 my $line = $typevalue[$i];
566 if ($line =~ m/^(.):\s*(.*)/) {
570 if (file_match_pattern
($file, $value)) {
579 for ($i = $start; $i < $end; $i++) {
580 my $line = $typevalue[$i];
581 if ($line =~ m/^(.):\s*(.*)/) {
585 if (file_match_pattern
($file, $value)) {
586 my $value_pd = ($value =~ tr
@/@@);
587 my $file_pd = ($file =~ tr
@/@@);
588 $value_pd++ if (substr($value,-1,1) ne "/");
589 $value_pd = -1 if ($value =~ /^\.\*/);
590 if ($value_pd >= $file_pd &&
591 range_is_maintained
($start, $end) &&
592 range_has_maintainer
($start, $end)) {
593 $exact_pattern_match_hash{$file} = 1;
595 if ($pattern_depth == 0 ||
596 (($file_pd - $value_pd) < $pattern_depth)) {
597 $hash{$tvi} = $value_pd;
607 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
608 add_categories
($line);
611 my $start = find_starting_index
($line);
612 my $end = find_ending_index
($line);
613 for ($i = $start; $i < $end; $i++) {
614 my $line = $typevalue[$i];
615 if ($line =~ /^[FX]:/) { ##Restore file patterns
616 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
617 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
618 $line =~ s/\\\./\./g; ##Convert \. to .
619 $line =~ s/\.\*/\*/g; ##Convert .* to *
621 $line =~ s/^([A-Z]):/$1:\t/g;
630 @keyword_tvi = sort_and_uniq
(@keyword_tvi);
631 foreach my $line (@keyword_tvi) {
632 add_categories
($line);
636 foreach my $email (@email_to, @list_to) {
637 $email->[0] = deduplicate_email
($email->[0]);
641 if (! $interactive) {
642 $email_git_fallback = 0 if @email_to > 0 || $email_git || $email_git_blame;
643 if ($email_git_fallback) {
644 print STDERR
"get_maintainer.pl: No maintainers found, printing recent contributors.\n";
645 print STDERR
"get_maintainer.pl: Do not blindly cc: them on patches! Use common sense.\n";
650 foreach my $file (@files) {
651 if ($email_git || ($email_git_fallback &&
652 !$exact_pattern_match_hash{$file})) {
653 vcs_file_signoffs
($file);
655 if ($email_git_blame) {
656 vcs_file_blame
($file);
660 foreach my $email (@file_emails) {
661 my ($name, $address) = parse_email
($email);
663 my $tmp_email = format_email
($name, $address, $email_usename);
664 push_email_address
($tmp_email, '');
665 add_role
($tmp_email, 'in file');
670 if ($email || $email_list) {
672 @to = (@to, @email_to);
675 @to = (@to, @list_to);
680 @to = interactive_get_maintainers
(\
@to);
686 sub file_match_pattern
{
687 my ($file, $pattern) = @_;
688 if (substr($pattern, -1) eq "/") {
689 if ($file =~ m
@^$pattern@) {
693 if ($file =~ m
@^$pattern@) {
694 my $s1 = ($file =~ tr
@/@@);
695 my $s2 = ($pattern =~ tr
@/@@);
706 usage: $P [options] patchfile
707 $P [options] -f file|directory
710 MAINTAINER field selection options:
711 --email => print email address(es) if any
712 --git => include recent git \*-by: signers
713 --git-all-signature-types => include signers regardless of signature type
714 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
715 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
716 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
717 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
718 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
719 --git-blame => use git blame to find modified commits for patch or file
720 --git-since => git history to use (default: $email_git_since)
721 --hg-since => hg history to use (default: $email_hg_since)
722 --interactive => display a menu (mostly useful if used with the --git option)
723 --m => include maintainer(s) if any
724 --r => include reviewer(s) if any
725 --n => include name 'Full Name <addr\@domain.tld>'
726 --l => include list(s) if any
727 --s => include subscriber only list(s) if any
728 --remove-duplicates => minimize duplicate email names/addresses
729 --roles => show roles (status:subsystem, git-signer, list, etc...)
730 --rolestats => show roles and statistics (commits/total_commits, %)
731 --file-emails => add email addresses found in -f file (default: 0 (off))
732 --scm => print SCM tree(s) if any
733 --status => print status if any
734 --subsystem => print subsystem name if any
735 --web => print website(s) if any
738 --separator [, ] => separator for multiple entries on 1 line
739 using --separator also sets --nomultiline if --separator is not [, ]
740 --multiline => print 1 entry per line
743 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
744 --keywords => scan patch for keywords (default: $keywords)
745 --sections => print all of the subsystem sections with pattern matches
746 --mailmap => use .mailmap file (default: $email_use_mailmap)
747 --version => show version
748 --help => show this help information
751 [--email --nogit --git-fallback --m --n --l --multiline --pattern-depth=0
752 --remove-duplicates --rolestats]
755 Using "-f directory" may give unexpected results:
756 Used with "--git", git signators for _all_ files in and below
757 directory are examined as git recurses directories.
758 Any specified X: (exclude) pattern matches are _not_ ignored.
759 Used with "--nogit", directory is used as a pattern match,
760 no individual file within the directory or subdirectory
762 Used with "--git-blame", does not iterate all files in directory
763 Using "--git-blame" is slow and may add old committers and authors
764 that are no longer active maintainers to the output.
765 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
766 other automated tools that expect only ["name"] <email address>
767 may not work because of additional output after <email address>.
768 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
769 not the percentage of the entire file authored. # of commits is
770 not a good measure of amount of code authored. 1 major commit may
771 contain a thousand lines, 5 trivial commits may modify a single line.
772 If git is not installed, but mercurial (hg) is installed and an .hg
773 repository exists, the following options apply to mercurial:
775 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
777 Use --hg-since not --git-since to control date selection
778 File ".get_maintainer.conf", if it exists in the QEMU source root
779 directory, can change whatever get_maintainer defaults are desired.
780 Entries in this file can be any command line argument.
781 This file is prepended to any additional command line arguments.
782 Multiple lines and # comments are allowed.
789 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
792 if ( (-f
"${lk_path}COPYING")
793 && (-f
"${lk_path}MAINTAINERS")
794 && (-f
"${lk_path}Makefile")
795 && (-d
"${lk_path}docs")
796 && (-f
"${lk_path}VERSION")
797 && (-f
"${lk_path}vl.c")) {
804 my ($formatted_email) = @_;
809 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
812 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
814 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
818 $name =~ s/^\s+|\s+$//g;
819 $name =~ s/^\"|\"$//g;
820 $address =~ s/^\s+|\s+$//g;
822 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
823 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
827 return ($name, $address);
831 my ($name, $address, $usename) = @_;
835 $name =~ s/^\s+|\s+$//g;
836 $name =~ s/^\"|\"$//g;
837 $address =~ s/^\s+|\s+$//g;
839 if ($name =~ /[^\w \-]/i) { ##has "must quote
" chars
840 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
846 $formatted_email = "$address";
848 $formatted_email = "$name <$address>";
851 $formatted_email = $address;
854 return $formatted_email;
857 sub find_first_section
{
860 while ($index < @typevalue) {
861 my $tv = $typevalue[$index];
862 if (($tv =~ m/^(.):\s*(.*)/)) {
871 sub find_starting_index
{
875 my $tv = $typevalue[$index];
876 if (!($tv =~ m/^(.):\s*(.*)/)) {
885 sub find_ending_index
{
888 while ($index < @typevalue) {
889 my $tv = $typevalue[$index];
890 if (!($tv =~ m/^(.):\s*(.*)/)) {
899 sub get_maintainer_role
{
903 my $start = find_starting_index
($index);
904 my $end = find_ending_index
($index);
906 my $role = "unknown";
907 my $subsystem = $typevalue[$start];
908 if (length($subsystem) > 20) {
909 $subsystem = substr($subsystem, 0, 17);
910 $subsystem =~ s/\s*$//;
911 $subsystem = $subsystem . "...";
914 for ($i = $start + 1; $i < $end; $i++) {
915 my $tv = $typevalue[$i];
916 if ($tv =~ m/^(.):\s*(.*)/) {
926 if ($role eq "supported") {
928 } elsif ($role eq "maintained") {
929 $role = "maintainer";
930 } elsif ($role eq "odd fixes") {
932 } elsif ($role eq "orphan") {
933 $role = "orphan minder";
934 } elsif ($role eq "obsolete") {
935 $role = "obsolete minder";
936 } elsif ($role eq "buried alive in reporters") {
937 $role = "chief penguin";
940 return $role . ":" . $subsystem;
947 my $start = find_starting_index
($index);
948 my $end = find_ending_index
($index);
950 my $subsystem = $typevalue[$start];
951 if (length($subsystem) > 20) {
952 $subsystem = substr($subsystem, 0, 17);
953 $subsystem =~ s/\s*$//;
954 $subsystem = $subsystem . "...";
957 if ($subsystem eq "THE REST") {
968 my $start = find_starting_index
($index);
969 my $end = find_ending_index
($index);
971 push(@subsystem, $typevalue[$start]);
973 for ($i = $start + 1; $i < $end; $i++) {
974 my $tv = $typevalue[$i];
975 if ($tv =~ m/^(.):\s*(.*)/) {
979 my $list_address = $pvalue;
980 my $list_additional = "";
981 my $list_role = get_list_role
($i);
983 if ($list_role ne "") {
984 $list_role = ":" . $list_role;
986 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
988 $list_additional = $2;
990 if ($list_additional =~ m/subscribers-only/) {
991 if ($email_subscriber_list) {
992 if (!$hash_list_to{lc($list_address)}) {
993 $hash_list_to{lc($list_address)} = 1;
994 push(@list_to, [$list_address,
995 "subscriber list${list_role}"]);
1000 if (!$hash_list_to{lc($list_address)}) {
1001 $hash_list_to{lc($list_address)} = 1;
1002 if ($list_additional =~ m/moderated/) {
1003 push(@list_to, [$list_address,
1004 "moderated list${list_role}"]);
1006 push(@list_to, [$list_address,
1007 "open list${list_role}"]);
1012 } elsif ($ptype eq "M") {
1013 my ($name, $address) = parse_email
($pvalue);
1016 my $tv = $typevalue[$i - 1];
1017 if ($tv =~ m/^(.):\s*(.*)/) {
1020 $pvalue = format_email
($name, $address, $email_usename);
1025 if ($email_maintainer) {
1026 my $role = get_maintainer_role
($i);
1027 push_email_addresses
($pvalue, $role);
1029 } elsif ($ptype eq "R") {
1030 my ($name, $address) = parse_email
($pvalue);
1033 my $tv = $typevalue[$i - 1];
1034 if ($tv =~ m/^(.):\s*(.*)/) {
1037 $pvalue = format_email
($name, $address, $email_usename);
1042 if ($email_reviewer) {
1043 push_email_addresses
($pvalue, 'reviewer');
1045 } elsif ($ptype eq "T") {
1046 push(@scm, $pvalue);
1047 } elsif ($ptype eq "W") {
1048 push(@web, $pvalue);
1049 } elsif ($ptype eq "S") {
1050 push(@status, $pvalue);
1057 my ($name, $address) = @_;
1059 return 1 if (($name eq "") && ($address eq ""));
1060 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1061 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1066 sub push_email_address
{
1067 my ($line, $role) = @_;
1069 my ($name, $address) = parse_email
($line);
1071 if ($address eq "") {
1075 if (!$email_remove_duplicates) {
1076 push(@email_to, [format_email
($name, $address, $email_usename), $role]);
1077 } elsif (!email_inuse
($name, $address)) {
1078 push(@email_to, [format_email
($name, $address, $email_usename), $role]);
1079 $email_hash_name{lc($name)}++ if ($name ne "");
1080 $email_hash_address{lc($address)}++;
1086 sub push_email_addresses
{
1087 my ($address, $role) = @_;
1089 my @address_list = ();
1091 if (rfc822_valid
($address)) {
1092 push_email_address
($address, $role);
1093 } elsif (@address_list = rfc822_validlist
($address)) {
1094 my $array_count = shift(@address_list);
1095 while (my $entry = shift(@address_list)) {
1096 push_email_address
($entry, $role);
1099 if (!push_email_address
($address, $role)) {
1100 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1106 my ($line, $role) = @_;
1108 my ($name, $address) = parse_email
($line);
1109 my $email = format_email
($name, $address, $email_usename);
1111 foreach my $entry (@email_to) {
1112 if ($email_remove_duplicates) {
1113 my ($entry_name, $entry_address) = parse_email
($entry->[0]);
1114 if (($name eq $entry_name || $address eq $entry_address)
1115 && ($role eq "" || !($entry->[1] =~ m/$role/))
1117 if ($entry->[1] eq "") {
1118 $entry->[1] = "$role";
1120 $entry->[1] = "$entry->[1],$role";
1124 if ($email eq $entry->[0]
1125 && ($role eq "" || !($entry->[1] =~ m/$role/))
1127 if ($entry->[1] eq "") {
1128 $entry->[1] = "$role";
1130 $entry->[1] = "$entry->[1],$role";
1140 foreach my $path (split(/:/, $ENV{PATH
})) {
1141 if (-e
"$path/$bin") {
1142 return "$path/$bin";
1152 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1153 if (-e
"$path/$conf") {
1154 return "$path/$conf";
1164 my ($name, $address) = parse_email
($line);
1165 my $email = format_email
($name, $address, 1);
1166 my $real_name = $name;
1167 my $real_address = $address;
1169 if (exists $mailmap->{names
}->{$email} ||
1170 exists $mailmap->{addresses
}->{$email}) {
1171 if (exists $mailmap->{names
}->{$email}) {
1172 $real_name = $mailmap->{names
}->{$email};
1174 if (exists $mailmap->{addresses
}->{$email}) {
1175 $real_address = $mailmap->{addresses
}->{$email};
1178 if (exists $mailmap->{names
}->{$address}) {
1179 $real_name = $mailmap->{names
}->{$address};
1181 if (exists $mailmap->{addresses
}->{$address}) {
1182 $real_address = $mailmap->{addresses
}->{$address};
1185 return format_email
($real_name, $real_address, 1);
1189 my (@addresses) = @_;
1191 my @mapped_emails = ();
1192 foreach my $line (@addresses) {
1193 push(@mapped_emails, mailmap_email
($line));
1195 merge_by_realname
(@mapped_emails) if ($email_use_mailmap);
1196 return @mapped_emails;
1199 sub merge_by_realname
{
1203 foreach my $email (@emails) {
1204 my ($name, $address) = parse_email
($email);
1205 if (exists $address_map{$name}) {
1206 $address = $address_map{$name};
1207 $email = format_email
($name, $address, 1);
1209 $address_map{$name} = $address;
1214 sub git_execute_cmd
{
1218 my $output = `$cmd`;
1219 $output =~ s/^\s*//gm;
1220 @lines = split("\n", $output);
1225 sub hg_execute_cmd {
1229 my $output = `$cmd`;
1230 @lines = split("\n", $output);
1235 sub extract_formatted_signatures
{
1236 my (@signature_lines) = @_;
1238 my @type = @signature_lines;
1240 s/\s*(.*):.*/$1/ for (@type);
1243 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1245 ## Reformat email addresses (with names) to avoid badly written signatures
1247 foreach my $signer (@signature_lines) {
1248 $signer = deduplicate_email
($signer);
1251 return (\
@type, \
@signature_lines);
1254 sub vcs_find_signers
{
1258 my @signatures = ();
1260 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1262 my $pattern = $VCS_cmds{"commit_pattern"};
1264 $commits = grep(/$pattern/, @lines); # of commits
1266 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1268 return (0, @signatures) if !@signatures;
1270 save_commits_by_author
(@lines) if ($interactive);
1271 save_commits_by_signer
(@lines) if ($interactive);
1273 my ($types_ref, $signers_ref) = extract_formatted_signatures
(@signatures);
1275 return ($commits, @$signers_ref);
1278 sub vcs_find_author
{
1282 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1284 return @lines if !@lines;
1287 foreach my $line (@lines) {
1288 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1290 my ($name, $address) = parse_email
($author);
1291 $author = format_email
($name, $address, 1);
1292 push(@authors, $author);
1296 save_commits_by_author
(@lines) if ($interactive);
1297 save_commits_by_signer
(@lines) if ($interactive);
1302 sub vcs_save_commits
{
1307 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1309 foreach my $line (@lines) {
1310 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1323 return @commits if (!(-f
$file));
1325 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1326 my @all_commits = ();
1328 $cmd = $VCS_cmds{"blame_file_cmd"};
1329 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1330 @all_commits = vcs_save_commits
($cmd);
1332 foreach my $file_range_diff (@range) {
1333 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1335 my $diff_start = $2;
1336 my $diff_length = $3;
1337 next if ("$file" ne "$diff_file");
1338 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1339 push(@commits, $all_commits[$i]);
1343 foreach my $file_range_diff (@range) {
1344 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1346 my $diff_start = $2;
1347 my $diff_length = $3;
1348 next if ("$file" ne "$diff_file");
1349 $cmd = $VCS_cmds{"blame_range_cmd"};
1350 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1351 push(@commits, vcs_save_commits
($cmd));
1354 $cmd = $VCS_cmds{"blame_file_cmd"};
1355 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1356 @commits = vcs_save_commits
($cmd);
1359 foreach my $commit (@commits) {
1360 $commit =~ s/^\^//g;
1366 my $printed_novcs = 0;
1368 %VCS_cmds = %VCS_cmds_git;
1369 return 1 if eval $VCS_cmds{"available"};
1370 %VCS_cmds = %VCS_cmds_hg;
1371 return 2 if eval $VCS_cmds{"available"};
1373 if (!$printed_novcs) {
1374 warn("$P: No supported VCS found. Add --nogit to options?\n");
1375 warn("Using a git repository produces better results.\n");
1376 warn("Try latest git repository using:\n");
1377 warn("git clone git://git.qemu-project.org/qemu.git\n");
1385 return $vcs_used == 1;
1389 return $vcs_used == 2;
1392 sub interactive_get_maintainers
{
1393 my ($list_ref) = @_;
1394 my @list = @$list_ref;
1403 foreach my $entry (@list) {
1404 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1405 $selected{$count} = 1;
1406 $authored{$count} = 0;
1407 $signed{$count} = 0;
1413 my $print_options = 0;
1418 printf STDERR
"\n%1s %2s %-65s",
1419 "*", "#", "email/list and role:stats";
1421 ($email_git_fallback && !$maintained) ||
1423 print STDERR
"auth sign";
1426 foreach my $entry (@list) {
1427 my $email = $entry->[0];
1428 my $role = $entry->[1];
1430 $sel = "*" if ($selected{$count});
1431 my $commit_author = $commit_author_hash{$email};
1432 my $commit_signer = $commit_signer_hash{$email};
1435 $authored++ for (@{$commit_author});
1436 $signed++ for (@{$commit_signer});
1437 printf STDERR
"%1s %2d %-65s", $sel, $count + 1, $email;
1438 printf STDERR
"%4d %4d", $authored, $signed
1439 if ($authored > 0 || $signed > 0);
1440 printf STDERR
"\n %s\n", $role;
1441 if ($authored{$count}) {
1442 my $commit_author = $commit_author_hash{$email};
1443 foreach my $ref (@{$commit_author}) {
1444 print STDERR
" Author: @{$ref}[1]\n";
1447 if ($signed{$count}) {
1448 my $commit_signer = $commit_signer_hash{$email};
1449 foreach my $ref (@{$commit_signer}) {
1450 print STDERR
" @{$ref}[2]: @{$ref}[1]\n";
1457 my $date_ref = \
$email_git_since;
1458 $date_ref = \
$email_hg_since if (vcs_is_hg
());
1459 if ($print_options) {
1464 Version Control options:
1465 g use git history [$email_git]
1466 gf use git-fallback [$email_git_fallback]
1467 b use git blame [$email_git_blame]
1468 bs use blame signatures [$email_git_blame_signatures]
1469 c# minimum commits [$email_git_min_signatures]
1470 %# min percent [$email_git_min_percent]
1471 d# history to use [$$date_ref]
1472 x# max maintainers [$email_git_max_maintainers]
1473 t all signature types [$email_git_all_signature_types]
1474 m use .mailmap [$email_use_mailmap]
1481 tm toggle maintainers
1482 tg toggle git entries
1483 tl toggle open list entries
1484 ts toggle subscriber list entries
1485 f emails in file [$file_emails]
1486 k keywords in file [$keywords]
1487 r remove duplicates [$email_remove_duplicates]
1488 p# pattern match depth [$pattern_depth]
1492 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1494 my $input = <STDIN
>;
1499 my @wish = split(/[, ]+/, $input);
1500 foreach my $nr (@wish) {
1502 my $sel = substr($nr, 0, 1);
1503 my $str = substr($nr, 1);
1505 $val = $1 if $str =~ /^(\d+)$/;
1510 $output_rolestats = 0;
1513 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1514 $selected{$nr - 1} = !$selected{$nr - 1};
1515 } elsif ($sel eq "*" || $sel eq '^') {
1517 $toggle = 1 if ($sel eq '*');
1518 for (my $i = 0; $i < $count; $i++) {
1519 $selected{$i} = $toggle;
1521 } elsif ($sel eq "0") {
1522 for (my $i = 0; $i < $count; $i++) {
1523 $selected{$i} = !$selected{$i};
1525 } elsif ($sel eq "t") {
1526 if (lc($str) eq "m") {
1527 for (my $i = 0; $i < $count; $i++) {
1528 $selected{$i} = !$selected{$i}
1529 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1531 } elsif (lc($str) eq "g") {
1532 for (my $i = 0; $i < $count; $i++) {
1533 $selected{$i} = !$selected{$i}
1534 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1536 } elsif (lc($str) eq "l") {
1537 for (my $i = 0; $i < $count; $i++) {
1538 $selected{$i} = !$selected{$i}
1539 if ($list[$i]->[1] =~ /^(open list)/i);
1541 } elsif (lc($str) eq "s") {
1542 for (my $i = 0; $i < $count; $i++) {
1543 $selected{$i} = !$selected{$i}
1544 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1547 } elsif ($sel eq "a") {
1548 if ($val > 0 && $val <= $count) {
1549 $authored{$val - 1} = !$authored{$val - 1};
1550 } elsif ($str eq '*' || $str eq '^') {
1552 $toggle = 1 if ($str eq '*');
1553 for (my $i = 0; $i < $count; $i++) {
1554 $authored{$i} = $toggle;
1557 } elsif ($sel eq "s") {
1558 if ($val > 0 && $val <= $count) {
1559 $signed{$val - 1} = !$signed{$val - 1};
1560 } elsif ($str eq '*' || $str eq '^') {
1562 $toggle = 1 if ($str eq '*');
1563 for (my $i = 0; $i < $count; $i++) {
1564 $signed{$i} = $toggle;
1567 } elsif ($sel eq "o") {
1570 } elsif ($sel eq "g") {
1572 bool_invert
(\
$email_git_fallback);
1574 bool_invert
(\
$email_git);
1577 } elsif ($sel eq "b") {
1579 bool_invert
(\
$email_git_blame_signatures);
1581 bool_invert
(\
$email_git_blame);
1584 } elsif ($sel eq "c") {
1586 $email_git_min_signatures = $val;
1589 } elsif ($sel eq "x") {
1591 $email_git_max_maintainers = $val;
1594 } elsif ($sel eq "%") {
1595 if ($str ne "" && $val >= 0) {
1596 $email_git_min_percent = $val;
1599 } elsif ($sel eq "d") {
1601 $email_git_since = $str;
1602 } elsif (vcs_is_hg
()) {
1603 $email_hg_since = $str;
1606 } elsif ($sel eq "t") {
1607 bool_invert
(\
$email_git_all_signature_types);
1609 } elsif ($sel eq "f") {
1610 bool_invert
(\
$file_emails);
1612 } elsif ($sel eq "r") {
1613 bool_invert
(\
$email_remove_duplicates);
1615 } elsif ($sel eq "m") {
1616 bool_invert
(\
$email_use_mailmap);
1619 } elsif ($sel eq "k") {
1620 bool_invert
(\
$keywords);
1622 } elsif ($sel eq "p") {
1623 if ($str ne "" && $val >= 0) {
1624 $pattern_depth = $val;
1627 } elsif ($sel eq "h" || $sel eq "?") {
1630 Interactive mode allows you to select the various maintainers, submitters,
1631 commit signers and mailing lists that could be CC'd on a patch.
1633 Any *'d entry is selected.
1635 If you have git or hg installed, you can choose to summarize the commit
1636 history of files in the patch. Also, each line of the current file can
1637 be matched to its commit author and that commits signers with blame.
1639 Various knobs exist to control the length of time for active commit
1640 tracking, the maximum number of commit authors and signers to add,
1643 Enter selections at the prompt until you are satisfied that the selected
1644 maintainers are appropriate. You may enter multiple selections separated
1645 by either commas or spaces.
1649 print STDERR
"invalid option: '$nr'\n";
1654 print STDERR
"git-blame can be very slow, please have patience..."
1655 if ($email_git_blame);
1656 goto &get_maintainers
;
1660 #drop not selected entries
1662 my @new_emailto = ();
1663 foreach my $entry (@list) {
1664 if ($selected{$count}) {
1665 push(@new_emailto, $list[$count]);
1669 return @new_emailto;
1673 my ($bool_ref) = @_;
1682 sub deduplicate_email
{
1686 my ($name, $address) = parse_email
($email);
1687 $email = format_email
($name, $address, 1);
1688 $email = mailmap_email
($email);
1690 return $email if (!$email_remove_duplicates);
1692 ($name, $address) = parse_email
($email);
1694 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1695 $name = $deduplicate_name_hash{lc($name)}->[0];
1696 $address = $deduplicate_name_hash{lc($name)}->[1];
1698 } elsif ($deduplicate_address_hash{lc($address)}) {
1699 $name = $deduplicate_address_hash{lc($address)}->[0];
1700 $address = $deduplicate_address_hash{lc($address)}->[1];
1704 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1705 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1707 $email = format_email
($name, $address, 1);
1708 $email = mailmap_email
($email);
1712 sub save_commits_by_author
{
1719 foreach my $line (@lines) {
1720 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1722 $author = deduplicate_email
($author);
1723 push(@authors, $author);
1725 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1726 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1729 for (my $i = 0; $i < @authors; $i++) {
1731 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1732 if (@{$ref}[0] eq $commits[$i] &&
1733 @{$ref}[1] eq $subjects[$i]) {
1739 push(@{$commit_author_hash{$authors[$i]}},
1740 [ ($commits[$i], $subjects[$i]) ]);
1745 sub save_commits_by_signer
{
1751 foreach my $line (@lines) {
1752 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1753 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1754 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1755 my @signatures = ($line);
1756 my ($types_ref, $signers_ref) = extract_formatted_signatures
(@signatures);
1757 my @types = @$types_ref;
1758 my @signers = @$signers_ref;
1760 my $type = $types[0];
1761 my $signer = $signers[0];
1763 $signer = deduplicate_email
($signer);
1766 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1767 if (@{$ref}[0] eq $commit &&
1768 @{$ref}[1] eq $subject &&
1769 @{$ref}[2] eq $type) {
1775 push(@{$commit_signer_hash{$signer}},
1776 [ ($commit, $subject, $type) ]);
1783 my ($role, $divisor, @lines) = @_;
1788 return if (@lines <= 0);
1790 if ($divisor <= 0) {
1791 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
1795 @lines = mailmap
(@lines);
1797 return if (@lines <= 0);
1799 @lines = sort(@lines);
1802 $hash{$_}++ for @lines;
1805 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
1806 my $sign_offs = $hash{$line};
1807 my $percent = $sign_offs * 100 / $divisor;
1809 $percent = 100 if ($percent > 100);
1811 last if ($sign_offs < $email_git_min_signatures ||
1812 $count > $email_git_max_maintainers ||
1813 $percent < $email_git_min_percent);
1814 push_email_address
($line, '');
1815 if ($output_rolestats) {
1816 my $fmt_percent = sprintf("%.0f", $percent);
1817 add_role
($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1819 add_role
($line, $role);
1824 sub vcs_file_signoffs
{
1830 $vcs_used = vcs_exists
();
1831 return if (!$vcs_used);
1833 my $cmd = $VCS_cmds{"find_signers_cmd"};
1834 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1836 ($commits, @signers) = vcs_find_signers
($cmd);
1838 foreach my $signer (@signers) {
1839 $signer = deduplicate_email
($signer);
1842 vcs_assign
("commit_signer", $commits, @signers);
1845 sub vcs_file_blame
{
1849 my @all_commits = ();
1854 $vcs_used = vcs_exists
();
1855 return if (!$vcs_used);
1857 @all_commits = vcs_blame
($file);
1858 @commits = uniq
(@all_commits);
1859 $total_commits = @commits;
1860 $total_lines = @all_commits;
1862 if ($email_git_blame_signatures) {
1865 my @commit_signers = ();
1866 my $commit = join(" -r ", @commits);
1869 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1870 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1872 ($commit_count, @commit_signers) = vcs_find_signers
($cmd);
1874 push(@signers, @commit_signers);
1876 foreach my $commit (@commits) {
1878 my @commit_signers = ();
1881 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1882 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1884 ($commit_count, @commit_signers) = vcs_find_signers
($cmd);
1886 push(@signers, @commit_signers);
1891 if ($from_filename) {
1892 if ($output_rolestats) {
1894 if (vcs_is_hg
()) {{ # Double brace for last exit
1896 my @commit_signers = ();
1897 @commits = uniq
(@commits);
1898 @commits = sort(@commits);
1899 my $commit = join(" -r ", @commits);
1902 $cmd = $VCS_cmds{"find_commit_author_cmd"};
1903 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1907 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1912 foreach my $line (@lines) {
1913 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1915 $author = deduplicate_email
($author);
1916 push(@authors, $author);
1920 save_commits_by_author
(@lines) if ($interactive);
1921 save_commits_by_signer
(@lines) if ($interactive);
1923 push(@signers, @authors);
1926 foreach my $commit (@commits) {
1928 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1929 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1930 my @author = vcs_find_author
($cmd);
1933 my $formatted_author = deduplicate_email
($author[0]);
1935 my $count = grep(/$commit/, @all_commits);
1936 for ($i = 0; $i < $count ; $i++) {
1937 push(@blame_signers, $formatted_author);
1941 if (@blame_signers) {
1942 vcs_assign
("authored lines", $total_lines, @blame_signers);
1945 foreach my $signer (@signers) {
1946 $signer = deduplicate_email
($signer);
1948 vcs_assign
("commits", $total_commits, @signers);
1950 foreach my $signer (@signers) {
1951 $signer = deduplicate_email
($signer);
1953 vcs_assign
("modified commits", $total_commits, @signers);
1961 @parms = grep(!$saw{$_}++, @parms);
1969 @parms = sort @parms;
1970 @parms = grep(!$saw{$_}++, @parms);
1974 sub clean_file_emails
{
1975 my (@file_emails) = @_;
1976 my @fmt_emails = ();
1978 foreach my $email (@file_emails) {
1979 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
1980 my ($name, $address) = parse_email
($email);
1981 if ($name eq '"[,\.]"') {
1985 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
1987 my $first = $nw[@nw - 3];
1988 my $middle = $nw[@nw - 2];
1989 my $last = $nw[@nw - 1];
1991 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
1992 (length($first) == 2 && substr($first, -1) eq ".")) ||
1993 (length($middle) == 1 ||
1994 (length($middle) == 2 && substr($middle, -1) eq "."))) {
1995 $name = "$first $middle $last";
1997 $name = "$middle $last";
2001 if (substr($name, -1) =~ /[,\.]/) {
2002 $name = substr($name, 0, length($name) - 1);
2003 } elsif (substr($name, -2) =~ /[,\.]"/) {
2004 $name = substr($name, 0, length($name) - 2) . '"';
2007 if (substr($name, 0, 1) =~ /[,\.]/) {
2008 $name = substr($name, 1, length($name) - 1);
2009 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2010 $name = '"' . substr($name, 2, length($name) - 2);
2013 my $fmt_email = format_email
($name, $address, $email_usename);
2014 push(@fmt_emails, $fmt_email);
2024 my ($address, $role) = @$_;
2025 if (!$saw{$address}) {
2026 if ($output_roles) {
2027 push(@lines, "$address ($role)");
2029 push(@lines, $address);
2041 if ($output_multiline) {
2042 foreach my $line (@parms) {
2046 print(join($output_separator, @parms));
2054 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2055 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2056 # This regexp will only work on addresses which have had comments stripped
2057 # and replaced with rfc822_lwsp.
2059 my $specials = '()<>@,;:\\\\".\\[\\]';
2060 my $controls = '\\000-\\037\\177';
2062 my $dtext = "[^\\[\\]\\r\\\\]";
2063 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2065 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2067 # Use zero-width assertion to spot the limit of an atom. A simple
2068 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2069 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2070 my $word = "(?:$atom|$quoted_string)";
2071 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2073 my $sub_domain = "(?:$atom|$domain_literal)";
2074 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2076 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2078 my $phrase = "$word*";
2079 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2080 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2081 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2083 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2084 my $address = "(?:$mailbox|$group)";
2086 return "$rfc822_lwsp*$address";
2089 sub rfc822_strip_comments
{
2091 # Recursively remove comments, and replace with a single space. The simpler
2092 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2093 # chars in atoms, for example.
2095 while ($s =~ s
/^((?
:[^"\\]|\\.)*
2096 (?:"(?
:[^"\\]|\\.)*"(?
:[^"\\]|\\.)*)*)
2097 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2101 # valid: returns true if the parameter is an RFC822 valid address
2104 my $s = rfc822_strip_comments(shift);
2107 $rfc822re = make_rfc822re();
2110 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2113 # validlist: In scalar context, returns true if the parameter is an RFC822
2114 # valid list of addresses.
2116 # In list context, returns an empty list on failure (an invalid
2117 # address was found); otherwise a list whose first element is the
2118 # number of addresses found and whose remaining elements are the
2119 # addresses. This is needed to disambiguate failure (invalid)
2120 # from success with no addresses found, because an empty string is
2123 sub rfc822_validlist {
2124 my $s = rfc822_strip_comments(shift);
2127 $rfc822re = make_rfc822re();
2129 # * null list items are valid according to the RFC
2130 # * the '1' business is to aid in distinguishing failure from no results
2133 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2134 $s =~ m/^$rfc822_char*$/) {
2135 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2138 return wantarray ? (scalar(@r), @r) : 1;
2140 return wantarray ? () : 0;