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;
61 my $email_file_emails = 0;
62 my $from_filename = 0;
63 my $pattern_depth = 0;
64 my $self_test = undef;
67 my $find_maintainer_files = 0;
74 my @fixes = (); # If a patch description includes Fixes: lines
79 my %commit_author_hash;
80 my %commit_signer_hash;
82 my @penguin_chief = ();
83 push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
84 #Andrew wants in on most everything - 2009/01/14
85 #push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
87 my @penguin_chief_names = ();
88 foreach my $chief (@penguin_chief) {
89 if ($chief =~ m/^(.*):(.*)/) {
92 push(@penguin_chief_names, $chief_name);
95 my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
97 # Signature types of people who are either
98 # a) responsible for the code in question, or
99 # b) familiar enough with it to give relevant feedback
100 my @signature_tags = ();
101 push(@signature_tags, "Signed-off-by:");
102 push(@signature_tags, "Reviewed-by:");
103 push(@signature_tags, "Acked-by:");
105 my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
107 # rfc822 email address - preloaded methods go here.
108 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
109 my $rfc822_char = '[\\000-\\377]';
111 # VCS command support: class-like functions and strings
116 "execute_cmd" => \&git_execute_cmd,
117 "available" => '(which("git") ne "") && (-e ".git")',
118 "find_signers_cmd" =>
119 "git log --no-color --follow --since=\$email_git_since " .
120 '--numstat --no-merges ' .
121 '--format="GitCommit: %H%n' .
122 'GitAuthor: %an <%ae>%n' .
127 "find_commit_signers_cmd" =>
128 "git log --no-color " .
130 '--format="GitCommit: %H%n' .
131 'GitAuthor: %an <%ae>%n' .
136 "find_commit_author_cmd" =>
137 "git log --no-color " .
139 '--format="GitCommit: %H%n' .
140 'GitAuthor: %an <%ae>%n' .
142 'GitSubject: %s%n"' .
144 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
145 "blame_file_cmd" => "git blame -l \$file",
146 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
147 "blame_commit_pattern" => "^([0-9a-f]+) ",
148 "author_pattern" => "^GitAuthor: (.*)",
149 "subject_pattern" => "^GitSubject: (.*)",
150 "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
151 "file_exists_cmd" => "git ls-files \$file",
152 "list_files_cmd" => "git ls-files \$file",
156 "execute_cmd" => \&hg_execute_cmd,
157 "available" => '(which("hg") ne "") && (-d ".hg")',
158 "find_signers_cmd" =>
159 "hg log --date=\$email_hg_since " .
160 "--template='HgCommit: {node}\\n" .
161 "HgAuthor: {author}\\n" .
162 "HgSubject: {desc}\\n'" .
164 "find_commit_signers_cmd" =>
166 "--template='HgSubject: {desc}\\n'" .
168 "find_commit_author_cmd" =>
170 "--template='HgCommit: {node}\\n" .
171 "HgAuthor: {author}\\n" .
172 "HgSubject: {desc|firstline}\\n'" .
174 "blame_range_cmd" => "", # not supported
175 "blame_file_cmd" => "hg blame -n \$file",
176 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
177 "blame_commit_pattern" => "^([ 0-9a-f]+):",
178 "author_pattern" => "^HgAuthor: (.*)",
179 "subject_pattern" => "^HgSubject: (.*)",
180 "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$",
181 "file_exists_cmd" => "hg files \$file",
182 "list_files_cmd" => "hg manifest -R \$file",
185 my $conf = which_conf(".get_maintainer.conf");
188 open(my $conffile, '<', "$conf")
189 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
191 while (<$conffile>) {
194 $line =~ s/\s*\n?$//g;
198 next if ($line =~ m/^\s*#/);
199 next if ($line =~ m/^\s*$/);
201 my @words = split(" ", $line);
202 foreach my $word (@words) {
203 last if ($word =~ m/^#/);
204 push (@conf_args, $word);
208 unshift(@ARGV, @conf_args) if @conf_args;
211 my @ignore_emails = ();
212 my $ignore_file = which_conf(".get_maintainer.ignore");
213 if (-f $ignore_file) {
214 open(my $ignore, '<', "$ignore_file")
215 or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n";
219 $line =~ s/\s*\n?$//;
224 next if ($line =~ m/^\s*$/);
225 if (rfc822_valid($line)) {
226 push(@ignore_emails, $line);
234 if ($_ =~ /^-{1,2}self-test(?:=|$)/) {
235 die "$P: using --self-test does not allow any other option or argument\n";
242 'git!' => \$email_git,
243 'git-all-signature-types!' => \$email_git_all_signature_types,
244 'git-blame!' => \$email_git_blame,
245 'git-blame-signatures!' => \$email_git_blame_signatures,
246 'git-fallback!' => \$email_git_fallback,
247 'git-chief-penguins!' => \$email_git_penguin_chiefs,
248 'git-min-signatures=i' => \$email_git_min_signatures,
249 'git-max-maintainers=i' => \$email_git_max_maintainers,
250 'git-min-percent=i' => \$email_git_min_percent,
251 'git-since=s' => \$email_git_since,
252 'hg-since=s' => \$email_hg_since,
253 'i|interactive!' => \$interactive,
254 'remove-duplicates!' => \$email_remove_duplicates,
255 'mailmap!' => \$email_use_mailmap,
256 'm!' => \$email_maintainer,
257 'r!' => \$email_reviewer,
258 'n!' => \$email_usename,
259 'l!' => \$email_list,
260 'fixes!' => \$email_fixes,
261 'moderated!' => \$email_moderated_list,
262 's!' => \$email_subscriber_list,
263 'multiline!' => \$output_multiline,
264 'roles!' => \$output_roles,
265 'rolestats!' => \$output_rolestats,
266 'separator=s' => \$output_separator,
267 'subsystem!' => \$subsystem,
268 'status!' => \$status,
272 'letters=s' => \$letters,
273 'pattern-depth=i' => \$pattern_depth,
274 'k|keywords!' => \$keywords,
275 'sections!' => \$sections,
276 'fe|file-emails!' => \$email_file_emails,
277 'f|file' => \$from_filename,
278 'find-maintainer-files' => \$find_maintainer_files,
279 'mpath|maintainer-path=s' => \$maintainer_path,
280 'self-test:s' => \$self_test,
281 'v|version' => \$version,
282 'h|help|usage' => \$help,
284 die "$P: invalid argument - use --help if necessary\n";
293 print("${P} ${V}\n");
297 if (defined $self_test) {
298 read_all_maintainer_files();
303 if (-t STDIN && !@ARGV) {
304 # We're talking to a terminal, but have no command line arguments.
305 die "$P: missing patchfile or -f file - use --help if necessary\n";
308 $output_multiline = 0 if ($output_separator ne ", ");
309 $output_rolestats = 1 if ($interactive);
310 $output_roles = 1 if ($output_rolestats);
312 if ($sections || $letters ne "") {
323 my $selections = $email + $scm + $status + $subsystem + $web;
324 if ($selections == 0) {
325 die "$P: Missing required option: email, scm, status, subsystem or web\n";
330 ($email_maintainer + $email_reviewer +
331 $email_list + $email_subscriber_list +
332 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
333 die "$P: Please select at least 1 email option\n";
336 if ($tree && !top_of_kernel_tree($lk_path)) {
337 die "$P: The current directory does not appear to be "
338 . "a linux kernel source tree.\n";
341 ## Read MAINTAINERS for type/value pairs
346 my @self_test_info = ();
348 sub read_maintainer_file {
351 open (my $maint, '<', "$file")
352 or die "$P: Can't open MAINTAINERS file '$file': $!\n";
358 if ($line =~ m/^([A-Z]):\s*(.*)/) {
362 ##Filename pattern matching
363 if ($type eq "F" || $type eq "X") {
364 $value =~ s@\.@\\\.@g; ##Convert . to \.
365 $value =~ s/\*/\.\*/g; ##Convert * to .*
366 $value =~ s/\?/\./g; ##Convert ? to .
367 ##if pattern is a directory and it lacks a trailing slash, add one
369 $value =~ s@([^/])$@$1/@;
371 } elsif ($type eq "K") {
372 $keyword_hash{@typevalue} = $value;
374 push(@typevalue, "$type:$value");
375 } elsif (!(/^\s*$/ || /^\s*\#/)) {
376 push(@typevalue, $line);
378 if (defined $self_test) {
379 push(@self_test_info, {file=>$file, linenr=>$i, line=>$line});
386 sub find_is_maintainer_file {
388 return if ($file !~ m@/MAINTAINERS$@);
389 $file = $File::Find::name;
390 return if (! -f $file);
391 push(@mfiles, $file);
394 sub find_ignore_git {
395 return grep { $_ !~ /^\.git$/; } @_;
398 read_all_maintainer_files();
400 sub read_all_maintainer_files {
401 my $path = "${lk_path}MAINTAINERS";
402 if (defined $maintainer_path) {
403 $path = $maintainer_path;
404 # Perl Cookbook tilde expansion if necessary
405 $path =~ s@^~([^/]*)@ $1 ? (getpwnam($1))[7] : ( $ENV{HOME} || $ENV{LOGDIR} || (getpwuid($<))[7])@ex;
409 $path .= '/' if ($path !~ m@/$@);
410 if ($find_maintainer_files) {
411 find( { wanted => \&find_is_maintainer_file,
412 preprocess => \&find_ignore_git,
416 opendir(DIR, "$path") or die $!;
417 my @files = readdir(DIR);
419 foreach my $file (@files) {
420 push(@mfiles, "$path$file") if ($file !~ /^\./);
423 } elsif (-f "$path") {
424 push(@mfiles, "$path");
426 die "$P: MAINTAINER file not found '$path'\n";
428 die "$P: No MAINTAINER files found in '$path'\n" if (scalar(@mfiles) == 0);
429 foreach my $file (@mfiles) {
430 read_maintainer_file("$file");
434 sub maintainers_in_file {
437 return if ($file =~ m@\bMAINTAINERS$@);
439 if (-f $file && ($email_file_emails || $file =~ /\.yaml$/)) {
440 open(my $f, '<', $file)
441 or die "$P: Can't open $file: $!\n";
442 my $text = do { local($/) ; <$f> };
445 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;
446 push(@file_emails, clean_file_emails(@poss_addr));
451 # Read mail address map
464 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
466 open(my $mailmap_file, '<', "${lk_path}.mailmap")
467 or warn "$P: Can't open .mailmap: $!\n";
469 while (<$mailmap_file>) {
470 s/#.*$//; #strip comments
471 s/^\s+|\s+$//g; #trim
473 next if (/^\s*$/); #skip empty lines
474 #entries have one of the following formats:
477 # name1 <mail1> <mail2>
478 # name1 <mail1> name2 <mail2>
479 # (see man git-shortlog)
481 if (/^([^<]+)<([^>]+)>$/) {
485 $real_name =~ s/\s+$//;
486 ($real_name, $address) = parse_email("$real_name <$address>");
487 $mailmap->{names}->{$address} = $real_name;
489 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
490 my $real_address = $1;
491 my $wrong_address = $2;
493 $mailmap->{addresses}->{$wrong_address} = $real_address;
495 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
497 my $real_address = $2;
498 my $wrong_address = $3;
500 $real_name =~ s/\s+$//;
501 ($real_name, $real_address) =
502 parse_email("$real_name <$real_address>");
503 $mailmap->{names}->{$wrong_address} = $real_name;
504 $mailmap->{addresses}->{$wrong_address} = $real_address;
506 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
508 my $real_address = $2;
510 my $wrong_address = $4;
512 $real_name =~ s/\s+$//;
513 ($real_name, $real_address) =
514 parse_email("$real_name <$real_address>");
516 $wrong_name =~ s/\s+$//;
517 ($wrong_name, $wrong_address) =
518 parse_email("$wrong_name <$wrong_address>");
520 my $wrong_email = format_email($wrong_name, $wrong_address, 1);
521 $mailmap->{names}->{$wrong_email} = $real_name;
522 $mailmap->{addresses}->{$wrong_email} = $real_address;
525 close($mailmap_file);
528 ## use the filenames on the command line or find the filenames in the patchfiles
531 push(@ARGV, "&STDIN");
534 foreach my $file (@ARGV) {
535 if ($file ne "&STDIN") {
536 $file = canonpath($file);
537 ##if $file is a directory and it lacks a trailing slash, add one
539 $file =~ s@([^/])$@$1/@;
540 } elsif (!(-f $file)) {
541 die "$P: file '${file}' not found\n";
544 if ($from_filename || ($file ne "&STDIN" && vcs_file_exists($file))) {
545 $file =~ s/^\Q${cur_path}\E//; #strip any absolute path
546 $file =~ s/^\Q${lk_path}\E//; #or the path to the lk tree
548 if ($file ne "MAINTAINERS" && -f $file && $keywords) {
549 open(my $f, '<', $file)
550 or die "$P: Can't open $file: $!\n";
551 my $text = do { local($/) ; <$f> };
554 foreach my $line (keys %keyword_hash) {
555 if ($text =~ m/$keyword_hash{$line}/x) {
556 push(@keyword_tvi, $line);
562 my $file_cnt = @files;
565 open(my $patch, "< $file")
566 or die "$P: Can't open $file: $!\n";
568 # We can check arbitrary information before the patch
569 # like the commit message, mail headers, etc...
570 # This allows us to match arbitrary keywords against any part
571 # of a git format-patch generated file (subject tags, etc...)
573 my $patch_prefix = ""; #Parsing the intro
577 if (m/^ mode change [0-7]+ => [0-7]+ (\S+)\s*$/) {
579 push(@files, $filename);
580 } elsif (m/^rename (?:from|to) (\S+)\s*$/) {
582 push(@files, $filename);
583 } elsif (m/^diff --git a\/(\S+) b\/(\S+)\s*$/) {
586 push(@files, $filename1);
587 push(@files, $filename2);
588 } elsif (m/^Fixes:\s+([0-9a-fA-F]{6,40})/) {
589 push(@fixes, $1) if ($email_fixes);
590 } elsif (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
592 $filename =~ s@^[^/]*/@@;
594 $lastfile = $filename;
595 push(@files, $filename);
596 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
597 } elsif (m/^\@\@ -(\d+),(\d+)/) {
598 if ($email_git_blame) {
599 push(@range, "$lastfile:$1:$2");
601 } elsif ($keywords) {
602 foreach my $line (keys %keyword_hash) {
603 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
604 push(@keyword_tvi, $line);
611 if ($file_cnt == @files) {
612 warn "$P: file '${file}' doesn't appear to be a patch. "
613 . "Add -f to options?\n";
615 @files = sort_and_uniq(@files);
619 @file_emails = uniq(@file_emails);
620 @fixes = uniq(@fixes);
623 my %email_hash_address;
631 my %deduplicate_name_hash = ();
632 my %deduplicate_address_hash = ();
634 my @maintainers = get_maintainers();
636 @maintainers = merge_email(@maintainers);
637 output(@maintainers);
646 @status = uniq(@status);
651 @subsystem = uniq(@subsystem);
666 my @section_headers = ();
669 @lsfiles = vcs_list_files($lk_path);
671 for my $x (@self_test_info) {
674 ## Section header duplication and missing section content
675 if (($self_test eq "" || $self_test =~ /\bsections\b/) &&
676 $x->{line} =~ /^\S[^:]/ &&
677 defined $self_test_info[$index] &&
678 $self_test_info[$index]->{line} =~ /^([A-Z]):\s*\S/) {
683 if (grep(m@^\Q$x->{line}\E@, @section_headers)) {
684 print("$x->{file}:$x->{linenr}: warning: duplicate section header\t$x->{line}\n");
686 push(@section_headers, $x->{line});
688 my $nextline = $index;
689 while (defined $self_test_info[$nextline] &&
690 $self_test_info[$nextline]->{line} =~ /^([A-Z]):\s*(\S.*)/) {
696 } elsif ($type eq "F" || $type eq "N") {
698 } elsif ($type eq "M" || $type eq "R" || $type eq "L") {
703 if (!$has_ML && $status !~ /orphan|obsolete/i) {
704 print("$x->{file}:$x->{linenr}: warning: section without email address\t$x->{line}\n");
707 print("$x->{file}:$x->{linenr}: warning: section without status \t$x->{line}\n");
710 print("$x->{file}:$x->{linenr}: warning: section without file pattern\t$x->{line}\n");
714 next if ($x->{line} !~ /^([A-Z]):\s*(.*)/);
719 ## Filename pattern matching
720 if (($type eq "F" || $type eq "X") &&
721 ($self_test eq "" || $self_test =~ /\bpatterns\b/)) {
722 $value =~ s@\.@\\\.@g; ##Convert . to \.
723 $value =~ s/\*/\.\*/g; ##Convert * to .*
724 $value =~ s/\?/\./g; ##Convert ? to .
725 ##if pattern is a directory and it lacks a trailing slash, add one
727 $value =~ s@([^/])$@$1/@;
729 if (!grep(m@^$value@, @lsfiles)) {
730 print("$x->{file}:$x->{linenr}: warning: no file matches\t$x->{line}\n");
734 } elsif (($type eq "W" || $type eq "Q" || $type eq "B") &&
735 $value =~ /^https?:/ &&
736 ($self_test eq "" || $self_test =~ /\blinks\b/)) {
737 next if (grep(m@^\Q$value\E$@, @good_links));
739 if (grep(m@^\Q$value\E$@, @bad_links)) {
742 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $value`;
744 push(@good_links, $value);
746 push(@bad_links, $value);
751 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
755 } elsif ($type eq "T" &&
756 ($self_test eq "" || $self_test =~ /\bscm\b/)) {
757 next if (grep(m@^\Q$value\E$@, @good_links));
759 if (grep(m@^\Q$value\E$@, @bad_links)) {
761 } elsif ($value !~ /^(?:git|quilt|hg)\s+\S/) {
762 print("$x->{file}:$x->{linenr}: warning: malformed entry\t$x->{line}\n");
763 } elsif ($value =~ /^git\s+(\S+)(\s+([^\(]+\S+))?/) {
767 my $output = `git ls-remote --exit-code -h "$url" $branch > /dev/null 2>&1`;
769 push(@good_links, $value);
771 push(@bad_links, $value);
774 } elsif ($value =~ /^(?:quilt|hg)\s+(https?:\S+)/) {
776 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $url`;
778 push(@good_links, $value);
780 push(@bad_links, $value);
785 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
791 sub ignore_email_address {
794 foreach my $ignore (@ignore_emails) {
795 return 1 if ($ignore eq $address);
801 sub range_is_maintained {
802 my ($start, $end) = @_;
804 for (my $i = $start; $i < $end; $i++) {
805 my $line = $typevalue[$i];
806 if ($line =~ m/^([A-Z]):\s*(.*)/) {
810 if ($value =~ /(maintain|support)/i) {
819 sub range_has_maintainer {
820 my ($start, $end) = @_;
822 for (my $i = $start; $i < $end; $i++) {
823 my $line = $typevalue[$i];
824 if ($line =~ m/^([A-Z]):\s*(.*)/) {
835 sub get_maintainers {
836 %email_hash_name = ();
837 %email_hash_address = ();
838 %commit_author_hash = ();
839 %commit_signer_hash = ();
847 %deduplicate_name_hash = ();
848 %deduplicate_address_hash = ();
849 if ($email_git_all_signature_types) {
850 $signature_pattern = "(.+?)[Bb][Yy]:";
852 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
855 # Find responsible parties
857 my %exact_pattern_match_hash = ();
859 foreach my $file (@files) {
862 my $tvi = find_first_section();
863 while ($tvi < @typevalue) {
864 my $start = find_starting_index($tvi);
865 my $end = find_ending_index($tvi);
869 #Do not match excluded file patterns
871 for ($i = $start; $i < $end; $i++) {
872 my $line = $typevalue[$i];
873 if ($line =~ m/^([A-Z]):\s*(.*)/) {
877 if (file_match_pattern($file, $value)) {
886 for ($i = $start; $i < $end; $i++) {
887 my $line = $typevalue[$i];
888 if ($line =~ m/^([A-Z]):\s*(.*)/) {
892 if (file_match_pattern($file, $value)) {
893 my $value_pd = ($value =~ tr@/@@);
894 my $file_pd = ($file =~ tr@/@@);
895 $value_pd++ if (substr($value,-1,1) ne "/");
896 $value_pd = -1 if ($value =~ /^\.\*/);
897 if ($value_pd >= $file_pd &&
898 range_is_maintained($start, $end) &&
899 range_has_maintainer($start, $end)) {
900 $exact_pattern_match_hash{$file} = 1;
902 if ($pattern_depth == 0 ||
903 (($file_pd - $value_pd) < $pattern_depth)) {
904 $hash{$tvi} = $value_pd;
907 } elsif ($type eq 'N') {
908 if ($file =~ m/$value/x) {
918 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
919 add_categories($line);
922 my $start = find_starting_index($line);
923 my $end = find_ending_index($line);
924 for ($i = $start; $i < $end; $i++) {
925 my $line = $typevalue[$i];
926 if ($line =~ /^[FX]:/) { ##Restore file patterns
927 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
928 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
929 $line =~ s/\\\./\./g; ##Convert \. to .
930 $line =~ s/\.\*/\*/g; ##Convert .* to *
932 my $count = $line =~ s/^([A-Z]):/$1:\t/g;
933 if ($letters eq "" || (!$count || $letters =~ /$1/i)) {
941 maintainers_in_file($file);
945 @keyword_tvi = sort_and_uniq(@keyword_tvi);
946 foreach my $line (@keyword_tvi) {
947 add_categories($line);
951 foreach my $email (@email_to, @list_to) {
952 $email->[0] = deduplicate_email($email->[0]);
955 foreach my $file (@files) {
957 ($email_git || ($email_git_fallback &&
958 !$exact_pattern_match_hash{$file}))) {
959 vcs_file_signoffs($file);
961 if ($email && $email_git_blame) {
962 vcs_file_blame($file);
967 foreach my $chief (@penguin_chief) {
968 if ($chief =~ m/^(.*):(.*)/) {
971 $email_address = format_email($1, $2, $email_usename);
972 if ($email_git_penguin_chiefs) {
973 push(@email_to, [$email_address, 'chief penguin']);
975 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
980 foreach my $email (@file_emails) {
981 my ($name, $address) = parse_email($email);
983 my $tmp_email = format_email($name, $address, $email_usename);
984 push_email_address($tmp_email, '');
985 add_role($tmp_email, 'in file');
989 foreach my $fix (@fixes) {
990 vcs_add_commit_signers($fix, "blamed_fixes");
994 if ($email || $email_list) {
996 @to = (@to, @email_to);
999 @to = (@to, @list_to);
1004 @to = interactive_get_maintainers(\@to);
1010 sub file_match_pattern {
1011 my ($file, $pattern) = @_;
1012 if (substr($pattern, -1) eq "/") {
1013 if ($file =~ m@^$pattern@) {
1017 if ($file =~ m@^$pattern@) {
1018 my $s1 = ($file =~ tr@/@@);
1019 my $s2 = ($pattern =~ tr@/@@);
1030 usage: $P [options] patchfile
1031 $P [options] -f file|directory
1034 MAINTAINER field selection options:
1035 --email => print email address(es) if any
1036 --git => include recent git \*-by: signers
1037 --git-all-signature-types => include signers regardless of signature type
1038 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
1039 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
1040 --git-chief-penguins => include ${penguin_chiefs}
1041 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
1042 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
1043 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
1044 --git-blame => use git blame to find modified commits for patch or file
1045 --git-blame-signatures => when used with --git-blame, also include all commit signers
1046 --git-since => git history to use (default: $email_git_since)
1047 --hg-since => hg history to use (default: $email_hg_since)
1048 --interactive => display a menu (mostly useful if used with the --git option)
1049 --m => include maintainer(s) if any
1050 --r => include reviewer(s) if any
1051 --n => include name 'Full Name <addr\@domain.tld>'
1052 --l => include list(s) if any
1053 --moderated => include moderated lists(s) if any (default: true)
1054 --s => include subscriber only list(s) if any (default: false)
1055 --remove-duplicates => minimize duplicate email names/addresses
1056 --roles => show roles (status:subsystem, git-signer, list, etc...)
1057 --rolestats => show roles and statistics (commits/total_commits, %)
1058 --file-emails => add email addresses found in -f file (default: 0 (off))
1059 --fixes => for patches, add signatures of commits with 'Fixes: <commit>' (default: 1 (on))
1060 --scm => print SCM tree(s) if any
1061 --status => print status if any
1062 --subsystem => print subsystem name if any
1063 --web => print website(s) if any
1065 Output type options:
1066 --separator [, ] => separator for multiple entries on 1 line
1067 using --separator also sets --nomultiline if --separator is not [, ]
1068 --multiline => print 1 entry per line
1071 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
1072 --keywords => scan patch for keywords (default: $keywords)
1073 --sections => print all of the subsystem sections with pattern matches
1074 --letters => print all matching 'letter' types from all matching sections
1075 --mailmap => use .mailmap file (default: $email_use_mailmap)
1076 --no-tree => run without a kernel tree
1077 --self-test => show potential issues with MAINTAINERS file content
1078 --version => show version
1079 --help => show this help information
1082 [--email --tree --nogit --git-fallback --m --r --n --l --multiline
1083 --pattern-depth=0 --remove-duplicates --rolestats]
1086 Using "-f directory" may give unexpected results:
1087 Used with "--git", git signators for _all_ files in and below
1088 directory are examined as git recurses directories.
1089 Any specified X: (exclude) pattern matches are _not_ ignored.
1090 Used with "--nogit", directory is used as a pattern match,
1091 no individual file within the directory or subdirectory
1093 Used with "--git-blame", does not iterate all files in directory
1094 Using "--git-blame" is slow and may add old committers and authors
1095 that are no longer active maintainers to the output.
1096 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
1097 other automated tools that expect only ["name"] <email address>
1098 may not work because of additional output after <email address>.
1099 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
1100 not the percentage of the entire file authored. # of commits is
1101 not a good measure of amount of code authored. 1 major commit may
1102 contain a thousand lines, 5 trivial commits may modify a single line.
1103 If git is not installed, but mercurial (hg) is installed and an .hg
1104 repository exists, the following options apply to mercurial:
1106 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
1108 Use --hg-since not --git-since to control date selection
1109 File ".get_maintainer.conf", if it exists in the linux kernel source root
1110 directory, can change whatever get_maintainer defaults are desired.
1111 Entries in this file can be any command line argument.
1112 This file is prepended to any additional command line arguments.
1113 Multiple lines and # comments are allowed.
1114 Most options have both positive and negative forms.
1115 The negative forms for --<foo> are --no<foo> and --no-<foo>.
1120 sub top_of_kernel_tree {
1123 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
1126 if ( (-f "${lk_path}COPYING")
1127 && (-f "${lk_path}CREDITS")
1128 && (-f "${lk_path}Kbuild")
1129 && (-e "${lk_path}MAINTAINERS")
1130 && (-f "${lk_path}Makefile")
1131 && (-f "${lk_path}README")
1132 && (-d "${lk_path}Documentation")
1133 && (-d "${lk_path}arch")
1134 && (-d "${lk_path}include")
1135 && (-d "${lk_path}drivers")
1136 && (-d "${lk_path}fs")
1137 && (-d "${lk_path}init")
1138 && (-d "${lk_path}ipc")
1139 && (-d "${lk_path}kernel")
1140 && (-d "${lk_path}lib")
1141 && (-d "${lk_path}scripts")) {
1148 my ($formatted_email) = @_;
1153 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
1156 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
1158 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
1162 $name =~ s/^\s+|\s+$//g;
1163 $name =~ s/^\"|\"$//g;
1164 $address =~ s/^\s+|\s+$//g;
1166 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
1167 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1168 $name = "\"$name\"";
1171 return ($name, $address);
1175 my ($name, $address, $usename) = @_;
1177 my $formatted_email;
1179 $name =~ s/^\s+|\s+$//g;
1180 $name =~ s/^\"|\"$//g;
1181 $address =~ s/^\s+|\s+$//g;
1183 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
1184 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1185 $name = "\"$name\"";
1189 if ("$name" eq "") {
1190 $formatted_email = "$address";
1192 $formatted_email = "$name <$address>";
1195 $formatted_email = $address;
1198 return $formatted_email;
1201 sub find_first_section {
1204 while ($index < @typevalue) {
1205 my $tv = $typevalue[$index];
1206 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
1215 sub find_starting_index {
1218 while ($index > 0) {
1219 my $tv = $typevalue[$index];
1220 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1229 sub find_ending_index {
1232 while ($index < @typevalue) {
1233 my $tv = $typevalue[$index];
1234 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1243 sub get_subsystem_name {
1246 my $start = find_starting_index($index);
1248 my $subsystem = $typevalue[$start];
1249 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
1250 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
1251 $subsystem =~ s/\s*$//;
1252 $subsystem = $subsystem . "...";
1257 sub get_maintainer_role {
1261 my $start = find_starting_index($index);
1262 my $end = find_ending_index($index);
1264 my $role = "unknown";
1265 my $subsystem = get_subsystem_name($index);
1267 for ($i = $start + 1; $i < $end; $i++) {
1268 my $tv = $typevalue[$i];
1269 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1272 if ($ptype eq "S") {
1279 if ($role eq "supported") {
1280 $role = "supporter";
1281 } elsif ($role eq "maintained") {
1282 $role = "maintainer";
1283 } elsif ($role eq "odd fixes") {
1284 $role = "odd fixer";
1285 } elsif ($role eq "orphan") {
1286 $role = "orphan minder";
1287 } elsif ($role eq "obsolete") {
1288 $role = "obsolete minder";
1289 } elsif ($role eq "buried alive in reporters") {
1290 $role = "chief penguin";
1293 return $role . ":" . $subsystem;
1299 my $subsystem = get_subsystem_name($index);
1301 if ($subsystem eq "THE REST") {
1308 sub add_categories {
1312 my $start = find_starting_index($index);
1313 my $end = find_ending_index($index);
1315 push(@subsystem, $typevalue[$start]);
1317 for ($i = $start + 1; $i < $end; $i++) {
1318 my $tv = $typevalue[$i];
1319 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1322 if ($ptype eq "L") {
1323 my $list_address = $pvalue;
1324 my $list_additional = "";
1325 my $list_role = get_list_role($i);
1327 if ($list_role ne "") {
1328 $list_role = ":" . $list_role;
1330 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1332 $list_additional = $2;
1334 if ($list_additional =~ m/subscribers-only/) {
1335 if ($email_subscriber_list) {
1336 if (!$hash_list_to{lc($list_address)}) {
1337 $hash_list_to{lc($list_address)} = 1;
1338 push(@list_to, [$list_address,
1339 "subscriber list${list_role}"]);
1344 if (!$hash_list_to{lc($list_address)}) {
1345 if ($list_additional =~ m/moderated/) {
1346 if ($email_moderated_list) {
1347 $hash_list_to{lc($list_address)} = 1;
1348 push(@list_to, [$list_address,
1349 "moderated list${list_role}"]);
1352 $hash_list_to{lc($list_address)} = 1;
1353 push(@list_to, [$list_address,
1354 "open list${list_role}"]);
1359 } elsif ($ptype eq "M") {
1360 if ($email_maintainer) {
1361 my $role = get_maintainer_role($i);
1362 push_email_addresses($pvalue, $role);
1364 } elsif ($ptype eq "R") {
1365 if ($email_reviewer) {
1366 my $subsystem = get_subsystem_name($i);
1367 push_email_addresses($pvalue, "reviewer:$subsystem");
1369 } elsif ($ptype eq "T") {
1370 push(@scm, $pvalue);
1371 } elsif ($ptype eq "W") {
1372 push(@web, $pvalue);
1373 } elsif ($ptype eq "S") {
1374 push(@status, $pvalue);
1381 my ($name, $address) = @_;
1383 return 1 if (($name eq "") && ($address eq ""));
1384 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1385 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1390 sub push_email_address {
1391 my ($line, $role) = @_;
1393 my ($name, $address) = parse_email($line);
1395 if ($address eq "") {
1399 if (!$email_remove_duplicates) {
1400 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1401 } elsif (!email_inuse($name, $address)) {
1402 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1403 $email_hash_name{lc($name)}++ if ($name ne "");
1404 $email_hash_address{lc($address)}++;
1410 sub push_email_addresses {
1411 my ($address, $role) = @_;
1413 my @address_list = ();
1415 if (rfc822_valid($address)) {
1416 push_email_address($address, $role);
1417 } elsif (@address_list = rfc822_validlist($address)) {
1418 my $array_count = shift(@address_list);
1419 while (my $entry = shift(@address_list)) {
1420 push_email_address($entry, $role);
1423 if (!push_email_address($address, $role)) {
1424 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1430 my ($line, $role) = @_;
1432 my ($name, $address) = parse_email($line);
1433 my $email = format_email($name, $address, $email_usename);
1435 foreach my $entry (@email_to) {
1436 if ($email_remove_duplicates) {
1437 my ($entry_name, $entry_address) = parse_email($entry->[0]);
1438 if (($name eq $entry_name || $address eq $entry_address)
1439 && ($role eq "" || !($entry->[1] =~ m/$role/))
1441 if ($entry->[1] eq "") {
1442 $entry->[1] = "$role";
1444 $entry->[1] = "$entry->[1],$role";
1448 if ($email eq $entry->[0]
1449 && ($role eq "" || !($entry->[1] =~ m/$role/))
1451 if ($entry->[1] eq "") {
1452 $entry->[1] = "$role";
1454 $entry->[1] = "$entry->[1],$role";
1464 foreach my $path (split(/:/, $ENV{PATH})) {
1465 if (-e "$path/$bin") {
1466 return "$path/$bin";
1476 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1477 if (-e "$path/$conf") {
1478 return "$path/$conf";
1488 my ($name, $address) = parse_email($line);
1489 my $email = format_email($name, $address, 1);
1490 my $real_name = $name;
1491 my $real_address = $address;
1493 if (exists $mailmap->{names}->{$email} ||
1494 exists $mailmap->{addresses}->{$email}) {
1495 if (exists $mailmap->{names}->{$email}) {
1496 $real_name = $mailmap->{names}->{$email};
1498 if (exists $mailmap->{addresses}->{$email}) {
1499 $real_address = $mailmap->{addresses}->{$email};
1502 if (exists $mailmap->{names}->{$address}) {
1503 $real_name = $mailmap->{names}->{$address};
1505 if (exists $mailmap->{addresses}->{$address}) {
1506 $real_address = $mailmap->{addresses}->{$address};
1509 return format_email($real_name, $real_address, 1);
1513 my (@addresses) = @_;
1515 my @mapped_emails = ();
1516 foreach my $line (@addresses) {
1517 push(@mapped_emails, mailmap_email($line));
1519 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1520 return @mapped_emails;
1523 sub merge_by_realname {
1527 foreach my $email (@emails) {
1528 my ($name, $address) = parse_email($email);
1529 if (exists $address_map{$name}) {
1530 $address = $address_map{$name};
1531 $email = format_email($name, $address, 1);
1533 $address_map{$name} = $address;
1538 sub git_execute_cmd {
1542 my $output = `$cmd`;
1543 $output =~ s/^\s*//gm;
1544 @lines = split("\n", $output);
1549 sub hg_execute_cmd {
1553 my $output = `$cmd`;
1554 @lines = split("\n", $output);
1559 sub extract_formatted_signatures {
1560 my (@signature_lines) = @_;
1562 my @type = @signature_lines;
1564 s/\s*(.*):.*/$1/ for (@type);
1567 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1569 ## Reformat email addresses (with names) to avoid badly written signatures
1571 foreach my $signer (@signature_lines) {
1572 $signer = deduplicate_email($signer);
1575 return (\@type, \@signature_lines);
1578 sub vcs_find_signers {
1579 my ($cmd, $file) = @_;
1582 my @signatures = ();
1586 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1588 my $pattern = $VCS_cmds{"commit_pattern"};
1589 my $author_pattern = $VCS_cmds{"author_pattern"};
1590 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1592 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
1594 $commits = grep(/$pattern/, @lines); # of commits
1596 @authors = grep(/$author_pattern/, @lines);
1597 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1598 @stats = grep(/$stat_pattern/, @lines);
1600 # print("stats: <@stats>\n");
1602 return (0, \@signatures, \@authors, \@stats) if !@signatures;
1604 save_commits_by_author(@lines) if ($interactive);
1605 save_commits_by_signer(@lines) if ($interactive);
1607 if (!$email_git_penguin_chiefs) {
1608 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1611 my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
1612 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1614 return ($commits, $signers_ref, $authors_ref, \@stats);
1617 sub vcs_find_author {
1621 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1623 if (!$email_git_penguin_chiefs) {
1624 @lines = grep(!/${penguin_chiefs}/i, @lines);
1627 return @lines if !@lines;
1630 foreach my $line (@lines) {
1631 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1633 my ($name, $address) = parse_email($author);
1634 $author = format_email($name, $address, 1);
1635 push(@authors, $author);
1639 save_commits_by_author(@lines) if ($interactive);
1640 save_commits_by_signer(@lines) if ($interactive);
1645 sub vcs_save_commits {
1650 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1652 foreach my $line (@lines) {
1653 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1666 return @commits if (!(-f $file));
1668 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1669 my @all_commits = ();
1671 $cmd = $VCS_cmds{"blame_file_cmd"};
1672 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1673 @all_commits = vcs_save_commits($cmd);
1675 foreach my $file_range_diff (@range) {
1676 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1678 my $diff_start = $2;
1679 my $diff_length = $3;
1680 next if ("$file" ne "$diff_file");
1681 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1682 push(@commits, $all_commits[$i]);
1686 foreach my $file_range_diff (@range) {
1687 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1689 my $diff_start = $2;
1690 my $diff_length = $3;
1691 next if ("$file" ne "$diff_file");
1692 $cmd = $VCS_cmds{"blame_range_cmd"};
1693 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1694 push(@commits, vcs_save_commits($cmd));
1697 $cmd = $VCS_cmds{"blame_file_cmd"};
1698 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1699 @commits = vcs_save_commits($cmd);
1702 foreach my $commit (@commits) {
1703 $commit =~ s/^\^//g;
1709 my $printed_novcs = 0;
1711 %VCS_cmds = %VCS_cmds_git;
1712 return 1 if eval $VCS_cmds{"available"};
1713 %VCS_cmds = %VCS_cmds_hg;
1714 return 2 if eval $VCS_cmds{"available"};
1716 if (!$printed_novcs) {
1717 warn("$P: No supported VCS found. Add --nogit to options?\n");
1718 warn("Using a git repository produces better results.\n");
1719 warn("Try Linus Torvalds' latest git repository using:\n");
1720 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
1728 return $vcs_used == 1;
1732 return $vcs_used == 2;
1735 sub vcs_add_commit_signers {
1736 return if (!vcs_exists());
1738 my ($commit, $desc) = @_;
1739 my $commit_count = 0;
1740 my $commit_authors_ref;
1741 my $commit_signers_ref;
1743 my @commit_authors = ();
1744 my @commit_signers = ();
1747 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1748 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1750 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, "");
1751 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
1752 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
1754 foreach my $signer (@commit_signers) {
1755 $signer = deduplicate_email($signer);
1758 vcs_assign($desc, 1, @commit_signers);
1761 sub interactive_get_maintainers {
1762 my ($list_ref) = @_;
1763 my @list = @$list_ref;
1772 foreach my $entry (@list) {
1773 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1774 $selected{$count} = 1;
1775 $authored{$count} = 0;
1776 $signed{$count} = 0;
1782 my $print_options = 0;
1787 printf STDERR "\n%1s %2s %-65s",
1788 "*", "#", "email/list and role:stats";
1790 ($email_git_fallback && !$maintained) ||
1792 print STDERR "auth sign";
1795 foreach my $entry (@list) {
1796 my $email = $entry->[0];
1797 my $role = $entry->[1];
1799 $sel = "*" if ($selected{$count});
1800 my $commit_author = $commit_author_hash{$email};
1801 my $commit_signer = $commit_signer_hash{$email};
1804 $authored++ for (@{$commit_author});
1805 $signed++ for (@{$commit_signer});
1806 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1807 printf STDERR "%4d %4d", $authored, $signed
1808 if ($authored > 0 || $signed > 0);
1809 printf STDERR "\n %s\n", $role;
1810 if ($authored{$count}) {
1811 my $commit_author = $commit_author_hash{$email};
1812 foreach my $ref (@{$commit_author}) {
1813 print STDERR " Author: @{$ref}[1]\n";
1816 if ($signed{$count}) {
1817 my $commit_signer = $commit_signer_hash{$email};
1818 foreach my $ref (@{$commit_signer}) {
1819 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1826 my $date_ref = \$email_git_since;
1827 $date_ref = \$email_hg_since if (vcs_is_hg());
1828 if ($print_options) {
1833 Version Control options:
1834 g use git history [$email_git]
1835 gf use git-fallback [$email_git_fallback]
1836 b use git blame [$email_git_blame]
1837 bs use blame signatures [$email_git_blame_signatures]
1838 c# minimum commits [$email_git_min_signatures]
1839 %# min percent [$email_git_min_percent]
1840 d# history to use [$$date_ref]
1841 x# max maintainers [$email_git_max_maintainers]
1842 t all signature types [$email_git_all_signature_types]
1843 m use .mailmap [$email_use_mailmap]
1850 tm toggle maintainers
1851 tg toggle git entries
1852 tl toggle open list entries
1853 ts toggle subscriber list entries
1854 f emails in file [$email_file_emails]
1855 k keywords in file [$keywords]
1856 r remove duplicates [$email_remove_duplicates]
1857 p# pattern match depth [$pattern_depth]
1861 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1863 my $input = <STDIN>;
1868 my @wish = split(/[, ]+/, $input);
1869 foreach my $nr (@wish) {
1871 my $sel = substr($nr, 0, 1);
1872 my $str = substr($nr, 1);
1874 $val = $1 if $str =~ /^(\d+)$/;
1879 $output_rolestats = 0;
1882 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1883 $selected{$nr - 1} = !$selected{$nr - 1};
1884 } elsif ($sel eq "*" || $sel eq '^') {
1886 $toggle = 1 if ($sel eq '*');
1887 for (my $i = 0; $i < $count; $i++) {
1888 $selected{$i} = $toggle;
1890 } elsif ($sel eq "0") {
1891 for (my $i = 0; $i < $count; $i++) {
1892 $selected{$i} = !$selected{$i};
1894 } elsif ($sel eq "t") {
1895 if (lc($str) eq "m") {
1896 for (my $i = 0; $i < $count; $i++) {
1897 $selected{$i} = !$selected{$i}
1898 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1900 } elsif (lc($str) eq "g") {
1901 for (my $i = 0; $i < $count; $i++) {
1902 $selected{$i} = !$selected{$i}
1903 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1905 } elsif (lc($str) eq "l") {
1906 for (my $i = 0; $i < $count; $i++) {
1907 $selected{$i} = !$selected{$i}
1908 if ($list[$i]->[1] =~ /^(open list)/i);
1910 } elsif (lc($str) eq "s") {
1911 for (my $i = 0; $i < $count; $i++) {
1912 $selected{$i} = !$selected{$i}
1913 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1916 } elsif ($sel eq "a") {
1917 if ($val > 0 && $val <= $count) {
1918 $authored{$val - 1} = !$authored{$val - 1};
1919 } elsif ($str eq '*' || $str eq '^') {
1921 $toggle = 1 if ($str eq '*');
1922 for (my $i = 0; $i < $count; $i++) {
1923 $authored{$i} = $toggle;
1926 } elsif ($sel eq "s") {
1927 if ($val > 0 && $val <= $count) {
1928 $signed{$val - 1} = !$signed{$val - 1};
1929 } elsif ($str eq '*' || $str eq '^') {
1931 $toggle = 1 if ($str eq '*');
1932 for (my $i = 0; $i < $count; $i++) {
1933 $signed{$i} = $toggle;
1936 } elsif ($sel eq "o") {
1939 } elsif ($sel eq "g") {
1941 bool_invert(\$email_git_fallback);
1943 bool_invert(\$email_git);
1946 } elsif ($sel eq "b") {
1948 bool_invert(\$email_git_blame_signatures);
1950 bool_invert(\$email_git_blame);
1953 } elsif ($sel eq "c") {
1955 $email_git_min_signatures = $val;
1958 } elsif ($sel eq "x") {
1960 $email_git_max_maintainers = $val;
1963 } elsif ($sel eq "%") {
1964 if ($str ne "" && $val >= 0) {
1965 $email_git_min_percent = $val;
1968 } elsif ($sel eq "d") {
1970 $email_git_since = $str;
1971 } elsif (vcs_is_hg()) {
1972 $email_hg_since = $str;
1975 } elsif ($sel eq "t") {
1976 bool_invert(\$email_git_all_signature_types);
1978 } elsif ($sel eq "f") {
1979 bool_invert(\$email_file_emails);
1981 } elsif ($sel eq "r") {
1982 bool_invert(\$email_remove_duplicates);
1984 } elsif ($sel eq "m") {
1985 bool_invert(\$email_use_mailmap);
1988 } elsif ($sel eq "k") {
1989 bool_invert(\$keywords);
1991 } elsif ($sel eq "p") {
1992 if ($str ne "" && $val >= 0) {
1993 $pattern_depth = $val;
1996 } elsif ($sel eq "h" || $sel eq "?") {
1999 Interactive mode allows you to select the various maintainers, submitters,
2000 commit signers and mailing lists that could be CC'd on a patch.
2002 Any *'d entry is selected.
2004 If you have git or hg installed, you can choose to summarize the commit
2005 history of files in the patch. Also, each line of the current file can
2006 be matched to its commit author and that commits signers with blame.
2008 Various knobs exist to control the length of time for active commit
2009 tracking, the maximum number of commit authors and signers to add,
2012 Enter selections at the prompt until you are satisfied that the selected
2013 maintainers are appropriate. You may enter multiple selections separated
2014 by either commas or spaces.
2018 print STDERR "invalid option: '$nr'\n";
2023 print STDERR "git-blame can be very slow, please have patience..."
2024 if ($email_git_blame);
2025 goto &get_maintainers;
2029 #drop not selected entries
2031 my @new_emailto = ();
2032 foreach my $entry (@list) {
2033 if ($selected{$count}) {
2034 push(@new_emailto, $list[$count]);
2038 return @new_emailto;
2042 my ($bool_ref) = @_;
2051 sub deduplicate_email {
2055 my ($name, $address) = parse_email($email);
2056 $email = format_email($name, $address, 1);
2057 $email = mailmap_email($email);
2059 return $email if (!$email_remove_duplicates);
2061 ($name, $address) = parse_email($email);
2063 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
2064 $name = $deduplicate_name_hash{lc($name)}->[0];
2065 $address = $deduplicate_name_hash{lc($name)}->[1];
2067 } elsif ($deduplicate_address_hash{lc($address)}) {
2068 $name = $deduplicate_address_hash{lc($address)}->[0];
2069 $address = $deduplicate_address_hash{lc($address)}->[1];
2073 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
2074 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
2076 $email = format_email($name, $address, 1);
2077 $email = mailmap_email($email);
2081 sub save_commits_by_author {
2088 foreach my $line (@lines) {
2089 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2091 $author = deduplicate_email($author);
2092 push(@authors, $author);
2094 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2095 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2098 for (my $i = 0; $i < @authors; $i++) {
2100 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
2101 if (@{$ref}[0] eq $commits[$i] &&
2102 @{$ref}[1] eq $subjects[$i]) {
2108 push(@{$commit_author_hash{$authors[$i]}},
2109 [ ($commits[$i], $subjects[$i]) ]);
2114 sub save_commits_by_signer {
2120 foreach my $line (@lines) {
2121 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2122 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2123 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
2124 my @signatures = ($line);
2125 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
2126 my @types = @$types_ref;
2127 my @signers = @$signers_ref;
2129 my $type = $types[0];
2130 my $signer = $signers[0];
2132 $signer = deduplicate_email($signer);
2135 foreach my $ref(@{$commit_signer_hash{$signer}}) {
2136 if (@{$ref}[0] eq $commit &&
2137 @{$ref}[1] eq $subject &&
2138 @{$ref}[2] eq $type) {
2144 push(@{$commit_signer_hash{$signer}},
2145 [ ($commit, $subject, $type) ]);
2152 my ($role, $divisor, @lines) = @_;
2157 return if (@lines <= 0);
2159 if ($divisor <= 0) {
2160 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
2164 @lines = mailmap(@lines);
2166 return if (@lines <= 0);
2168 @lines = sort(@lines);
2171 $hash{$_}++ for @lines;
2174 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
2175 my $sign_offs = $hash{$line};
2176 my $percent = $sign_offs * 100 / $divisor;
2178 $percent = 100 if ($percent > 100);
2179 next if (ignore_email_address($line));
2181 last if ($sign_offs < $email_git_min_signatures ||
2182 $count > $email_git_max_maintainers ||
2183 $percent < $email_git_min_percent);
2184 push_email_address($line, '');
2185 if ($output_rolestats) {
2186 my $fmt_percent = sprintf("%.0f", $percent);
2187 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
2189 add_role($line, $role);
2194 sub vcs_file_signoffs {
2205 $vcs_used = vcs_exists();
2206 return if (!$vcs_used);
2208 my $cmd = $VCS_cmds{"find_signers_cmd"};
2209 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2211 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2213 @signers = @{$signers_ref} if defined $signers_ref;
2214 @authors = @{$authors_ref} if defined $authors_ref;
2215 @stats = @{$stats_ref} if defined $stats_ref;
2217 # print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
2219 foreach my $signer (@signers) {
2220 $signer = deduplicate_email($signer);
2223 vcs_assign("commit_signer", $commits, @signers);
2224 vcs_assign("authored", $commits, @authors);
2225 if ($#authors == $#stats) {
2226 my $stat_pattern = $VCS_cmds{"stat_pattern"};
2227 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
2231 for (my $i = 0; $i <= $#stats; $i++) {
2232 if ($stats[$i] =~ /$stat_pattern/) {
2237 my @tmp_authors = uniq(@authors);
2238 foreach my $author (@tmp_authors) {
2239 $author = deduplicate_email($author);
2241 @tmp_authors = uniq(@tmp_authors);
2242 my @list_added = ();
2243 my @list_deleted = ();
2244 foreach my $author (@tmp_authors) {
2246 my $auth_deleted = 0;
2247 for (my $i = 0; $i <= $#stats; $i++) {
2248 if ($author eq deduplicate_email($authors[$i]) &&
2249 $stats[$i] =~ /$stat_pattern/) {
2251 $auth_deleted += $2;
2254 for (my $i = 0; $i < $auth_added; $i++) {
2255 push(@list_added, $author);
2257 for (my $i = 0; $i < $auth_deleted; $i++) {
2258 push(@list_deleted, $author);
2261 vcs_assign("added_lines", $added, @list_added);
2262 vcs_assign("removed_lines", $deleted, @list_deleted);
2266 sub vcs_file_blame {
2270 my @all_commits = ();
2275 $vcs_used = vcs_exists();
2276 return if (!$vcs_used);
2278 @all_commits = vcs_blame($file);
2279 @commits = uniq(@all_commits);
2280 $total_commits = @commits;
2281 $total_lines = @all_commits;
2283 if ($email_git_blame_signatures) {
2286 my $commit_authors_ref;
2287 my $commit_signers_ref;
2289 my @commit_authors = ();
2290 my @commit_signers = ();
2291 my $commit = join(" -r ", @commits);
2294 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2295 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2297 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2298 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2299 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2301 push(@signers, @commit_signers);
2303 foreach my $commit (@commits) {
2305 my $commit_authors_ref;
2306 my $commit_signers_ref;
2308 my @commit_authors = ();
2309 my @commit_signers = ();
2312 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2313 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2315 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2316 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2317 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2319 push(@signers, @commit_signers);
2324 if ($from_filename) {
2325 if ($output_rolestats) {
2327 if (vcs_is_hg()) {{ # Double brace for last exit
2329 my @commit_signers = ();
2330 @commits = uniq(@commits);
2331 @commits = sort(@commits);
2332 my $commit = join(" -r ", @commits);
2335 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2336 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2340 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2342 if (!$email_git_penguin_chiefs) {
2343 @lines = grep(!/${penguin_chiefs}/i, @lines);
2349 foreach my $line (@lines) {
2350 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2352 $author = deduplicate_email($author);
2353 push(@authors, $author);
2357 save_commits_by_author(@lines) if ($interactive);
2358 save_commits_by_signer(@lines) if ($interactive);
2360 push(@signers, @authors);
2363 foreach my $commit (@commits) {
2365 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2366 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2367 my @author = vcs_find_author($cmd);
2370 my $formatted_author = deduplicate_email($author[0]);
2372 my $count = grep(/$commit/, @all_commits);
2373 for ($i = 0; $i < $count ; $i++) {
2374 push(@blame_signers, $formatted_author);
2378 if (@blame_signers) {
2379 vcs_assign("authored lines", $total_lines, @blame_signers);
2382 foreach my $signer (@signers) {
2383 $signer = deduplicate_email($signer);
2385 vcs_assign("commits", $total_commits, @signers);
2387 foreach my $signer (@signers) {
2388 $signer = deduplicate_email($signer);
2390 vcs_assign("modified commits", $total_commits, @signers);
2394 sub vcs_file_exists {
2399 my $vcs_used = vcs_exists();
2400 return 0 if (!$vcs_used);
2402 my $cmd = $VCS_cmds{"file_exists_cmd"};
2403 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2405 $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
2407 return 0 if ($? != 0);
2412 sub vcs_list_files {
2417 my $vcs_used = vcs_exists();
2418 return 0 if (!$vcs_used);
2420 my $cmd = $VCS_cmds{"list_files_cmd"};
2421 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2422 @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd);
2424 return () if ($? != 0);
2433 @parms = grep(!$saw{$_}++, @parms);
2441 @parms = sort @parms;
2442 @parms = grep(!$saw{$_}++, @parms);
2446 sub clean_file_emails {
2447 my (@file_emails) = @_;
2448 my @fmt_emails = ();
2450 foreach my $email (@file_emails) {
2451 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2452 my ($name, $address) = parse_email($email);
2453 if ($name eq '"[,\.]"') {
2457 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2459 my $first = $nw[@nw - 3];
2460 my $middle = $nw[@nw - 2];
2461 my $last = $nw[@nw - 1];
2463 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2464 (length($first) == 2 && substr($first, -1) eq ".")) ||
2465 (length($middle) == 1 ||
2466 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2467 $name = "$first $middle $last";
2469 $name = "$middle $last";
2473 if (substr($name, -1) =~ /[,\.]/) {
2474 $name = substr($name, 0, length($name) - 1);
2475 } elsif (substr($name, -2) =~ /[,\.]"/) {
2476 $name = substr($name, 0, length($name) - 2) . '"';
2479 if (substr($name, 0, 1) =~ /[,\.]/) {
2480 $name = substr($name, 1, length($name) - 1);
2481 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2482 $name = '"' . substr($name, 2, length($name) - 2);
2485 my $fmt_email = format_email($name, $address, $email_usename);
2486 push(@fmt_emails, $fmt_email);
2496 my ($address, $role) = @$_;
2497 if (!$saw{$address}) {
2498 if ($output_roles) {
2499 push(@lines, "$address ($role)");
2501 push(@lines, $address);
2513 if ($output_multiline) {
2514 foreach my $line (@parms) {
2518 print(join($output_separator, @parms));
2526 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2527 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2528 # This regexp will only work on addresses which have had comments stripped
2529 # and replaced with rfc822_lwsp.
2531 my $specials = '()<>@,;:\\\\".\\[\\]';
2532 my $controls = '\\000-\\037\\177';
2534 my $dtext = "[^\\[\\]\\r\\\\]";
2535 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2537 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2539 # Use zero-width assertion to spot the limit of an atom. A simple
2540 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2541 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2542 my $word = "(?:$atom|$quoted_string)";
2543 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2545 my $sub_domain = "(?:$atom|$domain_literal)";
2546 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2548 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2550 my $phrase = "$word*";
2551 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2552 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2553 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2555 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2556 my $address = "(?:$mailbox|$group)";
2558 return "$rfc822_lwsp*$address";
2561 sub rfc822_strip_comments {
2563 # Recursively remove comments, and replace with a single space. The simpler
2564 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2565 # chars in atoms, for example.
2567 while ($s =~ s/^((?:[^"\\]|\\.)*
2568 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2569 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2573 # valid: returns true if the parameter is an RFC822 valid address
2576 my $s = rfc822_strip_comments(shift);
2579 $rfc822re = make_rfc822re();
2582 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2585 # validlist: In scalar context, returns true if the parameter is an RFC822
2586 # valid list of addresses.
2588 # In list context, returns an empty list on failure (an invalid
2589 # address was found); otherwise a list whose first element is the
2590 # number of addresses found and whose remaining elements are the
2591 # addresses. This is needed to disambiguate failure (invalid)
2592 # from success with no addresses found, because an empty string is
2595 sub rfc822_validlist {
2596 my $s = rfc822_strip_comments(shift);
2599 $rfc822re = make_rfc822re();
2601 # * null list items are valid according to the RFC
2602 # * the '1' business is to aid in distinguishing failure from no results
2605 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2606 $s =~ m/^$rfc822_char*$/) {
2607 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2610 return wantarray ? (scalar(@r), @r) : 1;
2612 return wantarray ? () : 0;