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
19 use Getopt
::Long
qw(:config no_auto_abbrev);
23 my $email_usename = 1;
24 my $email_maintainer = 1;
25 my $email_reviewer = 1;
27 my $email_subscriber_list = 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;
52 my $from_filename = 0;
53 my $pattern_depth = 0;
61 my %commit_author_hash;
62 my %commit_signer_hash;
64 # Signature types of people who are either
65 # a) responsible for the code in question, or
66 # b) familiar enough with it to give relevant feedback
67 my @signature_tags = ();
68 push(@signature_tags, "Signed-off-by:");
69 push(@signature_tags, "Reviewed-by:");
70 push(@signature_tags, "Acked-by:");
72 my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
74 # rfc822 email address - preloaded methods go here.
75 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
76 my $rfc822_char = '[\\000-\\377]';
78 # VCS command support: class-like functions and strings
83 "execute_cmd" => \
&git_execute_cmd
,
84 "available" => '(which("git") ne "") && (-e ".git")',
86 "git log --no-color --follow --since=\$email_git_since " .
87 '--format="GitCommit: %H%n' .
88 'GitAuthor: %an <%ae>%n' .
93 "find_commit_signers_cmd" =>
94 "git log --no-color " .
95 '--format="GitCommit: %H%n' .
96 'GitAuthor: %an <%ae>%n' .
101 "find_commit_author_cmd" =>
102 "git log --no-color " .
103 '--format="GitCommit: %H%n' .
104 'GitAuthor: %an <%ae>%n' .
106 'GitSubject: %s%n"' .
108 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
109 "blame_file_cmd" => "git blame -l \$file",
110 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
111 "blame_commit_pattern" => "^([0-9a-f]+) ",
112 "author_pattern" => "^GitAuthor: (.*)",
113 "subject_pattern" => "^GitSubject: (.*)",
117 "execute_cmd" => \
&hg_execute_cmd
,
118 "available" => '(which("hg") ne "") && (-d ".hg")',
119 "find_signers_cmd" =>
120 "hg log --date=\$email_hg_since " .
121 "--template='HgCommit: {node}\\n" .
122 "HgAuthor: {author}\\n" .
123 "HgSubject: {desc}\\n'" .
125 "find_commit_signers_cmd" =>
127 "--template='HgSubject: {desc}\\n'" .
129 "find_commit_author_cmd" =>
131 "--template='HgCommit: {node}\\n" .
132 "HgAuthor: {author}\\n" .
133 "HgSubject: {desc|firstline}\\n'" .
135 "blame_range_cmd" => "", # not supported
136 "blame_file_cmd" => "hg blame -n \$file",
137 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
138 "blame_commit_pattern" => "^([ 0-9a-f]+):",
139 "author_pattern" => "^HgAuthor: (.*)",
140 "subject_pattern" => "^HgSubject: (.*)",
143 my $conf = which_conf
(".get_maintainer.conf");
146 open(my $conffile, '<', "$conf")
147 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
149 while (<$conffile>) {
152 $line =~ s/\s*\n?$//g;
156 next if ($line =~ m/^\s*#/);
157 next if ($line =~ m/^\s*$/);
159 my @words = split(" ", $line);
160 foreach my $word (@words) {
161 last if ($word =~ m/^#/);
162 push (@conf_args, $word);
166 unshift(@ARGV, @conf_args) if @conf_args;
171 'git!' => \
$email_git,
172 'git-all-signature-types!' => \
$email_git_all_signature_types,
173 'git-blame!' => \
$email_git_blame,
174 'git-blame-signatures!' => \
$email_git_blame_signatures,
175 'git-fallback!' => \
$email_git_fallback,
176 'git-min-signatures=i' => \
$email_git_min_signatures,
177 'git-max-maintainers=i' => \
$email_git_max_maintainers,
178 'git-min-percent=i' => \
$email_git_min_percent,
179 'git-since=s' => \
$email_git_since,
180 'hg-since=s' => \
$email_hg_since,
181 'i|interactive!' => \
$interactive,
182 'remove-duplicates!' => \
$email_remove_duplicates,
183 'mailmap!' => \
$email_use_mailmap,
184 'm!' => \
$email_maintainer,
185 'r!' => \
$email_reviewer,
186 'n!' => \
$email_usename,
187 'l!' => \
$email_list,
188 's!' => \
$email_subscriber_list,
189 'multiline!' => \
$output_multiline,
190 'roles!' => \
$output_roles,
191 'rolestats!' => \
$output_rolestats,
192 'separator=s' => \
$output_separator,
193 'subsystem!' => \
$subsystem,
194 'status!' => \
$status,
197 'pattern-depth=i' => \
$pattern_depth,
198 'k|keywords!' => \
$keywords,
199 'sections!' => \
$sections,
200 'fe|file-emails!' => \
$file_emails,
201 'f|file' => \
$from_filename,
202 'v|version' => \
$version,
203 'h|help|usage' => \
$help,
205 die "$P: invalid argument - use --help if necessary\n";
214 print("${P} ${V}\n");
218 if (-t STDIN
&& !@ARGV) {
219 # We're talking to a terminal, but have no command line arguments.
220 die "$P: missing patchfile or -f file - use --help if necessary\n";
223 $output_multiline = 0 if ($output_separator ne ", ");
224 $output_rolestats = 1 if ($interactive);
225 $output_roles = 1 if ($output_rolestats);
237 my $selections = $email + $scm + $status + $subsystem + $web;
238 if ($selections == 0) {
239 die "$P: Missing required option: email, scm, status, subsystem or web\n";
244 ($email_maintainer + $email_reviewer +
245 $email_list + $email_subscriber_list +
246 $email_git + $email_git_blame) == 0) {
247 die "$P: Please select at least 1 email option\n";
250 if (!top_of_tree
($lk_path)) {
251 die "$P: The current directory does not appear to be "
252 . "a QEMU source tree.\n";
255 ## Read MAINTAINERS for type/value pairs
260 open (my $maint, '<', "${lk_path}MAINTAINERS")
261 or die "$P: Can't open MAINTAINERS: $!\n";
265 if ($line =~ m/^(.):\s*(.*)/) {
269 ##Filename pattern matching
270 if ($type eq "F" || $type eq "X") {
271 $value =~ s@\
.@
\\\
.@g; ##Convert . to \.
272 $value =~ s/\*/\.\*/g; ##Convert * to .*
273 $value =~ s/\?/\./g; ##Convert ? to .
274 ##if pattern is a directory and it lacks a trailing slash, add one
276 $value =~ s@
([^/])$@$1/@
;
278 } elsif ($type eq "K") {
279 $keyword_hash{@typevalue} = $value;
281 push(@typevalue, "$type:$value");
282 } elsif (!/^(\s)*$/) {
284 push(@typevalue, $line);
291 # Read mail address map
304 return if (!$email_use_mailmap || !(-f
"${lk_path}.mailmap"));
306 open(my $mailmap_file, '<', "${lk_path}.mailmap")
307 or warn "$P: Can't open .mailmap: $!\n";
309 while (<$mailmap_file>) {
310 s/#.*$//; #strip comments
311 s/^\s+|\s+$//g; #trim
313 next if (/^\s*$/); #skip empty lines
314 #entries have one of the following formats:
317 # name1 <mail1> <mail2>
318 # name1 <mail1> name2 <mail2>
319 # (see man git-shortlog)
321 if (/^([^<]+)<([^>]+)>$/) {
325 $real_name =~ s/\s+$//;
326 ($real_name, $address) = parse_email
("$real_name <$address>");
327 $mailmap->{names
}->{$address} = $real_name;
329 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
330 my $real_address = $1;
331 my $wrong_address = $2;
333 $mailmap->{addresses
}->{$wrong_address} = $real_address;
335 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
337 my $real_address = $2;
338 my $wrong_address = $3;
340 $real_name =~ s/\s+$//;
341 ($real_name, $real_address) =
342 parse_email
("$real_name <$real_address>");
343 $mailmap->{names
}->{$wrong_address} = $real_name;
344 $mailmap->{addresses
}->{$wrong_address} = $real_address;
346 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
348 my $real_address = $2;
350 my $wrong_address = $4;
352 $real_name =~ s/\s+$//;
353 ($real_name, $real_address) =
354 parse_email
("$real_name <$real_address>");
356 $wrong_name =~ s/\s+$//;
357 ($wrong_name, $wrong_address) =
358 parse_email
("$wrong_name <$wrong_address>");
360 my $wrong_email = format_email
($wrong_name, $wrong_address, 1);
361 $mailmap->{names
}->{$wrong_email} = $real_name;
362 $mailmap->{addresses
}->{$wrong_email} = $real_address;
365 close($mailmap_file);
368 ## use the filenames on the command line or find the filenames in the patchfiles
372 my @keyword_tvi = ();
373 my @file_emails = ();
376 push(@ARGV, "&STDIN");
379 foreach my $file (@ARGV) {
380 if ($file ne "&STDIN") {
381 ##if $file is a directory and it lacks a trailing slash, add one
383 $file =~ s@
([^/])$@$1/@
;
384 } elsif (!(stat $file)) {
385 die "$P: file '${file}' not found: $!\n";
388 if ($from_filename) {
390 if ($file ne "MAINTAINERS" && -f
$file && ($keywords || $file_emails)) {
391 open(my $f, '<', $file)
392 or die "$P: Can't open $file: $!\n";
393 my $text = do { local($/) ; <$f> };
396 foreach my $line (keys %keyword_hash) {
397 if ($text =~ m/$keyword_hash{$line}/x) {
398 push(@keyword_tvi, $line);
403 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;
404 push(@file_emails, clean_file_emails
(@poss_addr));
408 my $file_cnt = @files;
411 open(my $patch, "< $file")
412 or die "$P: Can't open $file: $!\n";
414 # We can check arbitrary information before the patch
415 # like the commit message, mail headers, etc...
416 # This allows us to match arbitrary keywords against any part
417 # of a git format-patch generated file (subject tags, etc...)
419 my $patch_prefix = ""; #Parsing the intro
423 if (m/^\+\+\+\s+(\S+)/) {
425 $filename =~ s@
^[^/]*/@@
;
427 $lastfile = $filename;
428 push(@files, $filename);
429 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
430 } elsif (m/^\@\@ -(\d+),(\d+)/) {
431 if ($email_git_blame) {
432 push(@range, "$lastfile:$1:$2");
434 } elsif ($keywords) {
435 foreach my $line (keys %keyword_hash) {
436 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
437 push(@keyword_tvi, $line);
444 if ($file_cnt == @files) {
445 warn "$P: file '${file}' doesn't appear to be a patch. "
446 . "Add -f to options?\n";
448 @files = sort_and_uniq
(@files);
452 @file_emails = uniq
(@file_emails);
455 my %email_hash_address;
463 my %deduplicate_name_hash = ();
464 my %deduplicate_address_hash = ();
466 my @maintainers = get_maintainers
();
469 @maintainers = merge_email
(@maintainers);
470 output
(@maintainers);
479 @status = uniq
(@status);
484 @subsystem = uniq
(@subsystem);
495 sub range_is_maintained
{
496 my ($start, $end) = @_;
498 for (my $i = $start; $i < $end; $i++) {
499 my $line = $typevalue[$i];
500 if ($line =~ m/^(.):\s*(.*)/) {
504 if ($value =~ /(maintain|support)/i) {
513 sub range_has_maintainer
{
514 my ($start, $end) = @_;
516 for (my $i = $start; $i < $end; $i++) {
517 my $line = $typevalue[$i];
518 if ($line =~ m/^(.):\s*(.*)/) {
529 sub get_maintainers
{
530 %email_hash_name = ();
531 %email_hash_address = ();
532 %commit_author_hash = ();
533 %commit_signer_hash = ();
541 %deduplicate_name_hash = ();
542 %deduplicate_address_hash = ();
543 if ($email_git_all_signature_types) {
544 $signature_pattern = "(.+?)[Bb][Yy]:";
546 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
549 # Find responsible parties
551 my %exact_pattern_match_hash = ();
553 foreach my $file (@files) {
556 my $tvi = find_first_section
();
557 while ($tvi < @typevalue) {
558 my $start = find_starting_index
($tvi);
559 my $end = find_ending_index
($tvi);
563 #Do not match excluded file patterns
565 for ($i = $start; $i < $end; $i++) {
566 my $line = $typevalue[$i];
567 if ($line =~ m/^(.):\s*(.*)/) {
571 if (file_match_pattern
($file, $value)) {
580 for ($i = $start; $i < $end; $i++) {
581 my $line = $typevalue[$i];
582 if ($line =~ m/^(.):\s*(.*)/) {
586 if (file_match_pattern
($file, $value)) {
587 my $value_pd = ($value =~ tr@
/@@
);
588 my $file_pd = ($file =~ tr@
/@@
);
589 $value_pd++ if (substr($value,-1,1) ne "/");
590 $value_pd = -1 if ($value =~ /^\.\*/);
591 if ($value_pd >= $file_pd &&
592 range_is_maintained
($start, $end) &&
593 range_has_maintainer
($start, $end)) {
594 $exact_pattern_match_hash{$file} = 1;
596 if ($pattern_depth == 0 ||
597 (($file_pd - $value_pd) < $pattern_depth)) {
598 $hash{$tvi} = $value_pd;
608 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
609 add_categories
($line);
612 my $start = find_starting_index
($line);
613 my $end = find_ending_index
($line);
614 for ($i = $start; $i < $end; $i++) {
615 my $line = $typevalue[$i];
616 if ($line =~ /^[FX]:/) { ##Restore file patterns
617 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
618 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
619 $line =~ s/\\\./\./g; ##Convert \. to .
620 $line =~ s/\.\*/\*/g; ##Convert .* to *
622 $line =~ s/^([A-Z]):/$1:\t/g;
631 @keyword_tvi = sort_and_uniq
(@keyword_tvi);
632 foreach my $line (@keyword_tvi) {
633 add_categories
($line);
637 foreach my $email (@email_to, @list_to) {
638 $email->[0] = deduplicate_email
($email->[0]);
642 if (! $interactive) {
643 $email_git_fallback = 0 if @email_to > 0 || $email_git || $email_git_blame;
644 if ($email_git_fallback) {
645 print STDERR
"get_maintainer.pl: No maintainers found, printing recent contributors.\n";
646 print STDERR
"get_maintainer.pl: Do not blindly cc: them on patches! Use common sense.\n";
651 foreach my $file (@files) {
652 if ($email_git || ($email_git_fallback &&
653 !$exact_pattern_match_hash{$file})) {
654 vcs_file_signoffs
($file);
656 if ($email_git_blame) {
657 vcs_file_blame
($file);
661 foreach my $email (@file_emails) {
662 my ($name, $address) = parse_email
($email);
664 my $tmp_email = format_email
($name, $address, $email_usename);
665 push_email_address
($tmp_email, '');
666 add_role
($tmp_email, 'in file');
671 if ($email || $email_list) {
673 @to = (@to, @email_to);
676 @to = (@to, @list_to);
681 @to = interactive_get_maintainers
(\
@to);
687 sub file_match_pattern
{
688 my ($file, $pattern) = @_;
689 if (substr($pattern, -1) eq "/") {
690 if ($file =~ m@
^$pattern@
) {
694 if ($file =~ m@
^$pattern@
) {
695 my $s1 = ($file =~ tr@
/@@
);
696 my $s2 = ($pattern =~ tr@
/@@
);
707 usage: $P [options] patchfile
708 $P [options] -f file|directory
711 MAINTAINER field selection options:
712 --email => print email address(es) if any
713 --git => include recent git \*-by: signers
714 --git-all-signature-types => include signers regardless of signature type
715 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
716 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
717 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
718 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
719 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
720 --git-blame => use git blame to find modified commits for patch or file
721 --git-since => git history to use (default: $email_git_since)
722 --hg-since => hg history to use (default: $email_hg_since)
723 --interactive => display a menu (mostly useful if used with the --git option)
724 --m => include maintainer(s) if any
725 --r => include reviewer(s) if any
726 --n => include name 'Full Name <addr\@domain.tld>'
727 --l => include list(s) if any
728 --s => include subscriber only list(s) if any
729 --remove-duplicates => minimize duplicate email names/addresses
730 --roles => show roles (status:subsystem, git-signer, list, etc...)
731 --rolestats => show roles and statistics (commits/total_commits, %)
732 --file-emails => add email addresses found in -f file (default: 0 (off))
733 --scm => print SCM tree(s) if any
734 --status => print status if any
735 --subsystem => print subsystem name if any
736 --web => print website(s) if any
739 --separator [, ] => separator for multiple entries on 1 line
740 using --separator also sets --nomultiline if --separator is not [, ]
741 --multiline => print 1 entry per line
744 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
745 --keywords => scan patch for keywords (default: $keywords)
746 --sections => print all of the subsystem sections with pattern matches
747 --mailmap => use .mailmap file (default: $email_use_mailmap)
748 --version => show version
749 --help => show this help information
752 [--email --nogit --git-fallback --m --r --n --l --multiline --pattern-depth=0
753 --remove-duplicates --rolestats]
756 Using "-f directory" may give unexpected results:
757 Used with "--git", git signators for _all_ files in and below
758 directory are examined as git recurses directories.
759 Any specified X: (exclude) pattern matches are _not_ ignored.
760 Used with "--nogit", directory is used as a pattern match,
761 no individual file within the directory or subdirectory
763 Used with "--git-blame", does not iterate all files in directory
764 Using "--git-blame" is slow and may add old committers and authors
765 that are no longer active maintainers to the output.
766 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
767 other automated tools that expect only ["name"] <email address>
768 may not work because of additional output after <email address>.
769 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
770 not the percentage of the entire file authored. # of commits is
771 not a good measure of amount of code authored. 1 major commit may
772 contain a thousand lines, 5 trivial commits may modify a single line.
773 If git is not installed, but mercurial (hg) is installed and an .hg
774 repository exists, the following options apply to mercurial:
776 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
778 Use --hg-since not --git-since to control date selection
779 File ".get_maintainer.conf", if it exists in the QEMU source root
780 directory, can change whatever get_maintainer defaults are desired.
781 Entries in this file can be any command line argument.
782 This file is prepended to any additional command line arguments.
783 Multiple lines and # comments are allowed.
790 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
793 if ( (-f
"${lk_path}COPYING")
794 && (-f
"${lk_path}MAINTAINERS")
795 && (-f
"${lk_path}Makefile")
796 && (-d
"${lk_path}docs")
797 && (-f
"${lk_path}VERSION")
798 && (-d
"${lk_path}linux-user/")
799 && (-d
"${lk_path}system/")) {
806 my ($formatted_email) = @_;
811 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
814 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
816 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
820 $name =~ s/^\s+|\s+$//g;
821 $name =~ s/^\"|\"$//g;
822 $address =~ s/^\s+|\s+$//g;
824 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
825 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
829 return ($name, $address);
833 my ($name, $address, $usename) = @_;
837 $name =~ s/^\s+|\s+$//g;
838 $name =~ s/^\"|\"$//g;
839 $address =~ s/^\s+|\s+$//g;
841 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
842 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
848 $formatted_email = "$address";
850 $formatted_email = "$name <$address>";
853 $formatted_email = $address;
856 return $formatted_email;
859 sub find_first_section
{
862 while ($index < @typevalue) {
863 my $tv = $typevalue[$index];
864 if (($tv =~ m/^(.):\s*(.*)/)) {
873 sub find_starting_index
{
877 my $tv = $typevalue[$index];
878 if (!($tv =~ m/^(.):\s*(.*)/)) {
887 sub find_ending_index
{
890 while ($index < @typevalue) {
891 my $tv = $typevalue[$index];
892 if (!($tv =~ m/^(.):\s*(.*)/)) {
901 sub get_subsystem_name
{
904 my $start = find_starting_index
($index);
906 my $subsystem = $typevalue[$start];
907 if (length($subsystem) > 20) {
908 $subsystem = substr($subsystem, 0, 17);
909 $subsystem =~ s/\s*$//;
910 $subsystem =~ s/[()]//g;
911 $subsystem = $subsystem . "...";
916 sub get_maintainer_role
{
920 my $start = find_starting_index
($index);
921 my $end = find_ending_index
($index);
923 my $role = "unknown";
924 my $subsystem = get_subsystem_name
($index);
926 for ($i = $start + 1; $i < $end; $i++) {
927 my $tv = $typevalue[$i];
928 if ($tv =~ m/^(.):\s*(.*)/) {
938 if ($role eq "supported") {
940 } elsif ($role eq "maintained") {
941 $role = "maintainer";
942 } elsif ($role eq "odd fixes") {
944 } elsif ($role eq "orphan") {
945 $role = "orphan minder";
946 } elsif ($role eq "obsolete") {
947 $role = "obsolete minder";
948 } elsif ($role eq "buried alive in reporters") {
949 $role = "chief penguin";
952 return $role . ":" . $subsystem;
958 my $subsystem = get_subsystem_name
($index);
960 if ($subsystem eq "THE REST") {
971 my $start = find_starting_index
($index);
972 my $end = find_ending_index
($index);
974 push(@subsystem, $typevalue[$start]);
976 for ($i = $start + 1; $i < $end; $i++) {
977 my $tv = $typevalue[$i];
978 if ($tv =~ m/^(.):\s*(.*)/) {
982 my $list_address = $pvalue;
983 my $list_additional = "";
984 my $list_role = get_list_role
($i);
986 if ($list_role ne "") {
987 $list_role = ":" . $list_role;
989 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
991 $list_additional = $2;
993 if ($list_additional =~ m/subscribers-only/) {
994 if ($email_subscriber_list) {
995 if (!$hash_list_to{lc($list_address)}) {
996 $hash_list_to{lc($list_address)} = 1;
997 push(@list_to, [$list_address,
998 "subscriber list${list_role}"]);
1003 if (!$hash_list_to{lc($list_address)}) {
1004 $hash_list_to{lc($list_address)} = 1;
1005 if ($list_additional =~ m/moderated/) {
1006 push(@list_to, [$list_address,
1007 "moderated list${list_role}"]);
1009 push(@list_to, [$list_address,
1010 "open list${list_role}"]);
1015 } elsif ($ptype eq "M") {
1016 my ($name, $address) = parse_email
($pvalue);
1019 my $tv = $typevalue[$i - 1];
1020 if ($tv =~ m/^(.):\s*(.*)/) {
1023 $pvalue = format_email
($name, $address, $email_usename);
1028 if ($email_maintainer) {
1029 my $role = get_maintainer_role
($i);
1030 push_email_addresses
($pvalue, $role);
1032 } elsif ($ptype eq "R") {
1033 my ($name, $address) = parse_email
($pvalue);
1036 my $tv = $typevalue[$i - 1];
1037 if ($tv =~ m/^(.):\s*(.*)/) {
1040 $pvalue = format_email
($name, $address, $email_usename);
1045 if ($email_reviewer) {
1046 my $subsystem = get_subsystem_name
($i);
1047 push_email_addresses
($pvalue, "reviewer:$subsystem");
1049 } elsif ($ptype eq "T") {
1050 push(@scm, $pvalue);
1051 } elsif ($ptype eq "W") {
1052 push(@web, $pvalue);
1053 } elsif ($ptype eq "S") {
1054 push(@status, $pvalue);
1061 my ($name, $address) = @_;
1063 return 1 if (($name eq "") && ($address eq ""));
1064 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1065 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1070 sub push_email_address
{
1071 my ($line, $role) = @_;
1073 my ($name, $address) = parse_email
($line);
1075 if ($address eq "") {
1079 if (!$email_remove_duplicates) {
1080 push(@email_to, [format_email
($name, $address, $email_usename), $role]);
1081 } elsif (!email_inuse
($name, $address)) {
1082 push(@email_to, [format_email
($name, $address, $email_usename), $role]);
1083 $email_hash_name{lc($name)}++ if ($name ne "");
1084 $email_hash_address{lc($address)}++;
1090 sub push_email_addresses
{
1091 my ($address, $role) = @_;
1093 my @address_list = ();
1095 if (rfc822_valid
($address)) {
1096 push_email_address
($address, $role);
1097 } elsif (@address_list = rfc822_validlist
($address)) {
1098 my $array_count = shift(@address_list);
1099 while (my $entry = shift(@address_list)) {
1100 push_email_address
($entry, $role);
1103 if (!push_email_address
($address, $role)) {
1104 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1110 my ($line, $role) = @_;
1112 my ($name, $address) = parse_email
($line);
1113 my $email = format_email
($name, $address, $email_usename);
1115 foreach my $entry (@email_to) {
1116 if ($email_remove_duplicates) {
1117 my ($entry_name, $entry_address) = parse_email
($entry->[0]);
1118 if (($name eq $entry_name || $address eq $entry_address)
1119 && ($role eq "" || !($entry->[1] =~ m/$role/))
1121 if ($entry->[1] eq "") {
1122 $entry->[1] = "$role";
1124 $entry->[1] = "$entry->[1],$role";
1128 if ($email eq $entry->[0]
1129 && ($role eq "" || !($entry->[1] =~ m/$role/))
1131 if ($entry->[1] eq "") {
1132 $entry->[1] = "$role";
1134 $entry->[1] = "$entry->[1],$role";
1144 foreach my $path (split(/:/, $ENV{PATH
})) {
1145 if (-e
"$path/$bin") {
1146 return "$path/$bin";
1156 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1157 if (-e
"$path/$conf") {
1158 return "$path/$conf";
1168 my ($name, $address) = parse_email
($line);
1169 my $email = format_email
($name, $address, 1);
1170 my $real_name = $name;
1171 my $real_address = $address;
1173 if (exists $mailmap->{names
}->{$email} ||
1174 exists $mailmap->{addresses
}->{$email}) {
1175 if (exists $mailmap->{names
}->{$email}) {
1176 $real_name = $mailmap->{names
}->{$email};
1178 if (exists $mailmap->{addresses
}->{$email}) {
1179 $real_address = $mailmap->{addresses
}->{$email};
1182 if (exists $mailmap->{names
}->{$address}) {
1183 $real_name = $mailmap->{names
}->{$address};
1185 if (exists $mailmap->{addresses
}->{$address}) {
1186 $real_address = $mailmap->{addresses
}->{$address};
1189 return format_email
($real_name, $real_address, 1);
1193 my (@addresses) = @_;
1195 my @mapped_emails = ();
1196 foreach my $line (@addresses) {
1197 push(@mapped_emails, mailmap_email
($line));
1199 merge_by_realname
(@mapped_emails) if ($email_use_mailmap);
1200 return @mapped_emails;
1203 sub merge_by_realname
{
1207 foreach my $email (@emails) {
1208 my ($name, $address) = parse_email
($email);
1209 if (exists $address_map{$name}) {
1210 $address = $address_map{$name};
1211 $email = format_email
($name, $address, 1);
1213 $address_map{$name} = $address;
1218 sub git_execute_cmd
{
1222 my $output = `$cmd`;
1223 $output =~ s/^\s*//gm;
1224 @lines = split("\n", $output);
1229 sub hg_execute_cmd
{
1233 my $output = `$cmd`;
1234 @lines = split("\n", $output);
1239 sub extract_formatted_signatures
{
1240 my (@signature_lines) = @_;
1242 my @type = @signature_lines;
1244 s/\s*(.*):.*/$1/ for (@type);
1247 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1249 ## Reformat email addresses (with names) to avoid badly written signatures
1251 foreach my $signer (@signature_lines) {
1252 $signer = deduplicate_email
($signer);
1255 return (\
@type, \
@signature_lines);
1258 sub vcs_find_signers
{
1262 my @signatures = ();
1264 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1266 my $pattern = $VCS_cmds{"commit_pattern"};
1268 $commits = grep(/$pattern/, @lines); # of commits
1270 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1272 return (0, @signatures) if !@signatures;
1274 save_commits_by_author
(@lines) if ($interactive);
1275 save_commits_by_signer
(@lines) if ($interactive);
1277 my ($types_ref, $signers_ref) = extract_formatted_signatures
(@signatures);
1279 return ($commits, @
$signers_ref);
1282 sub vcs_find_author
{
1286 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1288 return @lines if !@lines;
1291 foreach my $line (@lines) {
1292 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1294 my ($name, $address) = parse_email
($author);
1295 $author = format_email
($name, $address, 1);
1296 push(@authors, $author);
1300 save_commits_by_author
(@lines) if ($interactive);
1301 save_commits_by_signer
(@lines) if ($interactive);
1306 sub vcs_save_commits
{
1311 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1313 foreach my $line (@lines) {
1314 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1327 return @commits if (!(-f
$file));
1329 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1330 my @all_commits = ();
1332 $cmd = $VCS_cmds{"blame_file_cmd"};
1333 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1334 @all_commits = vcs_save_commits
($cmd);
1336 foreach my $file_range_diff (@range) {
1337 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1339 my $diff_start = $2;
1340 my $diff_length = $3;
1341 next if ("$file" ne "$diff_file");
1342 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1343 push(@commits, $all_commits[$i]);
1347 foreach my $file_range_diff (@range) {
1348 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1350 my $diff_start = $2;
1351 my $diff_length = $3;
1352 next if ("$file" ne "$diff_file");
1353 $cmd = $VCS_cmds{"blame_range_cmd"};
1354 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1355 push(@commits, vcs_save_commits
($cmd));
1358 $cmd = $VCS_cmds{"blame_file_cmd"};
1359 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1360 @commits = vcs_save_commits
($cmd);
1363 foreach my $commit (@commits) {
1364 $commit =~ s/^\^//g;
1370 my $printed_novcs = 0;
1372 %VCS_cmds = %VCS_cmds_git;
1373 return 1 if eval $VCS_cmds{"available"};
1374 %VCS_cmds = %VCS_cmds_hg;
1375 return 2 if eval $VCS_cmds{"available"};
1377 if (!$printed_novcs) {
1378 warn("$P: No supported VCS found. Add --nogit to options?\n");
1379 warn("Using a git repository produces better results.\n");
1380 warn("Try latest git repository using:\n");
1381 warn("git clone https://gitlab.com/qemu-project/qemu.git\n");
1389 return $vcs_used == 1;
1393 return $vcs_used == 2;
1396 sub interactive_get_maintainers
{
1397 my ($list_ref) = @_;
1398 my @list = @
$list_ref;
1407 foreach my $entry (@list) {
1408 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1409 $selected{$count} = 1;
1410 $authored{$count} = 0;
1411 $signed{$count} = 0;
1417 my $print_options = 0;
1422 printf STDERR
"\n%1s %2s %-65s",
1423 "*", "#", "email/list and role:stats";
1425 ($email_git_fallback && !$maintained) ||
1427 print STDERR
"auth sign";
1430 foreach my $entry (@list) {
1431 my $email = $entry->[0];
1432 my $role = $entry->[1];
1434 $sel = "*" if ($selected{$count});
1435 my $commit_author = $commit_author_hash{$email};
1436 my $commit_signer = $commit_signer_hash{$email};
1439 $authored++ for (@
{$commit_author});
1440 $signed++ for (@
{$commit_signer});
1441 printf STDERR
"%1s %2d %-65s", $sel, $count + 1, $email;
1442 printf STDERR
"%4d %4d", $authored, $signed
1443 if ($authored > 0 || $signed > 0);
1444 printf STDERR
"\n %s\n", $role;
1445 if ($authored{$count}) {
1446 my $commit_author = $commit_author_hash{$email};
1447 foreach my $ref (@
{$commit_author}) {
1448 print STDERR
" Author: @{$ref}[1]\n";
1451 if ($signed{$count}) {
1452 my $commit_signer = $commit_signer_hash{$email};
1453 foreach my $ref (@
{$commit_signer}) {
1454 print STDERR
" @{$ref}[2]: @{$ref}[1]\n";
1461 my $date_ref = \
$email_git_since;
1462 $date_ref = \
$email_hg_since if (vcs_is_hg
());
1463 if ($print_options) {
1468 Version Control options:
1469 g use git history [$email_git]
1470 gf use git-fallback [$email_git_fallback]
1471 b use git blame [$email_git_blame]
1472 bs use blame signatures [$email_git_blame_signatures]
1473 c# minimum commits [$email_git_min_signatures]
1474 %# min percent [$email_git_min_percent]
1475 d# history to use [$$date_ref]
1476 x# max maintainers [$email_git_max_maintainers]
1477 t all signature types [$email_git_all_signature_types]
1478 m use .mailmap [$email_use_mailmap]
1485 tm toggle maintainers
1486 tg toggle git entries
1487 tl toggle open list entries
1488 ts toggle subscriber list entries
1489 f emails in file [$file_emails]
1490 k keywords in file [$keywords]
1491 r remove duplicates [$email_remove_duplicates]
1492 p# pattern match depth [$pattern_depth]
1496 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1498 my $input = <STDIN
>;
1503 my @wish = split(/[, ]+/, $input);
1504 foreach my $nr (@wish) {
1506 my $sel = substr($nr, 0, 1);
1507 my $str = substr($nr, 1);
1509 $val = $1 if $str =~ /^(\d+)$/;
1514 $output_rolestats = 0;
1517 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1518 $selected{$nr - 1} = !$selected{$nr - 1};
1519 } elsif ($sel eq "*" || $sel eq '^') {
1521 $toggle = 1 if ($sel eq '*');
1522 for (my $i = 0; $i < $count; $i++) {
1523 $selected{$i} = $toggle;
1525 } elsif ($sel eq "0") {
1526 for (my $i = 0; $i < $count; $i++) {
1527 $selected{$i} = !$selected{$i};
1529 } elsif ($sel eq "t") {
1530 if (lc($str) eq "m") {
1531 for (my $i = 0; $i < $count; $i++) {
1532 $selected{$i} = !$selected{$i}
1533 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1535 } elsif (lc($str) eq "g") {
1536 for (my $i = 0; $i < $count; $i++) {
1537 $selected{$i} = !$selected{$i}
1538 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1540 } elsif (lc($str) eq "l") {
1541 for (my $i = 0; $i < $count; $i++) {
1542 $selected{$i} = !$selected{$i}
1543 if ($list[$i]->[1] =~ /^(open list)/i);
1545 } elsif (lc($str) eq "s") {
1546 for (my $i = 0; $i < $count; $i++) {
1547 $selected{$i} = !$selected{$i}
1548 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1551 } elsif ($sel eq "a") {
1552 if ($val > 0 && $val <= $count) {
1553 $authored{$val - 1} = !$authored{$val - 1};
1554 } elsif ($str eq '*' || $str eq '^') {
1556 $toggle = 1 if ($str eq '*');
1557 for (my $i = 0; $i < $count; $i++) {
1558 $authored{$i} = $toggle;
1561 } elsif ($sel eq "s") {
1562 if ($val > 0 && $val <= $count) {
1563 $signed{$val - 1} = !$signed{$val - 1};
1564 } elsif ($str eq '*' || $str eq '^') {
1566 $toggle = 1 if ($str eq '*');
1567 for (my $i = 0; $i < $count; $i++) {
1568 $signed{$i} = $toggle;
1571 } elsif ($sel eq "o") {
1574 } elsif ($sel eq "g") {
1576 bool_invert
(\
$email_git_fallback);
1578 bool_invert
(\
$email_git);
1581 } elsif ($sel eq "b") {
1583 bool_invert
(\
$email_git_blame_signatures);
1585 bool_invert
(\
$email_git_blame);
1588 } elsif ($sel eq "c") {
1590 $email_git_min_signatures = $val;
1593 } elsif ($sel eq "x") {
1595 $email_git_max_maintainers = $val;
1598 } elsif ($sel eq "%") {
1599 if ($str ne "" && $val >= 0) {
1600 $email_git_min_percent = $val;
1603 } elsif ($sel eq "d") {
1605 $email_git_since = $str;
1606 } elsif (vcs_is_hg
()) {
1607 $email_hg_since = $str;
1610 } elsif ($sel eq "t") {
1611 bool_invert
(\
$email_git_all_signature_types);
1613 } elsif ($sel eq "f") {
1614 bool_invert
(\
$file_emails);
1616 } elsif ($sel eq "r") {
1617 bool_invert
(\
$email_remove_duplicates);
1619 } elsif ($sel eq "m") {
1620 bool_invert
(\
$email_use_mailmap);
1623 } elsif ($sel eq "k") {
1624 bool_invert
(\
$keywords);
1626 } elsif ($sel eq "p") {
1627 if ($str ne "" && $val >= 0) {
1628 $pattern_depth = $val;
1631 } elsif ($sel eq "h" || $sel eq "?") {
1634 Interactive mode allows you to select the various maintainers, submitters,
1635 commit signers and mailing lists that could be CC'd on a patch.
1637 Any *'d entry is selected.
1639 If you have git or hg installed, you can choose to summarize the commit
1640 history of files in the patch. Also, each line of the current file can
1641 be matched to its commit author and that commits signers with blame.
1643 Various knobs exist to control the length of time for active commit
1644 tracking, the maximum number of commit authors and signers to add,
1647 Enter selections at the prompt until you are satisfied that the selected
1648 maintainers are appropriate. You may enter multiple selections separated
1649 by either commas or spaces.
1653 print STDERR
"invalid option: '$nr'\n";
1658 print STDERR
"git-blame can be very slow, please have patience..."
1659 if ($email_git_blame);
1660 goto &get_maintainers
;
1664 #drop not selected entries
1666 my @new_emailto = ();
1667 foreach my $entry (@list) {
1668 if ($selected{$count}) {
1669 push(@new_emailto, $list[$count]);
1673 return @new_emailto;
1677 my ($bool_ref) = @_;
1686 sub deduplicate_email
{
1690 my ($name, $address) = parse_email
($email);
1691 $email = format_email
($name, $address, 1);
1692 $email = mailmap_email
($email);
1694 return $email if (!$email_remove_duplicates);
1696 ($name, $address) = parse_email
($email);
1698 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1699 $name = $deduplicate_name_hash{lc($name)}->[0];
1700 $address = $deduplicate_name_hash{lc($name)}->[1];
1702 } elsif ($deduplicate_address_hash{lc($address)}) {
1703 $name = $deduplicate_address_hash{lc($address)}->[0];
1704 $address = $deduplicate_address_hash{lc($address)}->[1];
1708 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1709 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1711 $email = format_email
($name, $address, 1);
1712 $email = mailmap_email
($email);
1716 sub save_commits_by_author
{
1723 foreach my $line (@lines) {
1724 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1726 $author = deduplicate_email
($author);
1727 push(@authors, $author);
1729 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1730 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1733 for (my $i = 0; $i < @authors; $i++) {
1735 foreach my $ref(@
{$commit_author_hash{$authors[$i]}}) {
1736 if (@
{$ref}[0] eq $commits[$i] &&
1737 @
{$ref}[1] eq $subjects[$i]) {
1743 push(@
{$commit_author_hash{$authors[$i]}},
1744 [ ($commits[$i], $subjects[$i]) ]);
1749 sub save_commits_by_signer
{
1755 foreach my $line (@lines) {
1756 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1757 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1758 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1759 my @signatures = ($line);
1760 my ($types_ref, $signers_ref) = extract_formatted_signatures
(@signatures);
1761 my @types = @
$types_ref;
1762 my @signers = @
$signers_ref;
1764 my $type = $types[0];
1765 my $signer = $signers[0];
1767 $signer = deduplicate_email
($signer);
1770 foreach my $ref(@
{$commit_signer_hash{$signer}}) {
1771 if (@
{$ref}[0] eq $commit &&
1772 @
{$ref}[1] eq $subject &&
1773 @
{$ref}[2] eq $type) {
1779 push(@
{$commit_signer_hash{$signer}},
1780 [ ($commit, $subject, $type) ]);
1787 my ($role, $divisor, @lines) = @_;
1792 return if (@lines <= 0);
1794 if ($divisor <= 0) {
1795 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
1799 @lines = mailmap
(@lines);
1801 return if (@lines <= 0);
1803 @lines = sort(@lines);
1806 $hash{$_}++ for @lines;
1809 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
1810 my $sign_offs = $hash{$line};
1811 my $percent = $sign_offs * 100 / $divisor;
1813 $percent = 100 if ($percent > 100);
1815 last if ($sign_offs < $email_git_min_signatures ||
1816 $count > $email_git_max_maintainers ||
1817 $percent < $email_git_min_percent);
1818 push_email_address
($line, '');
1819 if ($output_rolestats) {
1820 my $fmt_percent = sprintf("%.0f", $percent);
1821 add_role
($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1823 add_role
($line, $role);
1828 sub vcs_file_signoffs
{
1834 $vcs_used = vcs_exists
();
1835 return if (!$vcs_used);
1837 my $cmd = $VCS_cmds{"find_signers_cmd"};
1838 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1840 ($commits, @signers) = vcs_find_signers
($cmd);
1842 foreach my $signer (@signers) {
1843 $signer = deduplicate_email
($signer);
1846 vcs_assign
("commit_signer", $commits, @signers);
1849 sub vcs_file_blame
{
1853 my @all_commits = ();
1858 $vcs_used = vcs_exists
();
1859 return if (!$vcs_used);
1861 @all_commits = vcs_blame
($file);
1862 @commits = uniq
(@all_commits);
1863 $total_commits = @commits;
1864 $total_lines = @all_commits;
1866 if ($email_git_blame_signatures) {
1869 my @commit_signers = ();
1870 my $commit = join(" -r ", @commits);
1873 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1874 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1876 ($commit_count, @commit_signers) = vcs_find_signers
($cmd);
1878 push(@signers, @commit_signers);
1880 foreach my $commit (@commits) {
1882 my @commit_signers = ();
1885 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1886 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1888 ($commit_count, @commit_signers) = vcs_find_signers
($cmd);
1890 push(@signers, @commit_signers);
1895 if ($from_filename) {
1896 if ($output_rolestats) {
1898 if (vcs_is_hg
()) {{ # Double brace for last exit
1900 my @commit_signers = ();
1901 @commits = uniq
(@commits);
1902 @commits = sort(@commits);
1903 my $commit = join(" -r ", @commits);
1906 $cmd = $VCS_cmds{"find_commit_author_cmd"};
1907 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1911 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1916 foreach my $line (@lines) {
1917 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1919 $author = deduplicate_email
($author);
1920 push(@authors, $author);
1924 save_commits_by_author
(@lines) if ($interactive);
1925 save_commits_by_signer
(@lines) if ($interactive);
1927 push(@signers, @authors);
1930 foreach my $commit (@commits) {
1932 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1933 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1934 my @author = vcs_find_author
($cmd);
1937 my $formatted_author = deduplicate_email
($author[0]);
1939 my $count = grep(/$commit/, @all_commits);
1940 for ($i = 0; $i < $count ; $i++) {
1941 push(@blame_signers, $formatted_author);
1945 if (@blame_signers) {
1946 vcs_assign
("authored lines", $total_lines, @blame_signers);
1949 foreach my $signer (@signers) {
1950 $signer = deduplicate_email
($signer);
1952 vcs_assign
("commits", $total_commits, @signers);
1954 foreach my $signer (@signers) {
1955 $signer = deduplicate_email
($signer);
1957 vcs_assign
("modified commits", $total_commits, @signers);
1965 @parms = grep(!$saw{$_}++, @parms);
1973 @parms = sort @parms;
1974 @parms = grep(!$saw{$_}++, @parms);
1978 sub clean_file_emails
{
1979 my (@file_emails) = @_;
1980 my @fmt_emails = ();
1982 foreach my $email (@file_emails) {
1983 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
1984 my ($name, $address) = parse_email
($email);
1985 if ($name eq '"[,\.]"') {
1989 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
1991 my $first = $nw[@nw - 3];
1992 my $middle = $nw[@nw - 2];
1993 my $last = $nw[@nw - 1];
1995 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
1996 (length($first) == 2 && substr($first, -1) eq ".")) ||
1997 (length($middle) == 1 ||
1998 (length($middle) == 2 && substr($middle, -1) eq "."))) {
1999 $name = "$first $middle $last";
2001 $name = "$middle $last";
2005 if (substr($name, -1) =~ /[,\.]/) {
2006 $name = substr($name, 0, length($name) - 1);
2007 } elsif (substr($name, -2) =~ /[,\.]"/) {
2008 $name = substr($name, 0, length($name) - 2) . '"';
2011 if (substr($name, 0, 1) =~ /[,\.]/) {
2012 $name = substr($name, 1, length($name) - 1);
2013 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2014 $name = '"' . substr($name, 2, length($name) - 2);
2017 my $fmt_email = format_email
($name, $address, $email_usename);
2018 push(@fmt_emails, $fmt_email);
2028 my ($address, $role) = @
$_;
2029 if (!$saw{$address}) {
2030 if ($output_roles) {
2031 push(@lines, "$address ($role)");
2033 push(@lines, $address);
2045 if ($output_multiline) {
2046 foreach my $line (@parms) {
2050 print(join($output_separator, @parms));
2058 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2059 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2060 # This regexp will only work on addresses which have had comments stripped
2061 # and replaced with rfc822_lwsp.
2063 my $specials = '()<>@,;:\\\\".\\[\\]';
2064 my $controls = '\\000-\\037\\177';
2066 my $dtext = "[^\\[\\]\\r\\\\]";
2067 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2069 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2071 # Use zero-width assertion to spot the limit of an atom. A simple
2072 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2073 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2074 my $word = "(?:$atom|$quoted_string)";
2075 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2077 my $sub_domain = "(?:$atom|$domain_literal)";
2078 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2080 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2082 my $phrase = "$word*";
2083 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2084 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2085 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2087 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2088 my $address = "(?:$mailbox|$group)";
2090 return "$rfc822_lwsp*$address";
2093 sub rfc822_strip_comments
{
2095 # Recursively remove comments, and replace with a single space. The simpler
2096 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2097 # chars in atoms, for example.
2099 while ($s =~ s
/^((?
:[^"\\]|\\.)*
2100 (?:"(?
:[^"\\]|\\.)*"(?
:[^"\\]|\\.)*)*)
2101 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2105 # valid: returns true if the parameter is an RFC822 valid address
2108 my $s = rfc822_strip_comments(shift);
2111 $rfc822re = make_rfc822re();
2114 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2117 # validlist: In scalar context, returns true if the parameter is an RFC822
2118 # valid list of addresses.
2120 # In list context, returns an empty list on failure (an invalid
2121 # address was found); otherwise a list whose first element is the
2122 # number of addresses found and whose remaining elements are the
2123 # addresses. This is needed to disambiguate failure (invalid)
2124 # from success with no addresses found, because an empty string is
2127 sub rfc822_validlist {
2128 my $s = rfc822_strip_comments(shift);
2131 $rfc822re = make_rfc822re();
2133 # * null list items are valid according to the RFC
2134 # * the '1' business is to aid in distinguishing failure from no results
2137 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2138 $s =~ m/^$rfc822_char*$/) {
2139 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2142 return wantarray ? (scalar(@r), @r) : 1;
2144 return wantarray ? () : 0;