2 # SPDX-License-Identifier: GPL-2.0
4 # (c) 2007, Joe Perches <joe@perches.com>
5 # created from checkpatch.pl
7 # Print selected MAINTAINERS information for
8 # the files modified in a patch or for a file
10 # usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
11 # perl scripts/get_maintainer.pl [OPTIONS] -f <file>
19 use Getopt::Long qw(:config no_auto_abbrev);
22 use File::Spec::Functions;
24 my $cur_path = fastgetcwd() . '/';
27 my $email_usename = 1;
28 my $email_maintainer = 1;
29 my $email_reviewer = 1;
32 my $email_moderated_list = 1;
33 my $email_subscriber_list = 0;
34 my $email_git_penguin_chiefs = 0;
36 my $email_git_all_signature_types = 0;
37 my $email_git_blame = 0;
38 my $email_git_blame_signatures = 1;
39 my $email_git_fallback = 1;
40 my $email_git_min_signatures = 1;
41 my $email_git_max_maintainers = 5;
42 my $email_git_min_percent = 5;
43 my $email_git_since = "1-year-ago";
44 my $email_hg_since = "-365";
46 my $email_remove_duplicates = 1;
47 my $email_use_mailmap = 1;
48 my $output_multiline = 1;
49 my $output_separator = ", ";
51 my $output_rolestats = 1;
52 my $output_section_maxlen = 50;
60 my $keywords_in_file = 0;
62 my $email_file_emails = 0;
63 my $from_filename = 0;
64 my $pattern_depth = 0;
65 my $self_test = undef;
68 my $find_maintainer_files = 0;
75 my @fixes = (); # If a patch description includes Fixes: lines
80 my %commit_author_hash;
81 my %commit_signer_hash;
83 my @penguin_chief = ();
84 push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
85 #Andrew wants in on most everything - 2009/01/14
86 #push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
88 my @penguin_chief_names = ();
89 foreach my $chief (@penguin_chief) {
90 if ($chief =~ m/^(.*):(.*)/) {
93 push(@penguin_chief_names, $chief_name);
96 my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
98 # Signature types of people who are either
99 # a) responsible for the code in question, or
100 # b) familiar enough with it to give relevant feedback
101 my @signature_tags = ();
102 push(@signature_tags, "Signed-off-by:");
103 push(@signature_tags, "Reviewed-by:");
104 push(@signature_tags, "Acked-by:");
106 my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
108 # rfc822 email address - preloaded methods go here.
109 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
110 my $rfc822_char = '[\\000-\\377]';
112 # VCS command support: class-like functions and strings
117 "execute_cmd" => \&git_execute_cmd,
118 "available" => '(which("git") ne "") && (-e ".git")',
119 "find_signers_cmd" =>
120 "git log --no-color --follow --since=\$email_git_since " .
121 '--numstat --no-merges ' .
122 '--format="GitCommit: %H%n' .
123 'GitAuthor: %an <%ae>%n' .
128 "find_commit_signers_cmd" =>
129 "git log --no-color " .
131 '--format="GitCommit: %H%n' .
132 'GitAuthor: %an <%ae>%n' .
137 "find_commit_author_cmd" =>
138 "git log --no-color " .
140 '--format="GitCommit: %H%n' .
141 'GitAuthor: %an <%ae>%n' .
143 'GitSubject: %s%n"' .
145 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
146 "blame_file_cmd" => "git blame -l \$file",
147 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
148 "blame_commit_pattern" => "^([0-9a-f]+) ",
149 "author_pattern" => "^GitAuthor: (.*)",
150 "subject_pattern" => "^GitSubject: (.*)",
151 "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
152 "file_exists_cmd" => "git ls-files \$file",
153 "list_files_cmd" => "git ls-files \$file",
157 "execute_cmd" => \&hg_execute_cmd,
158 "available" => '(which("hg") ne "") && (-d ".hg")',
159 "find_signers_cmd" =>
160 "hg log --date=\$email_hg_since " .
161 "--template='HgCommit: {node}\\n" .
162 "HgAuthor: {author}\\n" .
163 "HgSubject: {desc}\\n'" .
165 "find_commit_signers_cmd" =>
167 "--template='HgSubject: {desc}\\n'" .
169 "find_commit_author_cmd" =>
171 "--template='HgCommit: {node}\\n" .
172 "HgAuthor: {author}\\n" .
173 "HgSubject: {desc|firstline}\\n'" .
175 "blame_range_cmd" => "", # not supported
176 "blame_file_cmd" => "hg blame -n \$file",
177 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
178 "blame_commit_pattern" => "^([ 0-9a-f]+):",
179 "author_pattern" => "^HgAuthor: (.*)",
180 "subject_pattern" => "^HgSubject: (.*)",
181 "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$",
182 "file_exists_cmd" => "hg files \$file",
183 "list_files_cmd" => "hg manifest -R \$file",
186 my $conf = which_conf(".get_maintainer.conf");
189 open(my $conffile, '<', "$conf")
190 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
192 while (<$conffile>) {
195 $line =~ s/\s*\n?$//g;
199 next if ($line =~ m/^\s*#/);
200 next if ($line =~ m/^\s*$/);
202 my @words = split(" ", $line);
203 foreach my $word (@words) {
204 last if ($word =~ m/^#/);
205 push (@conf_args, $word);
209 unshift(@ARGV, @conf_args) if @conf_args;
212 my @ignore_emails = ();
213 my $ignore_file = which_conf(".get_maintainer.ignore");
214 if (-f $ignore_file) {
215 open(my $ignore, '<', "$ignore_file")
216 or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n";
220 $line =~ s/\s*\n?$//;
225 next if ($line =~ m/^\s*$/);
226 if (rfc822_valid($line)) {
227 push(@ignore_emails, $line);
235 if ($_ =~ /^-{1,2}self-test(?:=|$)/) {
236 die "$P: using --self-test does not allow any other option or argument\n";
243 'git!' => \$email_git,
244 'git-all-signature-types!' => \$email_git_all_signature_types,
245 'git-blame!' => \$email_git_blame,
246 'git-blame-signatures!' => \$email_git_blame_signatures,
247 'git-fallback!' => \$email_git_fallback,
248 'git-chief-penguins!' => \$email_git_penguin_chiefs,
249 'git-min-signatures=i' => \$email_git_min_signatures,
250 'git-max-maintainers=i' => \$email_git_max_maintainers,
251 'git-min-percent=i' => \$email_git_min_percent,
252 'git-since=s' => \$email_git_since,
253 'hg-since=s' => \$email_hg_since,
254 'i|interactive!' => \$interactive,
255 'remove-duplicates!' => \$email_remove_duplicates,
256 'mailmap!' => \$email_use_mailmap,
257 'm!' => \$email_maintainer,
258 'r!' => \$email_reviewer,
259 'n!' => \$email_usename,
260 'l!' => \$email_list,
261 'fixes!' => \$email_fixes,
262 'moderated!' => \$email_moderated_list,
263 's!' => \$email_subscriber_list,
264 'multiline!' => \$output_multiline,
265 'roles!' => \$output_roles,
266 'rolestats!' => \$output_rolestats,
267 'separator=s' => \$output_separator,
268 'subsystem!' => \$subsystem,
269 'status!' => \$status,
273 'letters=s' => \$letters,
274 'pattern-depth=i' => \$pattern_depth,
275 'k|keywords!' => \$keywords,
276 'kf|keywords-in-file!' => \$keywords_in_file,
277 'sections!' => \$sections,
278 'fe|file-emails!' => \$email_file_emails,
279 'f|file' => \$from_filename,
280 'find-maintainer-files' => \$find_maintainer_files,
281 'mpath|maintainer-path=s' => \$maintainer_path,
282 'self-test:s' => \$self_test,
283 'v|version' => \$version,
284 'h|help|usage' => \$help,
286 die "$P: invalid argument - use --help if necessary\n";
295 print("${P} ${V}\n");
299 if (defined $self_test) {
300 read_all_maintainer_files();
305 if (-t STDIN && !@ARGV) {
306 # We're talking to a terminal, but have no command line arguments.
307 die "$P: missing patchfile or -f file - use --help if necessary\n";
310 $output_multiline = 0 if ($output_separator ne ", ");
311 $output_rolestats = 1 if ($interactive);
312 $output_roles = 1 if ($output_rolestats);
314 if ($sections || $letters ne "") {
323 $keywords_in_file = 0;
326 my $selections = $email + $scm + $status + $subsystem + $web;
327 if ($selections == 0) {
328 die "$P: Missing required option: email, scm, status, subsystem or web\n";
333 ($email_maintainer + $email_reviewer +
334 $email_list + $email_subscriber_list +
335 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
336 die "$P: Please select at least 1 email option\n";
339 if ($tree && !top_of_kernel_tree($lk_path)) {
340 die "$P: The current directory does not appear to be "
341 . "a linux kernel source tree.\n";
344 ## Read MAINTAINERS for type/value pairs
349 my @self_test_info = ();
351 sub read_maintainer_file {
354 open (my $maint, '<', "$file")
355 or die "$P: Can't open MAINTAINERS file '$file': $!\n";
361 if ($line =~ m/^([A-Z]):\s*(.*)/) {
365 ##Filename pattern matching
366 if ($type eq "F" || $type eq "X") {
367 $value =~ s@\.@\\\.@g; ##Convert . to \.
368 $value =~ s/\*/\.\*/g; ##Convert * to .*
369 $value =~ s/\?/\./g; ##Convert ? to .
370 ##if pattern is a directory and it lacks a trailing slash, add one
372 $value =~ s@([^/])$@$1/@;
374 } elsif ($type eq "K") {
375 $keyword_hash{@typevalue} = $value;
377 push(@typevalue, "$type:$value");
378 } elsif (!(/^\s*$/ || /^\s*\#/)) {
379 push(@typevalue, $line);
381 if (defined $self_test) {
382 push(@self_test_info, {file=>$file, linenr=>$i, line=>$line});
389 sub find_is_maintainer_file {
391 return if ($file !~ m@/MAINTAINERS$@);
392 $file = $File::Find::name;
393 return if (! -f $file);
394 push(@mfiles, $file);
397 sub find_ignore_git {
398 return grep { $_ !~ /^\.git$/; } @_;
401 read_all_maintainer_files();
403 sub read_all_maintainer_files {
404 my $path = "${lk_path}MAINTAINERS";
405 if (defined $maintainer_path) {
406 $path = $maintainer_path;
407 # Perl Cookbook tilde expansion if necessary
408 $path =~ s@^~([^/]*)@ $1 ? (getpwnam($1))[7] : ( $ENV{HOME} || $ENV{LOGDIR} || (getpwuid($<))[7])@ex;
412 $path .= '/' if ($path !~ m@/$@);
413 if ($find_maintainer_files) {
414 find( { wanted => \&find_is_maintainer_file,
415 preprocess => \&find_ignore_git,
419 opendir(DIR, "$path") or die $!;
420 my @files = readdir(DIR);
422 foreach my $file (@files) {
423 push(@mfiles, "$path$file") if ($file !~ /^\./);
426 } elsif (-f "$path") {
427 push(@mfiles, "$path");
429 die "$P: MAINTAINER file not found '$path'\n";
431 die "$P: No MAINTAINER files found in '$path'\n" if (scalar(@mfiles) == 0);
432 foreach my $file (@mfiles) {
433 read_maintainer_file("$file");
437 sub maintainers_in_file {
440 return if ($file =~ m@\bMAINTAINERS$@);
442 if (-f $file && ($email_file_emails || $file =~ /\.yaml$/)) {
443 open(my $f, '<', $file)
444 or die "$P: Can't open $file: $!\n";
445 my $text = do { local($/) ; <$f> };
448 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;
449 push(@file_emails, clean_file_emails(@poss_addr));
454 # Read mail address map
467 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
469 open(my $mailmap_file, '<', "${lk_path}.mailmap")
470 or warn "$P: Can't open .mailmap: $!\n";
472 while (<$mailmap_file>) {
473 s/#.*$//; #strip comments
474 s/^\s+|\s+$//g; #trim
476 next if (/^\s*$/); #skip empty lines
477 #entries have one of the following formats:
480 # name1 <mail1> <mail2>
481 # name1 <mail1> name2 <mail2>
482 # (see man git-shortlog)
484 if (/^([^<]+)<([^>]+)>$/) {
488 $real_name =~ s/\s+$//;
489 ($real_name, $address) = parse_email("$real_name <$address>");
490 $mailmap->{names}->{$address} = $real_name;
492 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
493 my $real_address = $1;
494 my $wrong_address = $2;
496 $mailmap->{addresses}->{$wrong_address} = $real_address;
498 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
500 my $real_address = $2;
501 my $wrong_address = $3;
503 $real_name =~ s/\s+$//;
504 ($real_name, $real_address) =
505 parse_email("$real_name <$real_address>");
506 $mailmap->{names}->{$wrong_address} = $real_name;
507 $mailmap->{addresses}->{$wrong_address} = $real_address;
509 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
511 my $real_address = $2;
513 my $wrong_address = $4;
515 $real_name =~ s/\s+$//;
516 ($real_name, $real_address) =
517 parse_email("$real_name <$real_address>");
519 $wrong_name =~ s/\s+$//;
520 ($wrong_name, $wrong_address) =
521 parse_email("$wrong_name <$wrong_address>");
523 my $wrong_email = format_email($wrong_name, $wrong_address, 1);
524 $mailmap->{names}->{$wrong_email} = $real_name;
525 $mailmap->{addresses}->{$wrong_email} = $real_address;
528 close($mailmap_file);
531 ## use the filenames on the command line or find the filenames in the patchfiles
534 push(@ARGV, "&STDIN");
537 foreach my $file (@ARGV) {
538 if ($file ne "&STDIN") {
539 $file = canonpath($file);
540 ##if $file is a directory and it lacks a trailing slash, add one
542 $file =~ s@([^/])$@$1/@;
543 } elsif (!(-f $file)) {
544 die "$P: file '${file}' not found\n";
547 if ($from_filename && (vcs_exists() && !vcs_file_exists($file))) {
548 warn "$P: file '$file' not found in version control $!\n";
550 if ($from_filename || ($file ne "&STDIN" && vcs_file_exists($file))) {
551 $file =~ s/^\Q${cur_path}\E//; #strip any absolute path
552 $file =~ s/^\Q${lk_path}\E//; #or the path to the lk tree
554 if ($file ne "MAINTAINERS" && -f $file && $keywords && $keywords_in_file) {
555 open(my $f, '<', $file)
556 or die "$P: Can't open $file: $!\n";
557 my $text = do { local($/) ; <$f> };
559 foreach my $line (keys %keyword_hash) {
560 if ($text =~ m/$keyword_hash{$line}/x) {
561 push(@keyword_tvi, $line);
566 my $file_cnt = @files;
569 open(my $patch, "< $file")
570 or die "$P: Can't open $file: $!\n";
572 # We can check arbitrary information before the patch
573 # like the commit message, mail headers, etc...
574 # This allows us to match arbitrary keywords against any part
575 # of a git format-patch generated file (subject tags, etc...)
577 my $patch_prefix = ""; #Parsing the intro
581 if (m/^ mode change [0-7]+ => [0-7]+ (\S+)\s*$/) {
583 push(@files, $filename);
584 } elsif (m/^rename (?:from|to) (\S+)\s*$/) {
586 push(@files, $filename);
587 } elsif (m/^diff --git a\/(\S+) b\/(\S+)\s*$/) {
590 push(@files, $filename1);
591 push(@files, $filename2);
592 } elsif (m/^Fixes:\s+([0-9a-fA-F]{6,40})/) {
593 push(@fixes, $1) if ($email_fixes);
594 } elsif (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
596 $filename =~ s@^[^/]*/@@;
598 $lastfile = $filename;
599 push(@files, $filename);
600 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
601 } elsif (m/^\@\@ -(\d+),(\d+)/) {
602 if ($email_git_blame) {
603 push(@range, "$lastfile:$1:$2");
605 } elsif ($keywords) {
606 foreach my $line (keys %keyword_hash) {
607 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
608 push(@keyword_tvi, $line);
615 if ($file_cnt == @files) {
616 warn "$P: file '${file}' doesn't appear to be a patch. "
617 . "Add -f to options?\n";
619 @files = sort_and_uniq(@files);
623 @file_emails = uniq(@file_emails);
624 @fixes = uniq(@fixes);
627 my %email_hash_address;
635 my %deduplicate_name_hash = ();
636 my %deduplicate_address_hash = ();
638 my @maintainers = get_maintainers();
640 @maintainers = merge_email(@maintainers);
641 output(@maintainers);
650 @status = uniq(@status);
655 @subsystem = uniq(@subsystem);
670 my @section_headers = ();
673 @lsfiles = vcs_list_files($lk_path);
675 for my $x (@self_test_info) {
678 ## Section header duplication and missing section content
679 if (($self_test eq "" || $self_test =~ /\bsections\b/) &&
680 $x->{line} =~ /^\S[^:]/ &&
681 defined $self_test_info[$index] &&
682 $self_test_info[$index]->{line} =~ /^([A-Z]):\s*\S/) {
687 if (grep(m@^\Q$x->{line}\E@, @section_headers)) {
688 print("$x->{file}:$x->{linenr}: warning: duplicate section header\t$x->{line}\n");
690 push(@section_headers, $x->{line});
692 my $nextline = $index;
693 while (defined $self_test_info[$nextline] &&
694 $self_test_info[$nextline]->{line} =~ /^([A-Z]):\s*(\S.*)/) {
700 } elsif ($type eq "F" || $type eq "N") {
702 } elsif ($type eq "M" || $type eq "R" || $type eq "L") {
707 if (!$has_ML && $status !~ /orphan|obsolete/i) {
708 print("$x->{file}:$x->{linenr}: warning: section without email address\t$x->{line}\n");
711 print("$x->{file}:$x->{linenr}: warning: section without status \t$x->{line}\n");
714 print("$x->{file}:$x->{linenr}: warning: section without file pattern\t$x->{line}\n");
718 next if ($x->{line} !~ /^([A-Z]):\s*(.*)/);
723 ## Filename pattern matching
724 if (($type eq "F" || $type eq "X") &&
725 ($self_test eq "" || $self_test =~ /\bpatterns\b/)) {
726 $value =~ s@\.@\\\.@g; ##Convert . to \.
727 $value =~ s/\*/\.\*/g; ##Convert * to .*
728 $value =~ s/\?/\./g; ##Convert ? to .
729 ##if pattern is a directory and it lacks a trailing slash, add one
731 $value =~ s@([^/])$@$1/@;
733 if (!grep(m@^$value@, @lsfiles)) {
734 print("$x->{file}:$x->{linenr}: warning: no file matches\t$x->{line}\n");
738 } elsif (($type eq "W" || $type eq "Q" || $type eq "B") &&
739 $value =~ /^https?:/ &&
740 ($self_test eq "" || $self_test =~ /\blinks\b/)) {
741 next if (grep(m@^\Q$value\E$@, @good_links));
743 if (grep(m@^\Q$value\E$@, @bad_links)) {
746 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $value`;
748 push(@good_links, $value);
750 push(@bad_links, $value);
755 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
759 } elsif ($type eq "T" &&
760 ($self_test eq "" || $self_test =~ /\bscm\b/)) {
761 next if (grep(m@^\Q$value\E$@, @good_links));
763 if (grep(m@^\Q$value\E$@, @bad_links)) {
765 } elsif ($value !~ /^(?:git|quilt|hg)\s+\S/) {
766 print("$x->{file}:$x->{linenr}: warning: malformed entry\t$x->{line}\n");
767 } elsif ($value =~ /^git\s+(\S+)(\s+([^\(]+\S+))?/) {
771 my $output = `git ls-remote --exit-code -h "$url" $branch > /dev/null 2>&1`;
773 push(@good_links, $value);
775 push(@bad_links, $value);
778 } elsif ($value =~ /^(?:quilt|hg)\s+(https?:\S+)/) {
780 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $url`;
782 push(@good_links, $value);
784 push(@bad_links, $value);
789 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
795 sub ignore_email_address {
798 foreach my $ignore (@ignore_emails) {
799 return 1 if ($ignore eq $address);
805 sub range_is_maintained {
806 my ($start, $end) = @_;
808 for (my $i = $start; $i < $end; $i++) {
809 my $line = $typevalue[$i];
810 if ($line =~ m/^([A-Z]):\s*(.*)/) {
814 if ($value =~ /(maintain|support)/i) {
823 sub range_has_maintainer {
824 my ($start, $end) = @_;
826 for (my $i = $start; $i < $end; $i++) {
827 my $line = $typevalue[$i];
828 if ($line =~ m/^([A-Z]):\s*(.*)/) {
839 sub get_maintainers {
840 %email_hash_name = ();
841 %email_hash_address = ();
842 %commit_author_hash = ();
843 %commit_signer_hash = ();
851 %deduplicate_name_hash = ();
852 %deduplicate_address_hash = ();
853 if ($email_git_all_signature_types) {
854 $signature_pattern = "(.+?)[Bb][Yy]:";
856 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
859 # Find responsible parties
861 my %exact_pattern_match_hash = ();
863 foreach my $file (@files) {
866 my $tvi = find_first_section();
867 while ($tvi < @typevalue) {
868 my $start = find_starting_index($tvi);
869 my $end = find_ending_index($tvi);
873 #Do not match excluded file patterns
875 for ($i = $start; $i < $end; $i++) {
876 my $line = $typevalue[$i];
877 if ($line =~ m/^([A-Z]):\s*(.*)/) {
881 if (file_match_pattern($file, $value)) {
890 for ($i = $start; $i < $end; $i++) {
891 my $line = $typevalue[$i];
892 if ($line =~ m/^([A-Z]):\s*(.*)/) {
896 if (file_match_pattern($file, $value)) {
897 my $value_pd = ($value =~ tr@/@@);
898 my $file_pd = ($file =~ tr@/@@);
899 $value_pd++ if (substr($value,-1,1) ne "/");
900 $value_pd = -1 if ($value =~ /^\.\*/);
901 if ($value_pd >= $file_pd &&
902 range_is_maintained($start, $end) &&
903 range_has_maintainer($start, $end)) {
904 $exact_pattern_match_hash{$file} = 1;
906 if ($pattern_depth == 0 ||
907 (($file_pd - $value_pd) < $pattern_depth)) {
908 $hash{$tvi} = $value_pd;
911 } elsif ($type eq 'N') {
912 if ($file =~ m/$value/x) {
922 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
923 add_categories($line, "");
926 my $start = find_starting_index($line);
927 my $end = find_ending_index($line);
928 for ($i = $start; $i < $end; $i++) {
929 my $line = $typevalue[$i];
930 if ($line =~ /^[FX]:/) { ##Restore file patterns
931 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
932 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
933 $line =~ s/\\\./\./g; ##Convert \. to .
934 $line =~ s/\.\*/\*/g; ##Convert .* to *
936 my $count = $line =~ s/^([A-Z]):/$1:\t/g;
937 if ($letters eq "" || (!$count || $letters =~ /$1/i)) {
945 maintainers_in_file($file);
949 @keyword_tvi = sort_and_uniq(@keyword_tvi);
950 foreach my $line (@keyword_tvi) {
951 add_categories($line, ":Keyword:$keyword_hash{$line}");
955 foreach my $email (@email_to, @list_to) {
956 $email->[0] = deduplicate_email($email->[0]);
959 foreach my $file (@files) {
962 ($email_git_fallback &&
963 $file !~ /MAINTAINERS$/ &&
964 !$exact_pattern_match_hash{$file}))) {
965 vcs_file_signoffs($file);
967 if ($email && $email_git_blame) {
968 vcs_file_blame($file);
973 foreach my $chief (@penguin_chief) {
974 if ($chief =~ m/^(.*):(.*)/) {
977 $email_address = format_email($1, $2, $email_usename);
978 if ($email_git_penguin_chiefs) {
979 push(@email_to, [$email_address, 'chief penguin']);
981 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
986 foreach my $email (@file_emails) {
987 $email = mailmap_email($email);
988 my ($name, $address) = parse_email($email);
990 my $tmp_email = format_email($name, $address, $email_usename);
991 push_email_address($tmp_email, '');
992 add_role($tmp_email, 'in file');
996 foreach my $fix (@fixes) {
997 vcs_add_commit_signers($fix, "blamed_fixes");
1001 if ($email || $email_list) {
1003 @to = (@to, @email_to);
1006 @to = (@to, @list_to);
1011 @to = interactive_get_maintainers(\@to);
1017 sub file_match_pattern {
1018 my ($file, $pattern) = @_;
1019 if (substr($pattern, -1) eq "/") {
1020 if ($file =~ m@^$pattern@) {
1024 if ($file =~ m@^$pattern@) {
1025 my $s1 = ($file =~ tr@/@@);
1026 my $s2 = ($pattern =~ tr@/@@);
1037 usage: $P [options] patchfile
1038 $P [options] -f file|directory
1041 MAINTAINER field selection options:
1042 --email => print email address(es) if any
1043 --git => include recent git \*-by: signers
1044 --git-all-signature-types => include signers regardless of signature type
1045 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
1046 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
1047 --git-chief-penguins => include ${penguin_chiefs}
1048 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
1049 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
1050 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
1051 --git-blame => use git blame to find modified commits for patch or file
1052 --git-blame-signatures => when used with --git-blame, also include all commit signers
1053 --git-since => git history to use (default: $email_git_since)
1054 --hg-since => hg history to use (default: $email_hg_since)
1055 --interactive => display a menu (mostly useful if used with the --git option)
1056 --m => include maintainer(s) if any
1057 --r => include reviewer(s) if any
1058 --n => include name 'Full Name <addr\@domain.tld>'
1059 --l => include list(s) if any
1060 --moderated => include moderated lists(s) if any (default: true)
1061 --s => include subscriber only list(s) if any (default: false)
1062 --remove-duplicates => minimize duplicate email names/addresses
1063 --roles => show roles (status:subsystem, git-signer, list, etc...)
1064 --rolestats => show roles and statistics (commits/total_commits, %)
1065 --file-emails => add email addresses found in -f file (default: 0 (off))
1066 --fixes => for patches, add signatures of commits with 'Fixes: <commit>' (default: 1 (on))
1067 --scm => print SCM tree(s) if any
1068 --status => print status if any
1069 --subsystem => print subsystem name if any
1070 --web => print website(s) if any
1072 Output type options:
1073 --separator [, ] => separator for multiple entries on 1 line
1074 using --separator also sets --nomultiline if --separator is not [, ]
1075 --multiline => print 1 entry per line
1078 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
1079 --keywords => scan patch for keywords (default: $keywords)
1080 --keywords-in-file => scan file for keywords (default: $keywords_in_file)
1081 --sections => print all of the subsystem sections with pattern matches
1082 --letters => print all matching 'letter' types from all matching sections
1083 --mailmap => use .mailmap file (default: $email_use_mailmap)
1084 --no-tree => run without a kernel tree
1085 --self-test => show potential issues with MAINTAINERS file content
1086 --version => show version
1087 --help => show this help information
1090 [--email --tree --nogit --git-fallback --m --r --n --l --multiline
1091 --pattern-depth=0 --remove-duplicates --rolestats --keywords]
1094 Using "-f directory" may give unexpected results:
1095 Used with "--git", git signators for _all_ files in and below
1096 directory are examined as git recurses directories.
1097 Any specified X: (exclude) pattern matches are _not_ ignored.
1098 Used with "--nogit", directory is used as a pattern match,
1099 no individual file within the directory or subdirectory
1101 Used with "--git-blame", does not iterate all files in directory
1102 Using "--git-blame" is slow and may add old committers and authors
1103 that are no longer active maintainers to the output.
1104 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
1105 other automated tools that expect only ["name"] <email address>
1106 may not work because of additional output after <email address>.
1107 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
1108 not the percentage of the entire file authored. # of commits is
1109 not a good measure of amount of code authored. 1 major commit may
1110 contain a thousand lines, 5 trivial commits may modify a single line.
1111 If git is not installed, but mercurial (hg) is installed and an .hg
1112 repository exists, the following options apply to mercurial:
1114 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
1116 Use --hg-since not --git-since to control date selection
1117 File ".get_maintainer.conf", if it exists in the linux kernel source root
1118 directory, can change whatever get_maintainer defaults are desired.
1119 Entries in this file can be any command line argument.
1120 This file is prepended to any additional command line arguments.
1121 Multiple lines and # comments are allowed.
1122 Most options have both positive and negative forms.
1123 The negative forms for --<foo> are --no<foo> and --no-<foo>.
1128 sub top_of_kernel_tree {
1131 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
1134 if ( (-f "${lk_path}COPYING")
1135 && (-f "${lk_path}CREDITS")
1136 && (-f "${lk_path}Kbuild")
1137 && (-e "${lk_path}MAINTAINERS")
1138 && (-f "${lk_path}Makefile")
1139 && (-f "${lk_path}README")
1140 && (-d "${lk_path}Documentation")
1141 && (-d "${lk_path}arch")
1142 && (-d "${lk_path}include")
1143 && (-d "${lk_path}drivers")
1144 && (-d "${lk_path}fs")
1145 && (-d "${lk_path}init")
1146 && (-d "${lk_path}ipc")
1147 && (-d "${lk_path}kernel")
1148 && (-d "${lk_path}lib")
1149 && (-d "${lk_path}scripts")) {
1156 my ($formatted_email) = @_;
1161 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
1164 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
1166 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
1170 $name =~ s/^\s+|\s+$//g;
1171 $name =~ s/^\"|\"$//g;
1172 $address =~ s/^\s+|\s+$//g;
1174 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
1175 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1176 $name = "\"$name\"";
1179 return ($name, $address);
1183 my ($name, $address, $usename) = @_;
1185 my $formatted_email;
1187 $name =~ s/^\s+|\s+$//g;
1188 $name =~ s/^\"|\"$//g;
1189 $address =~ s/^\s+|\s+$//g;
1191 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
1192 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1193 $name = "\"$name\"";
1197 if ("$name" eq "") {
1198 $formatted_email = "$address";
1200 $formatted_email = "$name <$address>";
1203 $formatted_email = $address;
1206 return $formatted_email;
1209 sub find_first_section {
1212 while ($index < @typevalue) {
1213 my $tv = $typevalue[$index];
1214 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
1223 sub find_starting_index {
1226 while ($index > 0) {
1227 my $tv = $typevalue[$index];
1228 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1237 sub find_ending_index {
1240 while ($index < @typevalue) {
1241 my $tv = $typevalue[$index];
1242 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1251 sub get_subsystem_name {
1254 my $start = find_starting_index($index);
1256 my $subsystem = $typevalue[$start];
1257 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
1258 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
1259 $subsystem =~ s/\s*$//;
1260 $subsystem = $subsystem . "...";
1265 sub get_maintainer_role {
1269 my $start = find_starting_index($index);
1270 my $end = find_ending_index($index);
1272 my $role = "unknown";
1273 my $subsystem = get_subsystem_name($index);
1275 for ($i = $start + 1; $i < $end; $i++) {
1276 my $tv = $typevalue[$i];
1277 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1280 if ($ptype eq "S") {
1287 if ($role eq "supported") {
1288 $role = "supporter";
1289 } elsif ($role eq "maintained") {
1290 $role = "maintainer";
1291 } elsif ($role eq "odd fixes") {
1292 $role = "odd fixer";
1293 } elsif ($role eq "orphan") {
1294 $role = "orphan minder";
1295 } elsif ($role eq "obsolete") {
1296 $role = "obsolete minder";
1297 } elsif ($role eq "buried alive in reporters") {
1298 $role = "chief penguin";
1301 return $role . ":" . $subsystem;
1307 my $subsystem = get_subsystem_name($index);
1309 if ($subsystem eq "THE REST") {
1316 sub add_categories {
1317 my ($index, $suffix) = @_;
1320 my $start = find_starting_index($index);
1321 my $end = find_ending_index($index);
1323 push(@subsystem, $typevalue[$start]);
1325 for ($i = $start + 1; $i < $end; $i++) {
1326 my $tv = $typevalue[$i];
1327 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1330 if ($ptype eq "L") {
1331 my $list_address = $pvalue;
1332 my $list_additional = "";
1333 my $list_role = get_list_role($i);
1335 if ($list_role ne "") {
1336 $list_role = ":" . $list_role;
1338 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1340 $list_additional = $2;
1342 if ($list_additional =~ m/subscribers-only/) {
1343 if ($email_subscriber_list) {
1344 if (!$hash_list_to{lc($list_address)}) {
1345 $hash_list_to{lc($list_address)} = 1;
1346 push(@list_to, [$list_address,
1347 "subscriber list${list_role}" . $suffix]);
1352 if (!$hash_list_to{lc($list_address)}) {
1353 if ($list_additional =~ m/moderated/) {
1354 if ($email_moderated_list) {
1355 $hash_list_to{lc($list_address)} = 1;
1356 push(@list_to, [$list_address,
1357 "moderated list${list_role}" . $suffix]);
1360 $hash_list_to{lc($list_address)} = 1;
1361 push(@list_to, [$list_address,
1362 "open list${list_role}" . $suffix]);
1367 } elsif ($ptype eq "M") {
1368 if ($email_maintainer) {
1369 my $role = get_maintainer_role($i);
1370 push_email_addresses($pvalue, $role . $suffix);
1372 } elsif ($ptype eq "R") {
1373 if ($email_reviewer) {
1374 my $subsystem = get_subsystem_name($i);
1375 push_email_addresses($pvalue, "reviewer:$subsystem" . $suffix);
1377 } elsif ($ptype eq "T") {
1378 push(@scm, $pvalue . $suffix);
1379 } elsif ($ptype eq "W") {
1380 push(@web, $pvalue . $suffix);
1381 } elsif ($ptype eq "S") {
1382 push(@status, $pvalue . $suffix);
1389 my ($name, $address) = @_;
1391 return 1 if (($name eq "") && ($address eq ""));
1392 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1393 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1398 sub push_email_address {
1399 my ($line, $role) = @_;
1401 my ($name, $address) = parse_email($line);
1403 if ($address eq "") {
1407 if (!$email_remove_duplicates) {
1408 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1409 } elsif (!email_inuse($name, $address)) {
1410 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1411 $email_hash_name{lc($name)}++ if ($name ne "");
1412 $email_hash_address{lc($address)}++;
1418 sub push_email_addresses {
1419 my ($address, $role) = @_;
1421 my @address_list = ();
1423 if (rfc822_valid($address)) {
1424 push_email_address($address, $role);
1425 } elsif (@address_list = rfc822_validlist($address)) {
1426 my $array_count = shift(@address_list);
1427 while (my $entry = shift(@address_list)) {
1428 push_email_address($entry, $role);
1431 if (!push_email_address($address, $role)) {
1432 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1438 my ($line, $role) = @_;
1440 my ($name, $address) = parse_email($line);
1441 my $email = format_email($name, $address, $email_usename);
1443 foreach my $entry (@email_to) {
1444 if ($email_remove_duplicates) {
1445 my ($entry_name, $entry_address) = parse_email($entry->[0]);
1446 if (($name eq $entry_name || $address eq $entry_address)
1447 && ($role eq "" || !($entry->[1] =~ m/$role/))
1449 if ($entry->[1] eq "") {
1450 $entry->[1] = "$role";
1452 $entry->[1] = "$entry->[1],$role";
1456 if ($email eq $entry->[0]
1457 && ($role eq "" || !($entry->[1] =~ m/$role/))
1459 if ($entry->[1] eq "") {
1460 $entry->[1] = "$role";
1462 $entry->[1] = "$entry->[1],$role";
1472 foreach my $path (split(/:/, $ENV{PATH})) {
1473 if (-e "$path/$bin") {
1474 return "$path/$bin";
1484 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1485 if (-e "$path/$conf") {
1486 return "$path/$conf";
1496 my ($name, $address) = parse_email($line);
1497 my $email = format_email($name, $address, 1);
1498 my $real_name = $name;
1499 my $real_address = $address;
1501 if (exists $mailmap->{names}->{$email} ||
1502 exists $mailmap->{addresses}->{$email}) {
1503 if (exists $mailmap->{names}->{$email}) {
1504 $real_name = $mailmap->{names}->{$email};
1506 if (exists $mailmap->{addresses}->{$email}) {
1507 $real_address = $mailmap->{addresses}->{$email};
1510 if (exists $mailmap->{names}->{$address}) {
1511 $real_name = $mailmap->{names}->{$address};
1513 if (exists $mailmap->{addresses}->{$address}) {
1514 $real_address = $mailmap->{addresses}->{$address};
1517 return format_email($real_name, $real_address, 1);
1521 my (@addresses) = @_;
1523 my @mapped_emails = ();
1524 foreach my $line (@addresses) {
1525 push(@mapped_emails, mailmap_email($line));
1527 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1528 return @mapped_emails;
1531 sub merge_by_realname {
1535 foreach my $email (@emails) {
1536 my ($name, $address) = parse_email($email);
1537 if (exists $address_map{$name}) {
1538 $address = $address_map{$name};
1539 $email = format_email($name, $address, 1);
1541 $address_map{$name} = $address;
1546 sub git_execute_cmd {
1550 my $output = `$cmd`;
1551 $output =~ s/^\s*//gm;
1552 @lines = split("\n", $output);
1557 sub hg_execute_cmd {
1561 my $output = `$cmd`;
1562 @lines = split("\n", $output);
1567 sub extract_formatted_signatures {
1568 my (@signature_lines) = @_;
1570 my @type = @signature_lines;
1572 s/\s*(.*):.*/$1/ for (@type);
1575 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1577 ## Reformat email addresses (with names) to avoid badly written signatures
1579 foreach my $signer (@signature_lines) {
1580 $signer = deduplicate_email($signer);
1583 return (\@type, \@signature_lines);
1586 sub vcs_find_signers {
1587 my ($cmd, $file) = @_;
1590 my @signatures = ();
1594 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1596 my $pattern = $VCS_cmds{"commit_pattern"};
1597 my $author_pattern = $VCS_cmds{"author_pattern"};
1598 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1600 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
1602 $commits = grep(/$pattern/, @lines); # of commits
1604 @authors = grep(/$author_pattern/, @lines);
1605 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1606 @stats = grep(/$stat_pattern/, @lines);
1608 # print("stats: <@stats>\n");
1610 return (0, \@signatures, \@authors, \@stats) if !@signatures;
1612 save_commits_by_author(@lines) if ($interactive);
1613 save_commits_by_signer(@lines) if ($interactive);
1615 if (!$email_git_penguin_chiefs) {
1616 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1619 my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
1620 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1622 return ($commits, $signers_ref, $authors_ref, \@stats);
1625 sub vcs_find_author {
1629 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1631 if (!$email_git_penguin_chiefs) {
1632 @lines = grep(!/${penguin_chiefs}/i, @lines);
1635 return @lines if !@lines;
1638 foreach my $line (@lines) {
1639 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1641 my ($name, $address) = parse_email($author);
1642 $author = format_email($name, $address, 1);
1643 push(@authors, $author);
1647 save_commits_by_author(@lines) if ($interactive);
1648 save_commits_by_signer(@lines) if ($interactive);
1653 sub vcs_save_commits {
1658 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1660 foreach my $line (@lines) {
1661 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1674 return @commits if (!(-f $file));
1676 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1677 my @all_commits = ();
1679 $cmd = $VCS_cmds{"blame_file_cmd"};
1680 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1681 @all_commits = vcs_save_commits($cmd);
1683 foreach my $file_range_diff (@range) {
1684 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1686 my $diff_start = $2;
1687 my $diff_length = $3;
1688 next if ("$file" ne "$diff_file");
1689 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1690 push(@commits, $all_commits[$i]);
1694 foreach my $file_range_diff (@range) {
1695 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1697 my $diff_start = $2;
1698 my $diff_length = $3;
1699 next if ("$file" ne "$diff_file");
1700 $cmd = $VCS_cmds{"blame_range_cmd"};
1701 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1702 push(@commits, vcs_save_commits($cmd));
1705 $cmd = $VCS_cmds{"blame_file_cmd"};
1706 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1707 @commits = vcs_save_commits($cmd);
1710 foreach my $commit (@commits) {
1711 $commit =~ s/^\^//g;
1717 my $printed_novcs = 0;
1719 %VCS_cmds = %VCS_cmds_git;
1720 return 1 if eval $VCS_cmds{"available"};
1721 %VCS_cmds = %VCS_cmds_hg;
1722 return 2 if eval $VCS_cmds{"available"};
1724 if (!$printed_novcs && $email_git) {
1725 warn("$P: No supported VCS found. Add --nogit to options?\n");
1726 warn("Using a git repository produces better results.\n");
1727 warn("Try Linus Torvalds' latest git repository using:\n");
1728 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
1736 return $vcs_used == 1;
1740 return $vcs_used == 2;
1743 sub vcs_add_commit_signers {
1744 return if (!vcs_exists());
1746 my ($commit, $desc) = @_;
1747 my $commit_count = 0;
1748 my $commit_authors_ref;
1749 my $commit_signers_ref;
1751 my @commit_authors = ();
1752 my @commit_signers = ();
1755 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1756 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1758 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, "");
1759 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
1760 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
1762 foreach my $signer (@commit_signers) {
1763 $signer = deduplicate_email($signer);
1766 vcs_assign($desc, 1, @commit_signers);
1769 sub interactive_get_maintainers {
1770 my ($list_ref) = @_;
1771 my @list = @$list_ref;
1780 foreach my $entry (@list) {
1781 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1782 $selected{$count} = 1;
1783 $authored{$count} = 0;
1784 $signed{$count} = 0;
1790 my $print_options = 0;
1795 printf STDERR "\n%1s %2s %-65s",
1796 "*", "#", "email/list and role:stats";
1798 ($email_git_fallback && !$maintained) ||
1800 print STDERR "auth sign";
1803 foreach my $entry (@list) {
1804 my $email = $entry->[0];
1805 my $role = $entry->[1];
1807 $sel = "*" if ($selected{$count});
1808 my $commit_author = $commit_author_hash{$email};
1809 my $commit_signer = $commit_signer_hash{$email};
1812 $authored++ for (@{$commit_author});
1813 $signed++ for (@{$commit_signer});
1814 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1815 printf STDERR "%4d %4d", $authored, $signed
1816 if ($authored > 0 || $signed > 0);
1817 printf STDERR "\n %s\n", $role;
1818 if ($authored{$count}) {
1819 my $commit_author = $commit_author_hash{$email};
1820 foreach my $ref (@{$commit_author}) {
1821 print STDERR " Author: @{$ref}[1]\n";
1824 if ($signed{$count}) {
1825 my $commit_signer = $commit_signer_hash{$email};
1826 foreach my $ref (@{$commit_signer}) {
1827 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1834 my $date_ref = \$email_git_since;
1835 $date_ref = \$email_hg_since if (vcs_is_hg());
1836 if ($print_options) {
1841 Version Control options:
1842 g use git history [$email_git]
1843 gf use git-fallback [$email_git_fallback]
1844 b use git blame [$email_git_blame]
1845 bs use blame signatures [$email_git_blame_signatures]
1846 c# minimum commits [$email_git_min_signatures]
1847 %# min percent [$email_git_min_percent]
1848 d# history to use [$$date_ref]
1849 x# max maintainers [$email_git_max_maintainers]
1850 t all signature types [$email_git_all_signature_types]
1851 m use .mailmap [$email_use_mailmap]
1858 tm toggle maintainers
1859 tg toggle git entries
1860 tl toggle open list entries
1861 ts toggle subscriber list entries
1862 f emails in file [$email_file_emails]
1863 k keywords in file [$keywords]
1864 r remove duplicates [$email_remove_duplicates]
1865 p# pattern match depth [$pattern_depth]
1869 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1871 my $input = <STDIN>;
1876 my @wish = split(/[, ]+/, $input);
1877 foreach my $nr (@wish) {
1879 my $sel = substr($nr, 0, 1);
1880 my $str = substr($nr, 1);
1882 $val = $1 if $str =~ /^(\d+)$/;
1887 $output_rolestats = 0;
1890 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1891 $selected{$nr - 1} = !$selected{$nr - 1};
1892 } elsif ($sel eq "*" || $sel eq '^') {
1894 $toggle = 1 if ($sel eq '*');
1895 for (my $i = 0; $i < $count; $i++) {
1896 $selected{$i} = $toggle;
1898 } elsif ($sel eq "0") {
1899 for (my $i = 0; $i < $count; $i++) {
1900 $selected{$i} = !$selected{$i};
1902 } elsif ($sel eq "t") {
1903 if (lc($str) eq "m") {
1904 for (my $i = 0; $i < $count; $i++) {
1905 $selected{$i} = !$selected{$i}
1906 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1908 } elsif (lc($str) eq "g") {
1909 for (my $i = 0; $i < $count; $i++) {
1910 $selected{$i} = !$selected{$i}
1911 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1913 } elsif (lc($str) eq "l") {
1914 for (my $i = 0; $i < $count; $i++) {
1915 $selected{$i} = !$selected{$i}
1916 if ($list[$i]->[1] =~ /^(open list)/i);
1918 } elsif (lc($str) eq "s") {
1919 for (my $i = 0; $i < $count; $i++) {
1920 $selected{$i} = !$selected{$i}
1921 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1924 } elsif ($sel eq "a") {
1925 if ($val > 0 && $val <= $count) {
1926 $authored{$val - 1} = !$authored{$val - 1};
1927 } elsif ($str eq '*' || $str eq '^') {
1929 $toggle = 1 if ($str eq '*');
1930 for (my $i = 0; $i < $count; $i++) {
1931 $authored{$i} = $toggle;
1934 } elsif ($sel eq "s") {
1935 if ($val > 0 && $val <= $count) {
1936 $signed{$val - 1} = !$signed{$val - 1};
1937 } elsif ($str eq '*' || $str eq '^') {
1939 $toggle = 1 if ($str eq '*');
1940 for (my $i = 0; $i < $count; $i++) {
1941 $signed{$i} = $toggle;
1944 } elsif ($sel eq "o") {
1947 } elsif ($sel eq "g") {
1949 bool_invert(\$email_git_fallback);
1951 bool_invert(\$email_git);
1954 } elsif ($sel eq "b") {
1956 bool_invert(\$email_git_blame_signatures);
1958 bool_invert(\$email_git_blame);
1961 } elsif ($sel eq "c") {
1963 $email_git_min_signatures = $val;
1966 } elsif ($sel eq "x") {
1968 $email_git_max_maintainers = $val;
1971 } elsif ($sel eq "%") {
1972 if ($str ne "" && $val >= 0) {
1973 $email_git_min_percent = $val;
1976 } elsif ($sel eq "d") {
1978 $email_git_since = $str;
1979 } elsif (vcs_is_hg()) {
1980 $email_hg_since = $str;
1983 } elsif ($sel eq "t") {
1984 bool_invert(\$email_git_all_signature_types);
1986 } elsif ($sel eq "f") {
1987 bool_invert(\$email_file_emails);
1989 } elsif ($sel eq "r") {
1990 bool_invert(\$email_remove_duplicates);
1992 } elsif ($sel eq "m") {
1993 bool_invert(\$email_use_mailmap);
1996 } elsif ($sel eq "k") {
1997 bool_invert(\$keywords);
1999 } elsif ($sel eq "p") {
2000 if ($str ne "" && $val >= 0) {
2001 $pattern_depth = $val;
2004 } elsif ($sel eq "h" || $sel eq "?") {
2007 Interactive mode allows you to select the various maintainers, submitters,
2008 commit signers and mailing lists that could be CC'd on a patch.
2010 Any *'d entry is selected.
2012 If you have git or hg installed, you can choose to summarize the commit
2013 history of files in the patch. Also, each line of the current file can
2014 be matched to its commit author and that commits signers with blame.
2016 Various knobs exist to control the length of time for active commit
2017 tracking, the maximum number of commit authors and signers to add,
2020 Enter selections at the prompt until you are satisfied that the selected
2021 maintainers are appropriate. You may enter multiple selections separated
2022 by either commas or spaces.
2026 print STDERR "invalid option: '$nr'\n";
2031 print STDERR "git-blame can be very slow, please have patience..."
2032 if ($email_git_blame);
2033 goto &get_maintainers;
2037 #drop not selected entries
2039 my @new_emailto = ();
2040 foreach my $entry (@list) {
2041 if ($selected{$count}) {
2042 push(@new_emailto, $list[$count]);
2046 return @new_emailto;
2050 my ($bool_ref) = @_;
2059 sub deduplicate_email {
2063 my ($name, $address) = parse_email($email);
2064 $email = format_email($name, $address, 1);
2065 $email = mailmap_email($email);
2067 return $email if (!$email_remove_duplicates);
2069 ($name, $address) = parse_email($email);
2071 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
2072 $name = $deduplicate_name_hash{lc($name)}->[0];
2073 $address = $deduplicate_name_hash{lc($name)}->[1];
2075 } elsif ($deduplicate_address_hash{lc($address)}) {
2076 $name = $deduplicate_address_hash{lc($address)}->[0];
2077 $address = $deduplicate_address_hash{lc($address)}->[1];
2081 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
2082 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
2084 $email = format_email($name, $address, 1);
2085 $email = mailmap_email($email);
2089 sub save_commits_by_author {
2096 foreach my $line (@lines) {
2097 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2099 $author = deduplicate_email($author);
2100 push(@authors, $author);
2102 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2103 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2106 for (my $i = 0; $i < @authors; $i++) {
2108 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
2109 if (@{$ref}[0] eq $commits[$i] &&
2110 @{$ref}[1] eq $subjects[$i]) {
2116 push(@{$commit_author_hash{$authors[$i]}},
2117 [ ($commits[$i], $subjects[$i]) ]);
2122 sub save_commits_by_signer {
2128 foreach my $line (@lines) {
2129 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2130 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2131 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
2132 my @signatures = ($line);
2133 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
2134 my @types = @$types_ref;
2135 my @signers = @$signers_ref;
2137 my $type = $types[0];
2138 my $signer = $signers[0];
2140 $signer = deduplicate_email($signer);
2143 foreach my $ref(@{$commit_signer_hash{$signer}}) {
2144 if (@{$ref}[0] eq $commit &&
2145 @{$ref}[1] eq $subject &&
2146 @{$ref}[2] eq $type) {
2152 push(@{$commit_signer_hash{$signer}},
2153 [ ($commit, $subject, $type) ]);
2160 my ($role, $divisor, @lines) = @_;
2165 return if (@lines <= 0);
2167 if ($divisor <= 0) {
2168 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
2172 @lines = mailmap(@lines);
2174 return if (@lines <= 0);
2176 @lines = sort(@lines);
2179 $hash{$_}++ for @lines;
2182 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
2183 my $sign_offs = $hash{$line};
2184 my $percent = $sign_offs * 100 / $divisor;
2186 $percent = 100 if ($percent > 100);
2187 next if (ignore_email_address($line));
2189 last if ($sign_offs < $email_git_min_signatures ||
2190 $count > $email_git_max_maintainers ||
2191 $percent < $email_git_min_percent);
2192 push_email_address($line, '');
2193 if ($output_rolestats) {
2194 my $fmt_percent = sprintf("%.0f", $percent);
2195 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
2197 add_role($line, $role);
2202 sub vcs_file_signoffs {
2213 $vcs_used = vcs_exists();
2214 return if (!$vcs_used);
2216 my $cmd = $VCS_cmds{"find_signers_cmd"};
2217 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2219 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2221 @signers = @{$signers_ref} if defined $signers_ref;
2222 @authors = @{$authors_ref} if defined $authors_ref;
2223 @stats = @{$stats_ref} if defined $stats_ref;
2225 # print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
2227 foreach my $signer (@signers) {
2228 $signer = deduplicate_email($signer);
2231 vcs_assign("commit_signer", $commits, @signers);
2232 vcs_assign("authored", $commits, @authors);
2233 if ($#authors == $#stats) {
2234 my $stat_pattern = $VCS_cmds{"stat_pattern"};
2235 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
2239 for (my $i = 0; $i <= $#stats; $i++) {
2240 if ($stats[$i] =~ /$stat_pattern/) {
2245 my @tmp_authors = uniq(@authors);
2246 foreach my $author (@tmp_authors) {
2247 $author = deduplicate_email($author);
2249 @tmp_authors = uniq(@tmp_authors);
2250 my @list_added = ();
2251 my @list_deleted = ();
2252 foreach my $author (@tmp_authors) {
2254 my $auth_deleted = 0;
2255 for (my $i = 0; $i <= $#stats; $i++) {
2256 if ($author eq deduplicate_email($authors[$i]) &&
2257 $stats[$i] =~ /$stat_pattern/) {
2259 $auth_deleted += $2;
2262 for (my $i = 0; $i < $auth_added; $i++) {
2263 push(@list_added, $author);
2265 for (my $i = 0; $i < $auth_deleted; $i++) {
2266 push(@list_deleted, $author);
2269 vcs_assign("added_lines", $added, @list_added);
2270 vcs_assign("removed_lines", $deleted, @list_deleted);
2274 sub vcs_file_blame {
2278 my @all_commits = ();
2283 $vcs_used = vcs_exists();
2284 return if (!$vcs_used);
2286 @all_commits = vcs_blame($file);
2287 @commits = uniq(@all_commits);
2288 $total_commits = @commits;
2289 $total_lines = @all_commits;
2291 if ($email_git_blame_signatures) {
2294 my $commit_authors_ref;
2295 my $commit_signers_ref;
2297 my @commit_authors = ();
2298 my @commit_signers = ();
2299 my $commit = join(" -r ", @commits);
2302 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2303 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2305 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2306 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2307 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2309 push(@signers, @commit_signers);
2311 foreach my $commit (@commits) {
2313 my $commit_authors_ref;
2314 my $commit_signers_ref;
2316 my @commit_authors = ();
2317 my @commit_signers = ();
2320 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2321 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2323 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2324 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2325 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2327 push(@signers, @commit_signers);
2332 if ($from_filename) {
2333 if ($output_rolestats) {
2335 if (vcs_is_hg()) {{ # Double brace for last exit
2337 my @commit_signers = ();
2338 @commits = uniq(@commits);
2339 @commits = sort(@commits);
2340 my $commit = join(" -r ", @commits);
2343 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2344 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2348 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2350 if (!$email_git_penguin_chiefs) {
2351 @lines = grep(!/${penguin_chiefs}/i, @lines);
2357 foreach my $line (@lines) {
2358 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2360 $author = deduplicate_email($author);
2361 push(@authors, $author);
2365 save_commits_by_author(@lines) if ($interactive);
2366 save_commits_by_signer(@lines) if ($interactive);
2368 push(@signers, @authors);
2371 foreach my $commit (@commits) {
2373 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2374 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2375 my @author = vcs_find_author($cmd);
2378 my $formatted_author = deduplicate_email($author[0]);
2380 my $count = grep(/$commit/, @all_commits);
2381 for ($i = 0; $i < $count ; $i++) {
2382 push(@blame_signers, $formatted_author);
2386 if (@blame_signers) {
2387 vcs_assign("authored lines", $total_lines, @blame_signers);
2390 foreach my $signer (@signers) {
2391 $signer = deduplicate_email($signer);
2393 vcs_assign("commits", $total_commits, @signers);
2395 foreach my $signer (@signers) {
2396 $signer = deduplicate_email($signer);
2398 vcs_assign("modified commits", $total_commits, @signers);
2402 sub vcs_file_exists {
2407 my $vcs_used = vcs_exists();
2408 return 0 if (!$vcs_used);
2410 my $cmd = $VCS_cmds{"file_exists_cmd"};
2411 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2413 $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
2415 return 0 if ($? != 0);
2420 sub vcs_list_files {
2425 my $vcs_used = vcs_exists();
2426 return 0 if (!$vcs_used);
2428 my $cmd = $VCS_cmds{"list_files_cmd"};
2429 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2430 @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd);
2432 return () if ($? != 0);
2441 @parms = grep(!$saw{$_}++, @parms);
2449 @parms = sort @parms;
2450 @parms = grep(!$saw{$_}++, @parms);
2454 sub clean_file_emails {
2455 my (@file_emails) = @_;
2456 my @fmt_emails = ();
2458 foreach my $email (@file_emails) {
2459 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2460 my ($name, $address) = parse_email($email);
2461 if ($name eq '"[,\.]"') {
2465 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2467 my $first = $nw[@nw - 3];
2468 my $middle = $nw[@nw - 2];
2469 my $last = $nw[@nw - 1];
2471 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2472 (length($first) == 2 && substr($first, -1) eq ".")) ||
2473 (length($middle) == 1 ||
2474 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2475 $name = "$first $middle $last";
2477 $name = "$middle $last";
2481 if (substr($name, -1) =~ /[,\.]/) {
2482 $name = substr($name, 0, length($name) - 1);
2483 } elsif (substr($name, -2) =~ /[,\.]"/) {
2484 $name = substr($name, 0, length($name) - 2) . '"';
2487 if (substr($name, 0, 1) =~ /[,\.]/) {
2488 $name = substr($name, 1, length($name) - 1);
2489 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2490 $name = '"' . substr($name, 2, length($name) - 2);
2493 my $fmt_email = format_email($name, $address, $email_usename);
2494 push(@fmt_emails, $fmt_email);
2504 my ($address, $role) = @$_;
2505 if (!$saw{$address}) {
2506 if ($output_roles) {
2507 push(@lines, "$address ($role)");
2509 push(@lines, $address);
2521 if ($output_multiline) {
2522 foreach my $line (@parms) {
2526 print(join($output_separator, @parms));
2534 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2535 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2536 # This regexp will only work on addresses which have had comments stripped
2537 # and replaced with rfc822_lwsp.
2539 my $specials = '()<>@,;:\\\\".\\[\\]';
2540 my $controls = '\\000-\\037\\177';
2542 my $dtext = "[^\\[\\]\\r\\\\]";
2543 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2545 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2547 # Use zero-width assertion to spot the limit of an atom. A simple
2548 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2549 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2550 my $word = "(?:$atom|$quoted_string)";
2551 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2553 my $sub_domain = "(?:$atom|$domain_literal)";
2554 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2556 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2558 my $phrase = "$word*";
2559 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2560 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2561 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2563 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2564 my $address = "(?:$mailbox|$group)";
2566 return "$rfc822_lwsp*$address";
2569 sub rfc822_strip_comments {
2571 # Recursively remove comments, and replace with a single space. The simpler
2572 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2573 # chars in atoms, for example.
2575 while ($s =~ s/^((?:[^"\\]|\\.)*
2576 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2577 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2581 # valid: returns true if the parameter is an RFC822 valid address
2584 my $s = rfc822_strip_comments(shift);
2587 $rfc822re = make_rfc822re();
2590 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2593 # validlist: In scalar context, returns true if the parameter is an RFC822
2594 # valid list of addresses.
2596 # In list context, returns an empty list on failure (an invalid
2597 # address was found); otherwise a list whose first element is the
2598 # number of addresses found and whose remaining elements are the
2599 # addresses. This is needed to disambiguate failure (invalid)
2600 # from success with no addresses found, because an empty string is
2603 sub rfc822_validlist {
2604 my $s = rfc822_strip_comments(shift);
2607 $rfc822re = make_rfc822re();
2609 # * null list items are valid according to the RFC
2610 # * the '1' business is to aid in distinguishing failure from no results
2613 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2614 $s =~ m/^$rfc822_char*$/) {
2615 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2618 return wantarray ? (scalar(@r), @r) : 1;
2620 return wantarray ? () : 0;