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, "Tom Rini:trini\@ti.com");
66 my @penguin_chief_names = ();
67 foreach my $chief (@penguin_chief) {
68 if ($chief =~ m/^(.*):(.*)/) {
71 push(@penguin_chief_names, $chief_name);
74 my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
76 # Signature types of people who are either
77 # a) responsible for the code in question, or
78 # b) familiar enough with it to give relevant feedback
79 my @signature_tags = ();
80 push(@signature_tags, "Signed-off-by:");
81 push(@signature_tags, "Reviewed-by:");
82 push(@signature_tags, "Acked-by:");
84 my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
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 "") && (-e ".git")',
98 "git log --no-color --follow --since=\$email_git_since " .
99 '--numstat --no-merges ' .
100 '--format="GitCommit: %H%n' .
101 'GitAuthor: %an <%ae>%n' .
106 "find_commit_signers_cmd" =>
107 "git log --no-color " .
109 '--format="GitCommit: %H%n' .
110 'GitAuthor: %an <%ae>%n' .
115 "find_commit_author_cmd" =>
116 "git log --no-color " .
118 '--format="GitCommit: %H%n' .
119 'GitAuthor: %an <%ae>%n' .
121 'GitSubject: %s%n"' .
123 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
124 "blame_file_cmd" => "git blame -l \$file",
125 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
126 "blame_commit_pattern" => "^([0-9a-f]+) ",
127 "author_pattern" => "^GitAuthor: (.*)",
128 "subject_pattern" => "^GitSubject: (.*)",
129 "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
133 "execute_cmd" => \&hg_execute_cmd,
134 "available" => '(which("hg") ne "") && (-d ".hg")',
135 "find_signers_cmd" =>
136 "hg log --date=\$email_hg_since " .
137 "--template='HgCommit: {node}\\n" .
138 "HgAuthor: {author}\\n" .
139 "HgSubject: {desc}\\n'" .
141 "find_commit_signers_cmd" =>
143 "--template='HgSubject: {desc}\\n'" .
145 "find_commit_author_cmd" =>
147 "--template='HgCommit: {node}\\n" .
148 "HgAuthor: {author}\\n" .
149 "HgSubject: {desc|firstline}\\n'" .
151 "blame_range_cmd" => "", # not supported
152 "blame_file_cmd" => "hg blame -n \$file",
153 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
154 "blame_commit_pattern" => "^([ 0-9a-f]+):",
155 "author_pattern" => "^HgAuthor: (.*)",
156 "subject_pattern" => "^HgSubject: (.*)",
157 "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$",
160 my $conf = which_conf(".get_maintainer.conf");
163 open(my $conffile, '<', "$conf")
164 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
166 while (<$conffile>) {
169 $line =~ s/\s*\n?$//g;
173 next if ($line =~ m/^\s*#/);
174 next if ($line =~ m/^\s*$/);
176 my @words = split(" ", $line);
177 foreach my $word (@words) {
178 last if ($word =~ m/^#/);
179 push (@conf_args, $word);
183 unshift(@ARGV, @conf_args) if @conf_args;
188 'git!' => \$email_git,
189 'git-all-signature-types!' => \$email_git_all_signature_types,
190 'git-blame!' => \$email_git_blame,
191 'git-blame-signatures!' => \$email_git_blame_signatures,
192 'git-fallback!' => \$email_git_fallback,
193 'git-chief-penguins!' => \$email_git_penguin_chiefs,
194 'git-min-signatures=i' => \$email_git_min_signatures,
195 'git-max-maintainers=i' => \$email_git_max_maintainers,
196 'git-min-percent=i' => \$email_git_min_percent,
197 'git-since=s' => \$email_git_since,
198 'hg-since=s' => \$email_hg_since,
199 'i|interactive!' => \$interactive,
200 'remove-duplicates!' => \$email_remove_duplicates,
201 'mailmap!' => \$email_use_mailmap,
202 'm!' => \$email_maintainer,
203 'n!' => \$email_usename,
204 'l!' => \$email_list,
205 's!' => \$email_subscriber_list,
206 'multiline!' => \$output_multiline,
207 'roles!' => \$output_roles,
208 'rolestats!' => \$output_rolestats,
209 'separator=s' => \$output_separator,
210 'subsystem!' => \$subsystem,
211 'status!' => \$status,
214 'pattern-depth=i' => \$pattern_depth,
215 'k|keywords!' => \$keywords,
216 'sections!' => \$sections,
217 'fe|file-emails!' => \$file_emails,
218 'f|file' => \$from_filename,
219 'v|version' => \$version,
220 'h|help|usage' => \$help,
222 die "$P: invalid argument - use --help if necessary\n";
231 print("${P} ${V}\n");
235 if (-t STDIN && !@ARGV) {
236 # We're talking to a terminal, but have no command line arguments.
237 die "$P: missing patchfile or -f file - use --help if necessary\n";
240 $output_multiline = 0 if ($output_separator ne ", ");
241 $output_rolestats = 1 if ($interactive);
242 $output_roles = 1 if ($output_rolestats);
254 my $selections = $email + $scm + $status + $subsystem + $web;
255 if ($selections == 0) {
256 die "$P: Missing required option: email, scm, status, subsystem or web\n";
261 ($email_maintainer + $email_list + $email_subscriber_list +
262 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
263 die "$P: Please select at least 1 email option\n";
266 if (!top_of_kernel_tree($lk_path)) {
267 die "$P: The current directory does not appear to be "
268 . "a linux kernel source tree.\n";
271 ## Read MAINTAINERS for type/value pairs
276 open (my $maint, '<', "${lk_path}MAINTAINERS")
277 or die "$P: Can't open MAINTAINERS: $!\n";
281 if ($line =~ m/^(\C):\s*(.*)/) {
285 ##Filename pattern matching
286 if ($type eq "F" || $type eq "X") {
287 $value =~ s@\.@\\\.@g; ##Convert . to \.
288 $value =~ s/\*/\.\*/g; ##Convert * to .*
289 $value =~ s/\?/\./g; ##Convert ? to .
290 ##if pattern is a directory and it lacks a trailing slash, add one
292 $value =~ s@([^/])$@$1/@;
294 } elsif ($type eq "K") {
295 $keyword_hash{@typevalue} = $value;
297 push(@typevalue, "$type:$value");
298 } elsif (!/^(\s)*$/) {
300 push(@typevalue, $line);
307 # Read mail address map
320 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
322 open(my $mailmap_file, '<', "${lk_path}.mailmap")
323 or warn "$P: Can't open .mailmap: $!\n";
325 while (<$mailmap_file>) {
326 s/#.*$//; #strip comments
327 s/^\s+|\s+$//g; #trim
329 next if (/^\s*$/); #skip empty lines
330 #entries have one of the following formats:
333 # name1 <mail1> <mail2>
334 # name1 <mail1> name2 <mail2>
335 # (see man git-shortlog)
337 if (/^([^<]+)<([^>]+)>$/) {
341 $real_name =~ s/\s+$//;
342 ($real_name, $address) = parse_email("$real_name <$address>");
343 $mailmap->{names}->{$address} = $real_name;
345 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
346 my $real_address = $1;
347 my $wrong_address = $2;
349 $mailmap->{addresses}->{$wrong_address} = $real_address;
351 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
353 my $real_address = $2;
354 my $wrong_address = $3;
356 $real_name =~ s/\s+$//;
357 ($real_name, $real_address) =
358 parse_email("$real_name <$real_address>");
359 $mailmap->{names}->{$wrong_address} = $real_name;
360 $mailmap->{addresses}->{$wrong_address} = $real_address;
362 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
364 my $real_address = $2;
366 my $wrong_address = $4;
368 $real_name =~ s/\s+$//;
369 ($real_name, $real_address) =
370 parse_email("$real_name <$real_address>");
372 $wrong_name =~ s/\s+$//;
373 ($wrong_name, $wrong_address) =
374 parse_email("$wrong_name <$wrong_address>");
376 my $wrong_email = format_email($wrong_name, $wrong_address, 1);
377 $mailmap->{names}->{$wrong_email} = $real_name;
378 $mailmap->{addresses}->{$wrong_email} = $real_address;
381 close($mailmap_file);
384 ## use the filenames on the command line or find the filenames in the patchfiles
388 my @keyword_tvi = ();
389 my @file_emails = ();
392 push(@ARGV, "&STDIN");
395 foreach my $file (@ARGV) {
396 if ($file ne "&STDIN") {
397 ##if $file is a directory and it lacks a trailing slash, add one
399 $file =~ s@([^/])$@$1/@;
400 } elsif (!(-f $file)) {
401 die "$P: file '${file}' not found\n";
404 if ($from_filename) {
406 if ($file ne "MAINTAINERS" && -f $file && ($keywords || $file_emails)) {
407 open(my $f, '<', $file)
408 or die "$P: Can't open $file: $!\n";
409 my $text = do { local($/) ; <$f> };
412 foreach my $line (keys %keyword_hash) {
413 if ($text =~ m/$keyword_hash{$line}/x) {
414 push(@keyword_tvi, $line);
419 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;
420 push(@file_emails, clean_file_emails(@poss_addr));
424 my $file_cnt = @files;
427 open(my $patch, "< $file")
428 or die "$P: Can't open $file: $!\n";
430 # We can check arbitrary information before the patch
431 # like the commit message, mail headers, etc...
432 # This allows us to match arbitrary keywords against any part
433 # of a git format-patch generated file (subject tags, etc...)
435 my $patch_prefix = ""; #Parsing the intro
439 if (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
441 $filename =~ s@^[^/]*/@@;
443 $lastfile = $filename;
444 push(@files, $filename);
445 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
446 } elsif (m/^\@\@ -(\d+),(\d+)/) {
447 if ($email_git_blame) {
448 push(@range, "$lastfile:$1:$2");
450 } elsif ($keywords) {
451 foreach my $line (keys %keyword_hash) {
452 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
453 push(@keyword_tvi, $line);
460 if ($file_cnt == @files) {
461 warn "$P: file '${file}' doesn't appear to be a patch. "
462 . "Add -f to options?\n";
464 @files = sort_and_uniq(@files);
468 @file_emails = uniq(@file_emails);
471 my %email_hash_address;
479 my %deduplicate_name_hash = ();
480 my %deduplicate_address_hash = ();
482 my @maintainers = get_maintainers();
485 @maintainers = merge_email(@maintainers);
486 output(@maintainers);
495 @status = uniq(@status);
500 @subsystem = uniq(@subsystem);
511 sub range_is_maintained {
512 my ($start, $end) = @_;
514 for (my $i = $start; $i < $end; $i++) {
515 my $line = $typevalue[$i];
516 if ($line =~ m/^(\C):\s*(.*)/) {
520 if ($value =~ /(maintain|support)/i) {
529 sub range_has_maintainer {
530 my ($start, $end) = @_;
532 for (my $i = $start; $i < $end; $i++) {
533 my $line = $typevalue[$i];
534 if ($line =~ m/^(\C):\s*(.*)/) {
545 sub get_maintainers {
546 %email_hash_name = ();
547 %email_hash_address = ();
548 %commit_author_hash = ();
549 %commit_signer_hash = ();
557 %deduplicate_name_hash = ();
558 %deduplicate_address_hash = ();
559 if ($email_git_all_signature_types) {
560 $signature_pattern = "(.+?)[Bb][Yy]:";
562 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
565 # Find responsible parties
567 my %exact_pattern_match_hash = ();
569 foreach my $file (@files) {
572 my $tvi = find_first_section();
573 while ($tvi < @typevalue) {
574 my $start = find_starting_index($tvi);
575 my $end = find_ending_index($tvi);
579 #Do not match excluded file patterns
581 for ($i = $start; $i < $end; $i++) {
582 my $line = $typevalue[$i];
583 if ($line =~ m/^(\C):\s*(.*)/) {
587 if (file_match_pattern($file, $value)) {
596 for ($i = $start; $i < $end; $i++) {
597 my $line = $typevalue[$i];
598 if ($line =~ m/^(\C):\s*(.*)/) {
602 if (file_match_pattern($file, $value)) {
603 my $value_pd = ($value =~ tr@/@@);
604 my $file_pd = ($file =~ tr@/@@);
605 $value_pd++ if (substr($value,-1,1) ne "/");
606 $value_pd = -1 if ($value =~ /^\.\*/);
607 if ($value_pd >= $file_pd &&
608 range_is_maintained($start, $end) &&
609 range_has_maintainer($start, $end)) {
610 $exact_pattern_match_hash{$file} = 1;
612 if ($pattern_depth == 0 ||
613 (($file_pd - $value_pd) < $pattern_depth)) {
614 $hash{$tvi} = $value_pd;
617 } elsif ($type eq 'N') {
618 if ($file =~ m/$value/x) {
628 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
629 add_categories($line);
632 my $start = find_starting_index($line);
633 my $end = find_ending_index($line);
634 for ($i = $start; $i < $end; $i++) {
635 my $line = $typevalue[$i];
636 if ($line =~ /^[FX]:/) { ##Restore file patterns
637 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
638 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
639 $line =~ s/\\\./\./g; ##Convert \. to .
640 $line =~ s/\.\*/\*/g; ##Convert .* to *
642 $line =~ s/^([A-Z]):/$1:\t/g;
651 @keyword_tvi = sort_and_uniq(@keyword_tvi);
652 foreach my $line (@keyword_tvi) {
653 add_categories($line);
657 foreach my $email (@email_to, @list_to) {
658 $email->[0] = deduplicate_email($email->[0]);
661 foreach my $file (@files) {
663 ($email_git || ($email_git_fallback &&
664 !$exact_pattern_match_hash{$file}))) {
665 vcs_file_signoffs($file);
667 if ($email && $email_git_blame) {
668 vcs_file_blame($file);
673 foreach my $chief (@penguin_chief) {
674 if ($chief =~ m/^(.*):(.*)/) {
677 $email_address = format_email($1, $2, $email_usename);
678 if ($email_git_penguin_chiefs) {
679 push(@email_to, [$email_address, 'chief penguin']);
681 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
686 foreach my $email (@file_emails) {
687 my ($name, $address) = parse_email($email);
689 my $tmp_email = format_email($name, $address, $email_usename);
690 push_email_address($tmp_email, '');
691 add_role($tmp_email, 'in file');
696 if ($email || $email_list) {
698 @to = (@to, @email_to);
701 @to = (@to, @list_to);
706 @to = interactive_get_maintainers(\@to);
712 sub file_match_pattern {
713 my ($file, $pattern) = @_;
714 if (substr($pattern, -1) eq "/") {
715 if ($file =~ m@^$pattern@) {
719 if ($file =~ m@^$pattern@) {
720 my $s1 = ($file =~ tr@/@@);
721 my $s2 = ($pattern =~ tr@/@@);
732 usage: $P [options] patchfile
733 $P [options] -f file|directory
736 MAINTAINER field selection options:
737 --email => print email address(es) if any
738 --git => include recent git \*-by: signers
739 --git-all-signature-types => include signers regardless of signature type
740 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
741 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
742 --git-chief-penguins => include ${penguin_chiefs}
743 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
744 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
745 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
746 --git-blame => use git blame to find modified commits for patch or file
747 --git-since => git history to use (default: $email_git_since)
748 --hg-since => hg history to use (default: $email_hg_since)
749 --interactive => display a menu (mostly useful if used with the --git option)
750 --m => include maintainer(s) if any
751 --n => include name 'Full Name <addr\@domain.tld>'
752 --l => include list(s) if any
753 --s => include subscriber only list(s) if any
754 --remove-duplicates => minimize duplicate email names/addresses
755 --roles => show roles (status:subsystem, git-signer, list, etc...)
756 --rolestats => show roles and statistics (commits/total_commits, %)
757 --file-emails => add email addresses found in -f file (default: 0 (off))
758 --scm => print SCM tree(s) if any
759 --status => print status if any
760 --subsystem => print subsystem name if any
761 --web => print website(s) if any
764 --separator [, ] => separator for multiple entries on 1 line
765 using --separator also sets --nomultiline if --separator is not [, ]
766 --multiline => print 1 entry per line
769 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
770 --keywords => scan patch for keywords (default: $keywords)
771 --sections => print all of the subsystem sections with pattern matches
772 --mailmap => use .mailmap file (default: $email_use_mailmap)
773 --version => show version
774 --help => show this help information
777 [--email --nogit --git-fallback --m --n --l --multiline -pattern-depth=0
778 --remove-duplicates --rolestats]
781 Using "-f directory" may give unexpected results:
782 Used with "--git", git signators for _all_ files in and below
783 directory are examined as git recurses directories.
784 Any specified X: (exclude) pattern matches are _not_ ignored.
785 Used with "--nogit", directory is used as a pattern match,
786 no individual file within the directory or subdirectory
788 Used with "--git-blame", does not iterate all files in directory
789 Using "--git-blame" is slow and may add old committers and authors
790 that are no longer active maintainers to the output.
791 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
792 other automated tools that expect only ["name"] <email address>
793 may not work because of additional output after <email address>.
794 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
795 not the percentage of the entire file authored. # of commits is
796 not a good measure of amount of code authored. 1 major commit may
797 contain a thousand lines, 5 trivial commits may modify a single line.
798 If git is not installed, but mercurial (hg) is installed and an .hg
799 repository exists, the following options apply to mercurial:
801 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
803 Use --hg-since not --git-since to control date selection
804 File ".get_maintainer.conf", if it exists in the linux kernel source root
805 directory, can change whatever get_maintainer defaults are desired.
806 Entries in this file can be any command line argument.
807 This file is prepended to any additional command line arguments.
808 Multiple lines and # comments are allowed.
812 sub top_of_kernel_tree {
815 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
818 if ( (-f "${lk_path}CREDITS")
819 && (-f "${lk_path}Kbuild")
820 && (-f "${lk_path}MAINTAINERS")
821 && (-f "${lk_path}Makefile")
822 && (-f "${lk_path}README")
823 && (-d "${lk_path}arch")
824 && (-d "${lk_path}board")
825 && (-d "${lk_path}common")
826 && (-d "${lk_path}doc")
827 && (-d "${lk_path}drivers")
828 && (-d "${lk_path}dts")
829 && (-d "${lk_path}fs")
830 && (-d "${lk_path}lib")
831 && (-d "${lk_path}include")
832 && (-d "${lk_path}net")
833 && (-d "${lk_path}post")
834 && (-d "${lk_path}scripts")
835 && (-d "${lk_path}test")
836 && (-d "${lk_path}tools")) {
843 my ($formatted_email) = @_;
848 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
851 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
853 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
857 $name =~ s/^\s+|\s+$//g;
858 $name =~ s/^\"|\"$//g;
859 $address =~ s/^\s+|\s+$//g;
861 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
862 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
866 return ($name, $address);
870 my ($name, $address, $usename) = @_;
874 $name =~ s/^\s+|\s+$//g;
875 $name =~ s/^\"|\"$//g;
876 $address =~ s/^\s+|\s+$//g;
878 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
879 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
885 $formatted_email = "$address";
887 $formatted_email = "$name <$address>";
890 $formatted_email = $address;
893 return $formatted_email;
896 sub find_first_section {
899 while ($index < @typevalue) {
900 my $tv = $typevalue[$index];
901 if (($tv =~ m/^(\C):\s*(.*)/)) {
910 sub find_starting_index {
914 my $tv = $typevalue[$index];
915 if (!($tv =~ m/^(\C):\s*(.*)/)) {
924 sub find_ending_index {
927 while ($index < @typevalue) {
928 my $tv = $typevalue[$index];
929 if (!($tv =~ m/^(\C):\s*(.*)/)) {
938 sub get_maintainer_role {
942 my $start = find_starting_index($index);
943 my $end = find_ending_index($index);
945 my $role = "unknown";
946 my $subsystem = $typevalue[$start];
947 if (length($subsystem) > 20) {
948 $subsystem = substr($subsystem, 0, 17);
949 $subsystem =~ s/\s*$//;
950 $subsystem = $subsystem . "...";
953 for ($i = $start + 1; $i < $end; $i++) {
954 my $tv = $typevalue[$i];
955 if ($tv =~ m/^(\C):\s*(.*)/) {
965 if ($role eq "supported") {
967 } elsif ($role eq "maintained") {
968 $role = "maintainer";
969 } elsif ($role eq "odd fixes") {
971 } elsif ($role eq "orphan") {
972 $role = "orphan minder";
973 } elsif ($role eq "obsolete") {
974 $role = "obsolete minder";
975 } elsif ($role eq "buried alive in reporters") {
976 $role = "chief penguin";
979 return $role . ":" . $subsystem;
986 my $start = find_starting_index($index);
987 my $end = find_ending_index($index);
989 my $subsystem = $typevalue[$start];
990 if (length($subsystem) > 20) {
991 $subsystem = substr($subsystem, 0, 17);
992 $subsystem =~ s/\s*$//;
993 $subsystem = $subsystem . "...";
996 if ($subsystem eq "THE REST") {
1003 sub add_categories {
1007 my $start = find_starting_index($index);
1008 my $end = find_ending_index($index);
1010 push(@subsystem, $typevalue[$start]);
1012 for ($i = $start + 1; $i < $end; $i++) {
1013 my $tv = $typevalue[$i];
1014 if ($tv =~ m/^(\C):\s*(.*)/) {
1017 if ($ptype eq "L") {
1018 my $list_address = $pvalue;
1019 my $list_additional = "";
1020 my $list_role = get_list_role($i);
1022 if ($list_role ne "") {
1023 $list_role = ":" . $list_role;
1025 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1027 $list_additional = $2;
1029 if ($list_additional =~ m/subscribers-only/) {
1030 if ($email_subscriber_list) {
1031 if (!$hash_list_to{lc($list_address)}) {
1032 $hash_list_to{lc($list_address)} = 1;
1033 push(@list_to, [$list_address,
1034 "subscriber list${list_role}"]);
1039 if (!$hash_list_to{lc($list_address)}) {
1040 $hash_list_to{lc($list_address)} = 1;
1041 if ($list_additional =~ m/moderated/) {
1042 push(@list_to, [$list_address,
1043 "moderated list${list_role}"]);
1045 push(@list_to, [$list_address,
1046 "open list${list_role}"]);
1051 } elsif ($ptype eq "M") {
1052 my ($name, $address) = parse_email($pvalue);
1055 my $tv = $typevalue[$i - 1];
1056 if ($tv =~ m/^(\C):\s*(.*)/) {
1059 $pvalue = format_email($name, $address, $email_usename);
1064 if ($email_maintainer) {
1065 my $role = get_maintainer_role($i);
1066 push_email_addresses($pvalue, $role);
1068 } elsif ($ptype eq "T") {
1069 push(@scm, $pvalue);
1070 } elsif ($ptype eq "W") {
1071 push(@web, $pvalue);
1072 } elsif ($ptype eq "S") {
1073 push(@status, $pvalue);
1080 my ($name, $address) = @_;
1082 return 1 if (($name eq "") && ($address eq ""));
1083 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1084 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1089 sub push_email_address {
1090 my ($line, $role) = @_;
1092 my ($name, $address) = parse_email($line);
1094 if ($address eq "") {
1098 if (!$email_remove_duplicates) {
1099 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1100 } elsif (!email_inuse($name, $address)) {
1101 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1102 $email_hash_name{lc($name)}++ if ($name ne "");
1103 $email_hash_address{lc($address)}++;
1109 sub push_email_addresses {
1110 my ($address, $role) = @_;
1112 my @address_list = ();
1114 if (rfc822_valid($address)) {
1115 push_email_address($address, $role);
1116 } elsif (@address_list = rfc822_validlist($address)) {
1117 my $array_count = shift(@address_list);
1118 while (my $entry = shift(@address_list)) {
1119 push_email_address($entry, $role);
1122 if (!push_email_address($address, $role)) {
1123 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1129 my ($line, $role) = @_;
1131 my ($name, $address) = parse_email($line);
1132 my $email = format_email($name, $address, $email_usename);
1134 foreach my $entry (@email_to) {
1135 if ($email_remove_duplicates) {
1136 my ($entry_name, $entry_address) = parse_email($entry->[0]);
1137 if (($name eq $entry_name || $address eq $entry_address)
1138 && ($role eq "" || !($entry->[1] =~ m/$role/))
1140 if ($entry->[1] eq "") {
1141 $entry->[1] = "$role";
1143 $entry->[1] = "$entry->[1],$role";
1147 if ($email eq $entry->[0]
1148 && ($role eq "" || !($entry->[1] =~ m/$role/))
1150 if ($entry->[1] eq "") {
1151 $entry->[1] = "$role";
1153 $entry->[1] = "$entry->[1],$role";
1163 foreach my $path (split(/:/, $ENV{PATH})) {
1164 if (-e "$path/$bin") {
1165 return "$path/$bin";
1175 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1176 if (-e "$path/$conf") {
1177 return "$path/$conf";
1187 my ($name, $address) = parse_email($line);
1188 my $email = format_email($name, $address, 1);
1189 my $real_name = $name;
1190 my $real_address = $address;
1192 if (exists $mailmap->{names}->{$email} ||
1193 exists $mailmap->{addresses}->{$email}) {
1194 if (exists $mailmap->{names}->{$email}) {
1195 $real_name = $mailmap->{names}->{$email};
1197 if (exists $mailmap->{addresses}->{$email}) {
1198 $real_address = $mailmap->{addresses}->{$email};
1201 if (exists $mailmap->{names}->{$address}) {
1202 $real_name = $mailmap->{names}->{$address};
1204 if (exists $mailmap->{addresses}->{$address}) {
1205 $real_address = $mailmap->{addresses}->{$address};
1208 return format_email($real_name, $real_address, 1);
1212 my (@addresses) = @_;
1214 my @mapped_emails = ();
1215 foreach my $line (@addresses) {
1216 push(@mapped_emails, mailmap_email($line));
1218 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1219 return @mapped_emails;
1222 sub merge_by_realname {
1226 foreach my $email (@emails) {
1227 my ($name, $address) = parse_email($email);
1228 if (exists $address_map{$name}) {
1229 $address = $address_map{$name};
1230 $email = format_email($name, $address, 1);
1232 $address_map{$name} = $address;
1237 sub git_execute_cmd {
1241 my $output = `$cmd`;
1242 $output =~ s/^\s*//gm;
1243 @lines = split("\n", $output);
1248 sub hg_execute_cmd {
1252 my $output = `$cmd`;
1253 @lines = split("\n", $output);
1258 sub extract_formatted_signatures {
1259 my (@signature_lines) = @_;
1261 my @type = @signature_lines;
1263 s/\s*(.*):.*/$1/ for (@type);
1266 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1268 ## Reformat email addresses (with names) to avoid badly written signatures
1270 foreach my $signer (@signature_lines) {
1271 $signer = deduplicate_email($signer);
1274 return (\@type, \@signature_lines);
1277 sub vcs_find_signers {
1278 my ($cmd, $file) = @_;
1281 my @signatures = ();
1285 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1287 my $pattern = $VCS_cmds{"commit_pattern"};
1288 my $author_pattern = $VCS_cmds{"author_pattern"};
1289 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1291 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
1293 $commits = grep(/$pattern/, @lines); # of commits
1295 @authors = grep(/$author_pattern/, @lines);
1296 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1297 @stats = grep(/$stat_pattern/, @lines);
1299 # print("stats: <@stats>\n");
1301 return (0, \@signatures, \@authors, \@stats) if !@signatures;
1303 save_commits_by_author(@lines) if ($interactive);
1304 save_commits_by_signer(@lines) if ($interactive);
1306 if (!$email_git_penguin_chiefs) {
1307 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1310 my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
1311 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1313 return ($commits, $signers_ref, $authors_ref, \@stats);
1316 sub vcs_find_author {
1320 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1322 if (!$email_git_penguin_chiefs) {
1323 @lines = grep(!/${penguin_chiefs}/i, @lines);
1326 return @lines if !@lines;
1329 foreach my $line (@lines) {
1330 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1332 my ($name, $address) = parse_email($author);
1333 $author = format_email($name, $address, 1);
1334 push(@authors, $author);
1338 save_commits_by_author(@lines) if ($interactive);
1339 save_commits_by_signer(@lines) if ($interactive);
1344 sub vcs_save_commits {
1349 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1351 foreach my $line (@lines) {
1352 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1365 return @commits if (!(-f $file));
1367 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1368 my @all_commits = ();
1370 $cmd = $VCS_cmds{"blame_file_cmd"};
1371 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1372 @all_commits = vcs_save_commits($cmd);
1374 foreach my $file_range_diff (@range) {
1375 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1377 my $diff_start = $2;
1378 my $diff_length = $3;
1379 next if ("$file" ne "$diff_file");
1380 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1381 push(@commits, $all_commits[$i]);
1385 foreach my $file_range_diff (@range) {
1386 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1388 my $diff_start = $2;
1389 my $diff_length = $3;
1390 next if ("$file" ne "$diff_file");
1391 $cmd = $VCS_cmds{"blame_range_cmd"};
1392 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1393 push(@commits, vcs_save_commits($cmd));
1396 $cmd = $VCS_cmds{"blame_file_cmd"};
1397 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1398 @commits = vcs_save_commits($cmd);
1401 foreach my $commit (@commits) {
1402 $commit =~ s/^\^//g;
1408 my $printed_novcs = 0;
1410 %VCS_cmds = %VCS_cmds_git;
1411 return 1 if eval $VCS_cmds{"available"};
1412 %VCS_cmds = %VCS_cmds_hg;
1413 return 2 if eval $VCS_cmds{"available"};
1415 if (!$printed_novcs) {
1416 warn("$P: No supported VCS found. Add --nogit to options?\n");
1417 warn("Using a git repository produces better results.\n");
1418 warn("Try Linus Torvalds' latest git repository using:\n");
1419 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
1427 return $vcs_used == 1;
1431 return $vcs_used == 2;
1434 sub interactive_get_maintainers {
1435 my ($list_ref) = @_;
1436 my @list = @$list_ref;
1445 foreach my $entry (@list) {
1446 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1447 $selected{$count} = 1;
1448 $authored{$count} = 0;
1449 $signed{$count} = 0;
1455 my $print_options = 0;
1460 printf STDERR "\n%1s %2s %-65s",
1461 "*", "#", "email/list and role:stats";
1463 ($email_git_fallback && !$maintained) ||
1465 print STDERR "auth sign";
1468 foreach my $entry (@list) {
1469 my $email = $entry->[0];
1470 my $role = $entry->[1];
1472 $sel = "*" if ($selected{$count});
1473 my $commit_author = $commit_author_hash{$email};
1474 my $commit_signer = $commit_signer_hash{$email};
1477 $authored++ for (@{$commit_author});
1478 $signed++ for (@{$commit_signer});
1479 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1480 printf STDERR "%4d %4d", $authored, $signed
1481 if ($authored > 0 || $signed > 0);
1482 printf STDERR "\n %s\n", $role;
1483 if ($authored{$count}) {
1484 my $commit_author = $commit_author_hash{$email};
1485 foreach my $ref (@{$commit_author}) {
1486 print STDERR " Author: @{$ref}[1]\n";
1489 if ($signed{$count}) {
1490 my $commit_signer = $commit_signer_hash{$email};
1491 foreach my $ref (@{$commit_signer}) {
1492 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1499 my $date_ref = \$email_git_since;
1500 $date_ref = \$email_hg_since if (vcs_is_hg());
1501 if ($print_options) {
1506 Version Control options:
1507 g use git history [$email_git]
1508 gf use git-fallback [$email_git_fallback]
1509 b use git blame [$email_git_blame]
1510 bs use blame signatures [$email_git_blame_signatures]
1511 c# minimum commits [$email_git_min_signatures]
1512 %# min percent [$email_git_min_percent]
1513 d# history to use [$$date_ref]
1514 x# max maintainers [$email_git_max_maintainers]
1515 t all signature types [$email_git_all_signature_types]
1516 m use .mailmap [$email_use_mailmap]
1523 tm toggle maintainers
1524 tg toggle git entries
1525 tl toggle open list entries
1526 ts toggle subscriber list entries
1527 f emails in file [$file_emails]
1528 k keywords in file [$keywords]
1529 r remove duplicates [$email_remove_duplicates]
1530 p# pattern match depth [$pattern_depth]
1534 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1536 my $input = <STDIN>;
1541 my @wish = split(/[, ]+/, $input);
1542 foreach my $nr (@wish) {
1544 my $sel = substr($nr, 0, 1);
1545 my $str = substr($nr, 1);
1547 $val = $1 if $str =~ /^(\d+)$/;
1552 $output_rolestats = 0;
1555 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1556 $selected{$nr - 1} = !$selected{$nr - 1};
1557 } elsif ($sel eq "*" || $sel eq '^') {
1559 $toggle = 1 if ($sel eq '*');
1560 for (my $i = 0; $i < $count; $i++) {
1561 $selected{$i} = $toggle;
1563 } elsif ($sel eq "0") {
1564 for (my $i = 0; $i < $count; $i++) {
1565 $selected{$i} = !$selected{$i};
1567 } elsif ($sel eq "t") {
1568 if (lc($str) eq "m") {
1569 for (my $i = 0; $i < $count; $i++) {
1570 $selected{$i} = !$selected{$i}
1571 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1573 } elsif (lc($str) eq "g") {
1574 for (my $i = 0; $i < $count; $i++) {
1575 $selected{$i} = !$selected{$i}
1576 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1578 } elsif (lc($str) eq "l") {
1579 for (my $i = 0; $i < $count; $i++) {
1580 $selected{$i} = !$selected{$i}
1581 if ($list[$i]->[1] =~ /^(open list)/i);
1583 } elsif (lc($str) eq "s") {
1584 for (my $i = 0; $i < $count; $i++) {
1585 $selected{$i} = !$selected{$i}
1586 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1589 } elsif ($sel eq "a") {
1590 if ($val > 0 && $val <= $count) {
1591 $authored{$val - 1} = !$authored{$val - 1};
1592 } elsif ($str eq '*' || $str eq '^') {
1594 $toggle = 1 if ($str eq '*');
1595 for (my $i = 0; $i < $count; $i++) {
1596 $authored{$i} = $toggle;
1599 } elsif ($sel eq "s") {
1600 if ($val > 0 && $val <= $count) {
1601 $signed{$val - 1} = !$signed{$val - 1};
1602 } elsif ($str eq '*' || $str eq '^') {
1604 $toggle = 1 if ($str eq '*');
1605 for (my $i = 0; $i < $count; $i++) {
1606 $signed{$i} = $toggle;
1609 } elsif ($sel eq "o") {
1612 } elsif ($sel eq "g") {
1614 bool_invert(\$email_git_fallback);
1616 bool_invert(\$email_git);
1619 } elsif ($sel eq "b") {
1621 bool_invert(\$email_git_blame_signatures);
1623 bool_invert(\$email_git_blame);
1626 } elsif ($sel eq "c") {
1628 $email_git_min_signatures = $val;
1631 } elsif ($sel eq "x") {
1633 $email_git_max_maintainers = $val;
1636 } elsif ($sel eq "%") {
1637 if ($str ne "" && $val >= 0) {
1638 $email_git_min_percent = $val;
1641 } elsif ($sel eq "d") {
1643 $email_git_since = $str;
1644 } elsif (vcs_is_hg()) {
1645 $email_hg_since = $str;
1648 } elsif ($sel eq "t") {
1649 bool_invert(\$email_git_all_signature_types);
1651 } elsif ($sel eq "f") {
1652 bool_invert(\$file_emails);
1654 } elsif ($sel eq "r") {
1655 bool_invert(\$email_remove_duplicates);
1657 } elsif ($sel eq "m") {
1658 bool_invert(\$email_use_mailmap);
1661 } elsif ($sel eq "k") {
1662 bool_invert(\$keywords);
1664 } elsif ($sel eq "p") {
1665 if ($str ne "" && $val >= 0) {
1666 $pattern_depth = $val;
1669 } elsif ($sel eq "h" || $sel eq "?") {
1672 Interactive mode allows you to select the various maintainers, submitters,
1673 commit signers and mailing lists that could be CC'd on a patch.
1675 Any *'d entry is selected.
1677 If you have git or hg installed, you can choose to summarize the commit
1678 history of files in the patch. Also, each line of the current file can
1679 be matched to its commit author and that commits signers with blame.
1681 Various knobs exist to control the length of time for active commit
1682 tracking, the maximum number of commit authors and signers to add,
1685 Enter selections at the prompt until you are satisfied that the selected
1686 maintainers are appropriate. You may enter multiple selections separated
1687 by either commas or spaces.
1691 print STDERR "invalid option: '$nr'\n";
1696 print STDERR "git-blame can be very slow, please have patience..."
1697 if ($email_git_blame);
1698 goto &get_maintainers;
1702 #drop not selected entries
1704 my @new_emailto = ();
1705 foreach my $entry (@list) {
1706 if ($selected{$count}) {
1707 push(@new_emailto, $list[$count]);
1711 return @new_emailto;
1715 my ($bool_ref) = @_;
1724 sub deduplicate_email {
1728 my ($name, $address) = parse_email($email);
1729 $email = format_email($name, $address, 1);
1730 $email = mailmap_email($email);
1732 return $email if (!$email_remove_duplicates);
1734 ($name, $address) = parse_email($email);
1736 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1737 $name = $deduplicate_name_hash{lc($name)}->[0];
1738 $address = $deduplicate_name_hash{lc($name)}->[1];
1740 } elsif ($deduplicate_address_hash{lc($address)}) {
1741 $name = $deduplicate_address_hash{lc($address)}->[0];
1742 $address = $deduplicate_address_hash{lc($address)}->[1];
1746 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1747 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1749 $email = format_email($name, $address, 1);
1750 $email = mailmap_email($email);
1754 sub save_commits_by_author {
1761 foreach my $line (@lines) {
1762 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1764 $author = deduplicate_email($author);
1765 push(@authors, $author);
1767 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1768 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1771 for (my $i = 0; $i < @authors; $i++) {
1773 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1774 if (@{$ref}[0] eq $commits[$i] &&
1775 @{$ref}[1] eq $subjects[$i]) {
1781 push(@{$commit_author_hash{$authors[$i]}},
1782 [ ($commits[$i], $subjects[$i]) ]);
1787 sub save_commits_by_signer {
1793 foreach my $line (@lines) {
1794 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1795 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1796 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1797 my @signatures = ($line);
1798 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1799 my @types = @$types_ref;
1800 my @signers = @$signers_ref;
1802 my $type = $types[0];
1803 my $signer = $signers[0];
1805 $signer = deduplicate_email($signer);
1808 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1809 if (@{$ref}[0] eq $commit &&
1810 @{$ref}[1] eq $subject &&
1811 @{$ref}[2] eq $type) {
1817 push(@{$commit_signer_hash{$signer}},
1818 [ ($commit, $subject, $type) ]);
1825 my ($role, $divisor, @lines) = @_;
1830 return if (@lines <= 0);
1832 if ($divisor <= 0) {
1833 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
1837 @lines = mailmap(@lines);
1839 return if (@lines <= 0);
1841 @lines = sort(@lines);
1844 $hash{$_}++ for @lines;
1847 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
1848 my $sign_offs = $hash{$line};
1849 my $percent = $sign_offs * 100 / $divisor;
1851 $percent = 100 if ($percent > 100);
1853 last if ($sign_offs < $email_git_min_signatures ||
1854 $count > $email_git_max_maintainers ||
1855 $percent < $email_git_min_percent);
1856 push_email_address($line, '');
1857 if ($output_rolestats) {
1858 my $fmt_percent = sprintf("%.0f", $percent);
1859 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1861 add_role($line, $role);
1866 sub vcs_file_signoffs {
1877 $vcs_used = vcs_exists();
1878 return if (!$vcs_used);
1880 my $cmd = $VCS_cmds{"find_signers_cmd"};
1881 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1883 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
1885 @signers = @{$signers_ref} if defined $signers_ref;
1886 @authors = @{$authors_ref} if defined $authors_ref;
1887 @stats = @{$stats_ref} if defined $stats_ref;
1889 # print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
1891 foreach my $signer (@signers) {
1892 $signer = deduplicate_email($signer);
1895 vcs_assign("commit_signer", $commits, @signers);
1896 vcs_assign("authored", $commits, @authors);
1897 if ($#authors == $#stats) {
1898 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1899 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
1903 for (my $i = 0; $i <= $#stats; $i++) {
1904 if ($stats[$i] =~ /$stat_pattern/) {
1909 my @tmp_authors = uniq(@authors);
1910 foreach my $author (@tmp_authors) {
1911 $author = deduplicate_email($author);
1913 @tmp_authors = uniq(@tmp_authors);
1914 my @list_added = ();
1915 my @list_deleted = ();
1916 foreach my $author (@tmp_authors) {
1918 my $auth_deleted = 0;
1919 for (my $i = 0; $i <= $#stats; $i++) {
1920 if ($author eq deduplicate_email($authors[$i]) &&
1921 $stats[$i] =~ /$stat_pattern/) {
1923 $auth_deleted += $2;
1926 for (my $i = 0; $i < $auth_added; $i++) {
1927 push(@list_added, $author);
1929 for (my $i = 0; $i < $auth_deleted; $i++) {
1930 push(@list_deleted, $author);
1933 vcs_assign("added_lines", $added, @list_added);
1934 vcs_assign("removed_lines", $deleted, @list_deleted);
1938 sub vcs_file_blame {
1942 my @all_commits = ();
1947 $vcs_used = vcs_exists();
1948 return if (!$vcs_used);
1950 @all_commits = vcs_blame($file);
1951 @commits = uniq(@all_commits);
1952 $total_commits = @commits;
1953 $total_lines = @all_commits;
1955 if ($email_git_blame_signatures) {
1958 my $commit_authors_ref;
1959 my $commit_signers_ref;
1961 my @commit_authors = ();
1962 my @commit_signers = ();
1963 my $commit = join(" -r ", @commits);
1966 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1967 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1969 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
1970 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
1971 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
1973 push(@signers, @commit_signers);
1975 foreach my $commit (@commits) {
1977 my $commit_authors_ref;
1978 my $commit_signers_ref;
1980 my @commit_authors = ();
1981 my @commit_signers = ();
1984 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1985 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1987 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
1988 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
1989 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
1991 push(@signers, @commit_signers);
1996 if ($from_filename) {
1997 if ($output_rolestats) {
1999 if (vcs_is_hg()) {{ # Double brace for last exit
2001 my @commit_signers = ();
2002 @commits = uniq(@commits);
2003 @commits = sort(@commits);
2004 my $commit = join(" -r ", @commits);
2007 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2008 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2012 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2014 if (!$email_git_penguin_chiefs) {
2015 @lines = grep(!/${penguin_chiefs}/i, @lines);
2021 foreach my $line (@lines) {
2022 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2024 $author = deduplicate_email($author);
2025 push(@authors, $author);
2029 save_commits_by_author(@lines) if ($interactive);
2030 save_commits_by_signer(@lines) if ($interactive);
2032 push(@signers, @authors);
2035 foreach my $commit (@commits) {
2037 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2038 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2039 my @author = vcs_find_author($cmd);
2042 my $formatted_author = deduplicate_email($author[0]);
2044 my $count = grep(/$commit/, @all_commits);
2045 for ($i = 0; $i < $count ; $i++) {
2046 push(@blame_signers, $formatted_author);
2050 if (@blame_signers) {
2051 vcs_assign("authored lines", $total_lines, @blame_signers);
2054 foreach my $signer (@signers) {
2055 $signer = deduplicate_email($signer);
2057 vcs_assign("commits", $total_commits, @signers);
2059 foreach my $signer (@signers) {
2060 $signer = deduplicate_email($signer);
2062 vcs_assign("modified commits", $total_commits, @signers);
2070 @parms = grep(!$saw{$_}++, @parms);
2078 @parms = sort @parms;
2079 @parms = grep(!$saw{$_}++, @parms);
2083 sub clean_file_emails {
2084 my (@file_emails) = @_;
2085 my @fmt_emails = ();
2087 foreach my $email (@file_emails) {
2088 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2089 my ($name, $address) = parse_email($email);
2090 if ($name eq '"[,\.]"') {
2094 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2096 my $first = $nw[@nw - 3];
2097 my $middle = $nw[@nw - 2];
2098 my $last = $nw[@nw - 1];
2100 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2101 (length($first) == 2 && substr($first, -1) eq ".")) ||
2102 (length($middle) == 1 ||
2103 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2104 $name = "$first $middle $last";
2106 $name = "$middle $last";
2110 if (substr($name, -1) =~ /[,\.]/) {
2111 $name = substr($name, 0, length($name) - 1);
2112 } elsif (substr($name, -2) =~ /[,\.]"/) {
2113 $name = substr($name, 0, length($name) - 2) . '"';
2116 if (substr($name, 0, 1) =~ /[,\.]/) {
2117 $name = substr($name, 1, length($name) - 1);
2118 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2119 $name = '"' . substr($name, 2, length($name) - 2);
2122 my $fmt_email = format_email($name, $address, $email_usename);
2123 push(@fmt_emails, $fmt_email);
2133 my ($address, $role) = @$_;
2134 if (!$saw{$address}) {
2135 if ($output_roles) {
2136 push(@lines, "$address ($role)");
2138 push(@lines, $address);
2150 if ($output_multiline) {
2151 foreach my $line (@parms) {
2155 print(join($output_separator, @parms));
2163 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2164 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2165 # This regexp will only work on addresses which have had comments stripped
2166 # and replaced with rfc822_lwsp.
2168 my $specials = '()<>@,;:\\\\".\\[\\]';
2169 my $controls = '\\000-\\037\\177';
2171 my $dtext = "[^\\[\\]\\r\\\\]";
2172 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2174 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2176 # Use zero-width assertion to spot the limit of an atom. A simple
2177 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2178 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2179 my $word = "(?:$atom|$quoted_string)";
2180 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2182 my $sub_domain = "(?:$atom|$domain_literal)";
2183 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2185 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2187 my $phrase = "$word*";
2188 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2189 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2190 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2192 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2193 my $address = "(?:$mailbox|$group)";
2195 return "$rfc822_lwsp*$address";
2198 sub rfc822_strip_comments {
2200 # Recursively remove comments, and replace with a single space. The simpler
2201 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2202 # chars in atoms, for example.
2204 while ($s =~ s/^((?:[^"\\]|\\.)*
2205 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2206 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2210 # valid: returns true if the parameter is an RFC822 valid address
2213 my $s = rfc822_strip_comments(shift);
2216 $rfc822re = make_rfc822re();
2219 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2222 # validlist: In scalar context, returns true if the parameter is an RFC822
2223 # valid list of addresses.
2225 # In list context, returns an empty list on failure (an invalid
2226 # address was found); otherwise a list whose first element is the
2227 # number of addresses found and whose remaining elements are the
2228 # addresses. This is needed to disambiguate failure (invalid)
2229 # from success with no addresses found, because an empty string is
2232 sub rfc822_validlist {
2233 my $s = rfc822_strip_comments(shift);
2236 $rfc822re = make_rfc822re();
2238 # * null list items are valid according to the RFC
2239 # * the '1' business is to aid in distinguishing failure from no results
2242 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2243 $s =~ m/^$rfc822_char*$/) {
2244 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2247 return wantarray ? (scalar(@r), @r) : 1;
2249 return wantarray ? () : 0;