2 # (c) 2007, Joe Perches <joe@perches.com>
3 # created from checkpatch.pl
5 # Print selected MAINTAINERS information for
6 # the files modified in a patch or for a file
8 # usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
9 # perl scripts/get_maintainer.pl [OPTIONS] -f <file>
11 # Licensed under the terms of the GNU GPL License version 2
19 use Getopt::Long qw(:config no_auto_abbrev);
23 my $cur_path = fastgetcwd() . '/';
26 my $email_usename = 1;
27 my $email_maintainer = 1;
28 my $email_reviewer = 1;
30 my $email_subscriber_list = 0;
31 my $email_git_penguin_chiefs = 0;
33 my $email_git_all_signature_types = 0;
34 my $email_git_blame = 0;
35 my $email_git_blame_signatures = 1;
36 my $email_git_fallback = 1;
37 my $email_git_min_signatures = 1;
38 my $email_git_max_maintainers = 5;
39 my $email_git_min_percent = 5;
40 my $email_git_since = "1-year-ago";
41 my $email_hg_since = "-365";
43 my $email_remove_duplicates = 1;
44 my $email_use_mailmap = 1;
45 my $output_multiline = 1;
46 my $output_separator = ", ";
48 my $output_rolestats = 1;
49 my $output_section_maxlen = 50;
58 my $from_filename = 0;
59 my $pattern_depth = 0;
62 my $find_maintainer_files = 1;
68 my %commit_author_hash;
69 my %commit_signer_hash;
71 my @penguin_chief = ();
72 push(@penguin_chief, "Tom Rini:trini\@konsulko.com");
74 my @penguin_chief_names = ();
75 foreach my $chief (@penguin_chief) {
76 if ($chief =~ m/^(.*):(.*)/) {
79 push(@penguin_chief_names, $chief_name);
82 my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
84 # Signature types of people who are either
85 # a) responsible for the code in question, or
86 # b) familiar enough with it to give relevant feedback
87 my @signature_tags = ();
88 push(@signature_tags, "Signed-off-by:");
89 push(@signature_tags, "Reviewed-by:");
90 push(@signature_tags, "Acked-by:");
92 my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
94 # rfc822 email address - preloaded methods go here.
95 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
96 my $rfc822_char = '[\\000-\\377]';
98 # VCS command support: class-like functions and strings
103 "execute_cmd" => \&git_execute_cmd,
104 "available" => '(which("git") ne "") && (-e ".git")',
105 "find_signers_cmd" =>
106 "git log --no-color --follow --since=\$email_git_since " .
107 '--numstat --no-merges ' .
108 '--format="GitCommit: %H%n' .
109 'GitAuthor: %an <%ae>%n' .
114 "find_commit_signers_cmd" =>
115 "git log --no-color " .
117 '--format="GitCommit: %H%n' .
118 'GitAuthor: %an <%ae>%n' .
123 "find_commit_author_cmd" =>
124 "git log --no-color " .
126 '--format="GitCommit: %H%n' .
127 'GitAuthor: %an <%ae>%n' .
129 'GitSubject: %s%n"' .
131 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
132 "blame_file_cmd" => "git blame -l \$file",
133 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
134 "blame_commit_pattern" => "^([0-9a-f]+) ",
135 "author_pattern" => "^GitAuthor: (.*)",
136 "subject_pattern" => "^GitSubject: (.*)",
137 "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
138 "file_exists_cmd" => "git ls-files \$file",
142 "execute_cmd" => \&hg_execute_cmd,
143 "available" => '(which("hg") ne "") && (-d ".hg")',
144 "find_signers_cmd" =>
145 "hg log --date=\$email_hg_since " .
146 "--template='HgCommit: {node}\\n" .
147 "HgAuthor: {author}\\n" .
148 "HgSubject: {desc}\\n'" .
150 "find_commit_signers_cmd" =>
152 "--template='HgSubject: {desc}\\n'" .
154 "find_commit_author_cmd" =>
156 "--template='HgCommit: {node}\\n" .
157 "HgAuthor: {author}\\n" .
158 "HgSubject: {desc|firstline}\\n'" .
160 "blame_range_cmd" => "", # not supported
161 "blame_file_cmd" => "hg blame -n \$file",
162 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
163 "blame_commit_pattern" => "^([ 0-9a-f]+):",
164 "author_pattern" => "^HgAuthor: (.*)",
165 "subject_pattern" => "^HgSubject: (.*)",
166 "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$",
167 "file_exists_cmd" => "hg files \$file",
170 my $conf = which_conf(".get_maintainer.conf");
173 open(my $conffile, '<', "$conf")
174 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
176 while (<$conffile>) {
179 $line =~ s/\s*\n?$//g;
183 next if ($line =~ m/^\s*#/);
184 next if ($line =~ m/^\s*$/);
186 my @words = split(" ", $line);
187 foreach my $word (@words) {
188 last if ($word =~ m/^#/);
189 push (@conf_args, $word);
193 unshift(@ARGV, @conf_args) if @conf_args;
196 my @ignore_emails = ();
197 my $ignore_file = which_conf(".get_maintainer.ignore");
198 if (-f $ignore_file) {
199 open(my $ignore, '<', "$ignore_file")
200 or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n";
204 $line =~ s/\s*\n?$//;
209 next if ($line =~ m/^\s*$/);
210 if (rfc822_valid($line)) {
211 push(@ignore_emails, $line);
219 'git!' => \$email_git,
220 'git-all-signature-types!' => \$email_git_all_signature_types,
221 'git-blame!' => \$email_git_blame,
222 'git-blame-signatures!' => \$email_git_blame_signatures,
223 'git-fallback!' => \$email_git_fallback,
224 'git-chief-penguins!' => \$email_git_penguin_chiefs,
225 'git-min-signatures=i' => \$email_git_min_signatures,
226 'git-max-maintainers=i' => \$email_git_max_maintainers,
227 'git-min-percent=i' => \$email_git_min_percent,
228 'git-since=s' => \$email_git_since,
229 'hg-since=s' => \$email_hg_since,
230 'i|interactive!' => \$interactive,
231 'remove-duplicates!' => \$email_remove_duplicates,
232 'mailmap!' => \$email_use_mailmap,
233 'm!' => \$email_maintainer,
234 'r!' => \$email_reviewer,
235 'n!' => \$email_usename,
236 'l!' => \$email_list,
237 's!' => \$email_subscriber_list,
238 'multiline!' => \$output_multiline,
239 'roles!' => \$output_roles,
240 'rolestats!' => \$output_rolestats,
241 'separator=s' => \$output_separator,
242 'subsystem!' => \$subsystem,
243 'status!' => \$status,
246 'letters=s' => \$letters,
247 'pattern-depth=i' => \$pattern_depth,
248 'k|keywords!' => \$keywords,
249 'sections!' => \$sections,
250 'fe|file-emails!' => \$file_emails,
251 'f|file' => \$from_filename,
252 'find-maintainer-files' => \$find_maintainer_files,
253 'v|version' => \$version,
254 'h|help|usage' => \$help,
256 die "$P: invalid argument - use --help if necessary\n";
265 print("${P} ${V}\n");
269 if (-t STDIN && !@ARGV) {
270 # We're talking to a terminal, but have no command line arguments.
271 die "$P: missing patchfile or -f file - use --help if necessary\n";
274 $output_multiline = 0 if ($output_separator ne ", ");
275 $output_rolestats = 1 if ($interactive);
276 $output_roles = 1 if ($output_rolestats);
278 if ($sections || $letters ne "") {
289 my $selections = $email + $scm + $status + $subsystem + $web;
290 if ($selections == 0) {
291 die "$P: Missing required option: email, scm, status, subsystem or web\n";
296 ($email_maintainer + $email_reviewer +
297 $email_list + $email_subscriber_list +
298 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
299 die "$P: Please select at least 1 email option\n";
302 if (!top_of_kernel_tree($lk_path)) {
303 die "$P: The current directory does not appear to be "
304 . "a U-Boot source tree.\n";
307 ## Read MAINTAINERS for type/value pairs
313 sub read_maintainer_file {
316 open (my $maint, '<', "$file")
317 or die "$P: Can't open MAINTAINERS file '$file': $!\n";
321 if ($line =~ m/^([A-Z]):\s*(.*)/) {
325 ##Filename pattern matching
326 if ($type eq "F" || $type eq "X") {
327 $value =~ s@\.@\\\.@g; ##Convert . to \.
328 $value =~ s/\*/\.\*/g; ##Convert * to .*
329 $value =~ s/\?/\./g; ##Convert ? to .
330 ##if pattern is a directory and it lacks a trailing slash, add one
332 $value =~ s@([^/])$@$1/@;
334 } elsif ($type eq "K") {
335 $keyword_hash{@typevalue} = $value;
337 push(@typevalue, "$type:$value");
338 } elsif (!(/^\s*$/ || /^\s*\#/)) {
340 push(@typevalue, $line);
346 sub find_is_maintainer_file {
348 return if ($file !~ m@/MAINTAINERS$@);
349 $file = $File::Find::name;
350 return if (! -f $file);
351 push(@mfiles, $file);
354 sub find_ignore_git {
355 return grep { $_ !~ /^\.git$/; } @_;
358 if (-d "${lk_path}MAINTAINERS") {
359 opendir(DIR, "${lk_path}MAINTAINERS") or die $!;
360 my @files = readdir(DIR);
362 foreach my $file (@files) {
363 push(@mfiles, "${lk_path}MAINTAINERS/$file") if ($file !~ /^\./);
367 if ($find_maintainer_files) {
368 find( { wanted => \&find_is_maintainer_file,
369 preprocess => \&find_ignore_git,
373 push(@mfiles, "${lk_path}MAINTAINERS") if -f "${lk_path}MAINTAINERS";
376 foreach my $file (@mfiles) {
377 read_maintainer_file("$file");
381 # Read mail address map
394 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
396 open(my $mailmap_file, '<', "${lk_path}.mailmap")
397 or warn "$P: Can't open .mailmap: $!\n";
399 while (<$mailmap_file>) {
400 s/#.*$//; #strip comments
401 s/^\s+|\s+$//g; #trim
403 next if (/^\s*$/); #skip empty lines
404 #entries have one of the following formats:
407 # name1 <mail1> <mail2>
408 # name1 <mail1> name2 <mail2>
409 # (see man git-shortlog)
411 if (/^([^<]+)<([^>]+)>$/) {
415 $real_name =~ s/\s+$//;
416 ($real_name, $address) = parse_email("$real_name <$address>");
417 $mailmap->{names}->{$address} = $real_name;
419 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
420 my $real_address = $1;
421 my $wrong_address = $2;
423 $mailmap->{addresses}->{$wrong_address} = $real_address;
425 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
427 my $real_address = $2;
428 my $wrong_address = $3;
430 $real_name =~ s/\s+$//;
431 ($real_name, $real_address) =
432 parse_email("$real_name <$real_address>");
433 $mailmap->{names}->{$wrong_address} = $real_name;
434 $mailmap->{addresses}->{$wrong_address} = $real_address;
436 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
438 my $real_address = $2;
440 my $wrong_address = $4;
442 $real_name =~ s/\s+$//;
443 ($real_name, $real_address) =
444 parse_email("$real_name <$real_address>");
446 $wrong_name =~ s/\s+$//;
447 ($wrong_name, $wrong_address) =
448 parse_email("$wrong_name <$wrong_address>");
450 my $wrong_email = format_email($wrong_name, $wrong_address, 1);
451 $mailmap->{names}->{$wrong_email} = $real_name;
452 $mailmap->{addresses}->{$wrong_email} = $real_address;
455 close($mailmap_file);
458 ## use the filenames on the command line or find the filenames in the patchfiles
462 my @keyword_tvi = ();
463 my @file_emails = ();
466 push(@ARGV, "&STDIN");
469 foreach my $file (@ARGV) {
470 if ($file ne "&STDIN") {
471 ##if $file is a directory and it lacks a trailing slash, add one
473 $file =~ s@([^/])$@$1/@;
474 } elsif (!(-f $file)) {
475 die "$P: file '${file}' not found\n";
478 if ($from_filename || ($file ne "&STDIN" && vcs_file_exists($file))) {
479 $file =~ s/^\Q${cur_path}\E//; #strip any absolute path
480 $file =~ s/^\Q${lk_path}\E//; #or the path to the lk tree
482 if ($file ne "MAINTAINERS" && -f $file && ($keywords || $file_emails)) {
483 open(my $f, '<', $file)
484 or die "$P: Can't open $file: $!\n";
485 my $text = do { local($/) ; <$f> };
488 foreach my $line (keys %keyword_hash) {
489 if ($text =~ m/$keyword_hash{$line}/x) {
490 push(@keyword_tvi, $line);
495 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;
496 push(@file_emails, clean_file_emails(@poss_addr));
500 my $file_cnt = @files;
503 open(my $patch, "< $file")
504 or die "$P: Can't open $file: $!\n";
506 # We can check arbitrary information before the patch
507 # like the commit message, mail headers, etc...
508 # This allows us to match arbitrary keywords against any part
509 # of a git format-patch generated file (subject tags, etc...)
511 my $patch_prefix = ""; #Parsing the intro
515 if (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
517 $filename =~ s@^[^/]*/@@;
519 $lastfile = $filename;
520 push(@files, $filename);
521 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
522 } elsif (m/^\@\@ -(\d+),(\d+)/) {
523 if ($email_git_blame) {
524 push(@range, "$lastfile:$1:$2");
526 } elsif ($keywords) {
527 foreach my $line (keys %keyword_hash) {
528 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
529 push(@keyword_tvi, $line);
536 if ($file_cnt == @files) {
537 warn "$P: file '${file}' doesn't appear to be a patch. "
538 . "Add -f to options?\n";
540 @files = sort_and_uniq(@files);
544 @file_emails = uniq(@file_emails);
547 my %email_hash_address;
555 my %deduplicate_name_hash = ();
556 my %deduplicate_address_hash = ();
558 my @maintainers = get_maintainers();
561 @maintainers = merge_email(@maintainers);
562 output(@maintainers);
571 @status = uniq(@status);
576 @subsystem = uniq(@subsystem);
587 sub ignore_email_address {
590 foreach my $ignore (@ignore_emails) {
591 return 1 if ($ignore eq $address);
597 sub range_is_maintained {
598 my ($start, $end) = @_;
600 for (my $i = $start; $i < $end; $i++) {
601 my $line = $typevalue[$i];
602 if ($line =~ m/^([A-Z]):\s*(.*)/) {
606 if ($value =~ /(maintain|support)/i) {
615 sub range_has_maintainer {
616 my ($start, $end) = @_;
618 for (my $i = $start; $i < $end; $i++) {
619 my $line = $typevalue[$i];
620 if ($line =~ m/^([A-Z]):\s*(.*)/) {
631 sub get_maintainers {
632 %email_hash_name = ();
633 %email_hash_address = ();
634 %commit_author_hash = ();
635 %commit_signer_hash = ();
643 %deduplicate_name_hash = ();
644 %deduplicate_address_hash = ();
645 if ($email_git_all_signature_types) {
646 $signature_pattern = "(.+?)[Bb][Yy]:";
648 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
651 # Find responsible parties
653 my %exact_pattern_match_hash = ();
655 foreach my $file (@files) {
658 my $tvi = find_first_section();
659 while ($tvi < @typevalue) {
660 my $start = find_starting_index($tvi);
661 my $end = find_ending_index($tvi);
665 #Do not match excluded file patterns
667 for ($i = $start; $i < $end; $i++) {
668 my $line = $typevalue[$i];
669 if ($line =~ m/^([A-Z]):\s*(.*)/) {
673 if (file_match_pattern($file, $value)) {
682 for ($i = $start; $i < $end; $i++) {
683 my $line = $typevalue[$i];
684 if ($line =~ m/^([A-Z]):\s*(.*)/) {
688 if (file_match_pattern($file, $value)) {
689 my $value_pd = ($value =~ tr@/@@);
690 my $file_pd = ($file =~ tr@/@@);
691 $value_pd++ if (substr($value,-1,1) ne "/");
692 $value_pd = -1 if ($value =~ /^\.\*/);
693 if ($value_pd >= $file_pd &&
694 range_is_maintained($start, $end) &&
695 range_has_maintainer($start, $end)) {
696 $exact_pattern_match_hash{$file} = 1;
698 if ($pattern_depth == 0 ||
699 (($file_pd - $value_pd) < $pattern_depth)) {
700 $hash{$tvi} = $value_pd;
703 } elsif ($type eq 'N') {
704 if ($file =~ m/$value/x) {
714 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
715 add_categories($line);
718 my $start = find_starting_index($line);
719 my $end = find_ending_index($line);
720 for ($i = $start; $i < $end; $i++) {
721 my $line = $typevalue[$i];
722 if ($line =~ /^[FX]:/) { ##Restore file patterns
723 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
724 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
725 $line =~ s/\\\./\./g; ##Convert \. to .
726 $line =~ s/\.\*/\*/g; ##Convert .* to *
728 my $count = $line =~ s/^([A-Z]):/$1:\t/g;
729 if ($letters eq "" || (!$count || $letters =~ /$1/i)) {
739 @keyword_tvi = sort_and_uniq(@keyword_tvi);
740 foreach my $line (@keyword_tvi) {
741 add_categories($line);
745 foreach my $email (@email_to, @list_to) {
746 $email->[0] = deduplicate_email($email->[0]);
749 foreach my $file (@files) {
751 ($email_git || ($email_git_fallback &&
752 !$exact_pattern_match_hash{$file}))) {
753 vcs_file_signoffs($file);
755 if ($email && $email_git_blame) {
756 vcs_file_blame($file);
761 foreach my $chief (@penguin_chief) {
762 if ($chief =~ m/^(.*):(.*)/) {
765 $email_address = format_email($1, $2, $email_usename);
766 if ($email_git_penguin_chiefs) {
767 push(@email_to, [$email_address, 'chief penguin']);
769 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
774 foreach my $email (@file_emails) {
775 my ($name, $address) = parse_email($email);
777 my $tmp_email = format_email($name, $address, $email_usename);
778 push_email_address($tmp_email, '');
779 add_role($tmp_email, 'in file');
784 if ($email || $email_list) {
786 @to = (@to, @email_to);
789 @to = (@to, @list_to);
794 @to = interactive_get_maintainers(\@to);
800 sub file_match_pattern {
801 my ($file, $pattern) = @_;
802 if (substr($pattern, -1) eq "/") {
803 if ($file =~ m@^$pattern@) {
807 if ($file =~ m@^$pattern@) {
808 my $s1 = ($file =~ tr@/@@);
809 my $s2 = ($pattern =~ tr@/@@);
820 usage: $P [options] patchfile
821 $P [options] -f file|directory
824 MAINTAINER field selection options:
825 --email => print email address(es) if any
826 --git => include recent git \*-by: signers
827 --git-all-signature-types => include signers regardless of signature type
828 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
829 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
830 --git-chief-penguins => include ${penguin_chiefs}
831 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
832 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
833 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
834 --git-blame => use git blame to find modified commits for patch or file
835 --git-blame-signatures => when used with --git-blame, also include all commit signers
836 --git-since => git history to use (default: $email_git_since)
837 --hg-since => hg history to use (default: $email_hg_since)
838 --interactive => display a menu (mostly useful if used with the --git option)
839 --m => include maintainer(s) if any
840 --r => include reviewer(s) if any
841 --n => include name 'Full Name <addr\@domain.tld>'
842 --l => include list(s) if any
843 --s => include subscriber only list(s) if any
844 --remove-duplicates => minimize duplicate email names/addresses
845 --roles => show roles (status:subsystem, git-signer, list, etc...)
846 --rolestats => show roles and statistics (commits/total_commits, %)
847 --file-emails => add email addresses found in -f file (default: 0 (off))
848 --scm => print SCM tree(s) if any
849 --status => print status if any
850 --subsystem => print subsystem name if any
851 --web => print website(s) if any
854 --separator [, ] => separator for multiple entries on 1 line
855 using --separator also sets --nomultiline if --separator is not [, ]
856 --multiline => print 1 entry per line
859 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
860 --keywords => scan patch for keywords (default: $keywords)
861 --sections => print all of the subsystem sections with pattern matches
862 --letters => print all matching 'letter' types from all matching sections
863 --mailmap => use .mailmap file (default: $email_use_mailmap)
864 --version => show version
865 --help => show this help information
868 [--email --nogit --git-fallback --m --r --n --l --multiline --pattern-depth=0
869 --remove-duplicates --rolestats]
872 Using "-f directory" may give unexpected results:
873 Used with "--git", git signators for _all_ files in and below
874 directory are examined as git recurses directories.
875 Any specified X: (exclude) pattern matches are _not_ ignored.
876 Used with "--nogit", directory is used as a pattern match,
877 no individual file within the directory or subdirectory
879 Used with "--git-blame", does not iterate all files in directory
880 Using "--git-blame" is slow and may add old committers and authors
881 that are no longer active maintainers to the output.
882 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
883 other automated tools that expect only ["name"] <email address>
884 may not work because of additional output after <email address>.
885 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
886 not the percentage of the entire file authored. # of commits is
887 not a good measure of amount of code authored. 1 major commit may
888 contain a thousand lines, 5 trivial commits may modify a single line.
889 If git is not installed, but mercurial (hg) is installed and an .hg
890 repository exists, the following options apply to mercurial:
892 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
894 Use --hg-since not --git-since to control date selection
895 File ".get_maintainer.conf", if it exists in the linux kernel source root
896 directory, can change whatever get_maintainer defaults are desired.
897 Entries in this file can be any command line argument.
898 This file is prepended to any additional command line arguments.
899 Multiple lines and # comments are allowed.
900 Most options have both positive and negative forms.
901 The negative forms for --<foo> are --no<foo> and --no-<foo>.
906 sub top_of_kernel_tree {
909 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
912 if ( (-f "${lk_path}Kbuild")
913 && (-e "${lk_path}MAINTAINERS")
914 && (-f "${lk_path}Makefile")
915 && (-f "${lk_path}README")
916 && (-d "${lk_path}arch")
917 && (-d "${lk_path}board")
918 && (-d "${lk_path}common")
919 && (-d "${lk_path}doc")
920 && (-d "${lk_path}drivers")
921 && (-d "${lk_path}dts")
922 && (-d "${lk_path}fs")
923 && (-d "${lk_path}lib")
924 && (-d "${lk_path}include")
925 && (-d "${lk_path}net")
926 && (-d "${lk_path}post")
927 && (-d "${lk_path}scripts")
928 && (-d "${lk_path}test")
929 && (-d "${lk_path}tools")) {
936 my ($formatted_email) = @_;
941 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
944 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
946 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
950 $name =~ s/^\s+|\s+$//g;
951 $name =~ s/^\"|\"$//g;
952 $address =~ s/^\s+|\s+$//g;
954 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
955 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
959 return ($name, $address);
963 my ($name, $address, $usename) = @_;
967 $name =~ s/^\s+|\s+$//g;
968 $name =~ s/^\"|\"$//g;
969 $address =~ s/^\s+|\s+$//g;
971 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
972 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
978 $formatted_email = "$address";
980 $formatted_email = "$name <$address>";
983 $formatted_email = $address;
986 return $formatted_email;
989 sub find_first_section {
992 while ($index < @typevalue) {
993 my $tv = $typevalue[$index];
994 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
1003 sub find_starting_index {
1006 while ($index > 0) {
1007 my $tv = $typevalue[$index];
1008 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1017 sub find_ending_index {
1020 while ($index < @typevalue) {
1021 my $tv = $typevalue[$index];
1022 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1031 sub get_subsystem_name {
1034 my $start = find_starting_index($index);
1036 my $subsystem = $typevalue[$start];
1037 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
1038 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
1039 $subsystem =~ s/\s*$//;
1040 $subsystem = $subsystem . "...";
1045 sub get_maintainer_role {
1049 my $start = find_starting_index($index);
1050 my $end = find_ending_index($index);
1052 my $role = "unknown";
1053 my $subsystem = get_subsystem_name($index);
1055 for ($i = $start + 1; $i < $end; $i++) {
1056 my $tv = $typevalue[$i];
1057 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1060 if ($ptype eq "S") {
1067 if ($role eq "supported") {
1068 $role = "supporter";
1069 } elsif ($role eq "maintained") {
1070 $role = "maintainer";
1071 } elsif ($role eq "odd fixes") {
1072 $role = "odd fixer";
1073 } elsif ($role eq "orphan") {
1074 $role = "orphan minder";
1075 } elsif ($role eq "obsolete") {
1076 $role = "obsolete minder";
1077 } elsif ($role eq "buried alive in reporters") {
1078 $role = "chief penguin";
1081 return $role . ":" . $subsystem;
1087 my $subsystem = get_subsystem_name($index);
1089 if ($subsystem eq "THE REST") {
1096 sub add_categories {
1100 my $start = find_starting_index($index);
1101 my $end = find_ending_index($index);
1103 push(@subsystem, $typevalue[$start]);
1105 for ($i = $start + 1; $i < $end; $i++) {
1106 my $tv = $typevalue[$i];
1107 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1110 if ($ptype eq "L") {
1111 my $list_address = $pvalue;
1112 my $list_additional = "";
1113 my $list_role = get_list_role($i);
1115 if ($list_role ne "") {
1116 $list_role = ":" . $list_role;
1118 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1120 $list_additional = $2;
1122 if ($list_additional =~ m/subscribers-only/) {
1123 if ($email_subscriber_list) {
1124 if (!$hash_list_to{lc($list_address)}) {
1125 $hash_list_to{lc($list_address)} = 1;
1126 push(@list_to, [$list_address,
1127 "subscriber list${list_role}"]);
1132 if (!$hash_list_to{lc($list_address)}) {
1133 $hash_list_to{lc($list_address)} = 1;
1134 if ($list_additional =~ m/moderated/) {
1135 push(@list_to, [$list_address,
1136 "moderated list${list_role}"]);
1138 push(@list_to, [$list_address,
1139 "open list${list_role}"]);
1144 } elsif ($ptype eq "M") {
1145 my ($name, $address) = parse_email($pvalue);
1148 my $tv = $typevalue[$i - 1];
1149 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1152 $pvalue = format_email($name, $address, $email_usename);
1157 if ($email_maintainer) {
1158 my $role = get_maintainer_role($i);
1159 push_email_addresses($pvalue, $role);
1161 } elsif ($ptype eq "R") {
1162 my ($name, $address) = parse_email($pvalue);
1165 my $tv = $typevalue[$i - 1];
1166 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1169 $pvalue = format_email($name, $address, $email_usename);
1174 if ($email_reviewer) {
1175 my $subsystem = get_subsystem_name($i);
1176 push_email_addresses($pvalue, "reviewer:$subsystem");
1178 } elsif ($ptype eq "T") {
1179 push(@scm, $pvalue);
1180 } elsif ($ptype eq "W") {
1181 push(@web, $pvalue);
1182 } elsif ($ptype eq "S") {
1183 push(@status, $pvalue);
1190 my ($name, $address) = @_;
1192 return 1 if (($name eq "") && ($address eq ""));
1193 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1194 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1199 sub push_email_address {
1200 my ($line, $role) = @_;
1202 my ($name, $address) = parse_email($line);
1204 if ($address eq "") {
1208 if (!$email_remove_duplicates) {
1209 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1210 } elsif (!email_inuse($name, $address)) {
1211 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1212 $email_hash_name{lc($name)}++ if ($name ne "");
1213 $email_hash_address{lc($address)}++;
1219 sub push_email_addresses {
1220 my ($address, $role) = @_;
1222 my @address_list = ();
1224 if (rfc822_valid($address)) {
1225 push_email_address($address, $role);
1226 } elsif (@address_list = rfc822_validlist($address)) {
1227 my $array_count = shift(@address_list);
1228 while (my $entry = shift(@address_list)) {
1229 push_email_address($entry, $role);
1232 if (!push_email_address($address, $role)) {
1233 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1239 my ($line, $role) = @_;
1241 my ($name, $address) = parse_email($line);
1242 my $email = format_email($name, $address, $email_usename);
1244 foreach my $entry (@email_to) {
1245 if ($email_remove_duplicates) {
1246 my ($entry_name, $entry_address) = parse_email($entry->[0]);
1247 if (($name eq $entry_name || $address eq $entry_address)
1248 && ($role eq "" || !($entry->[1] =~ m/$role/))
1250 if ($entry->[1] eq "") {
1251 $entry->[1] = "$role";
1253 $entry->[1] = "$entry->[1],$role";
1257 if ($email eq $entry->[0]
1258 && ($role eq "" || !($entry->[1] =~ m/$role/))
1260 if ($entry->[1] eq "") {
1261 $entry->[1] = "$role";
1263 $entry->[1] = "$entry->[1],$role";
1273 foreach my $path (split(/:/, $ENV{PATH})) {
1274 if (-e "$path/$bin") {
1275 return "$path/$bin";
1285 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1286 if (-e "$path/$conf") {
1287 return "$path/$conf";
1297 my ($name, $address) = parse_email($line);
1298 my $email = format_email($name, $address, 1);
1299 my $real_name = $name;
1300 my $real_address = $address;
1302 if (exists $mailmap->{names}->{$email} ||
1303 exists $mailmap->{addresses}->{$email}) {
1304 if (exists $mailmap->{names}->{$email}) {
1305 $real_name = $mailmap->{names}->{$email};
1307 if (exists $mailmap->{addresses}->{$email}) {
1308 $real_address = $mailmap->{addresses}->{$email};
1311 if (exists $mailmap->{names}->{$address}) {
1312 $real_name = $mailmap->{names}->{$address};
1314 if (exists $mailmap->{addresses}->{$address}) {
1315 $real_address = $mailmap->{addresses}->{$address};
1318 return format_email($real_name, $real_address, 1);
1322 my (@addresses) = @_;
1324 my @mapped_emails = ();
1325 foreach my $line (@addresses) {
1326 push(@mapped_emails, mailmap_email($line));
1328 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1329 return @mapped_emails;
1332 sub merge_by_realname {
1336 foreach my $email (@emails) {
1337 my ($name, $address) = parse_email($email);
1338 if (exists $address_map{$name}) {
1339 $address = $address_map{$name};
1340 $email = format_email($name, $address, 1);
1342 $address_map{$name} = $address;
1347 sub git_execute_cmd {
1351 my $output = `$cmd`;
1352 $output =~ s/^\s*//gm;
1353 @lines = split("\n", $output);
1358 sub hg_execute_cmd {
1362 my $output = `$cmd`;
1363 @lines = split("\n", $output);
1368 sub extract_formatted_signatures {
1369 my (@signature_lines) = @_;
1371 my @type = @signature_lines;
1373 s/\s*(.*):.*/$1/ for (@type);
1376 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1378 ## Reformat email addresses (with names) to avoid badly written signatures
1380 foreach my $signer (@signature_lines) {
1381 $signer = deduplicate_email($signer);
1384 return (\@type, \@signature_lines);
1387 sub vcs_find_signers {
1388 my ($cmd, $file) = @_;
1391 my @signatures = ();
1395 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1397 my $pattern = $VCS_cmds{"commit_pattern"};
1398 my $author_pattern = $VCS_cmds{"author_pattern"};
1399 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1401 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
1403 $commits = grep(/$pattern/, @lines); # of commits
1405 @authors = grep(/$author_pattern/, @lines);
1406 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1407 @stats = grep(/$stat_pattern/, @lines);
1409 # print("stats: <@stats>\n");
1411 return (0, \@signatures, \@authors, \@stats) if !@signatures;
1413 save_commits_by_author(@lines) if ($interactive);
1414 save_commits_by_signer(@lines) if ($interactive);
1416 if (!$email_git_penguin_chiefs) {
1417 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1420 my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
1421 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1423 return ($commits, $signers_ref, $authors_ref, \@stats);
1426 sub vcs_find_author {
1430 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1432 if (!$email_git_penguin_chiefs) {
1433 @lines = grep(!/${penguin_chiefs}/i, @lines);
1436 return @lines if !@lines;
1439 foreach my $line (@lines) {
1440 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1442 my ($name, $address) = parse_email($author);
1443 $author = format_email($name, $address, 1);
1444 push(@authors, $author);
1448 save_commits_by_author(@lines) if ($interactive);
1449 save_commits_by_signer(@lines) if ($interactive);
1454 sub vcs_save_commits {
1459 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1461 foreach my $line (@lines) {
1462 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1475 return @commits if (!(-f $file));
1477 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1478 my @all_commits = ();
1480 $cmd = $VCS_cmds{"blame_file_cmd"};
1481 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1482 @all_commits = vcs_save_commits($cmd);
1484 foreach my $file_range_diff (@range) {
1485 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1487 my $diff_start = $2;
1488 my $diff_length = $3;
1489 next if ("$file" ne "$diff_file");
1490 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1491 push(@commits, $all_commits[$i]);
1495 foreach my $file_range_diff (@range) {
1496 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1498 my $diff_start = $2;
1499 my $diff_length = $3;
1500 next if ("$file" ne "$diff_file");
1501 $cmd = $VCS_cmds{"blame_range_cmd"};
1502 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1503 push(@commits, vcs_save_commits($cmd));
1506 $cmd = $VCS_cmds{"blame_file_cmd"};
1507 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1508 @commits = vcs_save_commits($cmd);
1511 foreach my $commit (@commits) {
1512 $commit =~ s/^\^//g;
1518 my $printed_novcs = 0;
1520 %VCS_cmds = %VCS_cmds_git;
1521 return 1 if eval $VCS_cmds{"available"};
1522 %VCS_cmds = %VCS_cmds_hg;
1523 return 2 if eval $VCS_cmds{"available"};
1525 if (!$printed_novcs) {
1526 warn("$P: No supported VCS found. Add --nogit to options?\n");
1527 warn("Using a git repository produces better results.\n");
1528 warn("Try Linus Torvalds' latest git repository using:\n");
1529 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
1537 return $vcs_used == 1;
1541 return $vcs_used == 2;
1544 sub interactive_get_maintainers {
1545 my ($list_ref) = @_;
1546 my @list = @$list_ref;
1555 foreach my $entry (@list) {
1556 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1557 $selected{$count} = 1;
1558 $authored{$count} = 0;
1559 $signed{$count} = 0;
1565 my $print_options = 0;
1570 printf STDERR "\n%1s %2s %-65s",
1571 "*", "#", "email/list and role:stats";
1573 ($email_git_fallback && !$maintained) ||
1575 print STDERR "auth sign";
1578 foreach my $entry (@list) {
1579 my $email = $entry->[0];
1580 my $role = $entry->[1];
1582 $sel = "*" if ($selected{$count});
1583 my $commit_author = $commit_author_hash{$email};
1584 my $commit_signer = $commit_signer_hash{$email};
1587 $authored++ for (@{$commit_author});
1588 $signed++ for (@{$commit_signer});
1589 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1590 printf STDERR "%4d %4d", $authored, $signed
1591 if ($authored > 0 || $signed > 0);
1592 printf STDERR "\n %s\n", $role;
1593 if ($authored{$count}) {
1594 my $commit_author = $commit_author_hash{$email};
1595 foreach my $ref (@{$commit_author}) {
1596 print STDERR " Author: @{$ref}[1]\n";
1599 if ($signed{$count}) {
1600 my $commit_signer = $commit_signer_hash{$email};
1601 foreach my $ref (@{$commit_signer}) {
1602 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1609 my $date_ref = \$email_git_since;
1610 $date_ref = \$email_hg_since if (vcs_is_hg());
1611 if ($print_options) {
1616 Version Control options:
1617 g use git history [$email_git]
1618 gf use git-fallback [$email_git_fallback]
1619 b use git blame [$email_git_blame]
1620 bs use blame signatures [$email_git_blame_signatures]
1621 c# minimum commits [$email_git_min_signatures]
1622 %# min percent [$email_git_min_percent]
1623 d# history to use [$$date_ref]
1624 x# max maintainers [$email_git_max_maintainers]
1625 t all signature types [$email_git_all_signature_types]
1626 m use .mailmap [$email_use_mailmap]
1633 tm toggle maintainers
1634 tg toggle git entries
1635 tl toggle open list entries
1636 ts toggle subscriber list entries
1637 f emails in file [$file_emails]
1638 k keywords in file [$keywords]
1639 r remove duplicates [$email_remove_duplicates]
1640 p# pattern match depth [$pattern_depth]
1644 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1646 my $input = <STDIN>;
1651 my @wish = split(/[, ]+/, $input);
1652 foreach my $nr (@wish) {
1654 my $sel = substr($nr, 0, 1);
1655 my $str = substr($nr, 1);
1657 $val = $1 if $str =~ /^(\d+)$/;
1662 $output_rolestats = 0;
1665 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1666 $selected{$nr - 1} = !$selected{$nr - 1};
1667 } elsif ($sel eq "*" || $sel eq '^') {
1669 $toggle = 1 if ($sel eq '*');
1670 for (my $i = 0; $i < $count; $i++) {
1671 $selected{$i} = $toggle;
1673 } elsif ($sel eq "0") {
1674 for (my $i = 0; $i < $count; $i++) {
1675 $selected{$i} = !$selected{$i};
1677 } elsif ($sel eq "t") {
1678 if (lc($str) eq "m") {
1679 for (my $i = 0; $i < $count; $i++) {
1680 $selected{$i} = !$selected{$i}
1681 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1683 } elsif (lc($str) eq "g") {
1684 for (my $i = 0; $i < $count; $i++) {
1685 $selected{$i} = !$selected{$i}
1686 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1688 } elsif (lc($str) eq "l") {
1689 for (my $i = 0; $i < $count; $i++) {
1690 $selected{$i} = !$selected{$i}
1691 if ($list[$i]->[1] =~ /^(open list)/i);
1693 } elsif (lc($str) eq "s") {
1694 for (my $i = 0; $i < $count; $i++) {
1695 $selected{$i} = !$selected{$i}
1696 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1699 } elsif ($sel eq "a") {
1700 if ($val > 0 && $val <= $count) {
1701 $authored{$val - 1} = !$authored{$val - 1};
1702 } elsif ($str eq '*' || $str eq '^') {
1704 $toggle = 1 if ($str eq '*');
1705 for (my $i = 0; $i < $count; $i++) {
1706 $authored{$i} = $toggle;
1709 } elsif ($sel eq "s") {
1710 if ($val > 0 && $val <= $count) {
1711 $signed{$val - 1} = !$signed{$val - 1};
1712 } elsif ($str eq '*' || $str eq '^') {
1714 $toggle = 1 if ($str eq '*');
1715 for (my $i = 0; $i < $count; $i++) {
1716 $signed{$i} = $toggle;
1719 } elsif ($sel eq "o") {
1722 } elsif ($sel eq "g") {
1724 bool_invert(\$email_git_fallback);
1726 bool_invert(\$email_git);
1729 } elsif ($sel eq "b") {
1731 bool_invert(\$email_git_blame_signatures);
1733 bool_invert(\$email_git_blame);
1736 } elsif ($sel eq "c") {
1738 $email_git_min_signatures = $val;
1741 } elsif ($sel eq "x") {
1743 $email_git_max_maintainers = $val;
1746 } elsif ($sel eq "%") {
1747 if ($str ne "" && $val >= 0) {
1748 $email_git_min_percent = $val;
1751 } elsif ($sel eq "d") {
1753 $email_git_since = $str;
1754 } elsif (vcs_is_hg()) {
1755 $email_hg_since = $str;
1758 } elsif ($sel eq "t") {
1759 bool_invert(\$email_git_all_signature_types);
1761 } elsif ($sel eq "f") {
1762 bool_invert(\$file_emails);
1764 } elsif ($sel eq "r") {
1765 bool_invert(\$email_remove_duplicates);
1767 } elsif ($sel eq "m") {
1768 bool_invert(\$email_use_mailmap);
1771 } elsif ($sel eq "k") {
1772 bool_invert(\$keywords);
1774 } elsif ($sel eq "p") {
1775 if ($str ne "" && $val >= 0) {
1776 $pattern_depth = $val;
1779 } elsif ($sel eq "h" || $sel eq "?") {
1782 Interactive mode allows you to select the various maintainers, submitters,
1783 commit signers and mailing lists that could be CC'd on a patch.
1785 Any *'d entry is selected.
1787 If you have git or hg installed, you can choose to summarize the commit
1788 history of files in the patch. Also, each line of the current file can
1789 be matched to its commit author and that commits signers with blame.
1791 Various knobs exist to control the length of time for active commit
1792 tracking, the maximum number of commit authors and signers to add,
1795 Enter selections at the prompt until you are satisfied that the selected
1796 maintainers are appropriate. You may enter multiple selections separated
1797 by either commas or spaces.
1801 print STDERR "invalid option: '$nr'\n";
1806 print STDERR "git-blame can be very slow, please have patience..."
1807 if ($email_git_blame);
1808 goto &get_maintainers;
1812 #drop not selected entries
1814 my @new_emailto = ();
1815 foreach my $entry (@list) {
1816 if ($selected{$count}) {
1817 push(@new_emailto, $list[$count]);
1821 return @new_emailto;
1825 my ($bool_ref) = @_;
1834 sub deduplicate_email {
1838 my ($name, $address) = parse_email($email);
1839 $email = format_email($name, $address, 1);
1840 $email = mailmap_email($email);
1842 return $email if (!$email_remove_duplicates);
1844 ($name, $address) = parse_email($email);
1846 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1847 $name = $deduplicate_name_hash{lc($name)}->[0];
1848 $address = $deduplicate_name_hash{lc($name)}->[1];
1850 } elsif ($deduplicate_address_hash{lc($address)}) {
1851 $name = $deduplicate_address_hash{lc($address)}->[0];
1852 $address = $deduplicate_address_hash{lc($address)}->[1];
1856 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1857 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1859 $email = format_email($name, $address, 1);
1860 $email = mailmap_email($email);
1864 sub save_commits_by_author {
1871 foreach my $line (@lines) {
1872 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1874 $author = deduplicate_email($author);
1875 push(@authors, $author);
1877 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1878 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1881 for (my $i = 0; $i < @authors; $i++) {
1883 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1884 if (@{$ref}[0] eq $commits[$i] &&
1885 @{$ref}[1] eq $subjects[$i]) {
1891 push(@{$commit_author_hash{$authors[$i]}},
1892 [ ($commits[$i], $subjects[$i]) ]);
1897 sub save_commits_by_signer {
1903 foreach my $line (@lines) {
1904 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1905 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1906 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1907 my @signatures = ($line);
1908 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1909 my @types = @$types_ref;
1910 my @signers = @$signers_ref;
1912 my $type = $types[0];
1913 my $signer = $signers[0];
1915 $signer = deduplicate_email($signer);
1918 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1919 if (@{$ref}[0] eq $commit &&
1920 @{$ref}[1] eq $subject &&
1921 @{$ref}[2] eq $type) {
1927 push(@{$commit_signer_hash{$signer}},
1928 [ ($commit, $subject, $type) ]);
1935 my ($role, $divisor, @lines) = @_;
1940 return if (@lines <= 0);
1942 if ($divisor <= 0) {
1943 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
1947 @lines = mailmap(@lines);
1949 return if (@lines <= 0);
1951 @lines = sort(@lines);
1954 $hash{$_}++ for @lines;
1957 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
1958 my $sign_offs = $hash{$line};
1959 my $percent = $sign_offs * 100 / $divisor;
1961 $percent = 100 if ($percent > 100);
1962 next if (ignore_email_address($line));
1964 last if ($sign_offs < $email_git_min_signatures ||
1965 $count > $email_git_max_maintainers ||
1966 $percent < $email_git_min_percent);
1967 push_email_address($line, '');
1968 if ($output_rolestats) {
1969 my $fmt_percent = sprintf("%.0f", $percent);
1970 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1972 add_role($line, $role);
1977 sub vcs_file_signoffs {
1988 $vcs_used = vcs_exists();
1989 return if (!$vcs_used);
1991 my $cmd = $VCS_cmds{"find_signers_cmd"};
1992 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1994 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
1996 @signers = @{$signers_ref} if defined $signers_ref;
1997 @authors = @{$authors_ref} if defined $authors_ref;
1998 @stats = @{$stats_ref} if defined $stats_ref;
2000 # print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
2002 foreach my $signer (@signers) {
2003 $signer = deduplicate_email($signer);
2006 vcs_assign("commit_signer", $commits, @signers);
2007 vcs_assign("authored", $commits, @authors);
2008 if ($#authors == $#stats) {
2009 my $stat_pattern = $VCS_cmds{"stat_pattern"};
2010 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
2014 for (my $i = 0; $i <= $#stats; $i++) {
2015 if ($stats[$i] =~ /$stat_pattern/) {
2020 my @tmp_authors = uniq(@authors);
2021 foreach my $author (@tmp_authors) {
2022 $author = deduplicate_email($author);
2024 @tmp_authors = uniq(@tmp_authors);
2025 my @list_added = ();
2026 my @list_deleted = ();
2027 foreach my $author (@tmp_authors) {
2029 my $auth_deleted = 0;
2030 for (my $i = 0; $i <= $#stats; $i++) {
2031 if ($author eq deduplicate_email($authors[$i]) &&
2032 $stats[$i] =~ /$stat_pattern/) {
2034 $auth_deleted += $2;
2037 for (my $i = 0; $i < $auth_added; $i++) {
2038 push(@list_added, $author);
2040 for (my $i = 0; $i < $auth_deleted; $i++) {
2041 push(@list_deleted, $author);
2044 vcs_assign("added_lines", $added, @list_added);
2045 vcs_assign("removed_lines", $deleted, @list_deleted);
2049 sub vcs_file_blame {
2053 my @all_commits = ();
2058 $vcs_used = vcs_exists();
2059 return if (!$vcs_used);
2061 @all_commits = vcs_blame($file);
2062 @commits = uniq(@all_commits);
2063 $total_commits = @commits;
2064 $total_lines = @all_commits;
2066 if ($email_git_blame_signatures) {
2069 my $commit_authors_ref;
2070 my $commit_signers_ref;
2072 my @commit_authors = ();
2073 my @commit_signers = ();
2074 my $commit = join(" -r ", @commits);
2077 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2078 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2080 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2081 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2082 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2084 push(@signers, @commit_signers);
2086 foreach my $commit (@commits) {
2088 my $commit_authors_ref;
2089 my $commit_signers_ref;
2091 my @commit_authors = ();
2092 my @commit_signers = ();
2095 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2096 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2098 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2099 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2100 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2102 push(@signers, @commit_signers);
2107 if ($from_filename) {
2108 if ($output_rolestats) {
2110 if (vcs_is_hg()) {{ # Double brace for last exit
2112 my @commit_signers = ();
2113 @commits = uniq(@commits);
2114 @commits = sort(@commits);
2115 my $commit = join(" -r ", @commits);
2118 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2119 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2123 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2125 if (!$email_git_penguin_chiefs) {
2126 @lines = grep(!/${penguin_chiefs}/i, @lines);
2132 foreach my $line (@lines) {
2133 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2135 $author = deduplicate_email($author);
2136 push(@authors, $author);
2140 save_commits_by_author(@lines) if ($interactive);
2141 save_commits_by_signer(@lines) if ($interactive);
2143 push(@signers, @authors);
2146 foreach my $commit (@commits) {
2148 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2149 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2150 my @author = vcs_find_author($cmd);
2153 my $formatted_author = deduplicate_email($author[0]);
2155 my $count = grep(/$commit/, @all_commits);
2156 for ($i = 0; $i < $count ; $i++) {
2157 push(@blame_signers, $formatted_author);
2161 if (@blame_signers) {
2162 vcs_assign("authored lines", $total_lines, @blame_signers);
2165 foreach my $signer (@signers) {
2166 $signer = deduplicate_email($signer);
2168 vcs_assign("commits", $total_commits, @signers);
2170 foreach my $signer (@signers) {
2171 $signer = deduplicate_email($signer);
2173 vcs_assign("modified commits", $total_commits, @signers);
2177 sub vcs_file_exists {
2182 my $vcs_used = vcs_exists();
2183 return 0 if (!$vcs_used);
2185 my $cmd = $VCS_cmds{"file_exists_cmd"};
2186 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2188 $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
2190 return 0 if ($? != 0);
2199 @parms = grep(!$saw{$_}++, @parms);
2207 @parms = sort @parms;
2208 @parms = grep(!$saw{$_}++, @parms);
2212 sub clean_file_emails {
2213 my (@file_emails) = @_;
2214 my @fmt_emails = ();
2216 foreach my $email (@file_emails) {
2217 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2218 my ($name, $address) = parse_email($email);
2219 if ($name eq '"[,\.]"') {
2223 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2225 my $first = $nw[@nw - 3];
2226 my $middle = $nw[@nw - 2];
2227 my $last = $nw[@nw - 1];
2229 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2230 (length($first) == 2 && substr($first, -1) eq ".")) ||
2231 (length($middle) == 1 ||
2232 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2233 $name = "$first $middle $last";
2235 $name = "$middle $last";
2239 if (substr($name, -1) =~ /[,\.]/) {
2240 $name = substr($name, 0, length($name) - 1);
2241 } elsif (substr($name, -2) =~ /[,\.]"/) {
2242 $name = substr($name, 0, length($name) - 2) . '"';
2245 if (substr($name, 0, 1) =~ /[,\.]/) {
2246 $name = substr($name, 1, length($name) - 1);
2247 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2248 $name = '"' . substr($name, 2, length($name) - 2);
2251 my $fmt_email = format_email($name, $address, $email_usename);
2252 push(@fmt_emails, $fmt_email);
2262 my ($address, $role) = @$_;
2263 if (!$saw{$address}) {
2264 if ($output_roles) {
2265 push(@lines, "$address ($role)");
2267 push(@lines, $address);
2279 if ($output_multiline) {
2280 foreach my $line (@parms) {
2284 print(join($output_separator, @parms));
2292 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2293 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2294 # This regexp will only work on addresses which have had comments stripped
2295 # and replaced with rfc822_lwsp.
2297 my $specials = '()<>@,;:\\\\".\\[\\]';
2298 my $controls = '\\000-\\037\\177';
2300 my $dtext = "[^\\[\\]\\r\\\\]";
2301 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2303 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2305 # Use zero-width assertion to spot the limit of an atom. A simple
2306 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2307 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2308 my $word = "(?:$atom|$quoted_string)";
2309 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2311 my $sub_domain = "(?:$atom|$domain_literal)";
2312 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2314 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2316 my $phrase = "$word*";
2317 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2318 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2319 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2321 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2322 my $address = "(?:$mailbox|$group)";
2324 return "$rfc822_lwsp*$address";
2327 sub rfc822_strip_comments {
2329 # Recursively remove comments, and replace with a single space. The simpler
2330 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2331 # chars in atoms, for example.
2333 while ($s =~ s/^((?:[^"\\]|\\.)*
2334 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2335 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2339 # valid: returns true if the parameter is an RFC822 valid address
2342 my $s = rfc822_strip_comments(shift);
2345 $rfc822re = make_rfc822re();
2348 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2351 # validlist: In scalar context, returns true if the parameter is an RFC822
2352 # valid list of addresses.
2354 # In list context, returns an empty list on failure (an invalid
2355 # address was found); otherwise a list whose first element is the
2356 # number of addresses found and whose remaining elements are the
2357 # addresses. This is needed to disambiguate failure (invalid)
2358 # from success with no addresses found, because an empty string is
2361 sub rfc822_validlist {
2362 my $s = rfc822_strip_comments(shift);
2365 $rfc822re = make_rfc822re();
2367 # * null list items are valid according to the RFC
2368 # * the '1' business is to aid in distinguishing failure from no results
2371 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2372 $s =~ m/^$rfc822_char*$/) {
2373 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2376 return wantarray ? (scalar(@r), @r) : 1;
2378 return wantarray ? () : 0;