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 = 1;
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 range_is_maintained {
498 my ($start, $end) = @_;
500 for (my $i = $start; $i < $end; $i++) {
501 my $line = $typevalue[$i];
502 if ($line =~ m/^(\C):\s*(.*)/) {
506 if ($value =~ /(maintain|support)/i) {
515 sub range_has_maintainer {
516 my ($start, $end) = @_;
518 for (my $i = $start; $i < $end; $i++) {
519 my $line = $typevalue[$i];
520 if ($line =~ m/^(\C):\s*(.*)/) {
531 sub get_maintainers {
532 %email_hash_name = ();
533 %email_hash_address = ();
534 %commit_author_hash = ();
535 %commit_signer_hash = ();
543 %deduplicate_name_hash = ();
544 %deduplicate_address_hash = ();
545 if ($email_git_all_signature_types) {
546 $signature_pattern = "(.+?)[Bb][Yy]:";
548 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
551 # Find responsible parties
553 my %exact_pattern_match_hash = ();
555 foreach my $file (@files) {
558 my $tvi = find_first_section();
559 while ($tvi < @typevalue) {
560 my $start = find_starting_index($tvi);
561 my $end = find_ending_index($tvi);
565 #Do not match excluded file patterns
567 for ($i = $start; $i < $end; $i++) {
568 my $line = $typevalue[$i];
569 if ($line =~ m/^(\C):\s*(.*)/) {
573 if (file_match_pattern($file, $value)) {
582 for ($i = $start; $i < $end; $i++) {
583 my $line = $typevalue[$i];
584 if ($line =~ m/^(\C):\s*(.*)/) {
588 if (file_match_pattern($file, $value)) {
589 my $value_pd = ($value =~ tr@/@@);
590 my $file_pd = ($file =~ tr@/@@);
591 $value_pd++ if (substr($value,-1,1) ne "/");
592 $value_pd = -1 if ($value =~ /^\.\*/);
593 if ($value_pd >= $file_pd &&
594 range_is_maintained($start, $end) &&
595 range_has_maintainer($start, $end)) {
596 $exact_pattern_match_hash{$file} = 1;
598 if ($pattern_depth == 0 ||
599 (($file_pd - $value_pd) < $pattern_depth)) {
600 $hash{$tvi} = $value_pd;
610 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
611 add_categories($line);
614 my $start = find_starting_index($line);
615 my $end = find_ending_index($line);
616 for ($i = $start; $i < $end; $i++) {
617 my $line = $typevalue[$i];
618 if ($line =~ /^[FX]:/) { ##Restore file patterns
619 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
620 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
621 $line =~ s/\\\./\./g; ##Convert \. to .
622 $line =~ s/\.\*/\*/g; ##Convert .* to *
624 $line =~ s/^([A-Z]):/$1:\t/g;
633 @keyword_tvi = sort_and_uniq(@keyword_tvi);
634 foreach my $line (@keyword_tvi) {
635 add_categories($line);
639 foreach my $email (@email_to, @list_to) {
640 $email->[0] = deduplicate_email($email->[0]);
643 foreach my $file (@files) {
645 ($email_git || ($email_git_fallback &&
646 !$exact_pattern_match_hash{$file}))) {
647 vcs_file_signoffs($file);
649 if ($email && $email_git_blame) {
650 vcs_file_blame($file);
655 foreach my $chief (@penguin_chief) {
656 if ($chief =~ m/^(.*):(.*)/) {
659 $email_address = format_email($1, $2, $email_usename);
660 if ($email_git_penguin_chiefs) {
661 push(@email_to, [$email_address, 'chief penguin']);
663 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
668 foreach my $email (@file_emails) {
669 my ($name, $address) = parse_email($email);
671 my $tmp_email = format_email($name, $address, $email_usename);
672 push_email_address($tmp_email, '');
673 add_role($tmp_email, 'in file');
678 if ($email || $email_list) {
680 @to = (@to, @email_to);
683 @to = (@to, @list_to);
688 @to = interactive_get_maintainers(\@to);
694 sub file_match_pattern {
695 my ($file, $pattern) = @_;
696 if (substr($pattern, -1) eq "/") {
697 if ($file =~ m@^$pattern@) {
701 if ($file =~ m@^$pattern@) {
702 my $s1 = ($file =~ tr@/@@);
703 my $s2 = ($pattern =~ tr@/@@);
714 usage: $P [options] patchfile
715 $P [options] -f file|directory
718 MAINTAINER field selection options:
719 --email => print email address(es) if any
720 --git => include recent git \*-by: signers
721 --git-all-signature-types => include signers regardless of signature type
722 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
723 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
724 --git-chief-penguins => include ${penguin_chiefs}
725 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
726 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
727 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
728 --git-blame => use git blame to find modified commits for patch or file
729 --git-since => git history to use (default: $email_git_since)
730 --hg-since => hg history to use (default: $email_hg_since)
731 --interactive => display a menu (mostly useful if used with the --git option)
732 --m => include maintainer(s) if any
733 --n => include name 'Full Name <addr\@domain.tld>'
734 --l => include list(s) if any
735 --s => include subscriber only list(s) if any
736 --remove-duplicates => minimize duplicate email names/addresses
737 --roles => show roles (status:subsystem, git-signer, list, etc...)
738 --rolestats => show roles and statistics (commits/total_commits, %)
739 --file-emails => add email addresses found in -f file (default: 0 (off))
740 --scm => print SCM tree(s) if any
741 --status => print status if any
742 --subsystem => print subsystem name if any
743 --web => print website(s) if any
746 --separator [, ] => separator for multiple entries on 1 line
747 using --separator also sets --nomultiline if --separator is not [, ]
748 --multiline => print 1 entry per line
751 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
752 --keywords => scan patch for keywords (default: $keywords)
753 --sections => print all of the subsystem sections with pattern matches
754 --mailmap => use .mailmap file (default: $email_use_mailmap)
755 --version => show version
756 --help => show this help information
759 [--email --nogit --git-fallback --m --n --l --multiline -pattern-depth=0
760 --remove-duplicates --rolestats]
763 Using "-f directory" may give unexpected results:
764 Used with "--git", git signators for _all_ files in and below
765 directory are examined as git recurses directories.
766 Any specified X: (exclude) pattern matches are _not_ ignored.
767 Used with "--nogit", directory is used as a pattern match,
768 no individual file within the directory or subdirectory
770 Used with "--git-blame", does not iterate all files in directory
771 Using "--git-blame" is slow and may add old committers and authors
772 that are no longer active maintainers to the output.
773 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
774 other automated tools that expect only ["name"] <email address>
775 may not work because of additional output after <email address>.
776 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
777 not the percentage of the entire file authored. # of commits is
778 not a good measure of amount of code authored. 1 major commit may
779 contain a thousand lines, 5 trivial commits may modify a single line.
780 If git is not installed, but mercurial (hg) is installed and an .hg
781 repository exists, the following options apply to mercurial:
783 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
785 Use --hg-since not --git-since to control date selection
786 File ".get_maintainer.conf", if it exists in the linux kernel source root
787 directory, can change whatever get_maintainer defaults are desired.
788 Entries in this file can be any command line argument.
789 This file is prepended to any additional command line arguments.
790 Multiple lines and # comments are allowed.
794 sub top_of_kernel_tree {
797 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
800 if ( (-f "${lk_path}COPYING")
801 && (-f "${lk_path}CREDITS")
802 && (-f "${lk_path}Kbuild")
803 && (-f "${lk_path}MAINTAINERS")
804 && (-f "${lk_path}Makefile")
805 && (-f "${lk_path}README")
806 && (-d "${lk_path}Documentation")
807 && (-d "${lk_path}arch")
808 && (-d "${lk_path}include")
809 && (-d "${lk_path}drivers")
810 && (-d "${lk_path}fs")
811 && (-d "${lk_path}init")
812 && (-d "${lk_path}ipc")
813 && (-d "${lk_path}kernel")
814 && (-d "${lk_path}lib")
815 && (-d "${lk_path}scripts")) {
822 my ($formatted_email) = @_;
827 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
830 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
832 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
836 $name =~ s/^\s+|\s+$//g;
837 $name =~ s/^\"|\"$//g;
838 $address =~ s/^\s+|\s+$//g;
840 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
841 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
845 return ($name, $address);
849 my ($name, $address, $usename) = @_;
853 $name =~ s/^\s+|\s+$//g;
854 $name =~ s/^\"|\"$//g;
855 $address =~ s/^\s+|\s+$//g;
857 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
858 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
864 $formatted_email = "$address";
866 $formatted_email = "$name <$address>";
869 $formatted_email = $address;
872 return $formatted_email;
875 sub find_first_section {
878 while ($index < @typevalue) {
879 my $tv = $typevalue[$index];
880 if (($tv =~ m/^(\C):\s*(.*)/)) {
889 sub find_starting_index {
893 my $tv = $typevalue[$index];
894 if (!($tv =~ m/^(\C):\s*(.*)/)) {
903 sub find_ending_index {
906 while ($index < @typevalue) {
907 my $tv = $typevalue[$index];
908 if (!($tv =~ m/^(\C):\s*(.*)/)) {
917 sub get_maintainer_role {
921 my $start = find_starting_index($index);
922 my $end = find_ending_index($index);
925 my $subsystem = $typevalue[$start];
926 if (length($subsystem) > 20) {
927 $subsystem = substr($subsystem, 0, 17);
928 $subsystem =~ s/\s*$//;
929 $subsystem = $subsystem . "...";
932 for ($i = $start + 1; $i < $end; $i++) {
933 my $tv = $typevalue[$i];
934 if ($tv =~ m/^(\C):\s*(.*)/) {
944 if ($role eq "supported") {
946 } elsif ($role eq "maintained") {
947 $role = "maintainer";
948 } elsif ($role eq "odd fixes") {
950 } elsif ($role eq "orphan") {
951 $role = "orphan minder";
952 } elsif ($role eq "obsolete") {
953 $role = "obsolete minder";
954 } elsif ($role eq "buried alive in reporters") {
955 $role = "chief penguin";
958 return $role . ":" . $subsystem;
965 my $start = find_starting_index($index);
966 my $end = find_ending_index($index);
968 my $subsystem = $typevalue[$start];
969 if (length($subsystem) > 20) {
970 $subsystem = substr($subsystem, 0, 17);
971 $subsystem =~ s/\s*$//;
972 $subsystem = $subsystem . "...";
975 if ($subsystem eq "THE REST") {
986 my $start = find_starting_index($index);
987 my $end = find_ending_index($index);
989 push(@subsystem, $typevalue[$start]);
991 for ($i = $start + 1; $i < $end; $i++) {
992 my $tv = $typevalue[$i];
993 if ($tv =~ m/^(\C):\s*(.*)/) {
997 my $list_address = $pvalue;
998 my $list_additional = "";
999 my $list_role = get_list_role($i);
1001 if ($list_role ne "") {
1002 $list_role = ":" . $list_role;
1004 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1006 $list_additional = $2;
1008 if ($list_additional =~ m/subscribers-only/) {
1009 if ($email_subscriber_list) {
1010 if (!$hash_list_to{lc($list_address)}) {
1011 $hash_list_to{lc($list_address)} = 1;
1012 push(@list_to, [$list_address,
1013 "subscriber list${list_role}"]);
1018 if (!$hash_list_to{lc($list_address)}) {
1019 $hash_list_to{lc($list_address)} = 1;
1020 push(@list_to, [$list_address,
1021 "open list${list_role}"]);
1025 } elsif ($ptype eq "M") {
1026 my ($name, $address) = parse_email($pvalue);
1029 my $tv = $typevalue[$i - 1];
1030 if ($tv =~ m/^(\C):\s*(.*)/) {
1033 $pvalue = format_email($name, $address, $email_usename);
1038 if ($email_maintainer) {
1039 my $role = get_maintainer_role($i);
1040 push_email_addresses($pvalue, $role);
1042 } elsif ($ptype eq "T") {
1043 push(@scm, $pvalue);
1044 } elsif ($ptype eq "W") {
1045 push(@web, $pvalue);
1046 } elsif ($ptype eq "S") {
1047 push(@status, $pvalue);
1054 my ($name, $address) = @_;
1056 return 1 if (($name eq "") && ($address eq ""));
1057 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1058 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1063 sub push_email_address {
1064 my ($line, $role) = @_;
1066 my ($name, $address) = parse_email($line);
1068 if ($address eq "") {
1072 if (!$email_remove_duplicates) {
1073 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1074 } elsif (!email_inuse($name, $address)) {
1075 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1076 $email_hash_name{lc($name)}++ if ($name ne "");
1077 $email_hash_address{lc($address)}++;
1083 sub push_email_addresses {
1084 my ($address, $role) = @_;
1086 my @address_list = ();
1088 if (rfc822_valid($address)) {
1089 push_email_address($address, $role);
1090 } elsif (@address_list = rfc822_validlist($address)) {
1091 my $array_count = shift(@address_list);
1092 while (my $entry = shift(@address_list)) {
1093 push_email_address($entry, $role);
1096 if (!push_email_address($address, $role)) {
1097 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1103 my ($line, $role) = @_;
1105 my ($name, $address) = parse_email($line);
1106 my $email = format_email($name, $address, $email_usename);
1108 foreach my $entry (@email_to) {
1109 if ($email_remove_duplicates) {
1110 my ($entry_name, $entry_address) = parse_email($entry->[0]);
1111 if (($name eq $entry_name || $address eq $entry_address)
1112 && ($role eq "" || !($entry->[1] =~ m/$role/))
1114 if ($entry->[1] eq "") {
1115 $entry->[1] = "$role";
1117 $entry->[1] = "$entry->[1],$role";
1121 if ($email eq $entry->[0]
1122 && ($role eq "" || !($entry->[1] =~ m/$role/))
1124 if ($entry->[1] eq "") {
1125 $entry->[1] = "$role";
1127 $entry->[1] = "$entry->[1],$role";
1137 foreach my $path (split(/:/, $ENV{PATH})) {
1138 if (-e "$path/$bin") {
1139 return "$path/$bin";
1149 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1150 if (-e "$path/$conf") {
1151 return "$path/$conf";
1161 my ($name, $address) = parse_email($line);
1162 my $email = format_email($name, $address, 1);
1163 my $real_name = $name;
1164 my $real_address = $address;
1166 if (exists $mailmap->{names}->{$email} ||
1167 exists $mailmap->{addresses}->{$email}) {
1168 if (exists $mailmap->{names}->{$email}) {
1169 $real_name = $mailmap->{names}->{$email};
1171 if (exists $mailmap->{addresses}->{$email}) {
1172 $real_address = $mailmap->{addresses}->{$email};
1175 if (exists $mailmap->{names}->{$address}) {
1176 $real_name = $mailmap->{names}->{$address};
1178 if (exists $mailmap->{addresses}->{$address}) {
1179 $real_address = $mailmap->{addresses}->{$address};
1182 return format_email($real_name, $real_address, 1);
1186 my (@addresses) = @_;
1188 my @mapped_emails = ();
1189 foreach my $line (@addresses) {
1190 push(@mapped_emails, mailmap_email($line));
1192 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1193 return @mapped_emails;
1196 sub merge_by_realname {
1200 foreach my $email (@emails) {
1201 my ($name, $address) = parse_email($email);
1202 if (exists $address_map{$name}) {
1203 $address = $address_map{$name};
1204 $email = format_email($name, $address, 1);
1206 $address_map{$name} = $address;
1211 sub git_execute_cmd {
1215 my $output = `$cmd`;
1216 $output =~ s/^\s*//gm;
1217 @lines = split("\n", $output);
1222 sub hg_execute_cmd {
1226 my $output = `$cmd`;
1227 @lines = split("\n", $output);
1232 sub extract_formatted_signatures {
1233 my (@signature_lines) = @_;
1235 my @type = @signature_lines;
1237 s/\s*(.*):.*/$1/ for (@type);
1240 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1242 ## Reformat email addresses (with names) to avoid badly written signatures
1244 foreach my $signer (@signature_lines) {
1245 $signer = deduplicate_email($signer);
1248 return (\@type, \@signature_lines);
1251 sub vcs_find_signers {
1255 my @signatures = ();
1257 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1259 my $pattern = $VCS_cmds{"commit_pattern"};
1261 $commits = grep(/$pattern/, @lines); # of commits
1263 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1265 return (0, @signatures) if !@signatures;
1267 save_commits_by_author(@lines) if ($interactive);
1268 save_commits_by_signer(@lines) if ($interactive);
1270 if (!$email_git_penguin_chiefs) {
1271 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1274 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1276 return ($commits, @$signers_ref);
1279 sub vcs_find_author {
1283 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1285 if (!$email_git_penguin_chiefs) {
1286 @lines = grep(!/${penguin_chiefs}/i, @lines);
1289 return @lines if !@lines;
1292 foreach my $line (@lines) {
1293 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1295 my ($name, $address) = parse_email($author);
1296 $author = format_email($name, $address, 1);
1297 push(@authors, $author);
1301 save_commits_by_author(@lines) if ($interactive);
1302 save_commits_by_signer(@lines) if ($interactive);
1307 sub vcs_save_commits {
1312 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1314 foreach my $line (@lines) {
1315 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1328 return @commits if (!(-f $file));
1330 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1331 my @all_commits = ();
1333 $cmd = $VCS_cmds{"blame_file_cmd"};
1334 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1335 @all_commits = vcs_save_commits($cmd);
1337 foreach my $file_range_diff (@range) {
1338 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1340 my $diff_start = $2;
1341 my $diff_length = $3;
1342 next if ("$file" ne "$diff_file");
1343 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1344 push(@commits, $all_commits[$i]);
1348 foreach my $file_range_diff (@range) {
1349 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1351 my $diff_start = $2;
1352 my $diff_length = $3;
1353 next if ("$file" ne "$diff_file");
1354 $cmd = $VCS_cmds{"blame_range_cmd"};
1355 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1356 push(@commits, vcs_save_commits($cmd));
1359 $cmd = $VCS_cmds{"blame_file_cmd"};
1360 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1361 @commits = vcs_save_commits($cmd);
1364 foreach my $commit (@commits) {
1365 $commit =~ s/^\^//g;
1371 my $printed_novcs = 0;
1373 %VCS_cmds = %VCS_cmds_git;
1374 return 1 if eval $VCS_cmds{"available"};
1375 %VCS_cmds = %VCS_cmds_hg;
1376 return 2 if eval $VCS_cmds{"available"};
1378 if (!$printed_novcs) {
1379 warn("$P: No supported VCS found. Add --nogit to options?\n");
1380 warn("Using a git repository produces better results.\n");
1381 warn("Try Linus Torvalds' latest git repository using:\n");
1382 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
1390 return $vcs_used == 1;
1394 return $vcs_used == 2;
1397 sub interactive_get_maintainers {
1398 my ($list_ref) = @_;
1399 my @list = @$list_ref;
1408 foreach my $entry (@list) {
1409 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1410 $selected{$count} = 1;
1411 $authored{$count} = 0;
1412 $signed{$count} = 0;
1418 my $print_options = 0;
1423 printf STDERR "\n%1s %2s %-65s",
1424 "*", "#", "email/list and role:stats";
1426 ($email_git_fallback && !$maintained) ||
1428 print STDERR "auth sign";
1431 foreach my $entry (@list) {
1432 my $email = $entry->[0];
1433 my $role = $entry->[1];
1435 $sel = "*" if ($selected{$count});
1436 my $commit_author = $commit_author_hash{$email};
1437 my $commit_signer = $commit_signer_hash{$email};
1440 $authored++ for (@{$commit_author});
1441 $signed++ for (@{$commit_signer});
1442 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1443 printf STDERR "%4d %4d", $authored, $signed
1444 if ($authored > 0 || $signed > 0);
1445 printf STDERR "\n %s\n", $role;
1446 if ($authored{$count}) {
1447 my $commit_author = $commit_author_hash{$email};
1448 foreach my $ref (@{$commit_author}) {
1449 print STDERR " Author: @{$ref}[1]\n";
1452 if ($signed{$count}) {
1453 my $commit_signer = $commit_signer_hash{$email};
1454 foreach my $ref (@{$commit_signer}) {
1455 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1462 my $date_ref = \$email_git_since;
1463 $date_ref = \$email_hg_since if (vcs_is_hg());
1464 if ($print_options) {
1469 Version Control options:
1470 g use git history [$email_git]
1471 gf use git-fallback [$email_git_fallback]
1472 b use git blame [$email_git_blame]
1473 bs use blame signatures [$email_git_blame_signatures]
1474 c# minimum commits [$email_git_min_signatures]
1475 %# min percent [$email_git_min_percent]
1476 d# history to use [$$date_ref]
1477 x# max maintainers [$email_git_max_maintainers]
1478 t all signature types [$email_git_all_signature_types]
1479 m use .mailmap [$email_use_mailmap]
1486 tm toggle maintainers
1487 tg toggle git entries
1488 tl toggle open list entries
1489 ts toggle subscriber list entries
1490 f emails in file [$file_emails]
1491 k keywords in file [$keywords]
1492 r remove duplicates [$email_remove_duplicates]
1493 p# pattern match depth [$pattern_depth]
1497 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1499 my $input = <STDIN>;
1504 my @wish = split(/[, ]+/, $input);
1505 foreach my $nr (@wish) {
1507 my $sel = substr($nr, 0, 1);
1508 my $str = substr($nr, 1);
1510 $val = $1 if $str =~ /^(\d+)$/;
1515 $output_rolestats = 0;
1518 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1519 $selected{$nr - 1} = !$selected{$nr - 1};
1520 } elsif ($sel eq "*" || $sel eq '^') {
1522 $toggle = 1 if ($sel eq '*');
1523 for (my $i = 0; $i < $count; $i++) {
1524 $selected{$i} = $toggle;
1526 } elsif ($sel eq "0") {
1527 for (my $i = 0; $i < $count; $i++) {
1528 $selected{$i} = !$selected{$i};
1530 } elsif ($sel eq "t") {
1531 if (lc($str) eq "m") {
1532 for (my $i = 0; $i < $count; $i++) {
1533 $selected{$i} = !$selected{$i}
1534 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1536 } elsif (lc($str) eq "g") {
1537 for (my $i = 0; $i < $count; $i++) {
1538 $selected{$i} = !$selected{$i}
1539 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1541 } elsif (lc($str) eq "l") {
1542 for (my $i = 0; $i < $count; $i++) {
1543 $selected{$i} = !$selected{$i}
1544 if ($list[$i]->[1] =~ /^(open list)/i);
1546 } elsif (lc($str) eq "s") {
1547 for (my $i = 0; $i < $count; $i++) {
1548 $selected{$i} = !$selected{$i}
1549 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1552 } elsif ($sel eq "a") {
1553 if ($val > 0 && $val <= $count) {
1554 $authored{$val - 1} = !$authored{$val - 1};
1555 } elsif ($str eq '*' || $str eq '^') {
1557 $toggle = 1 if ($str eq '*');
1558 for (my $i = 0; $i < $count; $i++) {
1559 $authored{$i} = $toggle;
1562 } elsif ($sel eq "s") {
1563 if ($val > 0 && $val <= $count) {
1564 $signed{$val - 1} = !$signed{$val - 1};
1565 } elsif ($str eq '*' || $str eq '^') {
1567 $toggle = 1 if ($str eq '*');
1568 for (my $i = 0; $i < $count; $i++) {
1569 $signed{$i} = $toggle;
1572 } elsif ($sel eq "o") {
1575 } elsif ($sel eq "g") {
1577 bool_invert(\$email_git_fallback);
1579 bool_invert(\$email_git);
1582 } elsif ($sel eq "b") {
1584 bool_invert(\$email_git_blame_signatures);
1586 bool_invert(\$email_git_blame);
1589 } elsif ($sel eq "c") {
1591 $email_git_min_signatures = $val;
1594 } elsif ($sel eq "x") {
1596 $email_git_max_maintainers = $val;
1599 } elsif ($sel eq "%") {
1600 if ($str ne "" && $val >= 0) {
1601 $email_git_min_percent = $val;
1604 } elsif ($sel eq "d") {
1606 $email_git_since = $str;
1607 } elsif (vcs_is_hg()) {
1608 $email_hg_since = $str;
1611 } elsif ($sel eq "t") {
1612 bool_invert(\$email_git_all_signature_types);
1614 } elsif ($sel eq "f") {
1615 bool_invert(\$file_emails);
1617 } elsif ($sel eq "r") {
1618 bool_invert(\$email_remove_duplicates);
1620 } elsif ($sel eq "m") {
1621 bool_invert(\$email_use_mailmap);
1624 } elsif ($sel eq "k") {
1625 bool_invert(\$keywords);
1627 } elsif ($sel eq "p") {
1628 if ($str ne "" && $val >= 0) {
1629 $pattern_depth = $val;
1632 } elsif ($sel eq "h" || $sel eq "?") {
1635 Interactive mode allows you to select the various maintainers, submitters,
1636 commit signers and mailing lists that could be CC'd on a patch.
1638 Any *'d entry is selected.
1640 If you have git or hg installed, you can choose to summarize the commit
1641 history of files in the patch. Also, each line of the current file can
1642 be matched to its commit author and that commits signers with blame.
1644 Various knobs exist to control the length of time for active commit
1645 tracking, the maximum number of commit authors and signers to add,
1648 Enter selections at the prompt until you are satisfied that the selected
1649 maintainers are appropriate. You may enter multiple selections separated
1650 by either commas or spaces.
1654 print STDERR "invalid option: '$nr'\n";
1659 print STDERR "git-blame can be very slow, please have patience..."
1660 if ($email_git_blame);
1661 goto &get_maintainers;
1665 #drop not selected entries
1667 my @new_emailto = ();
1668 foreach my $entry (@list) {
1669 if ($selected{$count}) {
1670 push(@new_emailto, $list[$count]);
1674 return @new_emailto;
1678 my ($bool_ref) = @_;
1687 sub deduplicate_email {
1691 my ($name, $address) = parse_email($email);
1692 $email = format_email($name, $address, 1);
1693 $email = mailmap_email($email);
1695 return $email if (!$email_remove_duplicates);
1697 ($name, $address) = parse_email($email);
1699 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1700 $name = $deduplicate_name_hash{lc($name)}->[0];
1701 $address = $deduplicate_name_hash{lc($name)}->[1];
1703 } elsif ($deduplicate_address_hash{lc($address)}) {
1704 $name = $deduplicate_address_hash{lc($address)}->[0];
1705 $address = $deduplicate_address_hash{lc($address)}->[1];
1709 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1710 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1712 $email = format_email($name, $address, 1);
1713 $email = mailmap_email($email);
1717 sub save_commits_by_author {
1724 foreach my $line (@lines) {
1725 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1727 $author = deduplicate_email($author);
1728 push(@authors, $author);
1730 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1731 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1734 for (my $i = 0; $i < @authors; $i++) {
1736 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1737 if (@{$ref}[0] eq $commits[$i] &&
1738 @{$ref}[1] eq $subjects[$i]) {
1744 push(@{$commit_author_hash{$authors[$i]}},
1745 [ ($commits[$i], $subjects[$i]) ]);
1750 sub save_commits_by_signer {
1756 foreach my $line (@lines) {
1757 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1758 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1759 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1760 my @signatures = ($line);
1761 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1762 my @types = @$types_ref;
1763 my @signers = @$signers_ref;
1765 my $type = $types[0];
1766 my $signer = $signers[0];
1768 $signer = deduplicate_email($signer);
1771 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1772 if (@{$ref}[0] eq $commit &&
1773 @{$ref}[1] eq $subject &&
1774 @{$ref}[2] eq $type) {
1780 push(@{$commit_signer_hash{$signer}},
1781 [ ($commit, $subject, $type) ]);
1788 my ($role, $divisor, @lines) = @_;
1793 return if (@lines <= 0);
1795 if ($divisor <= 0) {
1796 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
1800 @lines = mailmap(@lines);
1802 return if (@lines <= 0);
1804 @lines = sort(@lines);
1807 $hash{$_}++ for @lines;
1810 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
1811 my $sign_offs = $hash{$line};
1812 my $percent = $sign_offs * 100 / $divisor;
1814 $percent = 100 if ($percent > 100);
1816 last if ($sign_offs < $email_git_min_signatures ||
1817 $count > $email_git_max_maintainers ||
1818 $percent < $email_git_min_percent);
1819 push_email_address($line, '');
1820 if ($output_rolestats) {
1821 my $fmt_percent = sprintf("%.0f", $percent);
1822 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1824 add_role($line, $role);
1829 sub vcs_file_signoffs {
1835 $vcs_used = vcs_exists();
1836 return if (!$vcs_used);
1838 my $cmd = $VCS_cmds{"find_signers_cmd"};
1839 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1841 ($commits, @signers) = vcs_find_signers($cmd);
1843 foreach my $signer (@signers) {
1844 $signer = deduplicate_email($signer);
1847 vcs_assign("commit_signer", $commits, @signers);
1850 sub vcs_file_blame {
1854 my @all_commits = ();
1859 $vcs_used = vcs_exists();
1860 return if (!$vcs_used);
1862 @all_commits = vcs_blame($file);
1863 @commits = uniq(@all_commits);
1864 $total_commits = @commits;
1865 $total_lines = @all_commits;
1867 if ($email_git_blame_signatures) {
1870 my @commit_signers = ();
1871 my $commit = join(" -r ", @commits);
1874 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1875 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1877 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1879 push(@signers, @commit_signers);
1881 foreach my $commit (@commits) {
1883 my @commit_signers = ();
1886 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1887 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1889 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1891 push(@signers, @commit_signers);
1896 if ($from_filename) {
1897 if ($output_rolestats) {
1899 if (vcs_is_hg()) {{ # Double brace for last exit
1901 my @commit_signers = ();
1902 @commits = uniq(@commits);
1903 @commits = sort(@commits);
1904 my $commit = join(" -r ", @commits);
1907 $cmd = $VCS_cmds{"find_commit_author_cmd"};
1908 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1912 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1914 if (!$email_git_penguin_chiefs) {
1915 @lines = grep(!/${penguin_chiefs}/i, @lines);
1921 foreach my $line (@lines) {
1922 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1924 $author = deduplicate_email($author);
1925 push(@authors, $author);
1929 save_commits_by_author(@lines) if ($interactive);
1930 save_commits_by_signer(@lines) if ($interactive);
1932 push(@signers, @authors);
1935 foreach my $commit (@commits) {
1937 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1938 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1939 my @author = vcs_find_author($cmd);
1942 my $formatted_author = deduplicate_email($author[0]);
1944 my $count = grep(/$commit/, @all_commits);
1945 for ($i = 0; $i < $count ; $i++) {
1946 push(@blame_signers, $formatted_author);
1950 if (@blame_signers) {
1951 vcs_assign("authored lines", $total_lines, @blame_signers);
1954 foreach my $signer (@signers) {
1955 $signer = deduplicate_email($signer);
1957 vcs_assign("commits", $total_commits, @signers);
1959 foreach my $signer (@signers) {
1960 $signer = deduplicate_email($signer);
1962 vcs_assign("modified commits", $total_commits, @signers);
1970 @parms = grep(!$saw{$_}++, @parms);
1978 @parms = sort @parms;
1979 @parms = grep(!$saw{$_}++, @parms);
1983 sub clean_file_emails {
1984 my (@file_emails) = @_;
1985 my @fmt_emails = ();
1987 foreach my $email (@file_emails) {
1988 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
1989 my ($name, $address) = parse_email($email);
1990 if ($name eq '"[,\.]"') {
1994 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
1996 my $first = $nw[@nw - 3];
1997 my $middle = $nw[@nw - 2];
1998 my $last = $nw[@nw - 1];
2000 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2001 (length($first) == 2 && substr($first, -1) eq ".")) ||
2002 (length($middle) == 1 ||
2003 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2004 $name = "$first $middle $last";
2006 $name = "$middle $last";
2010 if (substr($name, -1) =~ /[,\.]/) {
2011 $name = substr($name, 0, length($name) - 1);
2012 } elsif (substr($name, -2) =~ /[,\.]"/) {
2013 $name = substr($name, 0, length($name) - 2) . '"';
2016 if (substr($name, 0, 1) =~ /[,\.]/) {
2017 $name = substr($name, 1, length($name) - 1);
2018 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2019 $name = '"' . substr($name, 2, length($name) - 2);
2022 my $fmt_email = format_email($name, $address, $email_usename);
2023 push(@fmt_emails, $fmt_email);
2033 my ($address, $role) = @$_;
2034 if (!$saw{$address}) {
2035 if ($output_roles) {
2036 push(@lines, "$address ($role)");
2038 push(@lines, $address);
2050 if ($output_multiline) {
2051 foreach my $line (@parms) {
2055 print(join($output_separator, @parms));
2063 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2064 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2065 # This regexp will only work on addresses which have had comments stripped
2066 # and replaced with rfc822_lwsp.
2068 my $specials = '()<>@,;:\\\\".\\[\\]';
2069 my $controls = '\\000-\\037\\177';
2071 my $dtext = "[^\\[\\]\\r\\\\]";
2072 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2074 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2076 # Use zero-width assertion to spot the limit of an atom. A simple
2077 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2078 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2079 my $word = "(?:$atom|$quoted_string)";
2080 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2082 my $sub_domain = "(?:$atom|$domain_literal)";
2083 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2085 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2087 my $phrase = "$word*";
2088 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2089 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2090 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2092 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2093 my $address = "(?:$mailbox|$group)";
2095 return "$rfc822_lwsp*$address";
2098 sub rfc822_strip_comments {
2100 # Recursively remove comments, and replace with a single space. The simpler
2101 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2102 # chars in atoms, for example.
2104 while ($s =~ s/^((?:[^"\\]|\\.)*
2105 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2106 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2110 # valid: returns true if the parameter is an RFC822 valid address
2113 my $s = rfc822_strip_comments(shift);
2116 $rfc822re = make_rfc822re();
2119 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2122 # validlist: In scalar context, returns true if the parameter is an RFC822
2123 # valid list of addresses.
2125 # In list context, returns an empty list on failure (an invalid
2126 # address was found); otherwise a list whose first element is the
2127 # number of addresses found and whose remaining elements are the
2128 # addresses. This is needed to disambiguate failure (invalid)
2129 # from success with no addresses found, because an empty string is
2132 sub rfc822_validlist {
2133 my $s = rfc822_strip_comments(shift);
2136 $rfc822re = make_rfc822re();
2138 # * null list items are valid according to the RFC
2139 # * the '1' business is to aid in distinguishing failure from no results
2142 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2143 $s =~ m/^$rfc822_char*$/) {
2144 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2147 return wantarray ? (scalar(@r), @r) : 1;
2149 return wantarray ? () : 0;