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);
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;
59 my $from_filename = 0;
60 my $pattern_depth = 0;
61 my $self_test = undef;
64 my $find_maintainer_files = 0;
70 my %commit_author_hash;
71 my %commit_signer_hash;
73 my @penguin_chief = ();
74 push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
75 #Andrew wants in on most everything - 2009/01/14
76 #push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
78 my @penguin_chief_names = ();
79 foreach my $chief (@penguin_chief) {
80 if ($chief =~ m/^(.*):(.*)/) {
83 push(@penguin_chief_names, $chief_name);
86 my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
88 # Signature types of people who are either
89 # a) responsible for the code in question, or
90 # b) familiar enough with it to give relevant feedback
91 my @signature_tags = ();
92 push(@signature_tags, "Signed-off-by:");
93 push(@signature_tags, "Reviewed-by:");
94 push(@signature_tags, "Acked-by:");
96 my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
98 # rfc822 email address - preloaded methods go here.
99 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
100 my $rfc822_char = '[\\000-\\377]';
102 # VCS command support: class-like functions and strings
107 "execute_cmd" => \
&git_execute_cmd
,
108 "available" => '(which("git") ne "") && (-e ".git")',
109 "find_signers_cmd" =>
110 "git log --no-color --follow --since=\$email_git_since " .
111 '--numstat --no-merges ' .
112 '--format="GitCommit: %H%n' .
113 'GitAuthor: %an <%ae>%n' .
118 "find_commit_signers_cmd" =>
119 "git log --no-color " .
121 '--format="GitCommit: %H%n' .
122 'GitAuthor: %an <%ae>%n' .
127 "find_commit_author_cmd" =>
128 "git log --no-color " .
130 '--format="GitCommit: %H%n' .
131 'GitAuthor: %an <%ae>%n' .
133 'GitSubject: %s%n"' .
135 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
136 "blame_file_cmd" => "git blame -l \$file",
137 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
138 "blame_commit_pattern" => "^([0-9a-f]+) ",
139 "author_pattern" => "^GitAuthor: (.*)",
140 "subject_pattern" => "^GitSubject: (.*)",
141 "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
142 "file_exists_cmd" => "git ls-files \$file",
143 "list_files_cmd" => "git ls-files \$file",
147 "execute_cmd" => \
&hg_execute_cmd
,
148 "available" => '(which("hg") ne "") && (-d ".hg")',
149 "find_signers_cmd" =>
150 "hg log --date=\$email_hg_since " .
151 "--template='HgCommit: {node}\\n" .
152 "HgAuthor: {author}\\n" .
153 "HgSubject: {desc}\\n'" .
155 "find_commit_signers_cmd" =>
157 "--template='HgSubject: {desc}\\n'" .
159 "find_commit_author_cmd" =>
161 "--template='HgCommit: {node}\\n" .
162 "HgAuthor: {author}\\n" .
163 "HgSubject: {desc|firstline}\\n'" .
165 "blame_range_cmd" => "", # not supported
166 "blame_file_cmd" => "hg blame -n \$file",
167 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
168 "blame_commit_pattern" => "^([ 0-9a-f]+):",
169 "author_pattern" => "^HgAuthor: (.*)",
170 "subject_pattern" => "^HgSubject: (.*)",
171 "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$",
172 "file_exists_cmd" => "hg files \$file",
173 "list_files_cmd" => "hg manifest -R \$file",
176 my $conf = which_conf
(".get_maintainer.conf");
179 open(my $conffile, '<', "$conf")
180 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
182 while (<$conffile>) {
185 $line =~ s/\s*\n?$//g;
189 next if ($line =~ m/^\s*#/);
190 next if ($line =~ m/^\s*$/);
192 my @words = split(" ", $line);
193 foreach my $word (@words) {
194 last if ($word =~ m/^#/);
195 push (@conf_args, $word);
199 unshift(@ARGV, @conf_args) if @conf_args;
202 my @ignore_emails = ();
203 my $ignore_file = which_conf
(".get_maintainer.ignore");
204 if (-f
$ignore_file) {
205 open(my $ignore, '<', "$ignore_file")
206 or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n";
210 $line =~ s/\s*\n?$//;
215 next if ($line =~ m/^\s*$/);
216 if (rfc822_valid
($line)) {
217 push(@ignore_emails, $line);
225 if ($_ =~ /^-{1,2}self-test(?:=|$)/) {
226 die "$P: using --self-test does not allow any other option or argument\n";
233 'git!' => \
$email_git,
234 'git-all-signature-types!' => \
$email_git_all_signature_types,
235 'git-blame!' => \
$email_git_blame,
236 'git-blame-signatures!' => \
$email_git_blame_signatures,
237 'git-fallback!' => \
$email_git_fallback,
238 'git-chief-penguins!' => \
$email_git_penguin_chiefs,
239 'git-min-signatures=i' => \
$email_git_min_signatures,
240 'git-max-maintainers=i' => \
$email_git_max_maintainers,
241 'git-min-percent=i' => \
$email_git_min_percent,
242 'git-since=s' => \
$email_git_since,
243 'hg-since=s' => \
$email_hg_since,
244 'i|interactive!' => \
$interactive,
245 'remove-duplicates!' => \
$email_remove_duplicates,
246 'mailmap!' => \
$email_use_mailmap,
247 'm!' => \
$email_maintainer,
248 'r!' => \
$email_reviewer,
249 'n!' => \
$email_usename,
250 'l!' => \
$email_list,
251 's!' => \
$email_subscriber_list,
252 'multiline!' => \
$output_multiline,
253 'roles!' => \
$output_roles,
254 'rolestats!' => \
$output_rolestats,
255 'separator=s' => \
$output_separator,
256 'subsystem!' => \
$subsystem,
257 'status!' => \
$status,
261 'letters=s' => \
$letters,
262 'pattern-depth=i' => \
$pattern_depth,
263 'k|keywords!' => \
$keywords,
264 'sections!' => \
$sections,
265 'fe|file-emails!' => \
$file_emails,
266 'f|file' => \
$from_filename,
267 'find-maintainer-files' => \
$find_maintainer_files,
268 'mpath|maintainer-path=s' => \
$maintainer_path,
269 'self-test:s' => \
$self_test,
270 'v|version' => \
$version,
271 'h|help|usage' => \
$help,
273 die "$P: invalid argument - use --help if necessary\n";
282 print("${P} ${V}\n");
286 if (defined $self_test) {
287 read_all_maintainer_files
();
292 if (-t STDIN
&& !@ARGV) {
293 # We're talking to a terminal, but have no command line arguments.
294 die "$P: missing patchfile or -f file - use --help if necessary\n";
297 $output_multiline = 0 if ($output_separator ne ", ");
298 $output_rolestats = 1 if ($interactive);
299 $output_roles = 1 if ($output_rolestats);
301 if ($sections || $letters ne "") {
312 my $selections = $email + $scm + $status + $subsystem + $web;
313 if ($selections == 0) {
314 die "$P: Missing required option: email, scm, status, subsystem or web\n";
319 ($email_maintainer + $email_reviewer +
320 $email_list + $email_subscriber_list +
321 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
322 die "$P: Please select at least 1 email option\n";
325 if ($tree && !top_of_kernel_tree
($lk_path)) {
326 die "$P: The current directory does not appear to be "
327 . "a linux kernel source tree.\n";
330 ## Read MAINTAINERS for type/value pairs
335 my @self_test_info = ();
337 sub read_maintainer_file
{
340 open (my $maint, '<', "$file")
341 or die "$P: Can't open MAINTAINERS file '$file': $!\n";
347 if ($line =~ m/^([A-Z]):\s*(.*)/) {
351 ##Filename pattern matching
352 if ($type eq "F" || $type eq "X") {
353 $value =~ s@\
.@
\\\
.@g; ##Convert . to \.
354 $value =~ s/\*/\.\*/g; ##Convert * to .*
355 $value =~ s/\?/\./g; ##Convert ? to .
356 ##if pattern is a directory and it lacks a trailing slash, add one
358 $value =~ s@
([^/])$@$1/@
;
360 } elsif ($type eq "K") {
361 $keyword_hash{@typevalue} = $value;
363 push(@typevalue, "$type:$value");
364 } elsif (!(/^\s*$/ || /^\s*\#/)) {
365 push(@typevalue, $line);
367 if (defined $self_test) {
368 push(@self_test_info, {file
=>$file, linenr
=>$i, line
=>$line});
375 sub find_is_maintainer_file
{
377 return if ($file !~ m@
/MAINTAINERS
$@
);
378 $file = $File::Find
::name
;
379 return if (! -f
$file);
380 push(@mfiles, $file);
383 sub find_ignore_git
{
384 return grep { $_ !~ /^\.git$/; } @_;
387 read_all_maintainer_files
();
389 sub read_all_maintainer_files
{
390 my $path = "${lk_path}MAINTAINERS";
391 if (defined $maintainer_path) {
392 $path = $maintainer_path;
393 # Perl Cookbook tilde expansion if necessary
394 $path =~ s@
^~([^/]*)@
$1 ?
(getpwnam($1))[7] : ( $ENV{HOME
} || $ENV{LOGDIR
} || (getpwuid($<))[7])@ex;
398 $path .= '/' if ($path !~ m@
/$@
);
399 if ($find_maintainer_files) {
400 find
( { wanted
=> \
&find_is_maintainer_file
,
401 preprocess
=> \
&find_ignore_git
,
405 opendir(DIR
, "$path") or die $!;
406 my @files = readdir(DIR
);
408 foreach my $file (@files) {
409 push(@mfiles, "$path$file") if ($file !~ /^\./);
412 } elsif (-f
"$path") {
413 push(@mfiles, "$path");
415 die "$P: MAINTAINER file not found '$path'\n";
417 die "$P: No MAINTAINER files found in '$path'\n" if (scalar(@mfiles) == 0);
418 foreach my $file (@mfiles) {
419 read_maintainer_file
("$file");
424 # Read mail address map
437 return if (!$email_use_mailmap || !(-f
"${lk_path}.mailmap"));
439 open(my $mailmap_file, '<', "${lk_path}.mailmap")
440 or warn "$P: Can't open .mailmap: $!\n";
442 while (<$mailmap_file>) {
443 s/#.*$//; #strip comments
444 s/^\s+|\s+$//g; #trim
446 next if (/^\s*$/); #skip empty lines
447 #entries have one of the following formats:
450 # name1 <mail1> <mail2>
451 # name1 <mail1> name2 <mail2>
452 # (see man git-shortlog)
454 if (/^([^<]+)<([^>]+)>$/) {
458 $real_name =~ s/\s+$//;
459 ($real_name, $address) = parse_email
("$real_name <$address>");
460 $mailmap->{names
}->{$address} = $real_name;
462 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
463 my $real_address = $1;
464 my $wrong_address = $2;
466 $mailmap->{addresses
}->{$wrong_address} = $real_address;
468 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
470 my $real_address = $2;
471 my $wrong_address = $3;
473 $real_name =~ s/\s+$//;
474 ($real_name, $real_address) =
475 parse_email
("$real_name <$real_address>");
476 $mailmap->{names
}->{$wrong_address} = $real_name;
477 $mailmap->{addresses
}->{$wrong_address} = $real_address;
479 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
481 my $real_address = $2;
483 my $wrong_address = $4;
485 $real_name =~ s/\s+$//;
486 ($real_name, $real_address) =
487 parse_email
("$real_name <$real_address>");
489 $wrong_name =~ s/\s+$//;
490 ($wrong_name, $wrong_address) =
491 parse_email
("$wrong_name <$wrong_address>");
493 my $wrong_email = format_email
($wrong_name, $wrong_address, 1);
494 $mailmap->{names
}->{$wrong_email} = $real_name;
495 $mailmap->{addresses
}->{$wrong_email} = $real_address;
498 close($mailmap_file);
501 ## use the filenames on the command line or find the filenames in the patchfiles
505 my @keyword_tvi = ();
506 my @file_emails = ();
509 push(@ARGV, "&STDIN");
512 foreach my $file (@ARGV) {
513 if ($file ne "&STDIN") {
514 ##if $file is a directory and it lacks a trailing slash, add one
516 $file =~ s@
([^/])$@$1/@
;
517 } elsif (!(-f
$file)) {
518 die "$P: file '${file}' not found\n";
521 if ($from_filename || ($file ne "&STDIN" && vcs_file_exists
($file))) {
522 $file =~ s/^\Q${cur_path}\E//; #strip any absolute path
523 $file =~ s/^\Q${lk_path}\E//; #or the path to the lk tree
525 if ($file ne "MAINTAINERS" && -f
$file && ($keywords || $file_emails)) {
526 open(my $f, '<', $file)
527 or die "$P: Can't open $file: $!\n";
528 my $text = do { local($/) ; <$f> };
531 foreach my $line (keys %keyword_hash) {
532 if ($text =~ m/$keyword_hash{$line}/x) {
533 push(@keyword_tvi, $line);
538 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;
539 push(@file_emails, clean_file_emails
(@poss_addr));
543 my $file_cnt = @files;
546 open(my $patch, "< $file")
547 or die "$P: Can't open $file: $!\n";
549 # We can check arbitrary information before the patch
550 # like the commit message, mail headers, etc...
551 # This allows us to match arbitrary keywords against any part
552 # of a git format-patch generated file (subject tags, etc...)
554 my $patch_prefix = ""; #Parsing the intro
558 if (m/^ mode change [0-7]+ => [0-7]+ (\S+)\s*$/) {
560 push(@files, $filename);
561 } elsif (m/^rename (?:from|to) (\S+)\s*$/) {
563 push(@files, $filename);
564 } elsif (m/^diff --git a\/(\S
+) b\
/(\S+)\s*$/) {
567 push(@files, $filename1);
568 push(@files, $filename2);
569 } elsif (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
571 $filename =~ s@
^[^/]*/@@
;
573 $lastfile = $filename;
574 push(@files, $filename);
575 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
576 } elsif (m/^\@\@ -(\d+),(\d+)/) {
577 if ($email_git_blame) {
578 push(@range, "$lastfile:$1:$2");
580 } elsif ($keywords) {
581 foreach my $line (keys %keyword_hash) {
582 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
583 push(@keyword_tvi, $line);
590 if ($file_cnt == @files) {
591 warn "$P: file '${file}' doesn't appear to be a patch. "
592 . "Add -f to options?\n";
594 @files = sort_and_uniq
(@files);
598 @file_emails = uniq
(@file_emails);
601 my %email_hash_address;
609 my %deduplicate_name_hash = ();
610 my %deduplicate_address_hash = ();
612 my @maintainers = get_maintainers
();
615 @maintainers = merge_email
(@maintainers);
616 output
(@maintainers);
625 @status = uniq
(@status);
630 @subsystem = uniq
(@subsystem);
645 my @section_headers = ();
648 @lsfiles = vcs_list_files
($lk_path);
650 for my $x (@self_test_info) {
653 ## Section header duplication and missing section content
654 if (($self_test eq "" || $self_test =~ /\bsections\b/) &&
655 $x->{line
} =~ /^\S[^:]/ &&
656 defined $self_test_info[$index] &&
657 $self_test_info[$index]->{line
} =~ /^([A-Z]):\s*\S/) {
662 if (grep(m@
^\Q
$x->{line
}\E@
, @section_headers)) {
663 print("$x->{file}:$x->{linenr}: warning: duplicate section header\t$x->{line}\n");
665 push(@section_headers, $x->{line
});
667 my $nextline = $index;
668 while (defined $self_test_info[$nextline] &&
669 $self_test_info[$nextline]->{line
} =~ /^([A-Z]):\s*(\S.*)/) {
675 } elsif ($type eq "F" || $type eq "N") {
677 } elsif ($type eq "M" || $type eq "R" || $type eq "L") {
682 if (!$has_ML && $status !~ /orphan|obsolete/i) {
683 print("$x->{file}:$x->{linenr}: warning: section without email address\t$x->{line}\n");
686 print("$x->{file}:$x->{linenr}: warning: section without status \t$x->{line}\n");
689 print("$x->{file}:$x->{linenr}: warning: section without file pattern\t$x->{line}\n");
693 next if ($x->{line
} !~ /^([A-Z]):\s*(.*)/);
698 ## Filename pattern matching
699 if (($type eq "F" || $type eq "X") &&
700 ($self_test eq "" || $self_test =~ /\bpatterns\b/)) {
701 $value =~ s@\
.@
\\\
.@g; ##Convert . to \.
702 $value =~ s/\*/\.\*/g; ##Convert * to .*
703 $value =~ s/\?/\./g; ##Convert ? to .
704 ##if pattern is a directory and it lacks a trailing slash, add one
706 $value =~ s@
([^/])$@$1/@
;
708 if (!grep(m@
^$value@
, @lsfiles)) {
709 print("$x->{file}:$x->{linenr}: warning: no file matches\t$x->{line}\n");
713 } elsif (($type eq "W" || $type eq "Q" || $type eq "B") &&
714 $value =~ /^https?:/ &&
715 ($self_test eq "" || $self_test =~ /\blinks\b/)) {
716 next if (grep(m@
^\Q
$value\E
$@
, @good_links));
718 if (grep(m@
^\Q
$value\E
$@
, @bad_links)) {
721 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $value`;
723 push(@good_links, $value);
725 push(@bad_links, $value);
730 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
734 } elsif ($type eq "T" &&
735 ($self_test eq "" || $self_test =~ /\bscm\b/)) {
736 next if (grep(m@
^\Q
$value\E
$@
, @good_links));
738 if (grep(m@
^\Q
$value\E
$@
, @bad_links)) {
740 } elsif ($value !~ /^(?:git|quilt|hg)\s+\S/) {
741 print("$x->{file}:$x->{linenr}: warning: malformed entry\t$x->{line}\n");
742 } elsif ($value =~ /^git\s+(\S+)(\s+([^\(]+\S+))?/) {
746 my $output = `git ls-remote --exit-code -h "$url" $branch > /dev/null 2>&1`;
748 push(@good_links, $value);
750 push(@bad_links, $value);
753 } elsif ($value =~ /^(?:quilt|hg)\s+(https?:\S+)/) {
755 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $url`;
757 push(@good_links, $value);
759 push(@bad_links, $value);
764 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
770 sub ignore_email_address
{
773 foreach my $ignore (@ignore_emails) {
774 return 1 if ($ignore eq $address);
780 sub range_is_maintained
{
781 my ($start, $end) = @_;
783 for (my $i = $start; $i < $end; $i++) {
784 my $line = $typevalue[$i];
785 if ($line =~ m/^([A-Z]):\s*(.*)/) {
789 if ($value =~ /(maintain|support)/i) {
798 sub range_has_maintainer
{
799 my ($start, $end) = @_;
801 for (my $i = $start; $i < $end; $i++) {
802 my $line = $typevalue[$i];
803 if ($line =~ m/^([A-Z]):\s*(.*)/) {
814 sub get_maintainers
{
815 %email_hash_name = ();
816 %email_hash_address = ();
817 %commit_author_hash = ();
818 %commit_signer_hash = ();
826 %deduplicate_name_hash = ();
827 %deduplicate_address_hash = ();
828 if ($email_git_all_signature_types) {
829 $signature_pattern = "(.+?)[Bb][Yy]:";
831 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
834 # Find responsible parties
836 my %exact_pattern_match_hash = ();
838 foreach my $file (@files) {
841 my $tvi = find_first_section
();
842 while ($tvi < @typevalue) {
843 my $start = find_starting_index
($tvi);
844 my $end = find_ending_index
($tvi);
848 #Do not match excluded file patterns
850 for ($i = $start; $i < $end; $i++) {
851 my $line = $typevalue[$i];
852 if ($line =~ m/^([A-Z]):\s*(.*)/) {
856 if (file_match_pattern
($file, $value)) {
865 for ($i = $start; $i < $end; $i++) {
866 my $line = $typevalue[$i];
867 if ($line =~ m/^([A-Z]):\s*(.*)/) {
871 if (file_match_pattern
($file, $value)) {
872 my $value_pd = ($value =~ tr@
/@@
);
873 my $file_pd = ($file =~ tr@
/@@
);
874 $value_pd++ if (substr($value,-1,1) ne "/");
875 $value_pd = -1 if ($value =~ /^\.\*/);
876 if ($value_pd >= $file_pd &&
877 range_is_maintained
($start, $end) &&
878 range_has_maintainer
($start, $end)) {
879 $exact_pattern_match_hash{$file} = 1;
881 if ($pattern_depth == 0 ||
882 (($file_pd - $value_pd) < $pattern_depth)) {
883 $hash{$tvi} = $value_pd;
886 } elsif ($type eq 'N') {
887 if ($file =~ m/$value/x) {
897 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
898 add_categories
($line);
901 my $start = find_starting_index
($line);
902 my $end = find_ending_index
($line);
903 for ($i = $start; $i < $end; $i++) {
904 my $line = $typevalue[$i];
905 if ($line =~ /^[FX]:/) { ##Restore file patterns
906 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
907 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
908 $line =~ s/\\\./\./g; ##Convert \. to .
909 $line =~ s/\.\*/\*/g; ##Convert .* to *
911 my $count = $line =~ s/^([A-Z]):/$1:\t/g;
912 if ($letters eq "" || (!$count || $letters =~ /$1/i)) {
922 @keyword_tvi = sort_and_uniq
(@keyword_tvi);
923 foreach my $line (@keyword_tvi) {
924 add_categories
($line);
928 foreach my $email (@email_to, @list_to) {
929 $email->[0] = deduplicate_email
($email->[0]);
932 foreach my $file (@files) {
934 ($email_git || ($email_git_fallback &&
935 !$exact_pattern_match_hash{$file}))) {
936 vcs_file_signoffs
($file);
938 if ($email && $email_git_blame) {
939 vcs_file_blame
($file);
944 foreach my $chief (@penguin_chief) {
945 if ($chief =~ m/^(.*):(.*)/) {
948 $email_address = format_email
($1, $2, $email_usename);
949 if ($email_git_penguin_chiefs) {
950 push(@email_to, [$email_address, 'chief penguin']);
952 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
957 foreach my $email (@file_emails) {
958 my ($name, $address) = parse_email
($email);
960 my $tmp_email = format_email
($name, $address, $email_usename);
961 push_email_address
($tmp_email, '');
962 add_role
($tmp_email, 'in file');
967 if ($email || $email_list) {
969 @to = (@to, @email_to);
972 @to = (@to, @list_to);
977 @to = interactive_get_maintainers
(\
@to);
983 sub file_match_pattern
{
984 my ($file, $pattern) = @_;
985 if (substr($pattern, -1) eq "/") {
986 if ($file =~ m@
^$pattern@
) {
990 if ($file =~ m@
^$pattern@
) {
991 my $s1 = ($file =~ tr@
/@@
);
992 my $s2 = ($pattern =~ tr@
/@@
);
1003 usage: $P [options] patchfile
1004 $P [options] -f file|directory
1007 MAINTAINER field selection options:
1008 --email => print email address(es) if any
1009 --git => include recent git \*-by: signers
1010 --git-all-signature-types => include signers regardless of signature type
1011 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
1012 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
1013 --git-chief-penguins => include ${penguin_chiefs}
1014 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
1015 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
1016 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
1017 --git-blame => use git blame to find modified commits for patch or file
1018 --git-blame-signatures => when used with --git-blame, also include all commit signers
1019 --git-since => git history to use (default: $email_git_since)
1020 --hg-since => hg history to use (default: $email_hg_since)
1021 --interactive => display a menu (mostly useful if used with the --git option)
1022 --m => include maintainer(s) if any
1023 --r => include reviewer(s) if any
1024 --n => include name 'Full Name <addr\@domain.tld>'
1025 --l => include list(s) if any
1026 --s => include subscriber only list(s) if any
1027 --remove-duplicates => minimize duplicate email names/addresses
1028 --roles => show roles (status:subsystem, git-signer, list, etc...)
1029 --rolestats => show roles and statistics (commits/total_commits, %)
1030 --file-emails => add email addresses found in -f file (default: 0 (off))
1031 --scm => print SCM tree(s) if any
1032 --status => print status if any
1033 --subsystem => print subsystem name if any
1034 --web => print website(s) if any
1036 Output type options:
1037 --separator [, ] => separator for multiple entries on 1 line
1038 using --separator also sets --nomultiline if --separator is not [, ]
1039 --multiline => print 1 entry per line
1042 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
1043 --keywords => scan patch for keywords (default: $keywords)
1044 --sections => print all of the subsystem sections with pattern matches
1045 --letters => print all matching 'letter' types from all matching sections
1046 --mailmap => use .mailmap file (default: $email_use_mailmap)
1047 --no-tree => run without a kernel tree
1048 --self-test => show potential issues with MAINTAINERS file content
1049 --version => show version
1050 --help => show this help information
1053 [--email --tree --nogit --git-fallback --m --r --n --l --multiline
1054 --pattern-depth=0 --remove-duplicates --rolestats]
1057 Using "-f directory" may give unexpected results:
1058 Used with "--git", git signators for _all_ files in and below
1059 directory are examined as git recurses directories.
1060 Any specified X: (exclude) pattern matches are _not_ ignored.
1061 Used with "--nogit", directory is used as a pattern match,
1062 no individual file within the directory or subdirectory
1064 Used with "--git-blame", does not iterate all files in directory
1065 Using "--git-blame" is slow and may add old committers and authors
1066 that are no longer active maintainers to the output.
1067 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
1068 other automated tools that expect only ["name"] <email address>
1069 may not work because of additional output after <email address>.
1070 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
1071 not the percentage of the entire file authored. # of commits is
1072 not a good measure of amount of code authored. 1 major commit may
1073 contain a thousand lines, 5 trivial commits may modify a single line.
1074 If git is not installed, but mercurial (hg) is installed and an .hg
1075 repository exists, the following options apply to mercurial:
1077 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
1079 Use --hg-since not --git-since to control date selection
1080 File ".get_maintainer.conf", if it exists in the linux kernel source root
1081 directory, can change whatever get_maintainer defaults are desired.
1082 Entries in this file can be any command line argument.
1083 This file is prepended to any additional command line arguments.
1084 Multiple lines and # comments are allowed.
1085 Most options have both positive and negative forms.
1086 The negative forms for --<foo> are --no<foo> and --no-<foo>.
1091 sub top_of_kernel_tree
{
1094 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
1097 if ( (-f
"${lk_path}COPYING")
1098 && (-f
"${lk_path}CREDITS")
1099 && (-f
"${lk_path}Kbuild")
1100 && (-e
"${lk_path}MAINTAINERS")
1101 && (-f
"${lk_path}Makefile")
1102 && (-f
"${lk_path}README")
1103 && (-d
"${lk_path}Documentation")
1104 && (-d
"${lk_path}arch")
1105 && (-d
"${lk_path}include")
1106 && (-d
"${lk_path}drivers")
1107 && (-d
"${lk_path}fs")
1108 && (-d
"${lk_path}init")
1109 && (-d
"${lk_path}ipc")
1110 && (-d
"${lk_path}kernel")
1111 && (-d
"${lk_path}lib")
1112 && (-d
"${lk_path}scripts")) {
1119 my ($formatted_email) = @_;
1124 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
1127 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
1129 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
1133 $name =~ s/^\s+|\s+$//g;
1134 $name =~ s/^\"|\"$//g;
1135 $address =~ s/^\s+|\s+$//g;
1137 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
1138 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1139 $name = "\"$name\"";
1142 return ($name, $address);
1146 my ($name, $address, $usename) = @_;
1148 my $formatted_email;
1150 $name =~ s/^\s+|\s+$//g;
1151 $name =~ s/^\"|\"$//g;
1152 $address =~ s/^\s+|\s+$//g;
1154 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
1155 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1156 $name = "\"$name\"";
1160 if ("$name" eq "") {
1161 $formatted_email = "$address";
1163 $formatted_email = "$name <$address>";
1166 $formatted_email = $address;
1169 return $formatted_email;
1172 sub find_first_section
{
1175 while ($index < @typevalue) {
1176 my $tv = $typevalue[$index];
1177 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
1186 sub find_starting_index
{
1189 while ($index > 0) {
1190 my $tv = $typevalue[$index];
1191 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1200 sub find_ending_index
{
1203 while ($index < @typevalue) {
1204 my $tv = $typevalue[$index];
1205 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1214 sub get_subsystem_name
{
1217 my $start = find_starting_index
($index);
1219 my $subsystem = $typevalue[$start];
1220 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
1221 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
1222 $subsystem =~ s/\s*$//;
1223 $subsystem = $subsystem . "...";
1228 sub get_maintainer_role
{
1232 my $start = find_starting_index
($index);
1233 my $end = find_ending_index
($index);
1235 my $role = "unknown";
1236 my $subsystem = get_subsystem_name
($index);
1238 for ($i = $start + 1; $i < $end; $i++) {
1239 my $tv = $typevalue[$i];
1240 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1243 if ($ptype eq "S") {
1250 if ($role eq "supported") {
1251 $role = "supporter";
1252 } elsif ($role eq "maintained") {
1253 $role = "maintainer";
1254 } elsif ($role eq "odd fixes") {
1255 $role = "odd fixer";
1256 } elsif ($role eq "orphan") {
1257 $role = "orphan minder";
1258 } elsif ($role eq "obsolete") {
1259 $role = "obsolete minder";
1260 } elsif ($role eq "buried alive in reporters") {
1261 $role = "chief penguin";
1264 return $role . ":" . $subsystem;
1270 my $subsystem = get_subsystem_name
($index);
1272 if ($subsystem eq "THE REST") {
1279 sub add_categories
{
1283 my $start = find_starting_index
($index);
1284 my $end = find_ending_index
($index);
1286 push(@subsystem, $typevalue[$start]);
1288 for ($i = $start + 1; $i < $end; $i++) {
1289 my $tv = $typevalue[$i];
1290 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1293 if ($ptype eq "L") {
1294 my $list_address = $pvalue;
1295 my $list_additional = "";
1296 my $list_role = get_list_role
($i);
1298 if ($list_role ne "") {
1299 $list_role = ":" . $list_role;
1301 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1303 $list_additional = $2;
1305 if ($list_additional =~ m/subscribers-only/) {
1306 if ($email_subscriber_list) {
1307 if (!$hash_list_to{lc($list_address)}) {
1308 $hash_list_to{lc($list_address)} = 1;
1309 push(@list_to, [$list_address,
1310 "subscriber list${list_role}"]);
1315 if (!$hash_list_to{lc($list_address)}) {
1316 $hash_list_to{lc($list_address)} = 1;
1317 if ($list_additional =~ m/moderated/) {
1318 push(@list_to, [$list_address,
1319 "moderated list${list_role}"]);
1321 push(@list_to, [$list_address,
1322 "open list${list_role}"]);
1327 } elsif ($ptype eq "M") {
1328 my ($name, $address) = parse_email
($pvalue);
1331 my $tv = $typevalue[$i - 1];
1332 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1335 $pvalue = format_email
($name, $address, $email_usename);
1340 if ($email_maintainer) {
1341 my $role = get_maintainer_role
($i);
1342 push_email_addresses
($pvalue, $role);
1344 } elsif ($ptype eq "R") {
1345 my ($name, $address) = parse_email
($pvalue);
1348 my $tv = $typevalue[$i - 1];
1349 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1352 $pvalue = format_email
($name, $address, $email_usename);
1357 if ($email_reviewer) {
1358 my $subsystem = get_subsystem_name
($i);
1359 push_email_addresses
($pvalue, "reviewer:$subsystem");
1361 } elsif ($ptype eq "T") {
1362 push(@scm, $pvalue);
1363 } elsif ($ptype eq "W") {
1364 push(@web, $pvalue);
1365 } elsif ($ptype eq "S") {
1366 push(@status, $pvalue);
1373 my ($name, $address) = @_;
1375 return 1 if (($name eq "") && ($address eq ""));
1376 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1377 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1382 sub push_email_address
{
1383 my ($line, $role) = @_;
1385 my ($name, $address) = parse_email
($line);
1387 if ($address eq "") {
1391 if (!$email_remove_duplicates) {
1392 push(@email_to, [format_email
($name, $address, $email_usename), $role]);
1393 } elsif (!email_inuse
($name, $address)) {
1394 push(@email_to, [format_email
($name, $address, $email_usename), $role]);
1395 $email_hash_name{lc($name)}++ if ($name ne "");
1396 $email_hash_address{lc($address)}++;
1402 sub push_email_addresses
{
1403 my ($address, $role) = @_;
1405 my @address_list = ();
1407 if (rfc822_valid
($address)) {
1408 push_email_address
($address, $role);
1409 } elsif (@address_list = rfc822_validlist
($address)) {
1410 my $array_count = shift(@address_list);
1411 while (my $entry = shift(@address_list)) {
1412 push_email_address
($entry, $role);
1415 if (!push_email_address
($address, $role)) {
1416 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1422 my ($line, $role) = @_;
1424 my ($name, $address) = parse_email
($line);
1425 my $email = format_email
($name, $address, $email_usename);
1427 foreach my $entry (@email_to) {
1428 if ($email_remove_duplicates) {
1429 my ($entry_name, $entry_address) = parse_email
($entry->[0]);
1430 if (($name eq $entry_name || $address eq $entry_address)
1431 && ($role eq "" || !($entry->[1] =~ m/$role/))
1433 if ($entry->[1] eq "") {
1434 $entry->[1] = "$role";
1436 $entry->[1] = "$entry->[1],$role";
1440 if ($email eq $entry->[0]
1441 && ($role eq "" || !($entry->[1] =~ m/$role/))
1443 if ($entry->[1] eq "") {
1444 $entry->[1] = "$role";
1446 $entry->[1] = "$entry->[1],$role";
1456 foreach my $path (split(/:/, $ENV{PATH
})) {
1457 if (-e
"$path/$bin") {
1458 return "$path/$bin";
1468 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1469 if (-e
"$path/$conf") {
1470 return "$path/$conf";
1480 my ($name, $address) = parse_email
($line);
1481 my $email = format_email
($name, $address, 1);
1482 my $real_name = $name;
1483 my $real_address = $address;
1485 if (exists $mailmap->{names
}->{$email} ||
1486 exists $mailmap->{addresses
}->{$email}) {
1487 if (exists $mailmap->{names
}->{$email}) {
1488 $real_name = $mailmap->{names
}->{$email};
1490 if (exists $mailmap->{addresses
}->{$email}) {
1491 $real_address = $mailmap->{addresses
}->{$email};
1494 if (exists $mailmap->{names
}->{$address}) {
1495 $real_name = $mailmap->{names
}->{$address};
1497 if (exists $mailmap->{addresses
}->{$address}) {
1498 $real_address = $mailmap->{addresses
}->{$address};
1501 return format_email
($real_name, $real_address, 1);
1505 my (@addresses) = @_;
1507 my @mapped_emails = ();
1508 foreach my $line (@addresses) {
1509 push(@mapped_emails, mailmap_email
($line));
1511 merge_by_realname
(@mapped_emails) if ($email_use_mailmap);
1512 return @mapped_emails;
1515 sub merge_by_realname
{
1519 foreach my $email (@emails) {
1520 my ($name, $address) = parse_email
($email);
1521 if (exists $address_map{$name}) {
1522 $address = $address_map{$name};
1523 $email = format_email
($name, $address, 1);
1525 $address_map{$name} = $address;
1530 sub git_execute_cmd
{
1534 my $output = `$cmd`;
1535 $output =~ s/^\s*//gm;
1536 @lines = split("\n", $output);
1541 sub hg_execute_cmd
{
1545 my $output = `$cmd`;
1546 @lines = split("\n", $output);
1551 sub extract_formatted_signatures
{
1552 my (@signature_lines) = @_;
1554 my @type = @signature_lines;
1556 s/\s*(.*):.*/$1/ for (@type);
1559 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1561 ## Reformat email addresses (with names) to avoid badly written signatures
1563 foreach my $signer (@signature_lines) {
1564 $signer = deduplicate_email
($signer);
1567 return (\
@type, \
@signature_lines);
1570 sub vcs_find_signers
{
1571 my ($cmd, $file) = @_;
1574 my @signatures = ();
1578 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1580 my $pattern = $VCS_cmds{"commit_pattern"};
1581 my $author_pattern = $VCS_cmds{"author_pattern"};
1582 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1584 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
1586 $commits = grep(/$pattern/, @lines); # of commits
1588 @authors = grep(/$author_pattern/, @lines);
1589 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1590 @stats = grep(/$stat_pattern/, @lines);
1592 # print("stats: <@stats>\n");
1594 return (0, \
@signatures, \
@authors, \
@stats) if !@signatures;
1596 save_commits_by_author
(@lines) if ($interactive);
1597 save_commits_by_signer
(@lines) if ($interactive);
1599 if (!$email_git_penguin_chiefs) {
1600 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1603 my ($author_ref, $authors_ref) = extract_formatted_signatures
(@authors);
1604 my ($types_ref, $signers_ref) = extract_formatted_signatures
(@signatures);
1606 return ($commits, $signers_ref, $authors_ref, \
@stats);
1609 sub vcs_find_author
{
1613 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1615 if (!$email_git_penguin_chiefs) {
1616 @lines = grep(!/${penguin_chiefs}/i, @lines);
1619 return @lines if !@lines;
1622 foreach my $line (@lines) {
1623 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1625 my ($name, $address) = parse_email
($author);
1626 $author = format_email
($name, $address, 1);
1627 push(@authors, $author);
1631 save_commits_by_author
(@lines) if ($interactive);
1632 save_commits_by_signer
(@lines) if ($interactive);
1637 sub vcs_save_commits
{
1642 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1644 foreach my $line (@lines) {
1645 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1658 return @commits if (!(-f
$file));
1660 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1661 my @all_commits = ();
1663 $cmd = $VCS_cmds{"blame_file_cmd"};
1664 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1665 @all_commits = vcs_save_commits
($cmd);
1667 foreach my $file_range_diff (@range) {
1668 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1670 my $diff_start = $2;
1671 my $diff_length = $3;
1672 next if ("$file" ne "$diff_file");
1673 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1674 push(@commits, $all_commits[$i]);
1678 foreach my $file_range_diff (@range) {
1679 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1681 my $diff_start = $2;
1682 my $diff_length = $3;
1683 next if ("$file" ne "$diff_file");
1684 $cmd = $VCS_cmds{"blame_range_cmd"};
1685 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1686 push(@commits, vcs_save_commits
($cmd));
1689 $cmd = $VCS_cmds{"blame_file_cmd"};
1690 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1691 @commits = vcs_save_commits
($cmd);
1694 foreach my $commit (@commits) {
1695 $commit =~ s/^\^//g;
1701 my $printed_novcs = 0;
1703 %VCS_cmds = %VCS_cmds_git;
1704 return 1 if eval $VCS_cmds{"available"};
1705 %VCS_cmds = %VCS_cmds_hg;
1706 return 2 if eval $VCS_cmds{"available"};
1708 if (!$printed_novcs) {
1709 warn("$P: No supported VCS found. Add --nogit to options?\n");
1710 warn("Using a git repository produces better results.\n");
1711 warn("Try Linus Torvalds' latest git repository using:\n");
1712 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
1720 return $vcs_used == 1;
1724 return $vcs_used == 2;
1727 sub interactive_get_maintainers
{
1728 my ($list_ref) = @_;
1729 my @list = @
$list_ref;
1738 foreach my $entry (@list) {
1739 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1740 $selected{$count} = 1;
1741 $authored{$count} = 0;
1742 $signed{$count} = 0;
1748 my $print_options = 0;
1753 printf STDERR
"\n%1s %2s %-65s",
1754 "*", "#", "email/list and role:stats";
1756 ($email_git_fallback && !$maintained) ||
1758 print STDERR
"auth sign";
1761 foreach my $entry (@list) {
1762 my $email = $entry->[0];
1763 my $role = $entry->[1];
1765 $sel = "*" if ($selected{$count});
1766 my $commit_author = $commit_author_hash{$email};
1767 my $commit_signer = $commit_signer_hash{$email};
1770 $authored++ for (@
{$commit_author});
1771 $signed++ for (@
{$commit_signer});
1772 printf STDERR
"%1s %2d %-65s", $sel, $count + 1, $email;
1773 printf STDERR
"%4d %4d", $authored, $signed
1774 if ($authored > 0 || $signed > 0);
1775 printf STDERR
"\n %s\n", $role;
1776 if ($authored{$count}) {
1777 my $commit_author = $commit_author_hash{$email};
1778 foreach my $ref (@
{$commit_author}) {
1779 print STDERR
" Author: @{$ref}[1]\n";
1782 if ($signed{$count}) {
1783 my $commit_signer = $commit_signer_hash{$email};
1784 foreach my $ref (@
{$commit_signer}) {
1785 print STDERR
" @{$ref}[2]: @{$ref}[1]\n";
1792 my $date_ref = \
$email_git_since;
1793 $date_ref = \
$email_hg_since if (vcs_is_hg
());
1794 if ($print_options) {
1799 Version Control options:
1800 g use git history [$email_git]
1801 gf use git-fallback [$email_git_fallback]
1802 b use git blame [$email_git_blame]
1803 bs use blame signatures [$email_git_blame_signatures]
1804 c# minimum commits [$email_git_min_signatures]
1805 %# min percent [$email_git_min_percent]
1806 d# history to use [$$date_ref]
1807 x# max maintainers [$email_git_max_maintainers]
1808 t all signature types [$email_git_all_signature_types]
1809 m use .mailmap [$email_use_mailmap]
1816 tm toggle maintainers
1817 tg toggle git entries
1818 tl toggle open list entries
1819 ts toggle subscriber list entries
1820 f emails in file [$file_emails]
1821 k keywords in file [$keywords]
1822 r remove duplicates [$email_remove_duplicates]
1823 p# pattern match depth [$pattern_depth]
1827 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1829 my $input = <STDIN
>;
1834 my @wish = split(/[, ]+/, $input);
1835 foreach my $nr (@wish) {
1837 my $sel = substr($nr, 0, 1);
1838 my $str = substr($nr, 1);
1840 $val = $1 if $str =~ /^(\d+)$/;
1845 $output_rolestats = 0;
1848 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1849 $selected{$nr - 1} = !$selected{$nr - 1};
1850 } elsif ($sel eq "*" || $sel eq '^') {
1852 $toggle = 1 if ($sel eq '*');
1853 for (my $i = 0; $i < $count; $i++) {
1854 $selected{$i} = $toggle;
1856 } elsif ($sel eq "0") {
1857 for (my $i = 0; $i < $count; $i++) {
1858 $selected{$i} = !$selected{$i};
1860 } elsif ($sel eq "t") {
1861 if (lc($str) eq "m") {
1862 for (my $i = 0; $i < $count; $i++) {
1863 $selected{$i} = !$selected{$i}
1864 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1866 } elsif (lc($str) eq "g") {
1867 for (my $i = 0; $i < $count; $i++) {
1868 $selected{$i} = !$selected{$i}
1869 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1871 } elsif (lc($str) eq "l") {
1872 for (my $i = 0; $i < $count; $i++) {
1873 $selected{$i} = !$selected{$i}
1874 if ($list[$i]->[1] =~ /^(open list)/i);
1876 } elsif (lc($str) eq "s") {
1877 for (my $i = 0; $i < $count; $i++) {
1878 $selected{$i} = !$selected{$i}
1879 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1882 } elsif ($sel eq "a") {
1883 if ($val > 0 && $val <= $count) {
1884 $authored{$val - 1} = !$authored{$val - 1};
1885 } elsif ($str eq '*' || $str eq '^') {
1887 $toggle = 1 if ($str eq '*');
1888 for (my $i = 0; $i < $count; $i++) {
1889 $authored{$i} = $toggle;
1892 } elsif ($sel eq "s") {
1893 if ($val > 0 && $val <= $count) {
1894 $signed{$val - 1} = !$signed{$val - 1};
1895 } elsif ($str eq '*' || $str eq '^') {
1897 $toggle = 1 if ($str eq '*');
1898 for (my $i = 0; $i < $count; $i++) {
1899 $signed{$i} = $toggle;
1902 } elsif ($sel eq "o") {
1905 } elsif ($sel eq "g") {
1907 bool_invert
(\
$email_git_fallback);
1909 bool_invert
(\
$email_git);
1912 } elsif ($sel eq "b") {
1914 bool_invert
(\
$email_git_blame_signatures);
1916 bool_invert
(\
$email_git_blame);
1919 } elsif ($sel eq "c") {
1921 $email_git_min_signatures = $val;
1924 } elsif ($sel eq "x") {
1926 $email_git_max_maintainers = $val;
1929 } elsif ($sel eq "%") {
1930 if ($str ne "" && $val >= 0) {
1931 $email_git_min_percent = $val;
1934 } elsif ($sel eq "d") {
1936 $email_git_since = $str;
1937 } elsif (vcs_is_hg
()) {
1938 $email_hg_since = $str;
1941 } elsif ($sel eq "t") {
1942 bool_invert
(\
$email_git_all_signature_types);
1944 } elsif ($sel eq "f") {
1945 bool_invert
(\
$file_emails);
1947 } elsif ($sel eq "r") {
1948 bool_invert
(\
$email_remove_duplicates);
1950 } elsif ($sel eq "m") {
1951 bool_invert
(\
$email_use_mailmap);
1954 } elsif ($sel eq "k") {
1955 bool_invert
(\
$keywords);
1957 } elsif ($sel eq "p") {
1958 if ($str ne "" && $val >= 0) {
1959 $pattern_depth = $val;
1962 } elsif ($sel eq "h" || $sel eq "?") {
1965 Interactive mode allows you to select the various maintainers, submitters,
1966 commit signers and mailing lists that could be CC'd on a patch.
1968 Any *'d entry is selected.
1970 If you have git or hg installed, you can choose to summarize the commit
1971 history of files in the patch. Also, each line of the current file can
1972 be matched to its commit author and that commits signers with blame.
1974 Various knobs exist to control the length of time for active commit
1975 tracking, the maximum number of commit authors and signers to add,
1978 Enter selections at the prompt until you are satisfied that the selected
1979 maintainers are appropriate. You may enter multiple selections separated
1980 by either commas or spaces.
1984 print STDERR
"invalid option: '$nr'\n";
1989 print STDERR
"git-blame can be very slow, please have patience..."
1990 if ($email_git_blame);
1991 goto &get_maintainers
;
1995 #drop not selected entries
1997 my @new_emailto = ();
1998 foreach my $entry (@list) {
1999 if ($selected{$count}) {
2000 push(@new_emailto, $list[$count]);
2004 return @new_emailto;
2008 my ($bool_ref) = @_;
2017 sub deduplicate_email
{
2021 my ($name, $address) = parse_email
($email);
2022 $email = format_email
($name, $address, 1);
2023 $email = mailmap_email
($email);
2025 return $email if (!$email_remove_duplicates);
2027 ($name, $address) = parse_email
($email);
2029 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
2030 $name = $deduplicate_name_hash{lc($name)}->[0];
2031 $address = $deduplicate_name_hash{lc($name)}->[1];
2033 } elsif ($deduplicate_address_hash{lc($address)}) {
2034 $name = $deduplicate_address_hash{lc($address)}->[0];
2035 $address = $deduplicate_address_hash{lc($address)}->[1];
2039 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
2040 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
2042 $email = format_email
($name, $address, 1);
2043 $email = mailmap_email
($email);
2047 sub save_commits_by_author
{
2054 foreach my $line (@lines) {
2055 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2057 $author = deduplicate_email
($author);
2058 push(@authors, $author);
2060 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2061 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2064 for (my $i = 0; $i < @authors; $i++) {
2066 foreach my $ref(@
{$commit_author_hash{$authors[$i]}}) {
2067 if (@
{$ref}[0] eq $commits[$i] &&
2068 @
{$ref}[1] eq $subjects[$i]) {
2074 push(@
{$commit_author_hash{$authors[$i]}},
2075 [ ($commits[$i], $subjects[$i]) ]);
2080 sub save_commits_by_signer
{
2086 foreach my $line (@lines) {
2087 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2088 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2089 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
2090 my @signatures = ($line);
2091 my ($types_ref, $signers_ref) = extract_formatted_signatures
(@signatures);
2092 my @types = @
$types_ref;
2093 my @signers = @
$signers_ref;
2095 my $type = $types[0];
2096 my $signer = $signers[0];
2098 $signer = deduplicate_email
($signer);
2101 foreach my $ref(@
{$commit_signer_hash{$signer}}) {
2102 if (@
{$ref}[0] eq $commit &&
2103 @
{$ref}[1] eq $subject &&
2104 @
{$ref}[2] eq $type) {
2110 push(@
{$commit_signer_hash{$signer}},
2111 [ ($commit, $subject, $type) ]);
2118 my ($role, $divisor, @lines) = @_;
2123 return if (@lines <= 0);
2125 if ($divisor <= 0) {
2126 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
2130 @lines = mailmap
(@lines);
2132 return if (@lines <= 0);
2134 @lines = sort(@lines);
2137 $hash{$_}++ for @lines;
2140 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
2141 my $sign_offs = $hash{$line};
2142 my $percent = $sign_offs * 100 / $divisor;
2144 $percent = 100 if ($percent > 100);
2145 next if (ignore_email_address
($line));
2147 last if ($sign_offs < $email_git_min_signatures ||
2148 $count > $email_git_max_maintainers ||
2149 $percent < $email_git_min_percent);
2150 push_email_address
($line, '');
2151 if ($output_rolestats) {
2152 my $fmt_percent = sprintf("%.0f", $percent);
2153 add_role
($line, "$role:$sign_offs/$divisor=$fmt_percent%");
2155 add_role
($line, $role);
2160 sub vcs_file_signoffs
{
2171 $vcs_used = vcs_exists
();
2172 return if (!$vcs_used);
2174 my $cmd = $VCS_cmds{"find_signers_cmd"};
2175 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2177 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers
($cmd, $file);
2179 @signers = @
{$signers_ref} if defined $signers_ref;
2180 @authors = @
{$authors_ref} if defined $authors_ref;
2181 @stats = @
{$stats_ref} if defined $stats_ref;
2183 # print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
2185 foreach my $signer (@signers) {
2186 $signer = deduplicate_email
($signer);
2189 vcs_assign
("commit_signer", $commits, @signers);
2190 vcs_assign
("authored", $commits, @authors);
2191 if ($#authors == $#stats) {
2192 my $stat_pattern = $VCS_cmds{"stat_pattern"};
2193 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
2197 for (my $i = 0; $i <= $#stats; $i++) {
2198 if ($stats[$i] =~ /$stat_pattern/) {
2203 my @tmp_authors = uniq
(@authors);
2204 foreach my $author (@tmp_authors) {
2205 $author = deduplicate_email
($author);
2207 @tmp_authors = uniq
(@tmp_authors);
2208 my @list_added = ();
2209 my @list_deleted = ();
2210 foreach my $author (@tmp_authors) {
2212 my $auth_deleted = 0;
2213 for (my $i = 0; $i <= $#stats; $i++) {
2214 if ($author eq deduplicate_email
($authors[$i]) &&
2215 $stats[$i] =~ /$stat_pattern/) {
2217 $auth_deleted += $2;
2220 for (my $i = 0; $i < $auth_added; $i++) {
2221 push(@list_added, $author);
2223 for (my $i = 0; $i < $auth_deleted; $i++) {
2224 push(@list_deleted, $author);
2227 vcs_assign
("added_lines", $added, @list_added);
2228 vcs_assign
("removed_lines", $deleted, @list_deleted);
2232 sub vcs_file_blame
{
2236 my @all_commits = ();
2241 $vcs_used = vcs_exists
();
2242 return if (!$vcs_used);
2244 @all_commits = vcs_blame
($file);
2245 @commits = uniq
(@all_commits);
2246 $total_commits = @commits;
2247 $total_lines = @all_commits;
2249 if ($email_git_blame_signatures) {
2252 my $commit_authors_ref;
2253 my $commit_signers_ref;
2255 my @commit_authors = ();
2256 my @commit_signers = ();
2257 my $commit = join(" -r ", @commits);
2260 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2261 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2263 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers
($cmd, $file);
2264 @commit_authors = @
{$commit_authors_ref} if defined $commit_authors_ref;
2265 @commit_signers = @
{$commit_signers_ref} if defined $commit_signers_ref;
2267 push(@signers, @commit_signers);
2269 foreach my $commit (@commits) {
2271 my $commit_authors_ref;
2272 my $commit_signers_ref;
2274 my @commit_authors = ();
2275 my @commit_signers = ();
2278 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2279 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2281 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers
($cmd, $file);
2282 @commit_authors = @
{$commit_authors_ref} if defined $commit_authors_ref;
2283 @commit_signers = @
{$commit_signers_ref} if defined $commit_signers_ref;
2285 push(@signers, @commit_signers);
2290 if ($from_filename) {
2291 if ($output_rolestats) {
2293 if (vcs_is_hg
()) {{ # Double brace for last exit
2295 my @commit_signers = ();
2296 @commits = uniq
(@commits);
2297 @commits = sort(@commits);
2298 my $commit = join(" -r ", @commits);
2301 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2302 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2306 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2308 if (!$email_git_penguin_chiefs) {
2309 @lines = grep(!/${penguin_chiefs}/i, @lines);
2315 foreach my $line (@lines) {
2316 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2318 $author = deduplicate_email
($author);
2319 push(@authors, $author);
2323 save_commits_by_author
(@lines) if ($interactive);
2324 save_commits_by_signer
(@lines) if ($interactive);
2326 push(@signers, @authors);
2329 foreach my $commit (@commits) {
2331 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2332 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2333 my @author = vcs_find_author
($cmd);
2336 my $formatted_author = deduplicate_email
($author[0]);
2338 my $count = grep(/$commit/, @all_commits);
2339 for ($i = 0; $i < $count ; $i++) {
2340 push(@blame_signers, $formatted_author);
2344 if (@blame_signers) {
2345 vcs_assign
("authored lines", $total_lines, @blame_signers);
2348 foreach my $signer (@signers) {
2349 $signer = deduplicate_email
($signer);
2351 vcs_assign
("commits", $total_commits, @signers);
2353 foreach my $signer (@signers) {
2354 $signer = deduplicate_email
($signer);
2356 vcs_assign
("modified commits", $total_commits, @signers);
2360 sub vcs_file_exists
{
2365 my $vcs_used = vcs_exists
();
2366 return 0 if (!$vcs_used);
2368 my $cmd = $VCS_cmds{"file_exists_cmd"};
2369 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2371 $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
2373 return 0 if ($?
!= 0);
2378 sub vcs_list_files
{
2383 my $vcs_used = vcs_exists
();
2384 return 0 if (!$vcs_used);
2386 my $cmd = $VCS_cmds{"list_files_cmd"};
2387 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2388 @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd);
2390 return () if ($?
!= 0);
2399 @parms = grep(!$saw{$_}++, @parms);
2407 @parms = sort @parms;
2408 @parms = grep(!$saw{$_}++, @parms);
2412 sub clean_file_emails
{
2413 my (@file_emails) = @_;
2414 my @fmt_emails = ();
2416 foreach my $email (@file_emails) {
2417 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2418 my ($name, $address) = parse_email
($email);
2419 if ($name eq '"[,\.]"') {
2423 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2425 my $first = $nw[@nw - 3];
2426 my $middle = $nw[@nw - 2];
2427 my $last = $nw[@nw - 1];
2429 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2430 (length($first) == 2 && substr($first, -1) eq ".")) ||
2431 (length($middle) == 1 ||
2432 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2433 $name = "$first $middle $last";
2435 $name = "$middle $last";
2439 if (substr($name, -1) =~ /[,\.]/) {
2440 $name = substr($name, 0, length($name) - 1);
2441 } elsif (substr($name, -2) =~ /[,\.]"/) {
2442 $name = substr($name, 0, length($name) - 2) . '"';
2445 if (substr($name, 0, 1) =~ /[,\.]/) {
2446 $name = substr($name, 1, length($name) - 1);
2447 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2448 $name = '"' . substr($name, 2, length($name) - 2);
2451 my $fmt_email = format_email
($name, $address, $email_usename);
2452 push(@fmt_emails, $fmt_email);
2462 my ($address, $role) = @
$_;
2463 if (!$saw{$address}) {
2464 if ($output_roles) {
2465 push(@lines, "$address ($role)");
2467 push(@lines, $address);
2479 if ($output_multiline) {
2480 foreach my $line (@parms) {
2484 print(join($output_separator, @parms));
2492 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2493 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2494 # This regexp will only work on addresses which have had comments stripped
2495 # and replaced with rfc822_lwsp.
2497 my $specials = '()<>@,;:\\\\".\\[\\]';
2498 my $controls = '\\000-\\037\\177';
2500 my $dtext = "[^\\[\\]\\r\\\\]";
2501 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2503 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2505 # Use zero-width assertion to spot the limit of an atom. A simple
2506 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2507 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2508 my $word = "(?:$atom|$quoted_string)";
2509 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2511 my $sub_domain = "(?:$atom|$domain_literal)";
2512 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2514 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2516 my $phrase = "$word*";
2517 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2518 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2519 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2521 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2522 my $address = "(?:$mailbox|$group)";
2524 return "$rfc822_lwsp*$address";
2527 sub rfc822_strip_comments
{
2529 # Recursively remove comments, and replace with a single space. The simpler
2530 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2531 # chars in atoms, for example.
2533 while ($s =~ s
/^((?
:[^"\\]|\\.)*
2534 (?:"(?
:[^"\\]|\\.)*"(?
:[^"\\]|\\.)*)*)
2535 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2539 # valid: returns true if the parameter is an RFC822 valid address
2542 my $s = rfc822_strip_comments(shift);
2545 $rfc822re = make_rfc822re();
2548 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2551 # validlist: In scalar context, returns true if the parameter is an RFC822
2552 # valid list of addresses.
2554 # In list context, returns an empty list on failure (an invalid
2555 # address was found); otherwise a list whose first element is the
2556 # number of addresses found and whose remaining elements are the
2557 # addresses. This is needed to disambiguate failure (invalid)
2558 # from success with no addresses found, because an empty string is
2561 sub rfc822_validlist {
2562 my $s = rfc822_strip_comments(shift);
2565 $rfc822re = make_rfc822re();
2567 # * null list items are valid according to the RFC
2568 # * the '1' business is to aid in distinguishing failure from no results
2571 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2572 $s =~ m/^$rfc822_char*$/) {
2573 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2576 return wantarray ? (scalar(@r), @r) : 1;
2578 return wantarray ? () : 0;