2 # (c) 2007, Joe Perches <joe@perches.com>
3 # created from checkpatch.pl
5 # Print selected MAINTAINERS information for
6 # the files modified in a patch or for a file
8 # usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
9 # perl scripts/get_maintainer.pl [OPTIONS] -f <file>
11 # Licensed under the terms of the GNU GPL License version 2
13 # Copied from kernel v4.16-11490-gb284d4d5a678
21 use Getopt::Long qw(:config no_auto_abbrev);
25 my $cur_path = fastgetcwd() . '/';
28 my $email_usename = 1;
29 my $email_maintainer = 1;
30 my $email_reviewer = 1;
32 my $email_subscriber_list = 0;
33 my $email_git_penguin_chiefs = 0;
35 my $email_git_all_signature_types = 0;
36 my $email_git_blame = 0;
37 my $email_git_blame_signatures = 1;
38 my $email_git_fallback = 1;
39 my $email_git_min_signatures = 1;
40 my $email_git_max_maintainers = 5;
41 my $email_git_min_percent = 5;
42 my $email_git_since = "1-year-ago";
43 my $email_hg_since = "-365";
45 my $email_remove_duplicates = 1;
46 my $email_use_mailmap = 1;
47 my $output_multiline = 1;
48 my $output_separator = ", ";
50 my $output_rolestats = 1;
51 my $output_section_maxlen = 50;
60 my $from_filename = 0;
61 my $pattern_depth = 0;
62 my $self_test = undef;
65 my $find_maintainer_files = 0;
71 my %commit_author_hash;
72 my %commit_signer_hash;
74 my @penguin_chief = ();
75 push(@penguin_chief, "Oleg Drokin:green\@whamcloud.com");
76 #push(@penguin_chief, "Andreas Dilger:adilger\@whamcloud.com");
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,
260 'letters=s' => \$letters,
261 'pattern-depth=i' => \$pattern_depth,
262 'k|keywords!' => \$keywords,
263 'sections!' => \$sections,
264 'fe|file-emails!' => \$file_emails,
265 'f|file' => \$from_filename,
266 'find-maintainer-files' => \$find_maintainer_files,
267 'self-test:s' => \$self_test,
268 'v|version' => \$version,
269 'h|help|usage' => \$help,
271 die "$P: invalid argument - use --help if necessary\n";
280 print("${P} ${V}\n");
284 if (defined $self_test) {
285 read_all_maintainer_files();
290 if (-t STDIN && !@ARGV) {
291 # We're talking to a terminal, but have no command line arguments.
292 die "$P: missing patchfile or -f file - use --help if necessary\n";
295 $output_multiline = 0 if ($output_separator ne ", ");
296 $output_rolestats = 1 if ($interactive);
297 $output_roles = 1 if ($output_rolestats);
299 if ($sections || $letters ne "") {
310 my $selections = $email + $scm + $status + $subsystem + $web;
311 if ($selections == 0) {
312 die "$P: Missing required option: email, scm, status, subsystem or web\n";
317 ($email_maintainer + $email_reviewer +
318 $email_list + $email_subscriber_list +
319 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
320 die "$P: Please select at least 1 email option\n";
323 if (!top_of_kernel_tree($lk_path)) {
324 die "$P: The current directory does not appear to be "
325 . "a Lustre source tree.\n";
328 ## Read MAINTAINERS for type/value pairs
333 my @self_test_info = ();
335 sub read_maintainer_file {
338 open (my $maint, '<', "$file")
339 or die "$P: Can't open MAINTAINERS file '$file': $!\n";
345 if ($line =~ m/^([A-Z]):\s*(.*)/) {
349 ##Filename pattern matching
350 if ($type eq "F" || $type eq "X") {
351 $value =~ s@\.@\\\.@g; ##Convert . to \.
352 $value =~ s/\*/\.\*/g; ##Convert * to .*
353 $value =~ s/\?/\./g; ##Convert ? to .
354 ##if pattern is a directory and it lacks a trailing slash, add one
356 $value =~ s@([^/])$@$1/@;
358 } elsif ($type eq "K") {
359 $keyword_hash{@typevalue} = $value;
361 push(@typevalue, "$type:$value");
362 } elsif (!(/^\s*$/ || /^\s*\#/)) {
363 push(@typevalue, $line);
365 if (defined $self_test) {
366 push(@self_test_info, {file=>$file, linenr=>$i, line=>$line});
373 sub find_is_maintainer_file {
375 return if ($file !~ m@/MAINTAINERS$@);
376 $file = $File::Find::name;
377 return if (! -f $file);
378 push(@mfiles, $file);
381 sub find_ignore_git {
382 return grep { $_ !~ /^\.git$/; } @_;
385 read_all_maintainer_files();
387 sub read_all_maintainer_files {
388 if (-d "${lk_path}MAINTAINERS") {
389 opendir(DIR, "${lk_path}MAINTAINERS") or die $!;
390 my @files = readdir(DIR);
392 foreach my $file (@files) {
393 push(@mfiles, "${lk_path}MAINTAINERS/$file") if ($file !~ /^\./);
397 if ($find_maintainer_files) {
398 find( { wanted => \&find_is_maintainer_file,
399 preprocess => \&find_ignore_git,
403 push(@mfiles, "${lk_path}MAINTAINERS") if -f "${lk_path}MAINTAINERS";
406 foreach my $file (@mfiles) {
407 read_maintainer_file("$file");
412 # Read mail address map
425 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
427 open(my $mailmap_file, '<', "${lk_path}.mailmap")
428 or warn "$P: Can't open .mailmap: $!\n";
430 while (<$mailmap_file>) {
431 s/#.*$//; #strip comments
432 s/^\s+|\s+$//g; #trim
434 next if (/^\s*$/); #skip empty lines
435 #entries have one of the following formats:
438 # name1 <mail1> <mail2>
439 # name1 <mail1> name2 <mail2>
440 # (see man git-shortlog)
442 if (/^([^<]+)<([^>]+)>$/) {
446 $real_name =~ s/\s+$//;
447 ($real_name, $address) = parse_email("$real_name <$address>");
448 $mailmap->{names}->{$address} = $real_name;
450 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
451 my $real_address = $1;
452 my $wrong_address = $2;
454 $mailmap->{addresses}->{$wrong_address} = $real_address;
456 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
458 my $real_address = $2;
459 my $wrong_address = $3;
461 $real_name =~ s/\s+$//;
462 ($real_name, $real_address) =
463 parse_email("$real_name <$real_address>");
464 $mailmap->{names}->{$wrong_address} = $real_name;
465 $mailmap->{addresses}->{$wrong_address} = $real_address;
467 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
469 my $real_address = $2;
471 my $wrong_address = $4;
473 $real_name =~ s/\s+$//;
474 ($real_name, $real_address) =
475 parse_email("$real_name <$real_address>");
477 $wrong_name =~ s/\s+$//;
478 ($wrong_name, $wrong_address) =
479 parse_email("$wrong_name <$wrong_address>");
481 my $wrong_email = format_email($wrong_name, $wrong_address, 1);
482 $mailmap->{names}->{$wrong_email} = $real_name;
483 $mailmap->{addresses}->{$wrong_email} = $real_address;
486 close($mailmap_file);
489 ## use the filenames on the command line or find the filenames in the patchfiles
493 my @keyword_tvi = ();
494 my @file_emails = ();
497 push(@ARGV, "&STDIN");
500 foreach my $file (@ARGV) {
501 if ($file ne "&STDIN") {
502 ##if $file is a directory and it lacks a trailing slash, add one
504 $file =~ s@([^/])$@$1/@;
505 } elsif (!(-f $file)) {
506 die "$P: file '${file}' not found\n";
509 if ($from_filename || ($file ne "&STDIN" && vcs_file_exists($file))) {
510 $file =~ s/^\Q${cur_path}\E//; #strip any absolute path
511 $file =~ s/^\Q${lk_path}\E//; #or the path to the lk tree
513 if ($file ne "MAINTAINERS" && -f $file && ($keywords || $file_emails)) {
514 open(my $f, '<', $file)
515 or die "$P: Can't open $file: $!\n";
516 my $text = do { local($/) ; <$f> };
519 foreach my $line (keys %keyword_hash) {
520 if ($text =~ m/$keyword_hash{$line}/x) {
521 push(@keyword_tvi, $line);
526 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;
527 push(@file_emails, clean_file_emails(@poss_addr));
531 my $file_cnt = @files;
534 open(my $patch, "< $file")
535 or die "$P: Can't open $file: $!\n";
537 # We can check arbitrary information before the patch
538 # like the commit message, mail headers, etc...
539 # This allows us to match arbitrary keywords against any part
540 # of a git format-patch generated file (subject tags, etc...)
542 my $patch_prefix = ""; #Parsing the intro
546 if (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
548 $filename =~ s@^[^/]*/@@;
550 $lastfile = $filename;
551 push(@files, $filename);
552 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
553 } elsif (m/^\@\@ -(\d+),(\d+)/) {
554 if ($email_git_blame) {
555 push(@range, "$lastfile:$1:$2");
557 } elsif ($keywords) {
558 foreach my $line (keys %keyword_hash) {
559 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
560 push(@keyword_tvi, $line);
567 if ($file_cnt == @files) {
568 warn "$P: file '${file}' doesn't appear to be a patch. "
569 . "Add -f to options?\n";
571 @files = sort_and_uniq(@files);
575 @file_emails = uniq(@file_emails);
578 my %email_hash_address;
586 my %deduplicate_name_hash = ();
587 my %deduplicate_address_hash = ();
589 my @maintainers = get_maintainers();
592 @maintainers = merge_email(@maintainers);
593 output(@maintainers);
602 @status = uniq(@status);
607 @subsystem = uniq(@subsystem);
622 my @section_headers = ();
625 @lsfiles = vcs_list_files($lk_path);
627 for my $x (@self_test_info) {
630 ## Section header duplication and missing section content
631 if (($self_test eq "" || $self_test =~ /\bsections\b/) &&
632 $x->{line} =~ /^\S[^:]/ &&
633 defined $self_test_info[$index] &&
634 $self_test_info[$index]->{line} =~ /^([A-Z]):\s*\S/) {
639 if (grep(m@^\Q$x->{line}\E@, @section_headers)) {
640 print("$x->{file}:$x->{linenr}: warning: duplicate section header\t$x->{line}\n");
642 push(@section_headers, $x->{line});
644 my $nextline = $index;
645 while (defined $self_test_info[$nextline] &&
646 $self_test_info[$nextline]->{line} =~ /^([A-Z]):\s*(\S.*)/) {
652 } elsif ($type eq "F" || $type eq "N") {
654 } elsif ($type eq "M" || $type eq "R" || $type eq "L") {
659 if (!$has_ML && $status !~ /orphan|obsolete/i) {
660 print("$x->{file}:$x->{linenr}: warning: section without email address\t$x->{line}\n");
663 print("$x->{file}:$x->{linenr}: warning: section without status \t$x->{line}\n");
666 print("$x->{file}:$x->{linenr}: warning: section without file pattern\t$x->{line}\n");
670 next if ($x->{line} !~ /^([A-Z]):\s*(.*)/);
675 ## Filename pattern matching
676 if (($type eq "F" || $type eq "X") &&
677 ($self_test eq "" || $self_test =~ /\bpatterns\b/)) {
678 $value =~ s@\.@\\\.@g; ##Convert . to \.
679 $value =~ s/\*/\.\*/g; ##Convert * to .*
680 $value =~ s/\?/\./g; ##Convert ? to .
681 ##if pattern is a directory and it lacks a trailing slash, add one
683 $value =~ s@([^/])$@$1/@;
685 if (!grep(m@^$value@, @lsfiles)) {
686 print("$x->{file}:$x->{linenr}: warning: no file matches\t$x->{line}\n");
690 } elsif (($type eq "W" || $type eq "Q" || $type eq "B") &&
691 $value =~ /^https?:/ &&
692 ($self_test eq "" || $self_test =~ /\blinks\b/)) {
693 next if (grep(m@^\Q$value\E$@, @good_links));
695 if (grep(m@^\Q$value\E$@, @bad_links)) {
698 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $value`;
700 push(@good_links, $value);
702 push(@bad_links, $value);
707 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
711 } elsif ($type eq "T" &&
712 ($self_test eq "" || $self_test =~ /\bscm\b/)) {
713 next if (grep(m@^\Q$value\E$@, @good_links));
715 if (grep(m@^\Q$value\E$@, @bad_links)) {
717 } elsif ($value !~ /^(?:git|quilt|hg)\s+\S/) {
718 print("$x->{file}:$x->{linenr}: warning: malformed entry\t$x->{line}\n");
719 } elsif ($value =~ /^git\s+(\S+)(\s+([^\(]+\S+))?/) {
723 my $output = `git ls-remote --exit-code -h "$url" $branch > /dev/null 2>&1`;
725 push(@good_links, $value);
727 push(@bad_links, $value);
730 } elsif ($value =~ /^(?:quilt|hg)\s+(https?:\S+)/) {
732 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $url`;
734 push(@good_links, $value);
736 push(@bad_links, $value);
741 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
747 sub ignore_email_address {
750 foreach my $ignore (@ignore_emails) {
751 return 1 if ($ignore eq $address);
757 sub range_is_maintained {
758 my ($start, $end) = @_;
760 for (my $i = $start; $i < $end; $i++) {
761 my $line = $typevalue[$i];
762 if ($line =~ m/^([A-Z]):\s*(.*)/) {
766 if ($value =~ /(maintain|support)/i) {
775 sub range_has_maintainer {
776 my ($start, $end) = @_;
778 for (my $i = $start; $i < $end; $i++) {
779 my $line = $typevalue[$i];
780 if ($line =~ m/^([A-Z]):\s*(.*)/) {
791 sub get_maintainers {
792 %email_hash_name = ();
793 %email_hash_address = ();
794 %commit_author_hash = ();
795 %commit_signer_hash = ();
803 %deduplicate_name_hash = ();
804 %deduplicate_address_hash = ();
805 if ($email_git_all_signature_types) {
806 $signature_pattern = "(.+?)[Bb][Yy]:";
808 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
811 # Find responsible parties
813 my %exact_pattern_match_hash = ();
815 foreach my $file (@files) {
818 my $tvi = find_first_section();
819 while ($tvi < @typevalue) {
820 my $start = find_starting_index($tvi);
821 my $end = find_ending_index($tvi);
825 #Do not match excluded file patterns
827 for ($i = $start; $i < $end; $i++) {
828 my $line = $typevalue[$i];
829 if ($line =~ m/^([A-Z]):\s*(.*)/) {
833 if (file_match_pattern($file, $value)) {
842 for ($i = $start; $i < $end; $i++) {
843 my $line = $typevalue[$i];
844 if ($line =~ m/^([A-Z]):\s*(.*)/) {
848 if (file_match_pattern($file, $value)) {
849 my $value_pd = ($value =~ tr@/@@);
850 my $file_pd = ($file =~ tr@/@@);
851 $value_pd++ if (substr($value,-1,1) ne "/");
852 $value_pd = -1 if ($value =~ /^\.\*/);
853 if ($value_pd >= $file_pd &&
854 range_is_maintained($start, $end) &&
855 range_has_maintainer($start, $end)) {
856 $exact_pattern_match_hash{$file} = 1;
858 if ($pattern_depth == 0 ||
859 (($file_pd - $value_pd) < $pattern_depth)) {
860 $hash{$tvi} = $value_pd;
863 } elsif ($type eq 'N') {
864 if ($file =~ m/$value/x) {
874 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
875 add_categories($line);
878 my $start = find_starting_index($line);
879 my $end = find_ending_index($line);
880 for ($i = $start; $i < $end; $i++) {
881 my $line = $typevalue[$i];
882 if ($line =~ /^[FX]:/) { ##Restore file patterns
883 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
884 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
885 $line =~ s/\\\./\./g; ##Convert \. to .
886 $line =~ s/\.\*/\*/g; ##Convert .* to *
888 my $count = $line =~ s/^([A-Z]):/$1:\t/g;
889 if ($letters eq "" || (!$count || $letters =~ /$1/i)) {
899 @keyword_tvi = sort_and_uniq(@keyword_tvi);
900 foreach my $line (@keyword_tvi) {
901 add_categories($line);
905 foreach my $email (@email_to, @list_to) {
906 $email->[0] = deduplicate_email($email->[0]);
909 foreach my $file (@files) {
911 ($email_git || ($email_git_fallback &&
912 !$exact_pattern_match_hash{$file}))) {
913 vcs_file_signoffs($file);
915 if ($email && $email_git_blame) {
916 vcs_file_blame($file);
921 foreach my $chief (@penguin_chief) {
922 if ($chief =~ m/^(.*):(.*)/) {
925 $email_address = format_email($1, $2, $email_usename);
926 if ($email_git_penguin_chiefs) {
927 push(@email_to, [$email_address, 'chief penguin']);
929 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
934 foreach my $email (@file_emails) {
935 my ($name, $address) = parse_email($email);
937 my $tmp_email = format_email($name, $address, $email_usename);
938 push_email_address($tmp_email, '');
939 add_role($tmp_email, 'in file');
944 if ($email || $email_list) {
946 @to = (@to, @email_to);
949 @to = (@to, @list_to);
954 @to = interactive_get_maintainers(\@to);
960 sub file_match_pattern {
961 my ($file, $pattern) = @_;
962 if (substr($pattern, -1) eq "/") {
963 if ($file =~ m@^$pattern@) {
967 if ($file =~ m@^$pattern@) {
968 my $s1 = ($file =~ tr@/@@);
969 my $s2 = ($pattern =~ tr@/@@);
980 usage: $P [options] patchfile
981 $P [options] -f file|directory
984 MAINTAINER field selection options:
985 --email => print email address(es) if any
986 --git => include recent git \*-by: signers
987 --git-all-signature-types => include signers regardless of signature type
988 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
989 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
990 --git-chief-penguins => include ${penguin_chiefs}
991 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
992 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
993 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
994 --git-blame => use git blame to find modified commits for patch or file
995 --git-blame-signatures => when used with --git-blame, also include all commit signers
996 --git-since => git history to use (default: $email_git_since)
997 --hg-since => hg history to use (default: $email_hg_since)
998 --interactive => display a menu (mostly useful if used with the --git option)
999 --m => include maintainer(s) if any
1000 --r => include reviewer(s) if any
1001 --n => include name 'Full Name <addr\@domain.tld>'
1002 --l => include list(s) if any
1003 --s => include subscriber only list(s) if any
1004 --remove-duplicates => minimize duplicate email names/addresses
1005 --roles => show roles (status:subsystem, git-signer, list, etc...)
1006 --rolestats => show roles and statistics (commits/total_commits, %)
1007 --file-emails => add email addresses found in -f file (default: 0 (off))
1008 --scm => print SCM tree(s) if any
1009 --status => print status if any
1010 --subsystem => print subsystem name if any
1011 --web => print website(s) if any
1013 Output type options:
1014 --separator [, ] => separator for multiple entries on 1 line
1015 using --separator also sets --nomultiline if --separator is not [, ]
1016 --multiline => print 1 entry per line
1019 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
1020 --keywords => scan patch for keywords (default: $keywords)
1021 --sections => print all of the subsystem sections with pattern matches
1022 --letters => print all matching 'letter' types from all matching sections
1023 --mailmap => use .mailmap file (default: $email_use_mailmap)
1024 --self-test => show potential issues with MAINTAINERS file content
1025 --version => show version
1026 --help => show this help information
1029 [--email --nogit --git-fallback --m --r --n --l --multiline --pattern-depth=0
1030 --remove-duplicates --rolestats]
1033 Using "-f directory" may give unexpected results:
1034 Used with "--git", git signators for _all_ files in and below
1035 directory are examined as git recurses directories.
1036 Any specified X: (exclude) pattern matches are _not_ ignored.
1037 Used with "--nogit", directory is used as a pattern match,
1038 no individual file within the directory or subdirectory
1040 Used with "--git-blame", does not iterate all files in directory
1041 Using "--git-blame" is slow and may add old committers and authors
1042 that are no longer active maintainers to the output.
1043 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
1044 other automated tools that expect only ["name"] <email address>
1045 may not work because of additional output after <email address>.
1046 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
1047 not the percentage of the entire file authored. # of commits is
1048 not a good measure of amount of code authored. 1 major commit may
1049 contain a thousand lines, 5 trivial commits may modify a single line.
1050 If git is not installed, but mercurial (hg) is installed and an .hg
1051 repository exists, the following options apply to mercurial:
1053 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
1055 Use --hg-since not --git-since to control date selection
1056 File ".get_maintainer.conf", if it exists in the linux kernel source root
1057 directory, can change whatever get_maintainer defaults are desired.
1058 Entries in this file can be any command line argument.
1059 This file is prepended to any additional command line arguments.
1060 Multiple lines and # comments are allowed.
1061 Most options have both positive and negative forms.
1062 The negative forms for --<foo> are --no<foo> and --no-<foo>.
1067 sub top_of_kernel_tree {
1070 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
1073 if ( (-f "${lk_path}autoMakefile.am")
1074 && (-f "${lk_path}COPYING")
1075 && (-d "${lk_path}Documentation")
1076 && (-e "${lk_path}lustre.spec.in")
1077 && (-e "${lk_path}MAINTAINERS")
1078 && (-f "${lk_path}README")) {
1085 my ($formatted_email) = @_;
1090 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
1093 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
1095 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
1099 $name =~ s/^\s+|\s+$//g;
1100 $name =~ s/^\"|\"$//g;
1101 $address =~ s/^\s+|\s+$//g;
1103 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
1104 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1105 $name = "\"$name\"";
1108 return ($name, $address);
1112 my ($name, $address, $usename) = @_;
1114 my $formatted_email;
1116 $name =~ s/^\s+|\s+$//g;
1117 $name =~ s/^\"|\"$//g;
1118 $address =~ s/^\s+|\s+$//g;
1120 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
1121 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1122 $name = "\"$name\"";
1126 if ("$name" eq "") {
1127 $formatted_email = "$address";
1129 $formatted_email = "$name <$address>";
1132 $formatted_email = $address;
1135 return $formatted_email;
1138 sub find_first_section {
1141 while ($index < @typevalue) {
1142 my $tv = $typevalue[$index];
1143 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
1152 sub find_starting_index {
1155 while ($index > 0) {
1156 my $tv = $typevalue[$index];
1157 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1166 sub find_ending_index {
1169 while ($index < @typevalue) {
1170 my $tv = $typevalue[$index];
1171 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1180 sub get_subsystem_name {
1183 my $start = find_starting_index($index);
1185 my $subsystem = $typevalue[$start];
1186 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
1187 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
1188 $subsystem =~ s/\s*$//;
1189 $subsystem = $subsystem . "...";
1194 sub get_maintainer_role {
1198 my $start = find_starting_index($index);
1199 my $end = find_ending_index($index);
1201 my $role = "unknown";
1202 my $subsystem = get_subsystem_name($index);
1204 for ($i = $start + 1; $i < $end; $i++) {
1205 my $tv = $typevalue[$i];
1206 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1209 if ($ptype eq "S") {
1216 if ($role eq "supported") {
1217 $role = "supporter";
1218 } elsif ($role eq "maintained") {
1219 $role = "maintainer";
1220 } elsif ($role eq "odd fixes") {
1221 $role = "odd fixer";
1222 } elsif ($role eq "orphan") {
1223 $role = "orphan minder";
1224 } elsif ($role eq "obsolete") {
1225 $role = "obsolete minder";
1226 } elsif ($role eq "buried alive in reporters") {
1227 $role = "chief penguin";
1230 return $role . ":" . $subsystem;
1236 my $subsystem = get_subsystem_name($index);
1238 if ($subsystem eq "THE REST") {
1245 sub add_categories {
1249 my $start = find_starting_index($index);
1250 my $end = find_ending_index($index);
1252 push(@subsystem, $typevalue[$start]);
1254 for ($i = $start + 1; $i < $end; $i++) {
1255 my $tv = $typevalue[$i];
1256 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1259 if ($ptype eq "L") {
1260 my $list_address = $pvalue;
1261 my $list_additional = "";
1262 my $list_role = get_list_role($i);
1264 if ($list_role ne "") {
1265 $list_role = ":" . $list_role;
1267 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1269 $list_additional = $2;
1271 if ($list_additional =~ m/subscribers-only/) {
1272 if ($email_subscriber_list) {
1273 if (!$hash_list_to{lc($list_address)}) {
1274 $hash_list_to{lc($list_address)} = 1;
1275 push(@list_to, [$list_address,
1276 "subscriber list${list_role}"]);
1281 if (!$hash_list_to{lc($list_address)}) {
1282 $hash_list_to{lc($list_address)} = 1;
1283 if ($list_additional =~ m/moderated/) {
1284 push(@list_to, [$list_address,
1285 "moderated list${list_role}"]);
1287 push(@list_to, [$list_address,
1288 "open list${list_role}"]);
1293 } elsif ($ptype eq "M") {
1294 my ($name, $address) = parse_email($pvalue);
1297 my $tv = $typevalue[$i - 1];
1298 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1301 $pvalue = format_email($name, $address, $email_usename);
1306 if ($email_maintainer) {
1307 my $role = get_maintainer_role($i);
1308 push_email_addresses($pvalue, $role);
1310 } elsif ($ptype eq "R") {
1311 my ($name, $address) = parse_email($pvalue);
1314 my $tv = $typevalue[$i - 1];
1315 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1318 $pvalue = format_email($name, $address, $email_usename);
1323 if ($email_reviewer) {
1324 my $subsystem = get_subsystem_name($i);
1325 push_email_addresses($pvalue, "reviewer:$subsystem");
1327 } elsif ($ptype eq "T") {
1328 push(@scm, $pvalue);
1329 } elsif ($ptype eq "W") {
1330 push(@web, $pvalue);
1331 } elsif ($ptype eq "S") {
1332 push(@status, $pvalue);
1339 my ($name, $address) = @_;
1341 return 1 if (($name eq "") && ($address eq ""));
1342 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1343 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1348 sub push_email_address {
1349 my ($line, $role) = @_;
1351 my ($name, $address) = parse_email($line);
1353 if ($address eq "") {
1357 if (!$email_remove_duplicates) {
1358 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1359 } elsif (!email_inuse($name, $address)) {
1360 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1361 $email_hash_name{lc($name)}++ if ($name ne "");
1362 $email_hash_address{lc($address)}++;
1368 sub push_email_addresses {
1369 my ($address, $role) = @_;
1371 my @address_list = ();
1373 if (rfc822_valid($address)) {
1374 push_email_address($address, $role);
1375 } elsif (@address_list = rfc822_validlist($address)) {
1376 my $array_count = shift(@address_list);
1377 while (my $entry = shift(@address_list)) {
1378 push_email_address($entry, $role);
1381 if (!push_email_address($address, $role)) {
1382 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1388 my ($line, $role) = @_;
1390 my ($name, $address) = parse_email($line);
1391 my $email = format_email($name, $address, $email_usename);
1393 foreach my $entry (@email_to) {
1394 if ($email_remove_duplicates) {
1395 my ($entry_name, $entry_address) = parse_email($entry->[0]);
1396 if (($name eq $entry_name || $address eq $entry_address)
1397 && ($role eq "" || !($entry->[1] =~ m/$role/))
1399 if ($entry->[1] eq "") {
1400 $entry->[1] = "$role";
1402 $entry->[1] = "$entry->[1],$role";
1406 if ($email eq $entry->[0]
1407 && ($role eq "" || !($entry->[1] =~ m/$role/))
1409 if ($entry->[1] eq "") {
1410 $entry->[1] = "$role";
1412 $entry->[1] = "$entry->[1],$role";
1422 foreach my $path (split(/:/, $ENV{PATH})) {
1423 if (-e "$path/$bin") {
1424 return "$path/$bin";
1434 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1435 if (-e "$path/$conf") {
1436 return "$path/$conf";
1446 my ($name, $address) = parse_email($line);
1447 my $email = format_email($name, $address, 1);
1448 my $real_name = $name;
1449 my $real_address = $address;
1451 if (exists $mailmap->{names}->{$email} ||
1452 exists $mailmap->{addresses}->{$email}) {
1453 if (exists $mailmap->{names}->{$email}) {
1454 $real_name = $mailmap->{names}->{$email};
1456 if (exists $mailmap->{addresses}->{$email}) {
1457 $real_address = $mailmap->{addresses}->{$email};
1460 if (exists $mailmap->{names}->{$address}) {
1461 $real_name = $mailmap->{names}->{$address};
1463 if (exists $mailmap->{addresses}->{$address}) {
1464 $real_address = $mailmap->{addresses}->{$address};
1467 return format_email($real_name, $real_address, 1);
1471 my (@addresses) = @_;
1473 my @mapped_emails = ();
1474 foreach my $line (@addresses) {
1475 push(@mapped_emails, mailmap_email($line));
1477 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1478 return @mapped_emails;
1481 sub merge_by_realname {
1485 foreach my $email (@emails) {
1486 my ($name, $address) = parse_email($email);
1487 if (exists $address_map{$name}) {
1488 $address = $address_map{$name};
1489 $email = format_email($name, $address, 1);
1491 $address_map{$name} = $address;
1496 sub git_execute_cmd {
1500 my $output = `$cmd`;
1501 $output =~ s/^\s*//gm;
1502 @lines = split("\n", $output);
1507 sub hg_execute_cmd {
1511 my $output = `$cmd`;
1512 @lines = split("\n", $output);
1517 sub extract_formatted_signatures {
1518 my (@signature_lines) = @_;
1520 my @type = @signature_lines;
1522 s/\s*(.*):.*/$1/ for (@type);
1525 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1527 ## Reformat email addresses (with names) to avoid badly written signatures
1529 foreach my $signer (@signature_lines) {
1530 $signer = deduplicate_email($signer);
1533 return (\@type, \@signature_lines);
1536 sub vcs_find_signers {
1537 my ($cmd, $file) = @_;
1540 my @signatures = ();
1544 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1546 my $pattern = $VCS_cmds{"commit_pattern"};
1547 my $author_pattern = $VCS_cmds{"author_pattern"};
1548 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1550 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
1552 $commits = grep(/$pattern/, @lines); # of commits
1554 @authors = grep(/$author_pattern/, @lines);
1555 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1556 @stats = grep(/$stat_pattern/, @lines);
1558 # print("stats: <@stats>\n");
1560 return (0, \@signatures, \@authors, \@stats) if !@signatures;
1562 save_commits_by_author(@lines) if ($interactive);
1563 save_commits_by_signer(@lines) if ($interactive);
1565 if (!$email_git_penguin_chiefs) {
1566 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1569 my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
1570 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1572 return ($commits, $signers_ref, $authors_ref, \@stats);
1575 sub vcs_find_author {
1579 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1581 if (!$email_git_penguin_chiefs) {
1582 @lines = grep(!/${penguin_chiefs}/i, @lines);
1585 return @lines if !@lines;
1588 foreach my $line (@lines) {
1589 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1591 my ($name, $address) = parse_email($author);
1592 $author = format_email($name, $address, 1);
1593 push(@authors, $author);
1597 save_commits_by_author(@lines) if ($interactive);
1598 save_commits_by_signer(@lines) if ($interactive);
1603 sub vcs_save_commits {
1608 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1610 foreach my $line (@lines) {
1611 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1624 return @commits if (!(-f $file));
1626 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1627 my @all_commits = ();
1629 $cmd = $VCS_cmds{"blame_file_cmd"};
1630 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1631 @all_commits = vcs_save_commits($cmd);
1633 foreach my $file_range_diff (@range) {
1634 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1636 my $diff_start = $2;
1637 my $diff_length = $3;
1638 next if ("$file" ne "$diff_file");
1639 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1640 push(@commits, $all_commits[$i]);
1644 foreach my $file_range_diff (@range) {
1645 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1647 my $diff_start = $2;
1648 my $diff_length = $3;
1649 next if ("$file" ne "$diff_file");
1650 $cmd = $VCS_cmds{"blame_range_cmd"};
1651 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1652 push(@commits, vcs_save_commits($cmd));
1655 $cmd = $VCS_cmds{"blame_file_cmd"};
1656 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1657 @commits = vcs_save_commits($cmd);
1660 foreach my $commit (@commits) {
1661 $commit =~ s/^\^//g;
1667 my $printed_novcs = 0;
1669 %VCS_cmds = %VCS_cmds_git;
1670 return 1 if eval $VCS_cmds{"available"};
1671 %VCS_cmds = %VCS_cmds_hg;
1672 return 2 if eval $VCS_cmds{"available"};
1674 if (!$printed_novcs) {
1675 warn("$P: No supported VCS found. Add --nogit to options?\n");
1676 warn("Using a git repository produces better results.\n");
1677 warn("Try Linus Torvalds' latest git repository using:\n");
1678 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
1686 return $vcs_used == 1;
1690 return $vcs_used == 2;
1693 sub interactive_get_maintainers {
1694 my ($list_ref) = @_;
1695 my @list = @$list_ref;
1704 foreach my $entry (@list) {
1705 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1706 $selected{$count} = 1;
1707 $authored{$count} = 0;
1708 $signed{$count} = 0;
1714 my $print_options = 0;
1719 printf STDERR "\n%1s %2s %-65s",
1720 "*", "#", "email/list and role:stats";
1722 ($email_git_fallback && !$maintained) ||
1724 print STDERR "auth sign";
1727 foreach my $entry (@list) {
1728 my $email = $entry->[0];
1729 my $role = $entry->[1];
1731 $sel = "*" if ($selected{$count});
1732 my $commit_author = $commit_author_hash{$email};
1733 my $commit_signer = $commit_signer_hash{$email};
1736 $authored++ for (@{$commit_author});
1737 $signed++ for (@{$commit_signer});
1738 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1739 printf STDERR "%4d %4d", $authored, $signed
1740 if ($authored > 0 || $signed > 0);
1741 printf STDERR "\n %s\n", $role;
1742 if ($authored{$count}) {
1743 my $commit_author = $commit_author_hash{$email};
1744 foreach my $ref (@{$commit_author}) {
1745 print STDERR " Author: @{$ref}[1]\n";
1748 if ($signed{$count}) {
1749 my $commit_signer = $commit_signer_hash{$email};
1750 foreach my $ref (@{$commit_signer}) {
1751 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1758 my $date_ref = \$email_git_since;
1759 $date_ref = \$email_hg_since if (vcs_is_hg());
1760 if ($print_options) {
1765 Version Control options:
1766 g use git history [$email_git]
1767 gf use git-fallback [$email_git_fallback]
1768 b use git blame [$email_git_blame]
1769 bs use blame signatures [$email_git_blame_signatures]
1770 c# minimum commits [$email_git_min_signatures]
1771 %# min percent [$email_git_min_percent]
1772 d# history to use [$$date_ref]
1773 x# max maintainers [$email_git_max_maintainers]
1774 t all signature types [$email_git_all_signature_types]
1775 m use .mailmap [$email_use_mailmap]
1782 tm toggle maintainers
1783 tg toggle git entries
1784 tl toggle open list entries
1785 ts toggle subscriber list entries
1786 f emails in file [$file_emails]
1787 k keywords in file [$keywords]
1788 r remove duplicates [$email_remove_duplicates]
1789 p# pattern match depth [$pattern_depth]
1793 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1795 my $input = <STDIN>;
1800 my @wish = split(/[, ]+/, $input);
1801 foreach my $nr (@wish) {
1803 my $sel = substr($nr, 0, 1);
1804 my $str = substr($nr, 1);
1806 $val = $1 if $str =~ /^(\d+)$/;
1811 $output_rolestats = 0;
1814 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1815 $selected{$nr - 1} = !$selected{$nr - 1};
1816 } elsif ($sel eq "*" || $sel eq '^') {
1818 $toggle = 1 if ($sel eq '*');
1819 for (my $i = 0; $i < $count; $i++) {
1820 $selected{$i} = $toggle;
1822 } elsif ($sel eq "0") {
1823 for (my $i = 0; $i < $count; $i++) {
1824 $selected{$i} = !$selected{$i};
1826 } elsif ($sel eq "t") {
1827 if (lc($str) eq "m") {
1828 for (my $i = 0; $i < $count; $i++) {
1829 $selected{$i} = !$selected{$i}
1830 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1832 } elsif (lc($str) eq "g") {
1833 for (my $i = 0; $i < $count; $i++) {
1834 $selected{$i} = !$selected{$i}
1835 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1837 } elsif (lc($str) eq "l") {
1838 for (my $i = 0; $i < $count; $i++) {
1839 $selected{$i} = !$selected{$i}
1840 if ($list[$i]->[1] =~ /^(open list)/i);
1842 } elsif (lc($str) eq "s") {
1843 for (my $i = 0; $i < $count; $i++) {
1844 $selected{$i} = !$selected{$i}
1845 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1848 } elsif ($sel eq "a") {
1849 if ($val > 0 && $val <= $count) {
1850 $authored{$val - 1} = !$authored{$val - 1};
1851 } elsif ($str eq '*' || $str eq '^') {
1853 $toggle = 1 if ($str eq '*');
1854 for (my $i = 0; $i < $count; $i++) {
1855 $authored{$i} = $toggle;
1858 } elsif ($sel eq "s") {
1859 if ($val > 0 && $val <= $count) {
1860 $signed{$val - 1} = !$signed{$val - 1};
1861 } elsif ($str eq '*' || $str eq '^') {
1863 $toggle = 1 if ($str eq '*');
1864 for (my $i = 0; $i < $count; $i++) {
1865 $signed{$i} = $toggle;
1868 } elsif ($sel eq "o") {
1871 } elsif ($sel eq "g") {
1873 bool_invert(\$email_git_fallback);
1875 bool_invert(\$email_git);
1878 } elsif ($sel eq "b") {
1880 bool_invert(\$email_git_blame_signatures);
1882 bool_invert(\$email_git_blame);
1885 } elsif ($sel eq "c") {
1887 $email_git_min_signatures = $val;
1890 } elsif ($sel eq "x") {
1892 $email_git_max_maintainers = $val;
1895 } elsif ($sel eq "%") {
1896 if ($str ne "" && $val >= 0) {
1897 $email_git_min_percent = $val;
1900 } elsif ($sel eq "d") {
1902 $email_git_since = $str;
1903 } elsif (vcs_is_hg()) {
1904 $email_hg_since = $str;
1907 } elsif ($sel eq "t") {
1908 bool_invert(\$email_git_all_signature_types);
1910 } elsif ($sel eq "f") {
1911 bool_invert(\$file_emails);
1913 } elsif ($sel eq "r") {
1914 bool_invert(\$email_remove_duplicates);
1916 } elsif ($sel eq "m") {
1917 bool_invert(\$email_use_mailmap);
1920 } elsif ($sel eq "k") {
1921 bool_invert(\$keywords);
1923 } elsif ($sel eq "p") {
1924 if ($str ne "" && $val >= 0) {
1925 $pattern_depth = $val;
1928 } elsif ($sel eq "h" || $sel eq "?") {
1931 Interactive mode allows you to select the various maintainers, submitters,
1932 commit signers and mailing lists that could be CC'd on a patch.
1934 Any *'d entry is selected.
1936 If you have git or hg installed, you can choose to summarize the commit
1937 history of files in the patch. Also, each line of the current file can
1938 be matched to its commit author and that commits signers with blame.
1940 Various knobs exist to control the length of time for active commit
1941 tracking, the maximum number of commit authors and signers to add,
1944 Enter selections at the prompt until you are satisfied that the selected
1945 maintainers are appropriate. You may enter multiple selections separated
1946 by either commas or spaces.
1950 print STDERR "invalid option: '$nr'\n";
1955 print STDERR "git-blame can be very slow, please have patience..."
1956 if ($email_git_blame);
1957 goto &get_maintainers;
1961 #drop not selected entries
1963 my @new_emailto = ();
1964 foreach my $entry (@list) {
1965 if ($selected{$count}) {
1966 push(@new_emailto, $list[$count]);
1970 return @new_emailto;
1974 my ($bool_ref) = @_;
1983 sub deduplicate_email {
1987 my ($name, $address) = parse_email($email);
1988 $email = format_email($name, $address, 1);
1989 $email = mailmap_email($email);
1991 return $email if (!$email_remove_duplicates);
1993 ($name, $address) = parse_email($email);
1995 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1996 $name = $deduplicate_name_hash{lc($name)}->[0];
1997 $address = $deduplicate_name_hash{lc($name)}->[1];
1999 } elsif ($deduplicate_address_hash{lc($address)}) {
2000 $name = $deduplicate_address_hash{lc($address)}->[0];
2001 $address = $deduplicate_address_hash{lc($address)}->[1];
2005 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
2006 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
2008 $email = format_email($name, $address, 1);
2009 $email = mailmap_email($email);
2013 sub save_commits_by_author {
2020 foreach my $line (@lines) {
2021 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2023 $author = deduplicate_email($author);
2024 push(@authors, $author);
2026 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2027 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2030 for (my $i = 0; $i < @authors; $i++) {
2032 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
2033 if (@{$ref}[0] eq $commits[$i] &&
2034 @{$ref}[1] eq $subjects[$i]) {
2040 push(@{$commit_author_hash{$authors[$i]}},
2041 [ ($commits[$i], $subjects[$i]) ]);
2046 sub save_commits_by_signer {
2052 foreach my $line (@lines) {
2053 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2054 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2055 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
2056 my @signatures = ($line);
2057 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
2058 my @types = @$types_ref;
2059 my @signers = @$signers_ref;
2061 my $type = $types[0];
2062 my $signer = $signers[0];
2064 $signer = deduplicate_email($signer);
2067 foreach my $ref(@{$commit_signer_hash{$signer}}) {
2068 if (@{$ref}[0] eq $commit &&
2069 @{$ref}[1] eq $subject &&
2070 @{$ref}[2] eq $type) {
2076 push(@{$commit_signer_hash{$signer}},
2077 [ ($commit, $subject, $type) ]);
2084 my ($role, $divisor, @lines) = @_;
2089 return if (@lines <= 0);
2091 if ($divisor <= 0) {
2092 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
2096 @lines = mailmap(@lines);
2098 return if (@lines <= 0);
2100 @lines = sort(@lines);
2103 $hash{$_}++ for @lines;
2106 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
2107 my $sign_offs = $hash{$line};
2108 my $percent = $sign_offs * 100 / $divisor;
2110 $percent = 100 if ($percent > 100);
2111 next if (ignore_email_address($line));
2113 last if ($sign_offs < $email_git_min_signatures ||
2114 $count > $email_git_max_maintainers ||
2115 $percent < $email_git_min_percent);
2116 push_email_address($line, '');
2117 if ($output_rolestats) {
2118 my $fmt_percent = sprintf("%.0f", $percent);
2119 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
2121 add_role($line, $role);
2126 sub vcs_file_signoffs {
2137 $vcs_used = vcs_exists();
2138 return if (!$vcs_used);
2140 my $cmd = $VCS_cmds{"find_signers_cmd"};
2141 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2143 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2145 @signers = @{$signers_ref} if defined $signers_ref;
2146 @authors = @{$authors_ref} if defined $authors_ref;
2147 @stats = @{$stats_ref} if defined $stats_ref;
2149 # print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
2151 foreach my $signer (@signers) {
2152 $signer = deduplicate_email($signer);
2155 vcs_assign("commit_signer", $commits, @signers);
2156 vcs_assign("authored", $commits, @authors);
2157 if ($#authors == $#stats) {
2158 my $stat_pattern = $VCS_cmds{"stat_pattern"};
2159 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
2163 for (my $i = 0; $i <= $#stats; $i++) {
2164 if ($stats[$i] =~ /$stat_pattern/) {
2169 my @tmp_authors = uniq(@authors);
2170 foreach my $author (@tmp_authors) {
2171 $author = deduplicate_email($author);
2173 @tmp_authors = uniq(@tmp_authors);
2174 my @list_added = ();
2175 my @list_deleted = ();
2176 foreach my $author (@tmp_authors) {
2178 my $auth_deleted = 0;
2179 for (my $i = 0; $i <= $#stats; $i++) {
2180 if ($author eq deduplicate_email($authors[$i]) &&
2181 $stats[$i] =~ /$stat_pattern/) {
2183 $auth_deleted += $2;
2186 for (my $i = 0; $i < $auth_added; $i++) {
2187 push(@list_added, $author);
2189 for (my $i = 0; $i < $auth_deleted; $i++) {
2190 push(@list_deleted, $author);
2193 vcs_assign("added_lines", $added, @list_added);
2194 vcs_assign("removed_lines", $deleted, @list_deleted);
2198 sub vcs_file_blame {
2202 my @all_commits = ();
2207 $vcs_used = vcs_exists();
2208 return if (!$vcs_used);
2210 @all_commits = vcs_blame($file);
2211 @commits = uniq(@all_commits);
2212 $total_commits = @commits;
2213 $total_lines = @all_commits;
2215 if ($email_git_blame_signatures) {
2218 my $commit_authors_ref;
2219 my $commit_signers_ref;
2221 my @commit_authors = ();
2222 my @commit_signers = ();
2223 my $commit = join(" -r ", @commits);
2226 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2227 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2229 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2230 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2231 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2233 push(@signers, @commit_signers);
2235 foreach my $commit (@commits) {
2237 my $commit_authors_ref;
2238 my $commit_signers_ref;
2240 my @commit_authors = ();
2241 my @commit_signers = ();
2244 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2245 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2247 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2248 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2249 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2251 push(@signers, @commit_signers);
2256 if ($from_filename) {
2257 if ($output_rolestats) {
2259 if (vcs_is_hg()) {{ # Double brace for last exit
2261 my @commit_signers = ();
2262 @commits = uniq(@commits);
2263 @commits = sort(@commits);
2264 my $commit = join(" -r ", @commits);
2267 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2268 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2272 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2274 if (!$email_git_penguin_chiefs) {
2275 @lines = grep(!/${penguin_chiefs}/i, @lines);
2281 foreach my $line (@lines) {
2282 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2284 $author = deduplicate_email($author);
2285 push(@authors, $author);
2289 save_commits_by_author(@lines) if ($interactive);
2290 save_commits_by_signer(@lines) if ($interactive);
2292 push(@signers, @authors);
2295 foreach my $commit (@commits) {
2297 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2298 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2299 my @author = vcs_find_author($cmd);
2302 my $formatted_author = deduplicate_email($author[0]);
2304 my $count = grep(/$commit/, @all_commits);
2305 for ($i = 0; $i < $count ; $i++) {
2306 push(@blame_signers, $formatted_author);
2310 if (@blame_signers) {
2311 vcs_assign("authored lines", $total_lines, @blame_signers);
2314 foreach my $signer (@signers) {
2315 $signer = deduplicate_email($signer);
2317 vcs_assign("commits", $total_commits, @signers);
2319 foreach my $signer (@signers) {
2320 $signer = deduplicate_email($signer);
2322 vcs_assign("modified commits", $total_commits, @signers);
2326 sub vcs_file_exists {
2331 my $vcs_used = vcs_exists();
2332 return 0 if (!$vcs_used);
2334 my $cmd = $VCS_cmds{"file_exists_cmd"};
2335 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2337 $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
2339 return 0 if ($? != 0);
2344 sub vcs_list_files {
2349 my $vcs_used = vcs_exists();
2350 return 0 if (!$vcs_used);
2352 my $cmd = $VCS_cmds{"list_files_cmd"};
2353 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2354 @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd);
2356 return () if ($? != 0);
2365 @parms = grep(!$saw{$_}++, @parms);
2373 @parms = sort @parms;
2374 @parms = grep(!$saw{$_}++, @parms);
2378 sub clean_file_emails {
2379 my (@file_emails) = @_;
2380 my @fmt_emails = ();
2382 foreach my $email (@file_emails) {
2383 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2384 my ($name, $address) = parse_email($email);
2385 if ($name eq '"[,\.]"') {
2389 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2391 my $first = $nw[@nw - 3];
2392 my $middle = $nw[@nw - 2];
2393 my $last = $nw[@nw - 1];
2395 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2396 (length($first) == 2 && substr($first, -1) eq ".")) ||
2397 (length($middle) == 1 ||
2398 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2399 $name = "$first $middle $last";
2401 $name = "$middle $last";
2405 if (substr($name, -1) =~ /[,\.]/) {
2406 $name = substr($name, 0, length($name) - 1);
2407 } elsif (substr($name, -2) =~ /[,\.]"/) {
2408 $name = substr($name, 0, length($name) - 2) . '"';
2411 if (substr($name, 0, 1) =~ /[,\.]/) {
2412 $name = substr($name, 1, length($name) - 1);
2413 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2414 $name = '"' . substr($name, 2, length($name) - 2);
2417 my $fmt_email = format_email($name, $address, $email_usename);
2418 push(@fmt_emails, $fmt_email);
2428 my ($address, $role) = @$_;
2429 if (!$saw{$address}) {
2430 if ($output_roles) {
2431 push(@lines, "$address ($role)");
2433 push(@lines, $address);
2445 if ($output_multiline) {
2446 foreach my $line (@parms) {
2450 print(join($output_separator, @parms));
2458 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2459 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2460 # This regexp will only work on addresses which have had comments stripped
2461 # and replaced with rfc822_lwsp.
2463 my $specials = '()<>@,;:\\\\".\\[\\]';
2464 my $controls = '\\000-\\037\\177';
2466 my $dtext = "[^\\[\\]\\r\\\\]";
2467 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2469 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2471 # Use zero-width assertion to spot the limit of an atom. A simple
2472 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2473 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2474 my $word = "(?:$atom|$quoted_string)";
2475 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2477 my $sub_domain = "(?:$atom|$domain_literal)";
2478 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2480 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2482 my $phrase = "$word*";
2483 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2484 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2485 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2487 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2488 my $address = "(?:$mailbox|$group)";
2490 return "$rfc822_lwsp*$address";
2493 sub rfc822_strip_comments {
2495 # Recursively remove comments, and replace with a single space. The simpler
2496 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2497 # chars in atoms, for example.
2499 while ($s =~ s/^((?:[^"\\]|\\.)*
2500 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2501 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2505 # valid: returns true if the parameter is an RFC822 valid address
2508 my $s = rfc822_strip_comments(shift);
2511 $rfc822re = make_rfc822re();
2514 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2517 # validlist: In scalar context, returns true if the parameter is an RFC822
2518 # valid list of addresses.
2520 # In list context, returns an empty list on failure (an invalid
2521 # address was found); otherwise a list whose first element is the
2522 # number of addresses found and whose remaining elements are the
2523 # addresses. This is needed to disambiguate failure (invalid)
2524 # from success with no addresses found, because an empty string is
2527 sub rfc822_validlist {
2528 my $s = rfc822_strip_comments(shift);
2531 $rfc822re = make_rfc822re();
2533 # * null list items are valid according to the RFC
2534 # * the '1' business is to aid in distinguishing failure from no results
2537 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2538 $s =~ m/^$rfc822_char*$/) {
2539 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2542 return wantarray ? (scalar(@r), @r) : 1;
2544 return wantarray ? () : 0;