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) {
504 if ($file ne "&STDIN") {
505 ##if $file is a directory and it lacks a trailing slash, add one
507 $file =~ s@([^/])$@$1/@;
508 } elsif (!(-f $file)) {
509 die "$P: file '${file}' not found\n";
512 if ($from_filename || ($file ne "&STDIN" && vcs_file_exists($file))) {
513 $file =~ s/^\Q${cur_path}\E//; #strip any absolute path
514 $file =~ s/^\Q${lk_path}\E//; #or the path to the lk tree
516 if ($file ne "MAINTAINERS" && -f $file && ($keywords || $file_emails)) {
517 open(my $f, '<', $file)
518 or die "$P: Can't open $file: $!\n";
519 my $text = do { local($/) ; <$f> };
522 foreach my $line (keys %keyword_hash) {
523 if ($text =~ m/$keyword_hash{$line}/x) {
524 push(@keyword_tvi, $line);
529 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;
530 push(@file_emails, clean_file_emails(@poss_addr));
534 my $file_cnt = @files;
537 open(my $patch, "< $file")
538 or die "$P: Can't open $file: $!\n";
540 # We can check arbitrary information before the patch
541 # like the commit message, mail headers, etc...
542 # This allows us to match arbitrary keywords against any part
543 # of a git format-patch generated file (subject tags, etc...)
545 my $patch_prefix = ""; #Parsing the intro
549 if (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
551 $filename =~ s@^[^/]*/@@;
553 $lastfile = $filename;
554 push(@files, $filename);
555 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
556 } elsif (m/^\@\@ -(\d+),(\d+)/) {
557 if ($email_git_blame) {
558 push(@range, "$lastfile:$1:$2");
560 } elsif ($keywords) {
561 foreach my $line (keys %keyword_hash) {
562 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
563 push(@keyword_tvi, $line);
570 if ($file_cnt == @files) {
571 warn "$P: file '${file}' doesn't appear to be a patch. "
572 . "Add -f to options?\n";
574 @files = sort_and_uniq(@files);
578 @file_emails = uniq(@file_emails);
581 my %email_hash_address;
589 my %deduplicate_name_hash = ();
590 my %deduplicate_address_hash = ();
592 my @maintainers = get_maintainers();
595 @maintainers = merge_email(@maintainers);
596 output(@maintainers);
605 @status = uniq(@status);
610 @subsystem = uniq(@subsystem);
625 my @section_headers = ();
628 @lsfiles = vcs_list_files($lk_path);
630 for my $x (@self_test_info) {
633 ## Section header duplication and missing section content
634 if (($self_test eq "" || $self_test =~ /\bsections\b/) &&
635 $x->{line} =~ /^\S[^:]/ &&
636 defined $self_test_info[$index] &&
637 $self_test_info[$index]->{line} =~ /^([A-Z]):\s*\S/) {
642 if (grep(m@^\Q$x->{line}\E@, @section_headers)) {
643 print("$x->{file}:$x->{linenr}: warning: duplicate section header\t$x->{line}\n");
645 push(@section_headers, $x->{line});
647 my $nextline = $index;
648 while (defined $self_test_info[$nextline] &&
649 $self_test_info[$nextline]->{line} =~ /^([A-Z]):\s*(\S.*)/) {
655 } elsif ($type eq "F" || $type eq "N") {
657 } elsif ($type eq "M" || $type eq "R" || $type eq "L") {
662 if (!$has_ML && $status !~ /orphan|obsolete/i) {
663 print("$x->{file}:$x->{linenr}: warning: section without email address\t$x->{line}\n");
666 print("$x->{file}:$x->{linenr}: warning: section without status \t$x->{line}\n");
669 print("$x->{file}:$x->{linenr}: warning: section without file pattern\t$x->{line}\n");
673 next if ($x->{line} !~ /^([A-Z]):\s*(.*)/);
678 ## Filename pattern matching
679 if (($type eq "F" || $type eq "X") &&
680 ($self_test eq "" || $self_test =~ /\bpatterns\b/)) {
681 $value =~ s@\.@\\\.@g; ##Convert . to \.
682 $value =~ s/\*/\.\*/g; ##Convert * to .*
683 $value =~ s/\?/\./g; ##Convert ? to .
684 ##if pattern is a directory and it lacks a trailing slash, add one
686 $value =~ s@([^/])$@$1/@;
688 if (!grep(m@^$value@, @lsfiles)) {
689 print("$x->{file}:$x->{linenr}: warning: no file matches\t$x->{line}\n");
693 } elsif (($type eq "W" || $type eq "Q" || $type eq "B") &&
694 $value =~ /^https?:/ &&
695 ($self_test eq "" || $self_test =~ /\blinks\b/)) {
696 next if (grep(m@^\Q$value\E$@, @good_links));
698 if (grep(m@^\Q$value\E$@, @bad_links)) {
701 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $value`;
703 push(@good_links, $value);
705 push(@bad_links, $value);
710 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
714 } elsif ($type eq "T" &&
715 ($self_test eq "" || $self_test =~ /\bscm\b/)) {
716 next if (grep(m@^\Q$value\E$@, @good_links));
718 if (grep(m@^\Q$value\E$@, @bad_links)) {
720 } elsif ($value !~ /^(?:git|quilt|hg)\s+\S/) {
721 print("$x->{file}:$x->{linenr}: warning: malformed entry\t$x->{line}\n");
722 } elsif ($value =~ /^git\s+(\S+)(\s+([^\(]+\S+))?/) {
726 my $output = `git ls-remote --exit-code -h "$url" $branch > /dev/null 2>&1`;
728 push(@good_links, $value);
730 push(@bad_links, $value);
733 } elsif ($value =~ /^(?:quilt|hg)\s+(https?:\S+)/) {
735 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $url`;
737 push(@good_links, $value);
739 push(@bad_links, $value);
744 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
750 sub ignore_email_address {
753 foreach my $ignore (@ignore_emails) {
754 return 1 if ($ignore eq $address);
760 sub range_is_maintained {
761 my ($start, $end) = @_;
763 for (my $i = $start; $i < $end; $i++) {
764 my $line = $typevalue[$i];
765 if ($line =~ m/^([A-Z]):\s*(.*)/) {
769 if ($value =~ /(maintain|support)/i) {
778 sub range_has_maintainer {
779 my ($start, $end) = @_;
781 for (my $i = $start; $i < $end; $i++) {
782 my $line = $typevalue[$i];
783 if ($line =~ m/^([A-Z]):\s*(.*)/) {
794 sub get_maintainers {
795 %email_hash_name = ();
796 %email_hash_address = ();
797 %commit_author_hash = ();
798 %commit_signer_hash = ();
806 %deduplicate_name_hash = ();
807 %deduplicate_address_hash = ();
808 if ($email_git_all_signature_types) {
809 $signature_pattern = "(.+?)[Bb][Yy]:";
811 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
814 # Find responsible parties
816 my %exact_pattern_match_hash = ();
818 foreach my $file (@files) {
821 my $tvi = find_first_section();
822 while ($tvi < @typevalue) {
823 my $start = find_starting_index($tvi);
824 my $end = find_ending_index($tvi);
828 #Do not match excluded file patterns
830 for ($i = $start; $i < $end; $i++) {
831 my $line = $typevalue[$i];
832 if ($line =~ m/^([A-Z]):\s*(.*)/) {
836 if (file_match_pattern($file, $value)) {
845 for ($i = $start; $i < $end; $i++) {
846 my $line = $typevalue[$i];
847 if ($line =~ m/^([A-Z]):\s*(.*)/) {
851 if (file_match_pattern($file, $value)) {
852 my $value_pd = ($value =~ tr@/@@);
853 my $file_pd = ($file =~ tr@/@@);
854 $value_pd++ if (substr($value,-1,1) ne "/");
855 $value_pd = -1 if ($value =~ /^\.\*/);
856 if ($value_pd >= $file_pd &&
857 range_is_maintained($start, $end) &&
858 range_has_maintainer($start, $end)) {
859 $exact_pattern_match_hash{$file} = 1;
861 if ($pattern_depth == 0 ||
862 (($file_pd - $value_pd) < $pattern_depth)) {
863 $hash{$tvi} = $value_pd;
866 } elsif ($type eq 'N') {
867 if ($file =~ m/$value/x) {
877 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
878 add_categories($line);
881 my $start = find_starting_index($line);
882 my $end = find_ending_index($line);
883 for ($i = $start; $i < $end; $i++) {
884 my $line = $typevalue[$i];
885 if ($line =~ /^[FX]:/) { ##Restore file patterns
886 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
887 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
888 $line =~ s/\\\./\./g; ##Convert \. to .
889 $line =~ s/\.\*/\*/g; ##Convert .* to *
891 my $count = $line =~ s/^([A-Z]):/$1:\t/g;
892 if ($letters eq "" || (!$count || $letters =~ /$1/i)) {
902 @keyword_tvi = sort_and_uniq(@keyword_tvi);
903 foreach my $line (@keyword_tvi) {
904 add_categories($line);
908 foreach my $email (@email_to, @list_to) {
909 $email->[0] = deduplicate_email($email->[0]);
912 foreach my $file (@files) {
914 ($email_git || ($email_git_fallback &&
915 !$exact_pattern_match_hash{$file}))) {
916 vcs_file_signoffs($file);
918 if ($email && $email_git_blame) {
919 vcs_file_blame($file);
924 foreach my $chief (@penguin_chief) {
925 if ($chief =~ m/^(.*):(.*)/) {
928 $email_address = format_email($1, $2, $email_usename);
929 if ($email_git_penguin_chiefs) {
930 push(@email_to, [$email_address, 'chief penguin']);
932 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
937 foreach my $email (@file_emails) {
938 my ($name, $address) = parse_email($email);
940 my $tmp_email = format_email($name, $address, $email_usename);
941 push_email_address($tmp_email, '');
942 add_role($tmp_email, 'in file');
947 if ($email || $email_list) {
949 @to = (@to, @email_to);
952 @to = (@to, @list_to);
957 @to = interactive_get_maintainers(\@to);
963 sub file_match_pattern {
964 my ($file, $pattern) = @_;
965 if (substr($pattern, -1) eq "/") {
966 if ($file =~ m@^$pattern@) {
970 if ($file =~ m@^$pattern@) {
971 my $s1 = ($file =~ tr@/@@);
972 my $s2 = ($pattern =~ tr@/@@);
983 usage: $P [options] patchfile
984 $P [options] -f file|directory
987 MAINTAINER field selection options:
988 --email => print email address(es) if any
989 --git => include recent git \*-by: signers
990 --git-all-signature-types => include signers regardless of signature type
991 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
992 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
993 --git-chief-penguins => include ${penguin_chiefs}
994 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
995 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
996 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
997 --git-blame => use git blame to find modified commits for patch or file
998 --git-blame-signatures => when used with --git-blame, also include all commit signers
999 --git-since => git history to use (default: $email_git_since)
1000 --hg-since => hg history to use (default: $email_hg_since)
1001 --interactive => display a menu (mostly useful if used with the --git option)
1002 --m => include maintainer(s) if any
1003 --r => include reviewer(s) if any
1004 --n => include name 'Full Name <addr\@domain.tld>'
1005 --l => include list(s) if any
1006 --s => include subscriber only list(s) if any
1007 --remove-duplicates => minimize duplicate email names/addresses
1008 --roles => show roles (status:subsystem, git-signer, list, etc...)
1009 --rolestats => show roles and statistics (commits/total_commits, %)
1010 --file-emails => add email addresses found in -f file (default: 0 (off))
1011 --scm => print SCM tree(s) if any
1012 --status => print status if any
1013 --subsystem => print subsystem name if any
1014 --web => print website(s) if any
1016 Output type options:
1017 --separator [, ] => separator for multiple entries on 1 line
1018 using --separator also sets --nomultiline if --separator is not [, ]
1019 --multiline => print 1 entry per line
1022 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
1023 --keywords => scan patch for keywords (default: $keywords)
1024 --sections => print all of the subsystem sections with pattern matches
1025 --letters => print all matching 'letter' types from all matching sections
1026 --mailmap => use .mailmap file (default: $email_use_mailmap)
1027 --self-test => show potential issues with MAINTAINERS file content
1028 --version => show version
1029 --help => show this help information
1032 [--email --nogit --git-fallback --m --r --n --l --multiline --pattern-depth=0
1033 --remove-duplicates --rolestats]
1036 Using "-f directory" may give unexpected results:
1037 Used with "--git", git signators for _all_ files in and below
1038 directory are examined as git recurses directories.
1039 Any specified X: (exclude) pattern matches are _not_ ignored.
1040 Used with "--nogit", directory is used as a pattern match,
1041 no individual file within the directory or subdirectory
1043 Used with "--git-blame", does not iterate all files in directory
1044 Using "--git-blame" is slow and may add old committers and authors
1045 that are no longer active maintainers to the output.
1046 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
1047 other automated tools that expect only ["name"] <email address>
1048 may not work because of additional output after <email address>.
1049 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
1050 not the percentage of the entire file authored. # of commits is
1051 not a good measure of amount of code authored. 1 major commit may
1052 contain a thousand lines, 5 trivial commits may modify a single line.
1053 If git is not installed, but mercurial (hg) is installed and an .hg
1054 repository exists, the following options apply to mercurial:
1056 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
1058 Use --hg-since not --git-since to control date selection
1059 File ".get_maintainer.conf", if it exists in the linux kernel source root
1060 directory, can change whatever get_maintainer defaults are desired.
1061 Entries in this file can be any command line argument.
1062 This file is prepended to any additional command line arguments.
1063 Multiple lines and # comments are allowed.
1064 Most options have both positive and negative forms.
1065 The negative forms for --<foo> are --no<foo> and --no-<foo>.
1070 sub top_of_kernel_tree {
1073 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
1076 if ( (-f "${lk_path}autoMakefile.am")
1077 && (-f "${lk_path}COPYING")
1078 && (-d "${lk_path}Documentation")
1079 && (-e "${lk_path}lustre.spec.in")
1080 && (-e "${lk_path}MAINTAINERS")
1081 && (-f "${lk_path}README")) {
1088 my ($formatted_email) = @_;
1093 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
1096 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
1098 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
1102 $name =~ s/^\s+|\s+$//g;
1103 $name =~ s/^\"|\"$//g;
1104 $address =~ s/^\s+|\s+$//g;
1106 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
1107 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1108 $name = "\"$name\"";
1111 return ($name, $address);
1115 my ($name, $address, $usename) = @_;
1117 my $formatted_email;
1119 $name =~ s/^\s+|\s+$//g;
1120 $name =~ s/^\"|\"$//g;
1121 $address =~ s/^\s+|\s+$//g;
1123 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
1124 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1125 $name = "\"$name\"";
1129 if ("$name" eq "") {
1130 $formatted_email = "$address";
1132 $formatted_email = "$name <$address>";
1135 $formatted_email = $address;
1138 return $formatted_email;
1141 sub find_first_section {
1144 while ($index < @typevalue) {
1145 my $tv = $typevalue[$index];
1146 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
1155 sub find_starting_index {
1158 while ($index > 0) {
1159 my $tv = $typevalue[$index];
1160 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1169 sub find_ending_index {
1172 while ($index < @typevalue) {
1173 my $tv = $typevalue[$index];
1174 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1183 sub get_subsystem_name {
1186 my $start = find_starting_index($index);
1188 my $subsystem = $typevalue[$start];
1189 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
1190 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
1191 $subsystem =~ s/\s*$//;
1192 $subsystem = $subsystem . "...";
1197 sub get_maintainer_role {
1201 my $start = find_starting_index($index);
1202 my $end = find_ending_index($index);
1204 my $role = "unknown";
1205 my $subsystem = get_subsystem_name($index);
1207 for ($i = $start + 1; $i < $end; $i++) {
1208 my $tv = $typevalue[$i];
1209 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1212 if ($ptype eq "S") {
1219 if ($role eq "supported") {
1220 $role = "supporter";
1221 } elsif ($role eq "maintained") {
1222 $role = "maintainer";
1223 } elsif ($role eq "odd fixes") {
1224 $role = "odd fixer";
1225 } elsif ($role eq "orphan") {
1226 $role = "orphan minder";
1227 } elsif ($role eq "obsolete") {
1228 $role = "obsolete minder";
1229 } elsif ($role eq "buried alive in reporters") {
1230 $role = "chief penguin";
1233 return $role . ":" . $subsystem;
1239 my $subsystem = get_subsystem_name($index);
1241 if ($subsystem eq "THE REST") {
1248 sub add_categories {
1252 my $start = find_starting_index($index);
1253 my $end = find_ending_index($index);
1255 push(@subsystem, $typevalue[$start]);
1257 for ($i = $start + 1; $i < $end; $i++) {
1258 my $tv = $typevalue[$i];
1259 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1262 if ($ptype eq "L") {
1263 my $list_address = $pvalue;
1264 my $list_additional = "";
1265 my $list_role = get_list_role($i);
1267 if ($list_role ne "") {
1268 $list_role = ":" . $list_role;
1270 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1272 $list_additional = $2;
1274 if ($list_additional =~ m/subscribers-only/) {
1275 if ($email_subscriber_list) {
1276 if (!$hash_list_to{lc($list_address)}) {
1277 $hash_list_to{lc($list_address)} = 1;
1278 push(@list_to, [$list_address,
1279 "subscriber list${list_role}"]);
1284 if (!$hash_list_to{lc($list_address)}) {
1285 $hash_list_to{lc($list_address)} = 1;
1286 if ($list_additional =~ m/moderated/) {
1287 push(@list_to, [$list_address,
1288 "moderated list${list_role}"]);
1290 push(@list_to, [$list_address,
1291 "open list${list_role}"]);
1296 } elsif ($ptype eq "M") {
1297 my ($name, $address) = parse_email($pvalue);
1300 my $tv = $typevalue[$i - 1];
1301 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1304 $pvalue = format_email($name, $address, $email_usename);
1309 if ($email_maintainer) {
1310 my $role = get_maintainer_role($i);
1311 push_email_addresses($pvalue, $role);
1313 } elsif ($ptype eq "R") {
1314 my ($name, $address) = parse_email($pvalue);
1317 my $tv = $typevalue[$i - 1];
1318 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1321 $pvalue = format_email($name, $address, $email_usename);
1326 if ($email_reviewer) {
1327 my $subsystem = get_subsystem_name($i);
1328 push_email_addresses($pvalue, "reviewer:$subsystem");
1330 } elsif ($ptype eq "T") {
1331 push(@scm, $pvalue);
1332 } elsif ($ptype eq "W") {
1333 push(@web, $pvalue);
1334 } elsif ($ptype eq "S") {
1335 push(@status, $pvalue);
1342 my ($name, $address) = @_;
1344 return 1 if (($name eq "") && ($address eq ""));
1345 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1346 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1351 sub push_email_address {
1352 my ($line, $role) = @_;
1354 my ($name, $address) = parse_email($line);
1356 if ($address eq "") {
1360 if (!$email_remove_duplicates) {
1361 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1362 } elsif (!email_inuse($name, $address)) {
1363 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1364 $email_hash_name{lc($name)}++ if ($name ne "");
1365 $email_hash_address{lc($address)}++;
1371 sub push_email_addresses {
1372 my ($address, $role) = @_;
1374 my @address_list = ();
1376 if (rfc822_valid($address)) {
1377 push_email_address($address, $role);
1378 } elsif (@address_list = rfc822_validlist($address)) {
1379 my $array_count = shift(@address_list);
1380 while (my $entry = shift(@address_list)) {
1381 push_email_address($entry, $role);
1384 if (!push_email_address($address, $role)) {
1385 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1391 my ($line, $role) = @_;
1393 my ($name, $address) = parse_email($line);
1394 my $email = format_email($name, $address, $email_usename);
1396 foreach my $entry (@email_to) {
1397 if ($email_remove_duplicates) {
1398 my ($entry_name, $entry_address) = parse_email($entry->[0]);
1399 if (($name eq $entry_name || $address eq $entry_address)
1400 && ($role eq "" || !($entry->[1] =~ m/$role/))
1402 if ($entry->[1] eq "") {
1403 $entry->[1] = "$role";
1405 $entry->[1] = "$entry->[1],$role";
1409 if ($email eq $entry->[0]
1410 && ($role eq "" || !($entry->[1] =~ m/$role/))
1412 if ($entry->[1] eq "") {
1413 $entry->[1] = "$role";
1415 $entry->[1] = "$entry->[1],$role";
1425 foreach my $path (split(/:/, $ENV{PATH})) {
1426 if (-e "$path/$bin") {
1427 return "$path/$bin";
1437 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1438 if (-e "$path/$conf") {
1439 return "$path/$conf";
1449 my ($name, $address) = parse_email($line);
1450 my $email = format_email($name, $address, 1);
1451 my $real_name = $name;
1452 my $real_address = $address;
1454 if (exists $mailmap->{names}->{$email} ||
1455 exists $mailmap->{addresses}->{$email}) {
1456 if (exists $mailmap->{names}->{$email}) {
1457 $real_name = $mailmap->{names}->{$email};
1459 if (exists $mailmap->{addresses}->{$email}) {
1460 $real_address = $mailmap->{addresses}->{$email};
1463 if (exists $mailmap->{names}->{$address}) {
1464 $real_name = $mailmap->{names}->{$address};
1466 if (exists $mailmap->{addresses}->{$address}) {
1467 $real_address = $mailmap->{addresses}->{$address};
1470 return format_email($real_name, $real_address, 1);
1474 my (@addresses) = @_;
1476 my @mapped_emails = ();
1477 foreach my $line (@addresses) {
1478 push(@mapped_emails, mailmap_email($line));
1480 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1481 return @mapped_emails;
1484 sub merge_by_realname {
1488 foreach my $email (@emails) {
1489 my ($name, $address) = parse_email($email);
1490 if (exists $address_map{$name}) {
1491 $address = $address_map{$name};
1492 $email = format_email($name, $address, 1);
1494 $address_map{$name} = $address;
1499 sub git_execute_cmd {
1503 my $output = `$cmd`;
1504 $output =~ s/^\s*//gm;
1505 @lines = split("\n", $output);
1510 sub hg_execute_cmd {
1514 my $output = `$cmd`;
1515 @lines = split("\n", $output);
1520 sub extract_formatted_signatures {
1521 my (@signature_lines) = @_;
1523 my @type = @signature_lines;
1525 s/\s*(.*):.*/$1/ for (@type);
1528 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1530 ## Reformat email addresses (with names) to avoid badly written signatures
1532 foreach my $signer (@signature_lines) {
1533 $signer = deduplicate_email($signer);
1536 return (\@type, \@signature_lines);
1539 sub vcs_find_signers {
1540 my ($cmd, $file) = @_;
1543 my @signatures = ();
1547 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1549 my $pattern = $VCS_cmds{"commit_pattern"};
1550 my $author_pattern = $VCS_cmds{"author_pattern"};
1551 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1553 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
1555 $commits = grep(/$pattern/, @lines); # of commits
1557 @authors = grep(/$author_pattern/, @lines);
1558 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1559 @stats = grep(/$stat_pattern/, @lines);
1561 # print("stats: <@stats>\n");
1563 return (0, \@signatures, \@authors, \@stats) if !@signatures;
1565 save_commits_by_author(@lines) if ($interactive);
1566 save_commits_by_signer(@lines) if ($interactive);
1568 if (!$email_git_penguin_chiefs) {
1569 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1572 my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
1573 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1575 return ($commits, $signers_ref, $authors_ref, \@stats);
1578 sub vcs_find_author {
1582 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1584 if (!$email_git_penguin_chiefs) {
1585 @lines = grep(!/${penguin_chiefs}/i, @lines);
1588 return @lines if !@lines;
1591 foreach my $line (@lines) {
1592 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1594 my ($name, $address) = parse_email($author);
1595 $author = format_email($name, $address, 1);
1596 push(@authors, $author);
1600 save_commits_by_author(@lines) if ($interactive);
1601 save_commits_by_signer(@lines) if ($interactive);
1606 sub vcs_save_commits {
1611 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1613 foreach my $line (@lines) {
1614 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1627 return @commits if (!(-f $file));
1629 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1630 my @all_commits = ();
1632 $cmd = $VCS_cmds{"blame_file_cmd"};
1633 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1634 @all_commits = vcs_save_commits($cmd);
1636 foreach my $file_range_diff (@range) {
1637 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1639 my $diff_start = $2;
1640 my $diff_length = $3;
1641 next if ("$file" ne "$diff_file");
1642 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1643 push(@commits, $all_commits[$i]);
1647 foreach my $file_range_diff (@range) {
1648 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1650 my $diff_start = $2;
1651 my $diff_length = $3;
1652 next if ("$file" ne "$diff_file");
1653 $cmd = $VCS_cmds{"blame_range_cmd"};
1654 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1655 push(@commits, vcs_save_commits($cmd));
1658 $cmd = $VCS_cmds{"blame_file_cmd"};
1659 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1660 @commits = vcs_save_commits($cmd);
1663 foreach my $commit (@commits) {
1664 $commit =~ s/^\^//g;
1670 my $printed_novcs = 0;
1672 %VCS_cmds = %VCS_cmds_git;
1673 return 1 if eval $VCS_cmds{"available"};
1674 %VCS_cmds = %VCS_cmds_hg;
1675 return 2 if eval $VCS_cmds{"available"};
1677 if (!$printed_novcs) {
1678 warn("$P: No supported VCS found. Add --nogit to options?\n");
1679 warn("Using a git repository produces better results.\n");
1680 warn("Try Linus Torvalds' latest git repository using:\n");
1681 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
1689 return $vcs_used == 1;
1693 return $vcs_used == 2;
1696 sub interactive_get_maintainers {
1697 my ($list_ref) = @_;
1698 my @list = @$list_ref;
1707 foreach my $entry (@list) {
1708 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1709 $selected{$count} = 1;
1710 $authored{$count} = 0;
1711 $signed{$count} = 0;
1717 my $print_options = 0;
1722 printf STDERR "\n%1s %2s %-65s",
1723 "*", "#", "email/list and role:stats";
1725 ($email_git_fallback && !$maintained) ||
1727 print STDERR "auth sign";
1730 foreach my $entry (@list) {
1731 my $email = $entry->[0];
1732 my $role = $entry->[1];
1734 $sel = "*" if ($selected{$count});
1735 my $commit_author = $commit_author_hash{$email};
1736 my $commit_signer = $commit_signer_hash{$email};
1739 $authored++ for (@{$commit_author});
1740 $signed++ for (@{$commit_signer});
1741 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1742 printf STDERR "%4d %4d", $authored, $signed
1743 if ($authored > 0 || $signed > 0);
1744 printf STDERR "\n %s\n", $role;
1745 if ($authored{$count}) {
1746 my $commit_author = $commit_author_hash{$email};
1747 foreach my $ref (@{$commit_author}) {
1748 print STDERR " Author: @{$ref}[1]\n";
1751 if ($signed{$count}) {
1752 my $commit_signer = $commit_signer_hash{$email};
1753 foreach my $ref (@{$commit_signer}) {
1754 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1761 my $date_ref = \$email_git_since;
1762 $date_ref = \$email_hg_since if (vcs_is_hg());
1763 if ($print_options) {
1768 Version Control options:
1769 g use git history [$email_git]
1770 gf use git-fallback [$email_git_fallback]
1771 b use git blame [$email_git_blame]
1772 bs use blame signatures [$email_git_blame_signatures]
1773 c# minimum commits [$email_git_min_signatures]
1774 %# min percent [$email_git_min_percent]
1775 d# history to use [$$date_ref]
1776 x# max maintainers [$email_git_max_maintainers]
1777 t all signature types [$email_git_all_signature_types]
1778 m use .mailmap [$email_use_mailmap]
1785 tm toggle maintainers
1786 tg toggle git entries
1787 tl toggle open list entries
1788 ts toggle subscriber list entries
1789 f emails in file [$file_emails]
1790 k keywords in file [$keywords]
1791 r remove duplicates [$email_remove_duplicates]
1792 p# pattern match depth [$pattern_depth]
1796 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1798 my $input = <STDIN>;
1803 my @wish = split(/[, ]+/, $input);
1804 foreach my $nr (@wish) {
1806 my $sel = substr($nr, 0, 1);
1807 my $str = substr($nr, 1);
1809 $val = $1 if $str =~ /^(\d+)$/;
1814 $output_rolestats = 0;
1817 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1818 $selected{$nr - 1} = !$selected{$nr - 1};
1819 } elsif ($sel eq "*" || $sel eq '^') {
1821 $toggle = 1 if ($sel eq '*');
1822 for (my $i = 0; $i < $count; $i++) {
1823 $selected{$i} = $toggle;
1825 } elsif ($sel eq "0") {
1826 for (my $i = 0; $i < $count; $i++) {
1827 $selected{$i} = !$selected{$i};
1829 } elsif ($sel eq "t") {
1830 if (lc($str) eq "m") {
1831 for (my $i = 0; $i < $count; $i++) {
1832 $selected{$i} = !$selected{$i}
1833 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1835 } elsif (lc($str) eq "g") {
1836 for (my $i = 0; $i < $count; $i++) {
1837 $selected{$i} = !$selected{$i}
1838 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1840 } elsif (lc($str) eq "l") {
1841 for (my $i = 0; $i < $count; $i++) {
1842 $selected{$i} = !$selected{$i}
1843 if ($list[$i]->[1] =~ /^(open list)/i);
1845 } elsif (lc($str) eq "s") {
1846 for (my $i = 0; $i < $count; $i++) {
1847 $selected{$i} = !$selected{$i}
1848 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1851 } elsif ($sel eq "a") {
1852 if ($val > 0 && $val <= $count) {
1853 $authored{$val - 1} = !$authored{$val - 1};
1854 } elsif ($str eq '*' || $str eq '^') {
1856 $toggle = 1 if ($str eq '*');
1857 for (my $i = 0; $i < $count; $i++) {
1858 $authored{$i} = $toggle;
1861 } elsif ($sel eq "s") {
1862 if ($val > 0 && $val <= $count) {
1863 $signed{$val - 1} = !$signed{$val - 1};
1864 } elsif ($str eq '*' || $str eq '^') {
1866 $toggle = 1 if ($str eq '*');
1867 for (my $i = 0; $i < $count; $i++) {
1868 $signed{$i} = $toggle;
1871 } elsif ($sel eq "o") {
1874 } elsif ($sel eq "g") {
1876 bool_invert(\$email_git_fallback);
1878 bool_invert(\$email_git);
1881 } elsif ($sel eq "b") {
1883 bool_invert(\$email_git_blame_signatures);
1885 bool_invert(\$email_git_blame);
1888 } elsif ($sel eq "c") {
1890 $email_git_min_signatures = $val;
1893 } elsif ($sel eq "x") {
1895 $email_git_max_maintainers = $val;
1898 } elsif ($sel eq "%") {
1899 if ($str ne "" && $val >= 0) {
1900 $email_git_min_percent = $val;
1903 } elsif ($sel eq "d") {
1905 $email_git_since = $str;
1906 } elsif (vcs_is_hg()) {
1907 $email_hg_since = $str;
1910 } elsif ($sel eq "t") {
1911 bool_invert(\$email_git_all_signature_types);
1913 } elsif ($sel eq "f") {
1914 bool_invert(\$file_emails);
1916 } elsif ($sel eq "r") {
1917 bool_invert(\$email_remove_duplicates);
1919 } elsif ($sel eq "m") {
1920 bool_invert(\$email_use_mailmap);
1923 } elsif ($sel eq "k") {
1924 bool_invert(\$keywords);
1926 } elsif ($sel eq "p") {
1927 if ($str ne "" && $val >= 0) {
1928 $pattern_depth = $val;
1931 } elsif ($sel eq "h" || $sel eq "?") {
1934 Interactive mode allows you to select the various maintainers, submitters,
1935 commit signers and mailing lists that could be CC'd on a patch.
1937 Any *'d entry is selected.
1939 If you have git or hg installed, you can choose to summarize the commit
1940 history of files in the patch. Also, each line of the current file can
1941 be matched to its commit author and that commits signers with blame.
1943 Various knobs exist to control the length of time for active commit
1944 tracking, the maximum number of commit authors and signers to add,
1947 Enter selections at the prompt until you are satisfied that the selected
1948 maintainers are appropriate. You may enter multiple selections separated
1949 by either commas or spaces.
1953 print STDERR "invalid option: '$nr'\n";
1958 print STDERR "git-blame can be very slow, please have patience..."
1959 if ($email_git_blame);
1960 goto &get_maintainers;
1964 #drop not selected entries
1966 my @new_emailto = ();
1967 foreach my $entry (@list) {
1968 if ($selected{$count}) {
1969 push(@new_emailto, $list[$count]);
1973 return @new_emailto;
1977 my ($bool_ref) = @_;
1986 sub deduplicate_email {
1990 my ($name, $address) = parse_email($email);
1991 $email = format_email($name, $address, 1);
1992 $email = mailmap_email($email);
1994 return $email if (!$email_remove_duplicates);
1996 ($name, $address) = parse_email($email);
1998 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1999 $name = $deduplicate_name_hash{lc($name)}->[0];
2000 $address = $deduplicate_name_hash{lc($name)}->[1];
2002 } elsif ($deduplicate_address_hash{lc($address)}) {
2003 $name = $deduplicate_address_hash{lc($address)}->[0];
2004 $address = $deduplicate_address_hash{lc($address)}->[1];
2008 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
2009 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
2011 $email = format_email($name, $address, 1);
2012 $email = mailmap_email($email);
2016 sub save_commits_by_author {
2023 foreach my $line (@lines) {
2024 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2026 $author = deduplicate_email($author);
2027 push(@authors, $author);
2029 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2030 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2033 for (my $i = 0; $i < @authors; $i++) {
2035 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
2036 if (@{$ref}[0] eq $commits[$i] &&
2037 @{$ref}[1] eq $subjects[$i]) {
2043 push(@{$commit_author_hash{$authors[$i]}},
2044 [ ($commits[$i], $subjects[$i]) ]);
2049 sub save_commits_by_signer {
2055 foreach my $line (@lines) {
2056 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2057 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2058 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
2059 my @signatures = ($line);
2060 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
2061 my @types = @$types_ref;
2062 my @signers = @$signers_ref;
2064 my $type = $types[0];
2065 my $signer = $signers[0];
2067 $signer = deduplicate_email($signer);
2070 foreach my $ref(@{$commit_signer_hash{$signer}}) {
2071 if (@{$ref}[0] eq $commit &&
2072 @{$ref}[1] eq $subject &&
2073 @{$ref}[2] eq $type) {
2079 push(@{$commit_signer_hash{$signer}},
2080 [ ($commit, $subject, $type) ]);
2087 my ($role, $divisor, @lines) = @_;
2092 return if (@lines <= 0);
2094 if ($divisor <= 0) {
2095 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
2099 @lines = mailmap(@lines);
2101 return if (@lines <= 0);
2103 @lines = sort(@lines);
2106 $hash{$_}++ for @lines;
2109 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
2110 my $sign_offs = $hash{$line};
2111 my $percent = $sign_offs * 100 / $divisor;
2113 $percent = 100 if ($percent > 100);
2114 next if (ignore_email_address($line));
2116 last if ($sign_offs < $email_git_min_signatures ||
2117 $count > $email_git_max_maintainers ||
2118 $percent < $email_git_min_percent);
2119 push_email_address($line, '');
2120 if ($output_rolestats) {
2121 my $fmt_percent = sprintf("%.0f", $percent);
2122 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
2124 add_role($line, $role);
2129 sub vcs_file_signoffs {
2140 $vcs_used = vcs_exists();
2141 return if (!$vcs_used);
2143 my $cmd = $VCS_cmds{"find_signers_cmd"};
2144 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2146 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2148 @signers = @{$signers_ref} if defined $signers_ref;
2149 @authors = @{$authors_ref} if defined $authors_ref;
2150 @stats = @{$stats_ref} if defined $stats_ref;
2152 # print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
2154 foreach my $signer (@signers) {
2155 $signer = deduplicate_email($signer);
2158 vcs_assign("commit_signer", $commits, @signers);
2159 vcs_assign("authored", $commits, @authors);
2160 if ($#authors == $#stats) {
2161 my $stat_pattern = $VCS_cmds{"stat_pattern"};
2162 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
2166 for (my $i = 0; $i <= $#stats; $i++) {
2167 if ($stats[$i] =~ /$stat_pattern/) {
2172 my @tmp_authors = uniq(@authors);
2173 foreach my $author (@tmp_authors) {
2174 $author = deduplicate_email($author);
2176 @tmp_authors = uniq(@tmp_authors);
2177 my @list_added = ();
2178 my @list_deleted = ();
2179 foreach my $author (@tmp_authors) {
2181 my $auth_deleted = 0;
2182 for (my $i = 0; $i <= $#stats; $i++) {
2183 if ($author eq deduplicate_email($authors[$i]) &&
2184 $stats[$i] =~ /$stat_pattern/) {
2186 $auth_deleted += $2;
2189 for (my $i = 0; $i < $auth_added; $i++) {
2190 push(@list_added, $author);
2192 for (my $i = 0; $i < $auth_deleted; $i++) {
2193 push(@list_deleted, $author);
2196 vcs_assign("added_lines", $added, @list_added);
2197 vcs_assign("removed_lines", $deleted, @list_deleted);
2201 sub vcs_file_blame {
2205 my @all_commits = ();
2210 $vcs_used = vcs_exists();
2211 return if (!$vcs_used);
2213 @all_commits = vcs_blame($file);
2214 @commits = uniq(@all_commits);
2215 $total_commits = @commits;
2216 $total_lines = @all_commits;
2218 if ($email_git_blame_signatures) {
2221 my $commit_authors_ref;
2222 my $commit_signers_ref;
2224 my @commit_authors = ();
2225 my @commit_signers = ();
2226 my $commit = join(" -r ", @commits);
2229 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2230 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2232 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2233 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2234 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2236 push(@signers, @commit_signers);
2238 foreach my $commit (@commits) {
2240 my $commit_authors_ref;
2241 my $commit_signers_ref;
2243 my @commit_authors = ();
2244 my @commit_signers = ();
2247 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2248 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2250 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2251 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2252 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2254 push(@signers, @commit_signers);
2259 if ($from_filename) {
2260 if ($output_rolestats) {
2262 if (vcs_is_hg()) {{ # Double brace for last exit
2264 my @commit_signers = ();
2265 @commits = uniq(@commits);
2266 @commits = sort(@commits);
2267 my $commit = join(" -r ", @commits);
2270 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2271 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2275 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2277 if (!$email_git_penguin_chiefs) {
2278 @lines = grep(!/${penguin_chiefs}/i, @lines);
2284 foreach my $line (@lines) {
2285 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2287 $author = deduplicate_email($author);
2288 push(@authors, $author);
2292 save_commits_by_author(@lines) if ($interactive);
2293 save_commits_by_signer(@lines) if ($interactive);
2295 push(@signers, @authors);
2298 foreach my $commit (@commits) {
2300 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2301 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2302 my @author = vcs_find_author($cmd);
2305 my $formatted_author = deduplicate_email($author[0]);
2307 my $count = grep(/$commit/, @all_commits);
2308 for ($i = 0; $i < $count ; $i++) {
2309 push(@blame_signers, $formatted_author);
2313 if (@blame_signers) {
2314 vcs_assign("authored lines", $total_lines, @blame_signers);
2317 foreach my $signer (@signers) {
2318 $signer = deduplicate_email($signer);
2320 vcs_assign("commits", $total_commits, @signers);
2322 foreach my $signer (@signers) {
2323 $signer = deduplicate_email($signer);
2325 vcs_assign("modified commits", $total_commits, @signers);
2329 sub vcs_file_exists {
2334 my $vcs_used = vcs_exists();
2335 return 0 if (!$vcs_used);
2337 my $cmd = $VCS_cmds{"file_exists_cmd"};
2338 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2340 $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
2342 return 0 if ($? != 0);
2347 sub vcs_list_files {
2352 my $vcs_used = vcs_exists();
2353 return 0 if (!$vcs_used);
2355 my $cmd = $VCS_cmds{"list_files_cmd"};
2356 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2357 @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd);
2359 return () if ($? != 0);
2368 @parms = grep(!$saw{$_}++, @parms);
2376 @parms = sort @parms;
2377 @parms = grep(!$saw{$_}++, @parms);
2381 sub clean_file_emails {
2382 my (@file_emails) = @_;
2383 my @fmt_emails = ();
2385 foreach my $email (@file_emails) {
2386 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2387 my ($name, $address) = parse_email($email);
2388 if ($name eq '"[,\.]"') {
2392 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2394 my $first = $nw[@nw - 3];
2395 my $middle = $nw[@nw - 2];
2396 my $last = $nw[@nw - 1];
2398 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2399 (length($first) == 2 && substr($first, -1) eq ".")) ||
2400 (length($middle) == 1 ||
2401 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2402 $name = "$first $middle $last";
2404 $name = "$middle $last";
2408 if (substr($name, -1) =~ /[,\.]/) {
2409 $name = substr($name, 0, length($name) - 1);
2410 } elsif (substr($name, -2) =~ /[,\.]"/) {
2411 $name = substr($name, 0, length($name) - 2) . '"';
2414 if (substr($name, 0, 1) =~ /[,\.]/) {
2415 $name = substr($name, 1, length($name) - 1);
2416 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2417 $name = '"' . substr($name, 2, length($name) - 2);
2420 my $fmt_email = format_email($name, $address, $email_usename);
2421 push(@fmt_emails, $fmt_email);
2431 my ($address, $role) = @$_;
2432 if (!$saw{$address}) {
2433 if ($output_roles) {
2434 push(@lines, "$address ($role)");
2436 push(@lines, $address);
2448 if ($output_multiline) {
2449 foreach my $line (@parms) {
2453 print(join($output_separator, @parms));
2461 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2462 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2463 # This regexp will only work on addresses which have had comments stripped
2464 # and replaced with rfc822_lwsp.
2466 my $specials = '()<>@,;:\\\\".\\[\\]';
2467 my $controls = '\\000-\\037\\177';
2469 my $dtext = "[^\\[\\]\\r\\\\]";
2470 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2472 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2474 # Use zero-width assertion to spot the limit of an atom. A simple
2475 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2476 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2477 my $word = "(?:$atom|$quoted_string)";
2478 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2480 my $sub_domain = "(?:$atom|$domain_literal)";
2481 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2483 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2485 my $phrase = "$word*";
2486 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2487 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2488 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2490 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2491 my $address = "(?:$mailbox|$group)";
2493 return "$rfc822_lwsp*$address";
2496 sub rfc822_strip_comments {
2498 # Recursively remove comments, and replace with a single space. The simpler
2499 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2500 # chars in atoms, for example.
2502 while ($s =~ s/^((?:[^"\\]|\\.)*
2503 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2504 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2508 # valid: returns true if the parameter is an RFC822 valid address
2511 my $s = rfc822_strip_comments(shift);
2514 $rfc822re = make_rfc822re();
2517 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2520 # validlist: In scalar context, returns true if the parameter is an RFC822
2521 # valid list of addresses.
2523 # In list context, returns an empty list on failure (an invalid
2524 # address was found); otherwise a list whose first element is the
2525 # number of addresses found and whose remaining elements are the
2526 # addresses. This is needed to disambiguate failure (invalid)
2527 # from success with no addresses found, because an empty string is
2530 sub rfc822_validlist {
2531 my $s = rfc822_strip_comments(shift);
2534 $rfc822re = make_rfc822re();
2536 # * null list items are valid according to the RFC
2537 # * the '1' business is to aid in distinguishing failure from no results
2540 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2541 $s =~ m/^$rfc822_char*$/) {
2542 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2545 return wantarray ? (scalar(@r), @r) : 1;
2547 return wantarray ? () : 0;