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";
424 # We can check arbitrary information before the patch
425 # like the commit message, mail headers, etc...
426 # This allows us to match arbitrary keywords against any part
427 # of a git format-patch generated file (subject tags, etc...)
429 my $patch_prefix = ""; #Parsing the intro
433 if (m/^\+\+\+\s+(\S+)/) {
435 $filename =~ s@^[^/]*/@@;
437 $lastfile = $filename;
438 push(@files, $filename);
439 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
440 } elsif (m/^\@\@ -(\d+),(\d+)/) {
441 if ($email_git_blame) {
442 push(@range, "$lastfile:$1:$2");
444 } elsif ($keywords) {
445 foreach my $line (keys %keyword_hash) {
446 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
447 push(@keyword_tvi, $line);
454 if ($file_cnt == @files) {
455 warn "$P: file '${file}' doesn't appear to be a patch. "
456 . "Add -f to options?\n";
458 @files = sort_and_uniq(@files);
462 @file_emails = uniq(@file_emails);
465 my %email_hash_address;
473 my %deduplicate_name_hash = ();
474 my %deduplicate_address_hash = ();
475 my $signature_pattern;
477 my @maintainers = get_maintainers();
480 @maintainers = merge_email(@maintainers);
481 output(@maintainers);
490 @status = uniq(@status);
495 @subsystem = uniq(@subsystem);
506 sub range_is_maintained {
507 my ($start, $end) = @_;
509 for (my $i = $start; $i < $end; $i++) {
510 my $line = $typevalue[$i];
511 if ($line =~ m/^(\C):\s*(.*)/) {
515 if ($value =~ /(maintain|support)/i) {
524 sub range_has_maintainer {
525 my ($start, $end) = @_;
527 for (my $i = $start; $i < $end; $i++) {
528 my $line = $typevalue[$i];
529 if ($line =~ m/^(\C):\s*(.*)/) {
540 sub get_maintainers {
541 %email_hash_name = ();
542 %email_hash_address = ();
543 %commit_author_hash = ();
544 %commit_signer_hash = ();
552 %deduplicate_name_hash = ();
553 %deduplicate_address_hash = ();
554 if ($email_git_all_signature_types) {
555 $signature_pattern = "(.+?)[Bb][Yy]:";
557 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
560 # Find responsible parties
562 my %exact_pattern_match_hash = ();
564 foreach my $file (@files) {
567 my $tvi = find_first_section();
568 while ($tvi < @typevalue) {
569 my $start = find_starting_index($tvi);
570 my $end = find_ending_index($tvi);
574 #Do not match excluded file patterns
576 for ($i = $start; $i < $end; $i++) {
577 my $line = $typevalue[$i];
578 if ($line =~ m/^(\C):\s*(.*)/) {
582 if (file_match_pattern($file, $value)) {
591 for ($i = $start; $i < $end; $i++) {
592 my $line = $typevalue[$i];
593 if ($line =~ m/^(\C):\s*(.*)/) {
597 if (file_match_pattern($file, $value)) {
598 my $value_pd = ($value =~ tr@/@@);
599 my $file_pd = ($file =~ tr@/@@);
600 $value_pd++ if (substr($value,-1,1) ne "/");
601 $value_pd = -1 if ($value =~ /^\.\*/);
602 if ($value_pd >= $file_pd &&
603 range_is_maintained($start, $end) &&
604 range_has_maintainer($start, $end)) {
605 $exact_pattern_match_hash{$file} = 1;
607 if ($pattern_depth == 0 ||
608 (($file_pd - $value_pd) < $pattern_depth)) {
609 $hash{$tvi} = $value_pd;
619 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
620 add_categories($line);
623 my $start = find_starting_index($line);
624 my $end = find_ending_index($line);
625 for ($i = $start; $i < $end; $i++) {
626 my $line = $typevalue[$i];
627 if ($line =~ /^[FX]:/) { ##Restore file patterns
628 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
629 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
630 $line =~ s/\\\./\./g; ##Convert \. to .
631 $line =~ s/\.\*/\*/g; ##Convert .* to *
633 $line =~ s/^([A-Z]):/$1:\t/g;
642 @keyword_tvi = sort_and_uniq(@keyword_tvi);
643 foreach my $line (@keyword_tvi) {
644 add_categories($line);
648 foreach my $email (@email_to, @list_to) {
649 $email->[0] = deduplicate_email($email->[0]);
652 foreach my $file (@files) {
654 ($email_git || ($email_git_fallback &&
655 !$exact_pattern_match_hash{$file}))) {
656 vcs_file_signoffs($file);
658 if ($email && $email_git_blame) {
659 vcs_file_blame($file);
664 foreach my $chief (@penguin_chief) {
665 if ($chief =~ m/^(.*):(.*)/) {
668 $email_address = format_email($1, $2, $email_usename);
669 if ($email_git_penguin_chiefs) {
670 push(@email_to, [$email_address, 'chief penguin']);
672 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
677 foreach my $email (@file_emails) {
678 my ($name, $address) = parse_email($email);
680 my $tmp_email = format_email($name, $address, $email_usename);
681 push_email_address($tmp_email, '');
682 add_role($tmp_email, 'in file');
687 if ($email || $email_list) {
689 @to = (@to, @email_to);
692 @to = (@to, @list_to);
697 @to = interactive_get_maintainers(\@to);
703 sub file_match_pattern {
704 my ($file, $pattern) = @_;
705 if (substr($pattern, -1) eq "/") {
706 if ($file =~ m@^$pattern@) {
710 if ($file =~ m@^$pattern@) {
711 my $s1 = ($file =~ tr@/@@);
712 my $s2 = ($pattern =~ tr@/@@);
723 usage: $P [options] patchfile
724 $P [options] -f file|directory
727 MAINTAINER field selection options:
728 --email => print email address(es) if any
729 --git => include recent git \*-by: signers
730 --git-all-signature-types => include signers regardless of signature type
731 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
732 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
733 --git-chief-penguins => include ${penguin_chiefs}
734 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
735 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
736 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
737 --git-blame => use git blame to find modified commits for patch or file
738 --git-since => git history to use (default: $email_git_since)
739 --hg-since => hg history to use (default: $email_hg_since)
740 --interactive => display a menu (mostly useful if used with the --git option)
741 --m => include maintainer(s) if any
742 --n => include name 'Full Name <addr\@domain.tld>'
743 --l => include list(s) if any
744 --s => include subscriber only list(s) if any
745 --remove-duplicates => minimize duplicate email names/addresses
746 --roles => show roles (status:subsystem, git-signer, list, etc...)
747 --rolestats => show roles and statistics (commits/total_commits, %)
748 --file-emails => add email addresses found in -f file (default: 0 (off))
749 --scm => print SCM tree(s) if any
750 --status => print status if any
751 --subsystem => print subsystem name if any
752 --web => print website(s) if any
755 --separator [, ] => separator for multiple entries on 1 line
756 using --separator also sets --nomultiline if --separator is not [, ]
757 --multiline => print 1 entry per line
760 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
761 --keywords => scan patch for keywords (default: $keywords)
762 --sections => print all of the subsystem sections with pattern matches
763 --mailmap => use .mailmap file (default: $email_use_mailmap)
764 --version => show version
765 --help => show this help information
768 [--email --nogit --git-fallback --m --n --l --multiline -pattern-depth=0
769 --remove-duplicates --rolestats]
772 Using "-f directory" may give unexpected results:
773 Used with "--git", git signators for _all_ files in and below
774 directory are examined as git recurses directories.
775 Any specified X: (exclude) pattern matches are _not_ ignored.
776 Used with "--nogit", directory is used as a pattern match,
777 no individual file within the directory or subdirectory
779 Used with "--git-blame", does not iterate all files in directory
780 Using "--git-blame" is slow and may add old committers and authors
781 that are no longer active maintainers to the output.
782 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
783 other automated tools that expect only ["name"] <email address>
784 may not work because of additional output after <email address>.
785 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
786 not the percentage of the entire file authored. # of commits is
787 not a good measure of amount of code authored. 1 major commit may
788 contain a thousand lines, 5 trivial commits may modify a single line.
789 If git is not installed, but mercurial (hg) is installed and an .hg
790 repository exists, the following options apply to mercurial:
792 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
794 Use --hg-since not --git-since to control date selection
795 File ".get_maintainer.conf", if it exists in the linux kernel source root
796 directory, can change whatever get_maintainer defaults are desired.
797 Entries in this file can be any command line argument.
798 This file is prepended to any additional command line arguments.
799 Multiple lines and # comments are allowed.
803 sub top_of_kernel_tree {
806 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
809 if ( (-f "${lk_path}COPYING")
810 && (-f "${lk_path}CREDITS")
811 && (-f "${lk_path}Kbuild")
812 && (-f "${lk_path}MAINTAINERS")
813 && (-f "${lk_path}Makefile")
814 && (-f "${lk_path}README")
815 && (-d "${lk_path}Documentation")
816 && (-d "${lk_path}arch")
817 && (-d "${lk_path}include")
818 && (-d "${lk_path}drivers")
819 && (-d "${lk_path}fs")
820 && (-d "${lk_path}init")
821 && (-d "${lk_path}ipc")
822 && (-d "${lk_path}kernel")
823 && (-d "${lk_path}lib")
824 && (-d "${lk_path}scripts")) {
831 my ($formatted_email) = @_;
836 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
839 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
841 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
845 $name =~ s/^\s+|\s+$//g;
846 $name =~ s/^\"|\"$//g;
847 $address =~ s/^\s+|\s+$//g;
849 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
850 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
854 return ($name, $address);
858 my ($name, $address, $usename) = @_;
862 $name =~ s/^\s+|\s+$//g;
863 $name =~ s/^\"|\"$//g;
864 $address =~ s/^\s+|\s+$//g;
866 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
867 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
873 $formatted_email = "$address";
875 $formatted_email = "$name <$address>";
878 $formatted_email = $address;
881 return $formatted_email;
884 sub find_first_section {
887 while ($index < @typevalue) {
888 my $tv = $typevalue[$index];
889 if (($tv =~ m/^(\C):\s*(.*)/)) {
898 sub find_starting_index {
902 my $tv = $typevalue[$index];
903 if (!($tv =~ m/^(\C):\s*(.*)/)) {
912 sub find_ending_index {
915 while ($index < @typevalue) {
916 my $tv = $typevalue[$index];
917 if (!($tv =~ m/^(\C):\s*(.*)/)) {
926 sub get_maintainer_role {
930 my $start = find_starting_index($index);
931 my $end = find_ending_index($index);
934 my $subsystem = $typevalue[$start];
935 if (length($subsystem) > 20) {
936 $subsystem = substr($subsystem, 0, 17);
937 $subsystem =~ s/\s*$//;
938 $subsystem = $subsystem . "...";
941 for ($i = $start + 1; $i < $end; $i++) {
942 my $tv = $typevalue[$i];
943 if ($tv =~ m/^(\C):\s*(.*)/) {
953 if ($role eq "supported") {
955 } elsif ($role eq "maintained") {
956 $role = "maintainer";
957 } elsif ($role eq "odd fixes") {
959 } elsif ($role eq "orphan") {
960 $role = "orphan minder";
961 } elsif ($role eq "obsolete") {
962 $role = "obsolete minder";
963 } elsif ($role eq "buried alive in reporters") {
964 $role = "chief penguin";
967 return $role . ":" . $subsystem;
974 my $start = find_starting_index($index);
975 my $end = find_ending_index($index);
977 my $subsystem = $typevalue[$start];
978 if (length($subsystem) > 20) {
979 $subsystem = substr($subsystem, 0, 17);
980 $subsystem =~ s/\s*$//;
981 $subsystem = $subsystem . "...";
984 if ($subsystem eq "THE REST") {
995 my $start = find_starting_index($index);
996 my $end = find_ending_index($index);
998 push(@subsystem, $typevalue[$start]);
1000 for ($i = $start + 1; $i < $end; $i++) {
1001 my $tv = $typevalue[$i];
1002 if ($tv =~ m/^(\C):\s*(.*)/) {
1005 if ($ptype eq "L") {
1006 my $list_address = $pvalue;
1007 my $list_additional = "";
1008 my $list_role = get_list_role($i);
1010 if ($list_role ne "") {
1011 $list_role = ":" . $list_role;
1013 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1015 $list_additional = $2;
1017 if ($list_additional =~ m/subscribers-only/) {
1018 if ($email_subscriber_list) {
1019 if (!$hash_list_to{lc($list_address)}) {
1020 $hash_list_to{lc($list_address)} = 1;
1021 push(@list_to, [$list_address,
1022 "subscriber list${list_role}"]);
1027 if (!$hash_list_to{lc($list_address)}) {
1028 $hash_list_to{lc($list_address)} = 1;
1029 push(@list_to, [$list_address,
1030 "open list${list_role}"]);
1034 } elsif ($ptype eq "M") {
1035 my ($name, $address) = parse_email($pvalue);
1038 my $tv = $typevalue[$i - 1];
1039 if ($tv =~ m/^(\C):\s*(.*)/) {
1042 $pvalue = format_email($name, $address, $email_usename);
1047 if ($email_maintainer) {
1048 my $role = get_maintainer_role($i);
1049 push_email_addresses($pvalue, $role);
1051 } elsif ($ptype eq "T") {
1052 push(@scm, $pvalue);
1053 } elsif ($ptype eq "W") {
1054 push(@web, $pvalue);
1055 } elsif ($ptype eq "S") {
1056 push(@status, $pvalue);
1063 my ($name, $address) = @_;
1065 return 1 if (($name eq "") && ($address eq ""));
1066 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1067 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1072 sub push_email_address {
1073 my ($line, $role) = @_;
1075 my ($name, $address) = parse_email($line);
1077 if ($address eq "") {
1081 if (!$email_remove_duplicates) {
1082 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1083 } elsif (!email_inuse($name, $address)) {
1084 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1085 $email_hash_name{lc($name)}++ if ($name ne "");
1086 $email_hash_address{lc($address)}++;
1092 sub push_email_addresses {
1093 my ($address, $role) = @_;
1095 my @address_list = ();
1097 if (rfc822_valid($address)) {
1098 push_email_address($address, $role);
1099 } elsif (@address_list = rfc822_validlist($address)) {
1100 my $array_count = shift(@address_list);
1101 while (my $entry = shift(@address_list)) {
1102 push_email_address($entry, $role);
1105 if (!push_email_address($address, $role)) {
1106 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1112 my ($line, $role) = @_;
1114 my ($name, $address) = parse_email($line);
1115 my $email = format_email($name, $address, $email_usename);
1117 foreach my $entry (@email_to) {
1118 if ($email_remove_duplicates) {
1119 my ($entry_name, $entry_address) = parse_email($entry->[0]);
1120 if (($name eq $entry_name || $address eq $entry_address)
1121 && ($role eq "" || !($entry->[1] =~ m/$role/))
1123 if ($entry->[1] eq "") {
1124 $entry->[1] = "$role";
1126 $entry->[1] = "$entry->[1],$role";
1130 if ($email eq $entry->[0]
1131 && ($role eq "" || !($entry->[1] =~ m/$role/))
1133 if ($entry->[1] eq "") {
1134 $entry->[1] = "$role";
1136 $entry->[1] = "$entry->[1],$role";
1146 foreach my $path (split(/:/, $ENV{PATH})) {
1147 if (-e "$path/$bin") {
1148 return "$path/$bin";
1158 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1159 if (-e "$path/$conf") {
1160 return "$path/$conf";
1170 my ($name, $address) = parse_email($line);
1171 my $email = format_email($name, $address, 1);
1172 my $real_name = $name;
1173 my $real_address = $address;
1175 if (exists $mailmap->{names}->{$email} ||
1176 exists $mailmap->{addresses}->{$email}) {
1177 if (exists $mailmap->{names}->{$email}) {
1178 $real_name = $mailmap->{names}->{$email};
1180 if (exists $mailmap->{addresses}->{$email}) {
1181 $real_address = $mailmap->{addresses}->{$email};
1184 if (exists $mailmap->{names}->{$address}) {
1185 $real_name = $mailmap->{names}->{$address};
1187 if (exists $mailmap->{addresses}->{$address}) {
1188 $real_address = $mailmap->{addresses}->{$address};
1191 return format_email($real_name, $real_address, 1);
1195 my (@addresses) = @_;
1197 my @mapped_emails = ();
1198 foreach my $line (@addresses) {
1199 push(@mapped_emails, mailmap_email($line));
1201 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1202 return @mapped_emails;
1205 sub merge_by_realname {
1209 foreach my $email (@emails) {
1210 my ($name, $address) = parse_email($email);
1211 if (exists $address_map{$name}) {
1212 $address = $address_map{$name};
1213 $email = format_email($name, $address, 1);
1215 $address_map{$name} = $address;
1220 sub git_execute_cmd {
1224 my $output = `$cmd`;
1225 $output =~ s/^\s*//gm;
1226 @lines = split("\n", $output);
1231 sub hg_execute_cmd {
1235 my $output = `$cmd`;
1236 @lines = split("\n", $output);
1241 sub extract_formatted_signatures {
1242 my (@signature_lines) = @_;
1244 my @type = @signature_lines;
1246 s/\s*(.*):.*/$1/ for (@type);
1249 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1251 ## Reformat email addresses (with names) to avoid badly written signatures
1253 foreach my $signer (@signature_lines) {
1254 $signer = deduplicate_email($signer);
1257 return (\@type, \@signature_lines);
1260 sub vcs_find_signers {
1264 my @signatures = ();
1266 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1268 my $pattern = $VCS_cmds{"commit_pattern"};
1270 $commits = grep(/$pattern/, @lines); # of commits
1272 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1274 return (0, @signatures) if !@signatures;
1276 save_commits_by_author(@lines) if ($interactive);
1277 save_commits_by_signer(@lines) if ($interactive);
1279 if (!$email_git_penguin_chiefs) {
1280 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1283 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1285 return ($commits, @$signers_ref);
1288 sub vcs_find_author {
1292 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1294 if (!$email_git_penguin_chiefs) {
1295 @lines = grep(!/${penguin_chiefs}/i, @lines);
1298 return @lines if !@lines;
1301 foreach my $line (@lines) {
1302 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1304 my ($name, $address) = parse_email($author);
1305 $author = format_email($name, $address, 1);
1306 push(@authors, $author);
1310 save_commits_by_author(@lines) if ($interactive);
1311 save_commits_by_signer(@lines) if ($interactive);
1316 sub vcs_save_commits {
1321 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1323 foreach my $line (@lines) {
1324 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1337 return @commits if (!(-f $file));
1339 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1340 my @all_commits = ();
1342 $cmd = $VCS_cmds{"blame_file_cmd"};
1343 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1344 @all_commits = vcs_save_commits($cmd);
1346 foreach my $file_range_diff (@range) {
1347 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1349 my $diff_start = $2;
1350 my $diff_length = $3;
1351 next if ("$file" ne "$diff_file");
1352 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1353 push(@commits, $all_commits[$i]);
1357 foreach my $file_range_diff (@range) {
1358 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1360 my $diff_start = $2;
1361 my $diff_length = $3;
1362 next if ("$file" ne "$diff_file");
1363 $cmd = $VCS_cmds{"blame_range_cmd"};
1364 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1365 push(@commits, vcs_save_commits($cmd));
1368 $cmd = $VCS_cmds{"blame_file_cmd"};
1369 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1370 @commits = vcs_save_commits($cmd);
1373 foreach my $commit (@commits) {
1374 $commit =~ s/^\^//g;
1380 my $printed_novcs = 0;
1382 %VCS_cmds = %VCS_cmds_git;
1383 return 1 if eval $VCS_cmds{"available"};
1384 %VCS_cmds = %VCS_cmds_hg;
1385 return 2 if eval $VCS_cmds{"available"};
1387 if (!$printed_novcs) {
1388 warn("$P: No supported VCS found. Add --nogit to options?\n");
1389 warn("Using a git repository produces better results.\n");
1390 warn("Try Linus Torvalds' latest git repository using:\n");
1391 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
1399 return $vcs_used == 1;
1403 return $vcs_used == 2;
1406 sub interactive_get_maintainers {
1407 my ($list_ref) = @_;
1408 my @list = @$list_ref;
1417 foreach my $entry (@list) {
1418 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1419 $selected{$count} = 1;
1420 $authored{$count} = 0;
1421 $signed{$count} = 0;
1427 my $print_options = 0;
1432 printf STDERR "\n%1s %2s %-65s",
1433 "*", "#", "email/list and role:stats";
1435 ($email_git_fallback && !$maintained) ||
1437 print STDERR "auth sign";
1440 foreach my $entry (@list) {
1441 my $email = $entry->[0];
1442 my $role = $entry->[1];
1444 $sel = "*" if ($selected{$count});
1445 my $commit_author = $commit_author_hash{$email};
1446 my $commit_signer = $commit_signer_hash{$email};
1449 $authored++ for (@{$commit_author});
1450 $signed++ for (@{$commit_signer});
1451 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1452 printf STDERR "%4d %4d", $authored, $signed
1453 if ($authored > 0 || $signed > 0);
1454 printf STDERR "\n %s\n", $role;
1455 if ($authored{$count}) {
1456 my $commit_author = $commit_author_hash{$email};
1457 foreach my $ref (@{$commit_author}) {
1458 print STDERR " Author: @{$ref}[1]\n";
1461 if ($signed{$count}) {
1462 my $commit_signer = $commit_signer_hash{$email};
1463 foreach my $ref (@{$commit_signer}) {
1464 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1471 my $date_ref = \$email_git_since;
1472 $date_ref = \$email_hg_since if (vcs_is_hg());
1473 if ($print_options) {
1478 Version Control options:
1479 g use git history [$email_git]
1480 gf use git-fallback [$email_git_fallback]
1481 b use git blame [$email_git_blame]
1482 bs use blame signatures [$email_git_blame_signatures]
1483 c# minimum commits [$email_git_min_signatures]
1484 %# min percent [$email_git_min_percent]
1485 d# history to use [$$date_ref]
1486 x# max maintainers [$email_git_max_maintainers]
1487 t all signature types [$email_git_all_signature_types]
1488 m use .mailmap [$email_use_mailmap]
1495 tm toggle maintainers
1496 tg toggle git entries
1497 tl toggle open list entries
1498 ts toggle subscriber list entries
1499 f emails in file [$file_emails]
1500 k keywords in file [$keywords]
1501 r remove duplicates [$email_remove_duplicates]
1502 p# pattern match depth [$pattern_depth]
1506 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1508 my $input = <STDIN>;
1513 my @wish = split(/[, ]+/, $input);
1514 foreach my $nr (@wish) {
1516 my $sel = substr($nr, 0, 1);
1517 my $str = substr($nr, 1);
1519 $val = $1 if $str =~ /^(\d+)$/;
1524 $output_rolestats = 0;
1527 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1528 $selected{$nr - 1} = !$selected{$nr - 1};
1529 } elsif ($sel eq "*" || $sel eq '^') {
1531 $toggle = 1 if ($sel eq '*');
1532 for (my $i = 0; $i < $count; $i++) {
1533 $selected{$i} = $toggle;
1535 } elsif ($sel eq "0") {
1536 for (my $i = 0; $i < $count; $i++) {
1537 $selected{$i} = !$selected{$i};
1539 } elsif ($sel eq "t") {
1540 if (lc($str) eq "m") {
1541 for (my $i = 0; $i < $count; $i++) {
1542 $selected{$i} = !$selected{$i}
1543 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1545 } elsif (lc($str) eq "g") {
1546 for (my $i = 0; $i < $count; $i++) {
1547 $selected{$i} = !$selected{$i}
1548 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1550 } elsif (lc($str) eq "l") {
1551 for (my $i = 0; $i < $count; $i++) {
1552 $selected{$i} = !$selected{$i}
1553 if ($list[$i]->[1] =~ /^(open list)/i);
1555 } elsif (lc($str) eq "s") {
1556 for (my $i = 0; $i < $count; $i++) {
1557 $selected{$i} = !$selected{$i}
1558 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1561 } elsif ($sel eq "a") {
1562 if ($val > 0 && $val <= $count) {
1563 $authored{$val - 1} = !$authored{$val - 1};
1564 } elsif ($str eq '*' || $str eq '^') {
1566 $toggle = 1 if ($str eq '*');
1567 for (my $i = 0; $i < $count; $i++) {
1568 $authored{$i} = $toggle;
1571 } elsif ($sel eq "s") {
1572 if ($val > 0 && $val <= $count) {
1573 $signed{$val - 1} = !$signed{$val - 1};
1574 } elsif ($str eq '*' || $str eq '^') {
1576 $toggle = 1 if ($str eq '*');
1577 for (my $i = 0; $i < $count; $i++) {
1578 $signed{$i} = $toggle;
1581 } elsif ($sel eq "o") {
1584 } elsif ($sel eq "g") {
1586 bool_invert(\$email_git_fallback);
1588 bool_invert(\$email_git);
1591 } elsif ($sel eq "b") {
1593 bool_invert(\$email_git_blame_signatures);
1595 bool_invert(\$email_git_blame);
1598 } elsif ($sel eq "c") {
1600 $email_git_min_signatures = $val;
1603 } elsif ($sel eq "x") {
1605 $email_git_max_maintainers = $val;
1608 } elsif ($sel eq "%") {
1609 if ($str ne "" && $val >= 0) {
1610 $email_git_min_percent = $val;
1613 } elsif ($sel eq "d") {
1615 $email_git_since = $str;
1616 } elsif (vcs_is_hg()) {
1617 $email_hg_since = $str;
1620 } elsif ($sel eq "t") {
1621 bool_invert(\$email_git_all_signature_types);
1623 } elsif ($sel eq "f") {
1624 bool_invert(\$file_emails);
1626 } elsif ($sel eq "r") {
1627 bool_invert(\$email_remove_duplicates);
1629 } elsif ($sel eq "m") {
1630 bool_invert(\$email_use_mailmap);
1633 } elsif ($sel eq "k") {
1634 bool_invert(\$keywords);
1636 } elsif ($sel eq "p") {
1637 if ($str ne "" && $val >= 0) {
1638 $pattern_depth = $val;
1641 } elsif ($sel eq "h" || $sel eq "?") {
1644 Interactive mode allows you to select the various maintainers, submitters,
1645 commit signers and mailing lists that could be CC'd on a patch.
1647 Any *'d entry is selected.
1649 If you have git or hg installed, you can choose to summarize the commit
1650 history of files in the patch. Also, each line of the current file can
1651 be matched to its commit author and that commits signers with blame.
1653 Various knobs exist to control the length of time for active commit
1654 tracking, the maximum number of commit authors and signers to add,
1657 Enter selections at the prompt until you are satisfied that the selected
1658 maintainers are appropriate. You may enter multiple selections separated
1659 by either commas or spaces.
1663 print STDERR "invalid option: '$nr'\n";
1668 print STDERR "git-blame can be very slow, please have patience..."
1669 if ($email_git_blame);
1670 goto &get_maintainers;
1674 #drop not selected entries
1676 my @new_emailto = ();
1677 foreach my $entry (@list) {
1678 if ($selected{$count}) {
1679 push(@new_emailto, $list[$count]);
1683 return @new_emailto;
1687 my ($bool_ref) = @_;
1696 sub deduplicate_email {
1700 my ($name, $address) = parse_email($email);
1701 $email = format_email($name, $address, 1);
1702 $email = mailmap_email($email);
1704 return $email if (!$email_remove_duplicates);
1706 ($name, $address) = parse_email($email);
1708 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1709 $name = $deduplicate_name_hash{lc($name)}->[0];
1710 $address = $deduplicate_name_hash{lc($name)}->[1];
1712 } elsif ($deduplicate_address_hash{lc($address)}) {
1713 $name = $deduplicate_address_hash{lc($address)}->[0];
1714 $address = $deduplicate_address_hash{lc($address)}->[1];
1718 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1719 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1721 $email = format_email($name, $address, 1);
1722 $email = mailmap_email($email);
1726 sub save_commits_by_author {
1733 foreach my $line (@lines) {
1734 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1736 $author = deduplicate_email($author);
1737 push(@authors, $author);
1739 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1740 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1743 for (my $i = 0; $i < @authors; $i++) {
1745 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1746 if (@{$ref}[0] eq $commits[$i] &&
1747 @{$ref}[1] eq $subjects[$i]) {
1753 push(@{$commit_author_hash{$authors[$i]}},
1754 [ ($commits[$i], $subjects[$i]) ]);
1759 sub save_commits_by_signer {
1765 foreach my $line (@lines) {
1766 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1767 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1768 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1769 my @signatures = ($line);
1770 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1771 my @types = @$types_ref;
1772 my @signers = @$signers_ref;
1774 my $type = $types[0];
1775 my $signer = $signers[0];
1777 $signer = deduplicate_email($signer);
1780 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1781 if (@{$ref}[0] eq $commit &&
1782 @{$ref}[1] eq $subject &&
1783 @{$ref}[2] eq $type) {
1789 push(@{$commit_signer_hash{$signer}},
1790 [ ($commit, $subject, $type) ]);
1797 my ($role, $divisor, @lines) = @_;
1802 return if (@lines <= 0);
1804 if ($divisor <= 0) {
1805 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
1809 @lines = mailmap(@lines);
1811 return if (@lines <= 0);
1813 @lines = sort(@lines);
1816 $hash{$_}++ for @lines;
1819 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
1820 my $sign_offs = $hash{$line};
1821 my $percent = $sign_offs * 100 / $divisor;
1823 $percent = 100 if ($percent > 100);
1825 last if ($sign_offs < $email_git_min_signatures ||
1826 $count > $email_git_max_maintainers ||
1827 $percent < $email_git_min_percent);
1828 push_email_address($line, '');
1829 if ($output_rolestats) {
1830 my $fmt_percent = sprintf("%.0f", $percent);
1831 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1833 add_role($line, $role);
1838 sub vcs_file_signoffs {
1844 $vcs_used = vcs_exists();
1845 return if (!$vcs_used);
1847 my $cmd = $VCS_cmds{"find_signers_cmd"};
1848 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1850 ($commits, @signers) = vcs_find_signers($cmd);
1852 foreach my $signer (@signers) {
1853 $signer = deduplicate_email($signer);
1856 vcs_assign("commit_signer", $commits, @signers);
1859 sub vcs_file_blame {
1863 my @all_commits = ();
1868 $vcs_used = vcs_exists();
1869 return if (!$vcs_used);
1871 @all_commits = vcs_blame($file);
1872 @commits = uniq(@all_commits);
1873 $total_commits = @commits;
1874 $total_lines = @all_commits;
1876 if ($email_git_blame_signatures) {
1879 my @commit_signers = ();
1880 my $commit = join(" -r ", @commits);
1883 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1884 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1886 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1888 push(@signers, @commit_signers);
1890 foreach my $commit (@commits) {
1892 my @commit_signers = ();
1895 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1896 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1898 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1900 push(@signers, @commit_signers);
1905 if ($from_filename) {
1906 if ($output_rolestats) {
1908 if (vcs_is_hg()) {{ # Double brace for last exit
1910 my @commit_signers = ();
1911 @commits = uniq(@commits);
1912 @commits = sort(@commits);
1913 my $commit = join(" -r ", @commits);
1916 $cmd = $VCS_cmds{"find_commit_author_cmd"};
1917 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1921 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1923 if (!$email_git_penguin_chiefs) {
1924 @lines = grep(!/${penguin_chiefs}/i, @lines);
1930 foreach my $line (@lines) {
1931 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1933 $author = deduplicate_email($author);
1934 push(@authors, $author);
1938 save_commits_by_author(@lines) if ($interactive);
1939 save_commits_by_signer(@lines) if ($interactive);
1941 push(@signers, @authors);
1944 foreach my $commit (@commits) {
1946 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1947 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1948 my @author = vcs_find_author($cmd);
1951 my $formatted_author = deduplicate_email($author[0]);
1953 my $count = grep(/$commit/, @all_commits);
1954 for ($i = 0; $i < $count ; $i++) {
1955 push(@blame_signers, $formatted_author);
1959 if (@blame_signers) {
1960 vcs_assign("authored lines", $total_lines, @blame_signers);
1963 foreach my $signer (@signers) {
1964 $signer = deduplicate_email($signer);
1966 vcs_assign("commits", $total_commits, @signers);
1968 foreach my $signer (@signers) {
1969 $signer = deduplicate_email($signer);
1971 vcs_assign("modified commits", $total_commits, @signers);
1979 @parms = grep(!$saw{$_}++, @parms);
1987 @parms = sort @parms;
1988 @parms = grep(!$saw{$_}++, @parms);
1992 sub clean_file_emails {
1993 my (@file_emails) = @_;
1994 my @fmt_emails = ();
1996 foreach my $email (@file_emails) {
1997 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
1998 my ($name, $address) = parse_email($email);
1999 if ($name eq '"[,\.]"') {
2003 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2005 my $first = $nw[@nw - 3];
2006 my $middle = $nw[@nw - 2];
2007 my $last = $nw[@nw - 1];
2009 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2010 (length($first) == 2 && substr($first, -1) eq ".")) ||
2011 (length($middle) == 1 ||
2012 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2013 $name = "$first $middle $last";
2015 $name = "$middle $last";
2019 if (substr($name, -1) =~ /[,\.]/) {
2020 $name = substr($name, 0, length($name) - 1);
2021 } elsif (substr($name, -2) =~ /[,\.]"/) {
2022 $name = substr($name, 0, length($name) - 2) . '"';
2025 if (substr($name, 0, 1) =~ /[,\.]/) {
2026 $name = substr($name, 1, length($name) - 1);
2027 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2028 $name = '"' . substr($name, 2, length($name) - 2);
2031 my $fmt_email = format_email($name, $address, $email_usename);
2032 push(@fmt_emails, $fmt_email);
2042 my ($address, $role) = @$_;
2043 if (!$saw{$address}) {
2044 if ($output_roles) {
2045 push(@lines, "$address ($role)");
2047 push(@lines, $address);
2059 if ($output_multiline) {
2060 foreach my $line (@parms) {
2064 print(join($output_separator, @parms));
2072 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2073 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2074 # This regexp will only work on addresses which have had comments stripped
2075 # and replaced with rfc822_lwsp.
2077 my $specials = '()<>@,;:\\\\".\\[\\]';
2078 my $controls = '\\000-\\037\\177';
2080 my $dtext = "[^\\[\\]\\r\\\\]";
2081 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2083 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2085 # Use zero-width assertion to spot the limit of an atom. A simple
2086 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2087 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2088 my $word = "(?:$atom|$quoted_string)";
2089 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2091 my $sub_domain = "(?:$atom|$domain_literal)";
2092 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2094 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2096 my $phrase = "$word*";
2097 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2098 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2099 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2101 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2102 my $address = "(?:$mailbox|$group)";
2104 return "$rfc822_lwsp*$address";
2107 sub rfc822_strip_comments {
2109 # Recursively remove comments, and replace with a single space. The simpler
2110 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2111 # chars in atoms, for example.
2113 while ($s =~ s/^((?:[^"\\]|\\.)*
2114 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2115 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2119 # valid: returns true if the parameter is an RFC822 valid address
2122 my $s = rfc822_strip_comments(shift);
2125 $rfc822re = make_rfc822re();
2128 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2131 # validlist: In scalar context, returns true if the parameter is an RFC822
2132 # valid list of addresses.
2134 # In list context, returns an empty list on failure (an invalid
2135 # address was found); otherwise a list whose first element is the
2136 # number of addresses found and whose remaining elements are the
2137 # addresses. This is needed to disambiguate failure (invalid)
2138 # from success with no addresses found, because an empty string is
2141 sub rfc822_validlist {
2142 my $s = rfc822_strip_comments(shift);
2145 $rfc822re = make_rfc822re();
2147 # * null list items are valid according to the RFC
2148 # * the '1' business is to aid in distinguishing failure from no results
2151 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2152 $s =~ m/^$rfc822_char*$/) {
2153 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2156 return wantarray ? (scalar(@r), @r) : 1;
2158 return wantarray ? () : 0;