Merge tag 'armsoc-defconfig' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc
[linux-2.6-microblaze.git] / scripts / get_maintainer.pl
1 #!/usr/bin/env perl
2 # SPDX-License-Identifier: GPL-2.0
3 #
4 # (c) 2007, Joe Perches <joe@perches.com>
5 #           created from checkpatch.pl
6 #
7 # Print selected MAINTAINERS information for
8 # the files modified in a patch or for a file
9 #
10 # usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
11 #        perl scripts/get_maintainer.pl [OPTIONS] -f <file>
12
13 use warnings;
14 use strict;
15
16 my $P = $0;
17 my $V = '0.26';
18
19 use Getopt::Long qw(:config no_auto_abbrev);
20 use Cwd;
21 use File::Find;
22
23 my $cur_path = fastgetcwd() . '/';
24 my $lk_path = "./";
25 my $email = 1;
26 my $email_usename = 1;
27 my $email_maintainer = 1;
28 my $email_reviewer = 1;
29 my $email_fixes = 1;
30 my $email_list = 1;
31 my $email_moderated_list = 1;
32 my $email_subscriber_list = 0;
33 my $email_git_penguin_chiefs = 0;
34 my $email_git = 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";
44 my $interactive = 0;
45 my $email_remove_duplicates = 1;
46 my $email_use_mailmap = 1;
47 my $output_multiline = 1;
48 my $output_separator = ", ";
49 my $output_roles = 0;
50 my $output_rolestats = 1;
51 my $output_section_maxlen = 50;
52 my $scm = 0;
53 my $tree = 1;
54 my $web = 0;
55 my $subsystem = 0;
56 my $status = 0;
57 my $letters = "";
58 my $keywords = 1;
59 my $sections = 0;
60 my $file_emails = 0;
61 my $from_filename = 0;
62 my $pattern_depth = 0;
63 my $self_test = undef;
64 my $version = 0;
65 my $help = 0;
66 my $find_maintainer_files = 0;
67 my $maintainer_path;
68 my $vcs_used = 0;
69
70 my $exit = 0;
71
72 my %commit_author_hash;
73 my %commit_signer_hash;
74
75 my @penguin_chief = ();
76 push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
77 #Andrew wants in on most everything - 2009/01/14
78 #push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
79
80 my @penguin_chief_names = ();
81 foreach my $chief (@penguin_chief) {
82     if ($chief =~ m/^(.*):(.*)/) {
83         my $chief_name = $1;
84         my $chief_addr = $2;
85         push(@penguin_chief_names, $chief_name);
86     }
87 }
88 my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
89
90 # Signature types of people who are either
91 #       a) responsible for the code in question, or
92 #       b) familiar enough with it to give relevant feedback
93 my @signature_tags = ();
94 push(@signature_tags, "Signed-off-by:");
95 push(@signature_tags, "Reviewed-by:");
96 push(@signature_tags, "Acked-by:");
97
98 my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
99
100 # rfc822 email address - preloaded methods go here.
101 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
102 my $rfc822_char = '[\\000-\\377]';
103
104 # VCS command support: class-like functions and strings
105
106 my %VCS_cmds;
107
108 my %VCS_cmds_git = (
109     "execute_cmd" => \&git_execute_cmd,
110     "available" => '(which("git") ne "") && (-e ".git")',
111     "find_signers_cmd" =>
112         "git log --no-color --follow --since=\$email_git_since " .
113             '--numstat --no-merges ' .
114             '--format="GitCommit: %H%n' .
115                       'GitAuthor: %an <%ae>%n' .
116                       'GitDate: %aD%n' .
117                       'GitSubject: %s%n' .
118                       '%b%n"' .
119             " -- \$file",
120     "find_commit_signers_cmd" =>
121         "git log --no-color " .
122             '--numstat ' .
123             '--format="GitCommit: %H%n' .
124                       'GitAuthor: %an <%ae>%n' .
125                       'GitDate: %aD%n' .
126                       'GitSubject: %s%n' .
127                       '%b%n"' .
128             " -1 \$commit",
129     "find_commit_author_cmd" =>
130         "git log --no-color " .
131             '--numstat ' .
132             '--format="GitCommit: %H%n' .
133                       'GitAuthor: %an <%ae>%n' .
134                       'GitDate: %aD%n' .
135                       'GitSubject: %s%n"' .
136             " -1 \$commit",
137     "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
138     "blame_file_cmd" => "git blame -l \$file",
139     "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
140     "blame_commit_pattern" => "^([0-9a-f]+) ",
141     "author_pattern" => "^GitAuthor: (.*)",
142     "subject_pattern" => "^GitSubject: (.*)",
143     "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
144     "file_exists_cmd" => "git ls-files \$file",
145     "list_files_cmd" => "git ls-files \$file",
146 );
147
148 my %VCS_cmds_hg = (
149     "execute_cmd" => \&hg_execute_cmd,
150     "available" => '(which("hg") ne "") && (-d ".hg")',
151     "find_signers_cmd" =>
152         "hg log --date=\$email_hg_since " .
153             "--template='HgCommit: {node}\\n" .
154                         "HgAuthor: {author}\\n" .
155                         "HgSubject: {desc}\\n'" .
156             " -- \$file",
157     "find_commit_signers_cmd" =>
158         "hg log " .
159             "--template='HgSubject: {desc}\\n'" .
160             " -r \$commit",
161     "find_commit_author_cmd" =>
162         "hg log " .
163             "--template='HgCommit: {node}\\n" .
164                         "HgAuthor: {author}\\n" .
165                         "HgSubject: {desc|firstline}\\n'" .
166             " -r \$commit",
167     "blame_range_cmd" => "",            # not supported
168     "blame_file_cmd" => "hg blame -n \$file",
169     "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
170     "blame_commit_pattern" => "^([ 0-9a-f]+):",
171     "author_pattern" => "^HgAuthor: (.*)",
172     "subject_pattern" => "^HgSubject: (.*)",
173     "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$",
174     "file_exists_cmd" => "hg files \$file",
175     "list_files_cmd" => "hg manifest -R \$file",
176 );
177
178 my $conf = which_conf(".get_maintainer.conf");
179 if (-f $conf) {
180     my @conf_args;
181     open(my $conffile, '<', "$conf")
182         or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
183
184     while (<$conffile>) {
185         my $line = $_;
186
187         $line =~ s/\s*\n?$//g;
188         $line =~ s/^\s*//g;
189         $line =~ s/\s+/ /g;
190
191         next if ($line =~ m/^\s*#/);
192         next if ($line =~ m/^\s*$/);
193
194         my @words = split(" ", $line);
195         foreach my $word (@words) {
196             last if ($word =~ m/^#/);
197             push (@conf_args, $word);
198         }
199     }
200     close($conffile);
201     unshift(@ARGV, @conf_args) if @conf_args;
202 }
203
204 my @ignore_emails = ();
205 my $ignore_file = which_conf(".get_maintainer.ignore");
206 if (-f $ignore_file) {
207     open(my $ignore, '<', "$ignore_file")
208         or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n";
209     while (<$ignore>) {
210         my $line = $_;
211
212         $line =~ s/\s*\n?$//;
213         $line =~ s/^\s*//;
214         $line =~ s/\s+$//;
215         $line =~ s/#.*$//;
216
217         next if ($line =~ m/^\s*$/);
218         if (rfc822_valid($line)) {
219             push(@ignore_emails, $line);
220         }
221     }
222     close($ignore);
223 }
224
225 if ($#ARGV > 0) {
226     foreach (@ARGV) {
227         if ($_ =~ /^-{1,2}self-test(?:=|$)/) {
228             die "$P: using --self-test does not allow any other option or argument\n";
229         }
230     }
231 }
232
233 if (!GetOptions(
234                 'email!' => \$email,
235                 'git!' => \$email_git,
236                 'git-all-signature-types!' => \$email_git_all_signature_types,
237                 'git-blame!' => \$email_git_blame,
238                 'git-blame-signatures!' => \$email_git_blame_signatures,
239                 'git-fallback!' => \$email_git_fallback,
240                 'git-chief-penguins!' => \$email_git_penguin_chiefs,
241                 'git-min-signatures=i' => \$email_git_min_signatures,
242                 'git-max-maintainers=i' => \$email_git_max_maintainers,
243                 'git-min-percent=i' => \$email_git_min_percent,
244                 'git-since=s' => \$email_git_since,
245                 'hg-since=s' => \$email_hg_since,
246                 'i|interactive!' => \$interactive,
247                 'remove-duplicates!' => \$email_remove_duplicates,
248                 'mailmap!' => \$email_use_mailmap,
249                 'm!' => \$email_maintainer,
250                 'r!' => \$email_reviewer,
251                 'n!' => \$email_usename,
252                 'l!' => \$email_list,
253                 'fixes!' => \$email_fixes,
254                 'moderated!' => \$email_moderated_list,
255                 's!' => \$email_subscriber_list,
256                 'multiline!' => \$output_multiline,
257                 'roles!' => \$output_roles,
258                 'rolestats!' => \$output_rolestats,
259                 'separator=s' => \$output_separator,
260                 'subsystem!' => \$subsystem,
261                 'status!' => \$status,
262                 'scm!' => \$scm,
263                 'tree!' => \$tree,
264                 'web!' => \$web,
265                 'letters=s' => \$letters,
266                 'pattern-depth=i' => \$pattern_depth,
267                 'k|keywords!' => \$keywords,
268                 'sections!' => \$sections,
269                 'fe|file-emails!' => \$file_emails,
270                 'f|file' => \$from_filename,
271                 'find-maintainer-files' => \$find_maintainer_files,
272                 'mpath|maintainer-path=s' => \$maintainer_path,
273                 'self-test:s' => \$self_test,
274                 'v|version' => \$version,
275                 'h|help|usage' => \$help,
276                 )) {
277     die "$P: invalid argument - use --help if necessary\n";
278 }
279
280 if ($help != 0) {
281     usage();
282     exit 0;
283 }
284
285 if ($version != 0) {
286     print("${P} ${V}\n");
287     exit 0;
288 }
289
290 if (defined $self_test) {
291     read_all_maintainer_files();
292     self_test();
293     exit 0;
294 }
295
296 if (-t STDIN && !@ARGV) {
297     # We're talking to a terminal, but have no command line arguments.
298     die "$P: missing patchfile or -f file - use --help if necessary\n";
299 }
300
301 $output_multiline = 0 if ($output_separator ne ", ");
302 $output_rolestats = 1 if ($interactive);
303 $output_roles = 1 if ($output_rolestats);
304
305 if ($sections || $letters ne "") {
306     $sections = 1;
307     $email = 0;
308     $email_list = 0;
309     $scm = 0;
310     $status = 0;
311     $subsystem = 0;
312     $web = 0;
313     $keywords = 0;
314     $interactive = 0;
315 } else {
316     my $selections = $email + $scm + $status + $subsystem + $web;
317     if ($selections == 0) {
318         die "$P:  Missing required option: email, scm, status, subsystem or web\n";
319     }
320 }
321
322 if ($email &&
323     ($email_maintainer + $email_reviewer +
324      $email_list + $email_subscriber_list +
325      $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
326     die "$P: Please select at least 1 email option\n";
327 }
328
329 if ($tree && !top_of_kernel_tree($lk_path)) {
330     die "$P: The current directory does not appear to be "
331         . "a linux kernel source tree.\n";
332 }
333
334 ## Read MAINTAINERS for type/value pairs
335
336 my @typevalue = ();
337 my %keyword_hash;
338 my @mfiles = ();
339 my @self_test_info = ();
340
341 sub read_maintainer_file {
342     my ($file) = @_;
343
344     open (my $maint, '<', "$file")
345         or die "$P: Can't open MAINTAINERS file '$file': $!\n";
346     my $i = 1;
347     while (<$maint>) {
348         my $line = $_;
349         chomp $line;
350
351         if ($line =~ m/^([A-Z]):\s*(.*)/) {
352             my $type = $1;
353             my $value = $2;
354
355             ##Filename pattern matching
356             if ($type eq "F" || $type eq "X") {
357                 $value =~ s@\.@\\\.@g;       ##Convert . to \.
358                 $value =~ s/\*/\.\*/g;       ##Convert * to .*
359                 $value =~ s/\?/\./g;         ##Convert ? to .
360                 ##if pattern is a directory and it lacks a trailing slash, add one
361                 if ((-d $value)) {
362                     $value =~ s@([^/])$@$1/@;
363                 }
364             } elsif ($type eq "K") {
365                 $keyword_hash{@typevalue} = $value;
366             }
367             push(@typevalue, "$type:$value");
368         } elsif (!(/^\s*$/ || /^\s*\#/)) {
369             push(@typevalue, $line);
370         }
371         if (defined $self_test) {
372             push(@self_test_info, {file=>$file, linenr=>$i, line=>$line});
373         }
374         $i++;
375     }
376     close($maint);
377 }
378
379 sub find_is_maintainer_file {
380     my ($file) = $_;
381     return if ($file !~ m@/MAINTAINERS$@);
382     $file = $File::Find::name;
383     return if (! -f $file);
384     push(@mfiles, $file);
385 }
386
387 sub find_ignore_git {
388     return grep { $_ !~ /^\.git$/; } @_;
389 }
390
391 read_all_maintainer_files();
392
393 sub read_all_maintainer_files {
394     my $path = "${lk_path}MAINTAINERS";
395     if (defined $maintainer_path) {
396         $path = $maintainer_path;
397         # Perl Cookbook tilde expansion if necessary
398         $path =~ s@^~([^/]*)@ $1 ? (getpwnam($1))[7] : ( $ENV{HOME} || $ENV{LOGDIR} || (getpwuid($<))[7])@ex;
399     }
400
401     if (-d $path) {
402         $path .= '/' if ($path !~ m@/$@);
403         if ($find_maintainer_files) {
404             find( { wanted => \&find_is_maintainer_file,
405                     preprocess => \&find_ignore_git,
406                     no_chdir => 1,
407                 }, "$path");
408         } else {
409             opendir(DIR, "$path") or die $!;
410             my @files = readdir(DIR);
411             closedir(DIR);
412             foreach my $file (@files) {
413                 push(@mfiles, "$path$file") if ($file !~ /^\./);
414             }
415         }
416     } elsif (-f "$path") {
417         push(@mfiles, "$path");
418     } else {
419         die "$P: MAINTAINER file not found '$path'\n";
420     }
421     die "$P: No MAINTAINER files found in '$path'\n" if (scalar(@mfiles) == 0);
422     foreach my $file (@mfiles) {
423         read_maintainer_file("$file");
424     }
425 }
426
427 #
428 # Read mail address map
429 #
430
431 my $mailmap;
432
433 read_mailmap();
434
435 sub read_mailmap {
436     $mailmap = {
437         names => {},
438         addresses => {}
439     };
440
441     return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
442
443     open(my $mailmap_file, '<', "${lk_path}.mailmap")
444         or warn "$P: Can't open .mailmap: $!\n";
445
446     while (<$mailmap_file>) {
447         s/#.*$//; #strip comments
448         s/^\s+|\s+$//g; #trim
449
450         next if (/^\s*$/); #skip empty lines
451         #entries have one of the following formats:
452         # name1 <mail1>
453         # <mail1> <mail2>
454         # name1 <mail1> <mail2>
455         # name1 <mail1> name2 <mail2>
456         # (see man git-shortlog)
457
458         if (/^([^<]+)<([^>]+)>$/) {
459             my $real_name = $1;
460             my $address = $2;
461
462             $real_name =~ s/\s+$//;
463             ($real_name, $address) = parse_email("$real_name <$address>");
464             $mailmap->{names}->{$address} = $real_name;
465
466         } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
467             my $real_address = $1;
468             my $wrong_address = $2;
469
470             $mailmap->{addresses}->{$wrong_address} = $real_address;
471
472         } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
473             my $real_name = $1;
474             my $real_address = $2;
475             my $wrong_address = $3;
476
477             $real_name =~ s/\s+$//;
478             ($real_name, $real_address) =
479                 parse_email("$real_name <$real_address>");
480             $mailmap->{names}->{$wrong_address} = $real_name;
481             $mailmap->{addresses}->{$wrong_address} = $real_address;
482
483         } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
484             my $real_name = $1;
485             my $real_address = $2;
486             my $wrong_name = $3;
487             my $wrong_address = $4;
488
489             $real_name =~ s/\s+$//;
490             ($real_name, $real_address) =
491                 parse_email("$real_name <$real_address>");
492
493             $wrong_name =~ s/\s+$//;
494             ($wrong_name, $wrong_address) =
495                 parse_email("$wrong_name <$wrong_address>");
496
497             my $wrong_email = format_email($wrong_name, $wrong_address, 1);
498             $mailmap->{names}->{$wrong_email} = $real_name;
499             $mailmap->{addresses}->{$wrong_email} = $real_address;
500         }
501     }
502     close($mailmap_file);
503 }
504
505 ## use the filenames on the command line or find the filenames in the patchfiles
506
507 my @files = ();
508 my @fixes = ();                 # If a patch description includes Fixes: lines
509 my @range = ();
510 my @keyword_tvi = ();
511 my @file_emails = ();
512
513 if (!@ARGV) {
514     push(@ARGV, "&STDIN");
515 }
516
517 foreach my $file (@ARGV) {
518     if ($file ne "&STDIN") {
519         ##if $file is a directory and it lacks a trailing slash, add one
520         if ((-d $file)) {
521             $file =~ s@([^/])$@$1/@;
522         } elsif (!(-f $file)) {
523             die "$P: file '${file}' not found\n";
524         }
525     }
526     if ($from_filename || ($file ne "&STDIN" && vcs_file_exists($file))) {
527         $file =~ s/^\Q${cur_path}\E//;  #strip any absolute path
528         $file =~ s/^\Q${lk_path}\E//;   #or the path to the lk tree
529         push(@files, $file);
530         if ($file ne "MAINTAINERS" && -f $file && ($keywords || $file_emails)) {
531             open(my $f, '<', $file)
532                 or die "$P: Can't open $file: $!\n";
533             my $text = do { local($/) ; <$f> };
534             close($f);
535             if ($keywords) {
536                 foreach my $line (keys %keyword_hash) {
537                     if ($text =~ m/$keyword_hash{$line}/x) {
538                         push(@keyword_tvi, $line);
539                     }
540                 }
541             }
542             if ($file_emails) {
543                 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;
544                 push(@file_emails, clean_file_emails(@poss_addr));
545             }
546         }
547     } else {
548         my $file_cnt = @files;
549         my $lastfile;
550
551         open(my $patch, "< $file")
552             or die "$P: Can't open $file: $!\n";
553
554         # We can check arbitrary information before the patch
555         # like the commit message, mail headers, etc...
556         # This allows us to match arbitrary keywords against any part
557         # of a git format-patch generated file (subject tags, etc...)
558
559         my $patch_prefix = "";                  #Parsing the intro
560
561         while (<$patch>) {
562             my $patch_line = $_;
563             if (m/^ mode change [0-7]+ => [0-7]+ (\S+)\s*$/) {
564                 my $filename = $1;
565                 push(@files, $filename);
566             } elsif (m/^rename (?:from|to) (\S+)\s*$/) {
567                 my $filename = $1;
568                 push(@files, $filename);
569             } elsif (m/^diff --git a\/(\S+) b\/(\S+)\s*$/) {
570                 my $filename1 = $1;
571                 my $filename2 = $2;
572                 push(@files, $filename1);
573                 push(@files, $filename2);
574             } elsif (m/^Fixes:\s+([0-9a-fA-F]{6,40})/) {
575                 push(@fixes, $1) if ($email_fixes);
576             } elsif (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
577                 my $filename = $1;
578                 $filename =~ s@^[^/]*/@@;
579                 $filename =~ s@\n@@;
580                 $lastfile = $filename;
581                 push(@files, $filename);
582                 $patch_prefix = "^[+-].*";      #Now parsing the actual patch
583             } elsif (m/^\@\@ -(\d+),(\d+)/) {
584                 if ($email_git_blame) {
585                     push(@range, "$lastfile:$1:$2");
586                 }
587             } elsif ($keywords) {
588                 foreach my $line (keys %keyword_hash) {
589                     if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
590                         push(@keyword_tvi, $line);
591                     }
592                 }
593             }
594         }
595         close($patch);
596
597         if ($file_cnt == @files) {
598             warn "$P: file '${file}' doesn't appear to be a patch.  "
599                 . "Add -f to options?\n";
600         }
601         @files = sort_and_uniq(@files);
602     }
603 }
604
605 @file_emails = uniq(@file_emails);
606 @fixes = uniq(@fixes);
607
608 my %email_hash_name;
609 my %email_hash_address;
610 my @email_to = ();
611 my %hash_list_to;
612 my @list_to = ();
613 my @scm = ();
614 my @web = ();
615 my @subsystem = ();
616 my @status = ();
617 my %deduplicate_name_hash = ();
618 my %deduplicate_address_hash = ();
619
620 my @maintainers = get_maintainers();
621 if (@maintainers) {
622     @maintainers = merge_email(@maintainers);
623     output(@maintainers);
624 }
625
626 if ($scm) {
627     @scm = uniq(@scm);
628     output(@scm);
629 }
630
631 if ($status) {
632     @status = uniq(@status);
633     output(@status);
634 }
635
636 if ($subsystem) {
637     @subsystem = uniq(@subsystem);
638     output(@subsystem);
639 }
640
641 if ($web) {
642     @web = uniq(@web);
643     output(@web);
644 }
645
646 exit($exit);
647
648 sub self_test {
649     my @lsfiles = ();
650     my @good_links = ();
651     my @bad_links = ();
652     my @section_headers = ();
653     my $index = 0;
654
655     @lsfiles = vcs_list_files($lk_path);
656
657     for my $x (@self_test_info) {
658         $index++;
659
660         ## Section header duplication and missing section content
661         if (($self_test eq "" || $self_test =~ /\bsections\b/) &&
662             $x->{line} =~ /^\S[^:]/ &&
663             defined $self_test_info[$index] &&
664             $self_test_info[$index]->{line} =~ /^([A-Z]):\s*\S/) {
665             my $has_S = 0;
666             my $has_F = 0;
667             my $has_ML = 0;
668             my $status = "";
669             if (grep(m@^\Q$x->{line}\E@, @section_headers)) {
670                 print("$x->{file}:$x->{linenr}: warning: duplicate section header\t$x->{line}\n");
671             } else {
672                 push(@section_headers, $x->{line});
673             }
674             my $nextline = $index;
675             while (defined $self_test_info[$nextline] &&
676                    $self_test_info[$nextline]->{line} =~ /^([A-Z]):\s*(\S.*)/) {
677                 my $type = $1;
678                 my $value = $2;
679                 if ($type eq "S") {
680                     $has_S = 1;
681                     $status = $value;
682                 } elsif ($type eq "F" || $type eq "N") {
683                     $has_F = 1;
684                 } elsif ($type eq "M" || $type eq "R" || $type eq "L") {
685                     $has_ML = 1;
686                 }
687                 $nextline++;
688             }
689             if (!$has_ML && $status !~ /orphan|obsolete/i) {
690                 print("$x->{file}:$x->{linenr}: warning: section without email address\t$x->{line}\n");
691             }
692             if (!$has_S) {
693                 print("$x->{file}:$x->{linenr}: warning: section without status \t$x->{line}\n");
694             }
695             if (!$has_F) {
696                 print("$x->{file}:$x->{linenr}: warning: section without file pattern\t$x->{line}\n");
697             }
698         }
699
700         next if ($x->{line} !~ /^([A-Z]):\s*(.*)/);
701
702         my $type = $1;
703         my $value = $2;
704
705         ## Filename pattern matching
706         if (($type eq "F" || $type eq "X") &&
707             ($self_test eq "" || $self_test =~ /\bpatterns\b/)) {
708             $value =~ s@\.@\\\.@g;       ##Convert . to \.
709             $value =~ s/\*/\.\*/g;       ##Convert * to .*
710             $value =~ s/\?/\./g;         ##Convert ? to .
711             ##if pattern is a directory and it lacks a trailing slash, add one
712             if ((-d $value)) {
713                 $value =~ s@([^/])$@$1/@;
714             }
715             if (!grep(m@^$value@, @lsfiles)) {
716                 print("$x->{file}:$x->{linenr}: warning: no file matches\t$x->{line}\n");
717             }
718
719         ## Link reachability
720         } elsif (($type eq "W" || $type eq "Q" || $type eq "B") &&
721                  $value =~ /^https?:/ &&
722                  ($self_test eq "" || $self_test =~ /\blinks\b/)) {
723             next if (grep(m@^\Q$value\E$@, @good_links));
724             my $isbad = 0;
725             if (grep(m@^\Q$value\E$@, @bad_links)) {
726                 $isbad = 1;
727             } else {
728                 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $value`;
729                 if ($? == 0) {
730                     push(@good_links, $value);
731                 } else {
732                     push(@bad_links, $value);
733                     $isbad = 1;
734                 }
735             }
736             if ($isbad) {
737                 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
738             }
739
740         ## SCM reachability
741         } elsif ($type eq "T" &&
742                  ($self_test eq "" || $self_test =~ /\bscm\b/)) {
743             next if (grep(m@^\Q$value\E$@, @good_links));
744             my $isbad = 0;
745             if (grep(m@^\Q$value\E$@, @bad_links)) {
746                 $isbad = 1;
747             } elsif ($value !~ /^(?:git|quilt|hg)\s+\S/) {
748                 print("$x->{file}:$x->{linenr}: warning: malformed entry\t$x->{line}\n");
749             } elsif ($value =~ /^git\s+(\S+)(\s+([^\(]+\S+))?/) {
750                 my $url = $1;
751                 my $branch = "";
752                 $branch = $3 if $3;
753                 my $output = `git ls-remote --exit-code -h "$url" $branch > /dev/null 2>&1`;
754                 if ($? == 0) {
755                     push(@good_links, $value);
756                 } else {
757                     push(@bad_links, $value);
758                     $isbad = 1;
759                 }
760             } elsif ($value =~ /^(?:quilt|hg)\s+(https?:\S+)/) {
761                 my $url = $1;
762                 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $url`;
763                 if ($? == 0) {
764                     push(@good_links, $value);
765                 } else {
766                     push(@bad_links, $value);
767                     $isbad = 1;
768                 }
769             }
770             if ($isbad) {
771                 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
772             }
773         }
774     }
775 }
776
777 sub ignore_email_address {
778     my ($address) = @_;
779
780     foreach my $ignore (@ignore_emails) {
781         return 1 if ($ignore eq $address);
782     }
783
784     return 0;
785 }
786
787 sub range_is_maintained {
788     my ($start, $end) = @_;
789
790     for (my $i = $start; $i < $end; $i++) {
791         my $line = $typevalue[$i];
792         if ($line =~ m/^([A-Z]):\s*(.*)/) {
793             my $type = $1;
794             my $value = $2;
795             if ($type eq 'S') {
796                 if ($value =~ /(maintain|support)/i) {
797                     return 1;
798                 }
799             }
800         }
801     }
802     return 0;
803 }
804
805 sub range_has_maintainer {
806     my ($start, $end) = @_;
807
808     for (my $i = $start; $i < $end; $i++) {
809         my $line = $typevalue[$i];
810         if ($line =~ m/^([A-Z]):\s*(.*)/) {
811             my $type = $1;
812             my $value = $2;
813             if ($type eq 'M') {
814                 return 1;
815             }
816         }
817     }
818     return 0;
819 }
820
821 sub get_maintainers {
822     %email_hash_name = ();
823     %email_hash_address = ();
824     %commit_author_hash = ();
825     %commit_signer_hash = ();
826     @email_to = ();
827     %hash_list_to = ();
828     @list_to = ();
829     @scm = ();
830     @web = ();
831     @subsystem = ();
832     @status = ();
833     %deduplicate_name_hash = ();
834     %deduplicate_address_hash = ();
835     if ($email_git_all_signature_types) {
836         $signature_pattern = "(.+?)[Bb][Yy]:";
837     } else {
838         $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
839     }
840
841     # Find responsible parties
842
843     my %exact_pattern_match_hash = ();
844
845     foreach my $file (@files) {
846
847         my %hash;
848         my $tvi = find_first_section();
849         while ($tvi < @typevalue) {
850             my $start = find_starting_index($tvi);
851             my $end = find_ending_index($tvi);
852             my $exclude = 0;
853             my $i;
854
855             #Do not match excluded file patterns
856
857             for ($i = $start; $i < $end; $i++) {
858                 my $line = $typevalue[$i];
859                 if ($line =~ m/^([A-Z]):\s*(.*)/) {
860                     my $type = $1;
861                     my $value = $2;
862                     if ($type eq 'X') {
863                         if (file_match_pattern($file, $value)) {
864                             $exclude = 1;
865                             last;
866                         }
867                     }
868                 }
869             }
870
871             if (!$exclude) {
872                 for ($i = $start; $i < $end; $i++) {
873                     my $line = $typevalue[$i];
874                     if ($line =~ m/^([A-Z]):\s*(.*)/) {
875                         my $type = $1;
876                         my $value = $2;
877                         if ($type eq 'F') {
878                             if (file_match_pattern($file, $value)) {
879                                 my $value_pd = ($value =~ tr@/@@);
880                                 my $file_pd = ($file  =~ tr@/@@);
881                                 $value_pd++ if (substr($value,-1,1) ne "/");
882                                 $value_pd = -1 if ($value =~ /^\.\*/);
883                                 if ($value_pd >= $file_pd &&
884                                     range_is_maintained($start, $end) &&
885                                     range_has_maintainer($start, $end)) {
886                                     $exact_pattern_match_hash{$file} = 1;
887                                 }
888                                 if ($pattern_depth == 0 ||
889                                     (($file_pd - $value_pd) < $pattern_depth)) {
890                                     $hash{$tvi} = $value_pd;
891                                 }
892                             }
893                         } elsif ($type eq 'N') {
894                             if ($file =~ m/$value/x) {
895                                 $hash{$tvi} = 0;
896                             }
897                         }
898                     }
899                 }
900             }
901             $tvi = $end + 1;
902         }
903
904         foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
905             add_categories($line);
906             if ($sections) {
907                 my $i;
908                 my $start = find_starting_index($line);
909                 my $end = find_ending_index($line);
910                 for ($i = $start; $i < $end; $i++) {
911                     my $line = $typevalue[$i];
912                     if ($line =~ /^[FX]:/) {            ##Restore file patterns
913                         $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
914                         $line =~ s/([^\\])\.$/$1\?/g;   ##Convert . back to ?
915                         $line =~ s/\\\./\./g;           ##Convert \. to .
916                         $line =~ s/\.\*/\*/g;           ##Convert .* to *
917                     }
918                     my $count = $line =~ s/^([A-Z]):/$1:\t/g;
919                     if ($letters eq "" || (!$count || $letters =~ /$1/i)) {
920                         print("$line\n");
921                     }
922                 }
923                 print("\n");
924             }
925         }
926     }
927
928     if ($keywords) {
929         @keyword_tvi = sort_and_uniq(@keyword_tvi);
930         foreach my $line (@keyword_tvi) {
931             add_categories($line);
932         }
933     }
934
935     foreach my $fix (@fixes) {
936         vcs_add_commit_signers($fix, "blamed_fixes");
937     }
938
939     foreach my $email (@email_to, @list_to) {
940         $email->[0] = deduplicate_email($email->[0]);
941     }
942
943     foreach my $file (@files) {
944         if ($email &&
945             ($email_git || ($email_git_fallback &&
946                             !$exact_pattern_match_hash{$file}))) {
947             vcs_file_signoffs($file);
948         }
949         if ($email && $email_git_blame) {
950             vcs_file_blame($file);
951         }
952     }
953
954     if ($email) {
955         foreach my $chief (@penguin_chief) {
956             if ($chief =~ m/^(.*):(.*)/) {
957                 my $email_address;
958
959                 $email_address = format_email($1, $2, $email_usename);
960                 if ($email_git_penguin_chiefs) {
961                     push(@email_to, [$email_address, 'chief penguin']);
962                 } else {
963                     @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
964                 }
965             }
966         }
967
968         foreach my $email (@file_emails) {
969             my ($name, $address) = parse_email($email);
970
971             my $tmp_email = format_email($name, $address, $email_usename);
972             push_email_address($tmp_email, '');
973             add_role($tmp_email, 'in file');
974         }
975     }
976
977     my @to = ();
978     if ($email || $email_list) {
979         if ($email) {
980             @to = (@to, @email_to);
981         }
982         if ($email_list) {
983             @to = (@to, @list_to);
984         }
985     }
986
987     if ($interactive) {
988         @to = interactive_get_maintainers(\@to);
989     }
990
991     return @to;
992 }
993
994 sub file_match_pattern {
995     my ($file, $pattern) = @_;
996     if (substr($pattern, -1) eq "/") {
997         if ($file =~ m@^$pattern@) {
998             return 1;
999         }
1000     } else {
1001         if ($file =~ m@^$pattern@) {
1002             my $s1 = ($file =~ tr@/@@);
1003             my $s2 = ($pattern =~ tr@/@@);
1004             if ($s1 == $s2) {
1005                 return 1;
1006             }
1007         }
1008     }
1009     return 0;
1010 }
1011
1012 sub usage {
1013     print <<EOT;
1014 usage: $P [options] patchfile
1015        $P [options] -f file|directory
1016 version: $V
1017
1018 MAINTAINER field selection options:
1019   --email => print email address(es) if any
1020     --git => include recent git \*-by: signers
1021     --git-all-signature-types => include signers regardless of signature type
1022         or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
1023     --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
1024     --git-chief-penguins => include ${penguin_chiefs}
1025     --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
1026     --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
1027     --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
1028     --git-blame => use git blame to find modified commits for patch or file
1029     --git-blame-signatures => when used with --git-blame, also include all commit signers
1030     --git-since => git history to use (default: $email_git_since)
1031     --hg-since => hg history to use (default: $email_hg_since)
1032     --interactive => display a menu (mostly useful if used with the --git option)
1033     --m => include maintainer(s) if any
1034     --r => include reviewer(s) if any
1035     --n => include name 'Full Name <addr\@domain.tld>'
1036     --l => include list(s) if any
1037     --moderated => include moderated lists(s) if any (default: true)
1038     --s => include subscriber only list(s) if any (default: false)
1039     --remove-duplicates => minimize duplicate email names/addresses
1040     --roles => show roles (status:subsystem, git-signer, list, etc...)
1041     --rolestats => show roles and statistics (commits/total_commits, %)
1042     --file-emails => add email addresses found in -f file (default: 0 (off))
1043     --fixes => for patches, add signatures of commits with 'Fixes: <commit>' (default: 1 (on))
1044   --scm => print SCM tree(s) if any
1045   --status => print status if any
1046   --subsystem => print subsystem name if any
1047   --web => print website(s) if any
1048
1049 Output type options:
1050   --separator [, ] => separator for multiple entries on 1 line
1051     using --separator also sets --nomultiline if --separator is not [, ]
1052   --multiline => print 1 entry per line
1053
1054 Other options:
1055   --pattern-depth => Number of pattern directory traversals (default: 0 (all))
1056   --keywords => scan patch for keywords (default: $keywords)
1057   --sections => print all of the subsystem sections with pattern matches
1058   --letters => print all matching 'letter' types from all matching sections
1059   --mailmap => use .mailmap file (default: $email_use_mailmap)
1060   --no-tree => run without a kernel tree
1061   --self-test => show potential issues with MAINTAINERS file content
1062   --version => show version
1063   --help => show this help information
1064
1065 Default options:
1066   [--email --tree --nogit --git-fallback --m --r --n --l --multiline
1067    --pattern-depth=0 --remove-duplicates --rolestats]
1068
1069 Notes:
1070   Using "-f directory" may give unexpected results:
1071       Used with "--git", git signators for _all_ files in and below
1072           directory are examined as git recurses directories.
1073           Any specified X: (exclude) pattern matches are _not_ ignored.
1074       Used with "--nogit", directory is used as a pattern match,
1075           no individual file within the directory or subdirectory
1076           is matched.
1077       Used with "--git-blame", does not iterate all files in directory
1078   Using "--git-blame" is slow and may add old committers and authors
1079       that are no longer active maintainers to the output.
1080   Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
1081       other automated tools that expect only ["name"] <email address>
1082       may not work because of additional output after <email address>.
1083   Using "--rolestats" and "--git-blame" shows the #/total=% commits,
1084       not the percentage of the entire file authored.  # of commits is
1085       not a good measure of amount of code authored.  1 major commit may
1086       contain a thousand lines, 5 trivial commits may modify a single line.
1087   If git is not installed, but mercurial (hg) is installed and an .hg
1088       repository exists, the following options apply to mercurial:
1089           --git,
1090           --git-min-signatures, --git-max-maintainers, --git-min-percent, and
1091           --git-blame
1092       Use --hg-since not --git-since to control date selection
1093   File ".get_maintainer.conf", if it exists in the linux kernel source root
1094       directory, can change whatever get_maintainer defaults are desired.
1095       Entries in this file can be any command line argument.
1096       This file is prepended to any additional command line arguments.
1097       Multiple lines and # comments are allowed.
1098   Most options have both positive and negative forms.
1099       The negative forms for --<foo> are --no<foo> and --no-<foo>.
1100
1101 EOT
1102 }
1103
1104 sub top_of_kernel_tree {
1105     my ($lk_path) = @_;
1106
1107     if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
1108         $lk_path .= "/";
1109     }
1110     if (   (-f "${lk_path}COPYING")
1111         && (-f "${lk_path}CREDITS")
1112         && (-f "${lk_path}Kbuild")
1113         && (-e "${lk_path}MAINTAINERS")
1114         && (-f "${lk_path}Makefile")
1115         && (-f "${lk_path}README")
1116         && (-d "${lk_path}Documentation")
1117         && (-d "${lk_path}arch")
1118         && (-d "${lk_path}include")
1119         && (-d "${lk_path}drivers")
1120         && (-d "${lk_path}fs")
1121         && (-d "${lk_path}init")
1122         && (-d "${lk_path}ipc")
1123         && (-d "${lk_path}kernel")
1124         && (-d "${lk_path}lib")
1125         && (-d "${lk_path}scripts")) {
1126         return 1;
1127     }
1128     return 0;
1129 }
1130
1131 sub parse_email {
1132     my ($formatted_email) = @_;
1133
1134     my $name = "";
1135     my $address = "";
1136
1137     if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
1138         $name = $1;
1139         $address = $2;
1140     } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
1141         $address = $1;
1142     } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
1143         $address = $1;
1144     }
1145
1146     $name =~ s/^\s+|\s+$//g;
1147     $name =~ s/^\"|\"$//g;
1148     $address =~ s/^\s+|\s+$//g;
1149
1150     if ($name =~ /[^\w \-]/i) {          ##has "must quote" chars
1151         $name =~ s/(?<!\\)"/\\"/g;       ##escape quotes
1152         $name = "\"$name\"";
1153     }
1154
1155     return ($name, $address);
1156 }
1157
1158 sub format_email {
1159     my ($name, $address, $usename) = @_;
1160
1161     my $formatted_email;
1162
1163     $name =~ s/^\s+|\s+$//g;
1164     $name =~ s/^\"|\"$//g;
1165     $address =~ s/^\s+|\s+$//g;
1166
1167     if ($name =~ /[^\w \-]/i) {          ##has "must quote" chars
1168         $name =~ s/(?<!\\)"/\\"/g;       ##escape quotes
1169         $name = "\"$name\"";
1170     }
1171
1172     if ($usename) {
1173         if ("$name" eq "") {
1174             $formatted_email = "$address";
1175         } else {
1176             $formatted_email = "$name <$address>";
1177         }
1178     } else {
1179         $formatted_email = $address;
1180     }
1181
1182     return $formatted_email;
1183 }
1184
1185 sub find_first_section {
1186     my $index = 0;
1187
1188     while ($index < @typevalue) {
1189         my $tv = $typevalue[$index];
1190         if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
1191             last;
1192         }
1193         $index++;
1194     }
1195
1196     return $index;
1197 }
1198
1199 sub find_starting_index {
1200     my ($index) = @_;
1201
1202     while ($index > 0) {
1203         my $tv = $typevalue[$index];
1204         if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1205             last;
1206         }
1207         $index--;
1208     }
1209
1210     return $index;
1211 }
1212
1213 sub find_ending_index {
1214     my ($index) = @_;
1215
1216     while ($index < @typevalue) {
1217         my $tv = $typevalue[$index];
1218         if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1219             last;
1220         }
1221         $index++;
1222     }
1223
1224     return $index;
1225 }
1226
1227 sub get_subsystem_name {
1228     my ($index) = @_;
1229
1230     my $start = find_starting_index($index);
1231
1232     my $subsystem = $typevalue[$start];
1233     if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
1234         $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
1235         $subsystem =~ s/\s*$//;
1236         $subsystem = $subsystem . "...";
1237     }
1238     return $subsystem;
1239 }
1240
1241 sub get_maintainer_role {
1242     my ($index) = @_;
1243
1244     my $i;
1245     my $start = find_starting_index($index);
1246     my $end = find_ending_index($index);
1247
1248     my $role = "unknown";
1249     my $subsystem = get_subsystem_name($index);
1250
1251     for ($i = $start + 1; $i < $end; $i++) {
1252         my $tv = $typevalue[$i];
1253         if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1254             my $ptype = $1;
1255             my $pvalue = $2;
1256             if ($ptype eq "S") {
1257                 $role = $pvalue;
1258             }
1259         }
1260     }
1261
1262     $role = lc($role);
1263     if      ($role eq "supported") {
1264         $role = "supporter";
1265     } elsif ($role eq "maintained") {
1266         $role = "maintainer";
1267     } elsif ($role eq "odd fixes") {
1268         $role = "odd fixer";
1269     } elsif ($role eq "orphan") {
1270         $role = "orphan minder";
1271     } elsif ($role eq "obsolete") {
1272         $role = "obsolete minder";
1273     } elsif ($role eq "buried alive in reporters") {
1274         $role = "chief penguin";
1275     }
1276
1277     return $role . ":" . $subsystem;
1278 }
1279
1280 sub get_list_role {
1281     my ($index) = @_;
1282
1283     my $subsystem = get_subsystem_name($index);
1284
1285     if ($subsystem eq "THE REST") {
1286         $subsystem = "";
1287     }
1288
1289     return $subsystem;
1290 }
1291
1292 sub add_categories {
1293     my ($index) = @_;
1294
1295     my $i;
1296     my $start = find_starting_index($index);
1297     my $end = find_ending_index($index);
1298
1299     push(@subsystem, $typevalue[$start]);
1300
1301     for ($i = $start + 1; $i < $end; $i++) {
1302         my $tv = $typevalue[$i];
1303         if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1304             my $ptype = $1;
1305             my $pvalue = $2;
1306             if ($ptype eq "L") {
1307                 my $list_address = $pvalue;
1308                 my $list_additional = "";
1309                 my $list_role = get_list_role($i);
1310
1311                 if ($list_role ne "") {
1312                     $list_role = ":" . $list_role;
1313                 }
1314                 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1315                     $list_address = $1;
1316                     $list_additional = $2;
1317                 }
1318                 if ($list_additional =~ m/subscribers-only/) {
1319                     if ($email_subscriber_list) {
1320                         if (!$hash_list_to{lc($list_address)}) {
1321                             $hash_list_to{lc($list_address)} = 1;
1322                             push(@list_to, [$list_address,
1323                                             "subscriber list${list_role}"]);
1324                         }
1325                     }
1326                 } else {
1327                     if ($email_list) {
1328                         if (!$hash_list_to{lc($list_address)}) {
1329                             if ($list_additional =~ m/moderated/) {
1330                                 if ($email_moderated_list) {
1331                                     $hash_list_to{lc($list_address)} = 1;
1332                                     push(@list_to, [$list_address,
1333                                                     "moderated list${list_role}"]);
1334                                 }
1335                             } else {
1336                                 $hash_list_to{lc($list_address)} = 1;
1337                                 push(@list_to, [$list_address,
1338                                                 "open list${list_role}"]);
1339                             }
1340                         }
1341                     }
1342                 }
1343             } elsif ($ptype eq "M") {
1344                 my ($name, $address) = parse_email($pvalue);
1345                 if ($name eq "") {
1346                     if ($i > 0) {
1347                         my $tv = $typevalue[$i - 1];
1348                         if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1349                             if ($1 eq "P") {
1350                                 $name = $2;
1351                                 $pvalue = format_email($name, $address, $email_usename);
1352                             }
1353                         }
1354                     }
1355                 }
1356                 if ($email_maintainer) {
1357                     my $role = get_maintainer_role($i);
1358                     push_email_addresses($pvalue, $role);
1359                 }
1360             } elsif ($ptype eq "R") {
1361                 my ($name, $address) = parse_email($pvalue);
1362                 if ($name eq "") {
1363                     if ($i > 0) {
1364                         my $tv = $typevalue[$i - 1];
1365                         if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1366                             if ($1 eq "P") {
1367                                 $name = $2;
1368                                 $pvalue = format_email($name, $address, $email_usename);
1369                             }
1370                         }
1371                     }
1372                 }
1373                 if ($email_reviewer) {
1374                     my $subsystem = get_subsystem_name($i);
1375                     push_email_addresses($pvalue, "reviewer:$subsystem");
1376                 }
1377             } elsif ($ptype eq "T") {
1378                 push(@scm, $pvalue);
1379             } elsif ($ptype eq "W") {
1380                 push(@web, $pvalue);
1381             } elsif ($ptype eq "S") {
1382                 push(@status, $pvalue);
1383             }
1384         }
1385     }
1386 }
1387
1388 sub email_inuse {
1389     my ($name, $address) = @_;
1390
1391     return 1 if (($name eq "") && ($address eq ""));
1392     return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1393     return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1394
1395     return 0;
1396 }
1397
1398 sub push_email_address {
1399     my ($line, $role) = @_;
1400
1401     my ($name, $address) = parse_email($line);
1402
1403     if ($address eq "") {
1404         return 0;
1405     }
1406
1407     if (!$email_remove_duplicates) {
1408         push(@email_to, [format_email($name, $address, $email_usename), $role]);
1409     } elsif (!email_inuse($name, $address)) {
1410         push(@email_to, [format_email($name, $address, $email_usename), $role]);
1411         $email_hash_name{lc($name)}++ if ($name ne "");
1412         $email_hash_address{lc($address)}++;
1413     }
1414
1415     return 1;
1416 }
1417
1418 sub push_email_addresses {
1419     my ($address, $role) = @_;
1420
1421     my @address_list = ();
1422
1423     if (rfc822_valid($address)) {
1424         push_email_address($address, $role);
1425     } elsif (@address_list = rfc822_validlist($address)) {
1426         my $array_count = shift(@address_list);
1427         while (my $entry = shift(@address_list)) {
1428             push_email_address($entry, $role);
1429         }
1430     } else {
1431         if (!push_email_address($address, $role)) {
1432             warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1433         }
1434     }
1435 }
1436
1437 sub add_role {
1438     my ($line, $role) = @_;
1439
1440     my ($name, $address) = parse_email($line);
1441     my $email = format_email($name, $address, $email_usename);
1442
1443     foreach my $entry (@email_to) {
1444         if ($email_remove_duplicates) {
1445             my ($entry_name, $entry_address) = parse_email($entry->[0]);
1446             if (($name eq $entry_name || $address eq $entry_address)
1447                 && ($role eq "" || !($entry->[1] =~ m/$role/))
1448             ) {
1449                 if ($entry->[1] eq "") {
1450                     $entry->[1] = "$role";
1451                 } else {
1452                     $entry->[1] = "$entry->[1],$role";
1453                 }
1454             }
1455         } else {
1456             if ($email eq $entry->[0]
1457                 && ($role eq "" || !($entry->[1] =~ m/$role/))
1458             ) {
1459                 if ($entry->[1] eq "") {
1460                     $entry->[1] = "$role";
1461                 } else {
1462                     $entry->[1] = "$entry->[1],$role";
1463                 }
1464             }
1465         }
1466     }
1467 }
1468
1469 sub which {
1470     my ($bin) = @_;
1471
1472     foreach my $path (split(/:/, $ENV{PATH})) {
1473         if (-e "$path/$bin") {
1474             return "$path/$bin";
1475         }
1476     }
1477
1478     return "";
1479 }
1480
1481 sub which_conf {
1482     my ($conf) = @_;
1483
1484     foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1485         if (-e "$path/$conf") {
1486             return "$path/$conf";
1487         }
1488     }
1489
1490     return "";
1491 }
1492
1493 sub mailmap_email {
1494     my ($line) = @_;
1495
1496     my ($name, $address) = parse_email($line);
1497     my $email = format_email($name, $address, 1);
1498     my $real_name = $name;
1499     my $real_address = $address;
1500
1501     if (exists $mailmap->{names}->{$email} ||
1502         exists $mailmap->{addresses}->{$email}) {
1503         if (exists $mailmap->{names}->{$email}) {
1504             $real_name = $mailmap->{names}->{$email};
1505         }
1506         if (exists $mailmap->{addresses}->{$email}) {
1507             $real_address = $mailmap->{addresses}->{$email};
1508         }
1509     } else {
1510         if (exists $mailmap->{names}->{$address}) {
1511             $real_name = $mailmap->{names}->{$address};
1512         }
1513         if (exists $mailmap->{addresses}->{$address}) {
1514             $real_address = $mailmap->{addresses}->{$address};
1515         }
1516     }
1517     return format_email($real_name, $real_address, 1);
1518 }
1519
1520 sub mailmap {
1521     my (@addresses) = @_;
1522
1523     my @mapped_emails = ();
1524     foreach my $line (@addresses) {
1525         push(@mapped_emails, mailmap_email($line));
1526     }
1527     merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1528     return @mapped_emails;
1529 }
1530
1531 sub merge_by_realname {
1532     my %address_map;
1533     my (@emails) = @_;
1534
1535     foreach my $email (@emails) {
1536         my ($name, $address) = parse_email($email);
1537         if (exists $address_map{$name}) {
1538             $address = $address_map{$name};
1539             $email = format_email($name, $address, 1);
1540         } else {
1541             $address_map{$name} = $address;
1542         }
1543     }
1544 }
1545
1546 sub git_execute_cmd {
1547     my ($cmd) = @_;
1548     my @lines = ();
1549
1550     my $output = `$cmd`;
1551     $output =~ s/^\s*//gm;
1552     @lines = split("\n", $output);
1553
1554     return @lines;
1555 }
1556
1557 sub hg_execute_cmd {
1558     my ($cmd) = @_;
1559     my @lines = ();
1560
1561     my $output = `$cmd`;
1562     @lines = split("\n", $output);
1563
1564     return @lines;
1565 }
1566
1567 sub extract_formatted_signatures {
1568     my (@signature_lines) = @_;
1569
1570     my @type = @signature_lines;
1571
1572     s/\s*(.*):.*/$1/ for (@type);
1573
1574     # cut -f2- -d":"
1575     s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1576
1577 ## Reformat email addresses (with names) to avoid badly written signatures
1578
1579     foreach my $signer (@signature_lines) {
1580         $signer = deduplicate_email($signer);
1581     }
1582
1583     return (\@type, \@signature_lines);
1584 }
1585
1586 sub vcs_find_signers {
1587     my ($cmd, $file) = @_;
1588     my $commits;
1589     my @lines = ();
1590     my @signatures = ();
1591     my @authors = ();
1592     my @stats = ();
1593
1594     @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1595
1596     my $pattern = $VCS_cmds{"commit_pattern"};
1597     my $author_pattern = $VCS_cmds{"author_pattern"};
1598     my $stat_pattern = $VCS_cmds{"stat_pattern"};
1599
1600     $stat_pattern =~ s/(\$\w+)/$1/eeg;          #interpolate $stat_pattern
1601
1602     $commits = grep(/$pattern/, @lines);        # of commits
1603
1604     @authors = grep(/$author_pattern/, @lines);
1605     @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1606     @stats = grep(/$stat_pattern/, @lines);
1607
1608 #    print("stats: <@stats>\n");
1609
1610     return (0, \@signatures, \@authors, \@stats) if !@signatures;
1611
1612     save_commits_by_author(@lines) if ($interactive);
1613     save_commits_by_signer(@lines) if ($interactive);
1614
1615     if (!$email_git_penguin_chiefs) {
1616         @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1617     }
1618
1619     my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
1620     my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1621
1622     return ($commits, $signers_ref, $authors_ref, \@stats);
1623 }
1624
1625 sub vcs_find_author {
1626     my ($cmd) = @_;
1627     my @lines = ();
1628
1629     @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1630
1631     if (!$email_git_penguin_chiefs) {
1632         @lines = grep(!/${penguin_chiefs}/i, @lines);
1633     }
1634
1635     return @lines if !@lines;
1636
1637     my @authors = ();
1638     foreach my $line (@lines) {
1639         if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1640             my $author = $1;
1641             my ($name, $address) = parse_email($author);
1642             $author = format_email($name, $address, 1);
1643             push(@authors, $author);
1644         }
1645     }
1646
1647     save_commits_by_author(@lines) if ($interactive);
1648     save_commits_by_signer(@lines) if ($interactive);
1649
1650     return @authors;
1651 }
1652
1653 sub vcs_save_commits {
1654     my ($cmd) = @_;
1655     my @lines = ();
1656     my @commits = ();
1657
1658     @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1659
1660     foreach my $line (@lines) {
1661         if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1662             push(@commits, $1);
1663         }
1664     }
1665
1666     return @commits;
1667 }
1668
1669 sub vcs_blame {
1670     my ($file) = @_;
1671     my $cmd;
1672     my @commits = ();
1673
1674     return @commits if (!(-f $file));
1675
1676     if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1677         my @all_commits = ();
1678
1679         $cmd = $VCS_cmds{"blame_file_cmd"};
1680         $cmd =~ s/(\$\w+)/$1/eeg;               #interpolate $cmd
1681         @all_commits = vcs_save_commits($cmd);
1682
1683         foreach my $file_range_diff (@range) {
1684             next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1685             my $diff_file = $1;
1686             my $diff_start = $2;
1687             my $diff_length = $3;
1688             next if ("$file" ne "$diff_file");
1689             for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1690                 push(@commits, $all_commits[$i]);
1691             }
1692         }
1693     } elsif (@range) {
1694         foreach my $file_range_diff (@range) {
1695             next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1696             my $diff_file = $1;
1697             my $diff_start = $2;
1698             my $diff_length = $3;
1699             next if ("$file" ne "$diff_file");
1700             $cmd = $VCS_cmds{"blame_range_cmd"};
1701             $cmd =~ s/(\$\w+)/$1/eeg;           #interpolate $cmd
1702             push(@commits, vcs_save_commits($cmd));
1703         }
1704     } else {
1705         $cmd = $VCS_cmds{"blame_file_cmd"};
1706         $cmd =~ s/(\$\w+)/$1/eeg;               #interpolate $cmd
1707         @commits = vcs_save_commits($cmd);
1708     }
1709
1710     foreach my $commit (@commits) {
1711         $commit =~ s/^\^//g;
1712     }
1713
1714     return @commits;
1715 }
1716
1717 my $printed_novcs = 0;
1718 sub vcs_exists {
1719     %VCS_cmds = %VCS_cmds_git;
1720     return 1 if eval $VCS_cmds{"available"};
1721     %VCS_cmds = %VCS_cmds_hg;
1722     return 2 if eval $VCS_cmds{"available"};
1723     %VCS_cmds = ();
1724     if (!$printed_novcs) {
1725         warn("$P: No supported VCS found.  Add --nogit to options?\n");
1726         warn("Using a git repository produces better results.\n");
1727         warn("Try Linus Torvalds' latest git repository using:\n");
1728         warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
1729         $printed_novcs = 1;
1730     }
1731     return 0;
1732 }
1733
1734 sub vcs_is_git {
1735     vcs_exists();
1736     return $vcs_used == 1;
1737 }
1738
1739 sub vcs_is_hg {
1740     return $vcs_used == 2;
1741 }
1742
1743 sub vcs_add_commit_signers {
1744     return if (!vcs_exists());
1745
1746     my ($commit, $desc) = @_;
1747     my $commit_count = 0;
1748     my $commit_authors_ref;
1749     my $commit_signers_ref;
1750     my $stats_ref;
1751     my @commit_authors = ();
1752     my @commit_signers = ();
1753     my $cmd;
1754
1755     $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1756     $cmd =~ s/(\$\w+)/$1/eeg;   #substitute variables in $cmd
1757
1758     ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, "");
1759     @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
1760     @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
1761
1762     foreach my $signer (@commit_signers) {
1763         $signer = deduplicate_email($signer);
1764     }
1765
1766     vcs_assign($desc, 1, @commit_signers);
1767 }
1768
1769 sub interactive_get_maintainers {
1770     my ($list_ref) = @_;
1771     my @list = @$list_ref;
1772
1773     vcs_exists();
1774
1775     my %selected;
1776     my %authored;
1777     my %signed;
1778     my $count = 0;
1779     my $maintained = 0;
1780     foreach my $entry (@list) {
1781         $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1782         $selected{$count} = 1;
1783         $authored{$count} = 0;
1784         $signed{$count} = 0;
1785         $count++;
1786     }
1787
1788     #menu loop
1789     my $done = 0;
1790     my $print_options = 0;
1791     my $redraw = 1;
1792     while (!$done) {
1793         $count = 0;
1794         if ($redraw) {
1795             printf STDERR "\n%1s %2s %-65s",
1796                           "*", "#", "email/list and role:stats";
1797             if ($email_git ||
1798                 ($email_git_fallback && !$maintained) ||
1799                 $email_git_blame) {
1800                 print STDERR "auth sign";
1801             }
1802             print STDERR "\n";
1803             foreach my $entry (@list) {
1804                 my $email = $entry->[0];
1805                 my $role = $entry->[1];
1806                 my $sel = "";
1807                 $sel = "*" if ($selected{$count});
1808                 my $commit_author = $commit_author_hash{$email};
1809                 my $commit_signer = $commit_signer_hash{$email};
1810                 my $authored = 0;
1811                 my $signed = 0;
1812                 $authored++ for (@{$commit_author});
1813                 $signed++ for (@{$commit_signer});
1814                 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1815                 printf STDERR "%4d %4d", $authored, $signed
1816                     if ($authored > 0 || $signed > 0);
1817                 printf STDERR "\n     %s\n", $role;
1818                 if ($authored{$count}) {
1819                     my $commit_author = $commit_author_hash{$email};
1820                     foreach my $ref (@{$commit_author}) {
1821                         print STDERR "     Author: @{$ref}[1]\n";
1822                     }
1823                 }
1824                 if ($signed{$count}) {
1825                     my $commit_signer = $commit_signer_hash{$email};
1826                     foreach my $ref (@{$commit_signer}) {
1827                         print STDERR "     @{$ref}[2]: @{$ref}[1]\n";
1828                     }
1829                 }
1830
1831                 $count++;
1832             }
1833         }
1834         my $date_ref = \$email_git_since;
1835         $date_ref = \$email_hg_since if (vcs_is_hg());
1836         if ($print_options) {
1837             $print_options = 0;
1838             if (vcs_exists()) {
1839                 print STDERR <<EOT
1840
1841 Version Control options:
1842 g  use git history      [$email_git]
1843 gf use git-fallback     [$email_git_fallback]
1844 b  use git blame        [$email_git_blame]
1845 bs use blame signatures [$email_git_blame_signatures]
1846 c# minimum commits      [$email_git_min_signatures]
1847 %# min percent          [$email_git_min_percent]
1848 d# history to use       [$$date_ref]
1849 x# max maintainers      [$email_git_max_maintainers]
1850 t  all signature types  [$email_git_all_signature_types]
1851 m  use .mailmap         [$email_use_mailmap]
1852 EOT
1853             }
1854             print STDERR <<EOT
1855
1856 Additional options:
1857 0  toggle all
1858 tm toggle maintainers
1859 tg toggle git entries
1860 tl toggle open list entries
1861 ts toggle subscriber list entries
1862 f  emails in file       [$file_emails]
1863 k  keywords in file     [$keywords]
1864 r  remove duplicates    [$email_remove_duplicates]
1865 p# pattern match depth  [$pattern_depth]
1866 EOT
1867         }
1868         print STDERR
1869 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1870
1871         my $input = <STDIN>;
1872         chomp($input);
1873
1874         $redraw = 1;
1875         my $rerun = 0;
1876         my @wish = split(/[, ]+/, $input);
1877         foreach my $nr (@wish) {
1878             $nr = lc($nr);
1879             my $sel = substr($nr, 0, 1);
1880             my $str = substr($nr, 1);
1881             my $val = 0;
1882             $val = $1 if $str =~ /^(\d+)$/;
1883
1884             if ($sel eq "y") {
1885                 $interactive = 0;
1886                 $done = 1;
1887                 $output_rolestats = 0;
1888                 $output_roles = 0;
1889                 last;
1890             } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1891                 $selected{$nr - 1} = !$selected{$nr - 1};
1892             } elsif ($sel eq "*" || $sel eq '^') {
1893                 my $toggle = 0;
1894                 $toggle = 1 if ($sel eq '*');
1895                 for (my $i = 0; $i < $count; $i++) {
1896                     $selected{$i} = $toggle;
1897                 }
1898             } elsif ($sel eq "0") {
1899                 for (my $i = 0; $i < $count; $i++) {
1900                     $selected{$i} = !$selected{$i};
1901                 }
1902             } elsif ($sel eq "t") {
1903                 if (lc($str) eq "m") {
1904                     for (my $i = 0; $i < $count; $i++) {
1905                         $selected{$i} = !$selected{$i}
1906                             if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1907                     }
1908                 } elsif (lc($str) eq "g") {
1909                     for (my $i = 0; $i < $count; $i++) {
1910                         $selected{$i} = !$selected{$i}
1911                             if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1912                     }
1913                 } elsif (lc($str) eq "l") {
1914                     for (my $i = 0; $i < $count; $i++) {
1915                         $selected{$i} = !$selected{$i}
1916                             if ($list[$i]->[1] =~ /^(open list)/i);
1917                     }
1918                 } elsif (lc($str) eq "s") {
1919                     for (my $i = 0; $i < $count; $i++) {
1920                         $selected{$i} = !$selected{$i}
1921                             if ($list[$i]->[1] =~ /^(subscriber list)/i);
1922                     }
1923                 }
1924             } elsif ($sel eq "a") {
1925                 if ($val > 0 && $val <= $count) {
1926                     $authored{$val - 1} = !$authored{$val - 1};
1927                 } elsif ($str eq '*' || $str eq '^') {
1928                     my $toggle = 0;
1929                     $toggle = 1 if ($str eq '*');
1930                     for (my $i = 0; $i < $count; $i++) {
1931                         $authored{$i} = $toggle;
1932                     }
1933                 }
1934             } elsif ($sel eq "s") {
1935                 if ($val > 0 && $val <= $count) {
1936                     $signed{$val - 1} = !$signed{$val - 1};
1937                 } elsif ($str eq '*' || $str eq '^') {
1938                     my $toggle = 0;
1939                     $toggle = 1 if ($str eq '*');
1940                     for (my $i = 0; $i < $count; $i++) {
1941                         $signed{$i} = $toggle;
1942                     }
1943                 }
1944             } elsif ($sel eq "o") {
1945                 $print_options = 1;
1946                 $redraw = 1;
1947             } elsif ($sel eq "g") {
1948                 if ($str eq "f") {
1949                     bool_invert(\$email_git_fallback);
1950                 } else {
1951                     bool_invert(\$email_git);
1952                 }
1953                 $rerun = 1;
1954             } elsif ($sel eq "b") {
1955                 if ($str eq "s") {
1956                     bool_invert(\$email_git_blame_signatures);
1957                 } else {
1958                     bool_invert(\$email_git_blame);
1959                 }
1960                 $rerun = 1;
1961             } elsif ($sel eq "c") {
1962                 if ($val > 0) {
1963                     $email_git_min_signatures = $val;
1964                     $rerun = 1;
1965                 }
1966             } elsif ($sel eq "x") {
1967                 if ($val > 0) {
1968                     $email_git_max_maintainers = $val;
1969                     $rerun = 1;
1970                 }
1971             } elsif ($sel eq "%") {
1972                 if ($str ne "" && $val >= 0) {
1973                     $email_git_min_percent = $val;
1974                     $rerun = 1;
1975                 }
1976             } elsif ($sel eq "d") {
1977                 if (vcs_is_git()) {
1978                     $email_git_since = $str;
1979                 } elsif (vcs_is_hg()) {
1980                     $email_hg_since = $str;
1981                 }
1982                 $rerun = 1;
1983             } elsif ($sel eq "t") {
1984                 bool_invert(\$email_git_all_signature_types);
1985                 $rerun = 1;
1986             } elsif ($sel eq "f") {
1987                 bool_invert(\$file_emails);
1988                 $rerun = 1;
1989             } elsif ($sel eq "r") {
1990                 bool_invert(\$email_remove_duplicates);
1991                 $rerun = 1;
1992             } elsif ($sel eq "m") {
1993                 bool_invert(\$email_use_mailmap);
1994                 read_mailmap();
1995                 $rerun = 1;
1996             } elsif ($sel eq "k") {
1997                 bool_invert(\$keywords);
1998                 $rerun = 1;
1999             } elsif ($sel eq "p") {
2000                 if ($str ne "" && $val >= 0) {
2001                     $pattern_depth = $val;
2002                     $rerun = 1;
2003                 }
2004             } elsif ($sel eq "h" || $sel eq "?") {
2005                 print STDERR <<EOT
2006
2007 Interactive mode allows you to select the various maintainers, submitters,
2008 commit signers and mailing lists that could be CC'd on a patch.
2009
2010 Any *'d entry is selected.
2011
2012 If you have git or hg installed, you can choose to summarize the commit
2013 history of files in the patch.  Also, each line of the current file can
2014 be matched to its commit author and that commits signers with blame.
2015
2016 Various knobs exist to control the length of time for active commit
2017 tracking, the maximum number of commit authors and signers to add,
2018 and such.
2019
2020 Enter selections at the prompt until you are satisfied that the selected
2021 maintainers are appropriate.  You may enter multiple selections separated
2022 by either commas or spaces.
2023
2024 EOT
2025             } else {
2026                 print STDERR "invalid option: '$nr'\n";
2027                 $redraw = 0;
2028             }
2029         }
2030         if ($rerun) {
2031             print STDERR "git-blame can be very slow, please have patience..."
2032                 if ($email_git_blame);
2033             goto &get_maintainers;
2034         }
2035     }
2036
2037     #drop not selected entries
2038     $count = 0;
2039     my @new_emailto = ();
2040     foreach my $entry (@list) {
2041         if ($selected{$count}) {
2042             push(@new_emailto, $list[$count]);
2043         }
2044         $count++;
2045     }
2046     return @new_emailto;
2047 }
2048
2049 sub bool_invert {
2050     my ($bool_ref) = @_;
2051
2052     if ($$bool_ref) {
2053         $$bool_ref = 0;
2054     } else {
2055         $$bool_ref = 1;
2056     }
2057 }
2058
2059 sub deduplicate_email {
2060     my ($email) = @_;
2061
2062     my $matched = 0;
2063     my ($name, $address) = parse_email($email);
2064     $email = format_email($name, $address, 1);
2065     $email = mailmap_email($email);
2066
2067     return $email if (!$email_remove_duplicates);
2068
2069     ($name, $address) = parse_email($email);
2070
2071     if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
2072         $name = $deduplicate_name_hash{lc($name)}->[0];
2073         $address = $deduplicate_name_hash{lc($name)}->[1];
2074         $matched = 1;
2075     } elsif ($deduplicate_address_hash{lc($address)}) {
2076         $name = $deduplicate_address_hash{lc($address)}->[0];
2077         $address = $deduplicate_address_hash{lc($address)}->[1];
2078         $matched = 1;
2079     }
2080     if (!$matched) {
2081         $deduplicate_name_hash{lc($name)} = [ $name, $address ];
2082         $deduplicate_address_hash{lc($address)} = [ $name, $address ];
2083     }
2084     $email = format_email($name, $address, 1);
2085     $email = mailmap_email($email);
2086     return $email;
2087 }
2088
2089 sub save_commits_by_author {
2090     my (@lines) = @_;
2091
2092     my @authors = ();
2093     my @commits = ();
2094     my @subjects = ();
2095
2096     foreach my $line (@lines) {
2097         if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2098             my $author = $1;
2099             $author = deduplicate_email($author);
2100             push(@authors, $author);
2101         }
2102         push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2103         push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2104     }
2105
2106     for (my $i = 0; $i < @authors; $i++) {
2107         my $exists = 0;
2108         foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
2109             if (@{$ref}[0] eq $commits[$i] &&
2110                 @{$ref}[1] eq $subjects[$i]) {
2111                 $exists = 1;
2112                 last;
2113             }
2114         }
2115         if (!$exists) {
2116             push(@{$commit_author_hash{$authors[$i]}},
2117                  [ ($commits[$i], $subjects[$i]) ]);
2118         }
2119     }
2120 }
2121
2122 sub save_commits_by_signer {
2123     my (@lines) = @_;
2124
2125     my $commit = "";
2126     my $subject = "";
2127
2128     foreach my $line (@lines) {
2129         $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2130         $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2131         if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
2132             my @signatures = ($line);
2133             my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
2134             my @types = @$types_ref;
2135             my @signers = @$signers_ref;
2136
2137             my $type = $types[0];
2138             my $signer = $signers[0];
2139
2140             $signer = deduplicate_email($signer);
2141
2142             my $exists = 0;
2143             foreach my $ref(@{$commit_signer_hash{$signer}}) {
2144                 if (@{$ref}[0] eq $commit &&
2145                     @{$ref}[1] eq $subject &&
2146                     @{$ref}[2] eq $type) {
2147                     $exists = 1;
2148                     last;
2149                 }
2150             }
2151             if (!$exists) {
2152                 push(@{$commit_signer_hash{$signer}},
2153                      [ ($commit, $subject, $type) ]);
2154             }
2155         }
2156     }
2157 }
2158
2159 sub vcs_assign {
2160     my ($role, $divisor, @lines) = @_;
2161
2162     my %hash;
2163     my $count = 0;
2164
2165     return if (@lines <= 0);
2166
2167     if ($divisor <= 0) {
2168         warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
2169         $divisor = 1;
2170     }
2171
2172     @lines = mailmap(@lines);
2173
2174     return if (@lines <= 0);
2175
2176     @lines = sort(@lines);
2177
2178     # uniq -c
2179     $hash{$_}++ for @lines;
2180
2181     # sort -rn
2182     foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
2183         my $sign_offs = $hash{$line};
2184         my $percent = $sign_offs * 100 / $divisor;
2185
2186         $percent = 100 if ($percent > 100);
2187         next if (ignore_email_address($line));
2188         $count++;
2189         last if ($sign_offs < $email_git_min_signatures ||
2190                  $count > $email_git_max_maintainers ||
2191                  $percent < $email_git_min_percent);
2192         push_email_address($line, '');
2193         if ($output_rolestats) {
2194             my $fmt_percent = sprintf("%.0f", $percent);
2195             add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
2196         } else {
2197             add_role($line, $role);
2198         }
2199     }
2200 }
2201
2202 sub vcs_file_signoffs {
2203     my ($file) = @_;
2204
2205     my $authors_ref;
2206     my $signers_ref;
2207     my $stats_ref;
2208     my @authors = ();
2209     my @signers = ();
2210     my @stats = ();
2211     my $commits;
2212
2213     $vcs_used = vcs_exists();
2214     return if (!$vcs_used);
2215
2216     my $cmd = $VCS_cmds{"find_signers_cmd"};
2217     $cmd =~ s/(\$\w+)/$1/eeg;           # interpolate $cmd
2218
2219     ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2220
2221     @signers = @{$signers_ref} if defined $signers_ref;
2222     @authors = @{$authors_ref} if defined $authors_ref;
2223     @stats = @{$stats_ref} if defined $stats_ref;
2224
2225 #    print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
2226
2227     foreach my $signer (@signers) {
2228         $signer = deduplicate_email($signer);
2229     }
2230
2231     vcs_assign("commit_signer", $commits, @signers);
2232     vcs_assign("authored", $commits, @authors);
2233     if ($#authors == $#stats) {
2234         my $stat_pattern = $VCS_cmds{"stat_pattern"};
2235         $stat_pattern =~ s/(\$\w+)/$1/eeg;      #interpolate $stat_pattern
2236
2237         my $added = 0;
2238         my $deleted = 0;
2239         for (my $i = 0; $i <= $#stats; $i++) {
2240             if ($stats[$i] =~ /$stat_pattern/) {
2241                 $added += $1;
2242                 $deleted += $2;
2243             }
2244         }
2245         my @tmp_authors = uniq(@authors);
2246         foreach my $author (@tmp_authors) {
2247             $author = deduplicate_email($author);
2248         }
2249         @tmp_authors = uniq(@tmp_authors);
2250         my @list_added = ();
2251         my @list_deleted = ();
2252         foreach my $author (@tmp_authors) {
2253             my $auth_added = 0;
2254             my $auth_deleted = 0;
2255             for (my $i = 0; $i <= $#stats; $i++) {
2256                 if ($author eq deduplicate_email($authors[$i]) &&
2257                     $stats[$i] =~ /$stat_pattern/) {
2258                     $auth_added += $1;
2259                     $auth_deleted += $2;
2260                 }
2261             }
2262             for (my $i = 0; $i < $auth_added; $i++) {
2263                 push(@list_added, $author);
2264             }
2265             for (my $i = 0; $i < $auth_deleted; $i++) {
2266                 push(@list_deleted, $author);
2267             }
2268         }
2269         vcs_assign("added_lines", $added, @list_added);
2270         vcs_assign("removed_lines", $deleted, @list_deleted);
2271     }
2272 }
2273
2274 sub vcs_file_blame {
2275     my ($file) = @_;
2276
2277     my @signers = ();
2278     my @all_commits = ();
2279     my @commits = ();
2280     my $total_commits;
2281     my $total_lines;
2282
2283     $vcs_used = vcs_exists();
2284     return if (!$vcs_used);
2285
2286     @all_commits = vcs_blame($file);
2287     @commits = uniq(@all_commits);
2288     $total_commits = @commits;
2289     $total_lines = @all_commits;
2290
2291     if ($email_git_blame_signatures) {
2292         if (vcs_is_hg()) {
2293             my $commit_count;
2294             my $commit_authors_ref;
2295             my $commit_signers_ref;
2296             my $stats_ref;
2297             my @commit_authors = ();
2298             my @commit_signers = ();
2299             my $commit = join(" -r ", @commits);
2300             my $cmd;
2301
2302             $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2303             $cmd =~ s/(\$\w+)/$1/eeg;   #substitute variables in $cmd
2304
2305             ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2306             @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2307             @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2308
2309             push(@signers, @commit_signers);
2310         } else {
2311             foreach my $commit (@commits) {
2312                 my $commit_count;
2313                 my $commit_authors_ref;
2314                 my $commit_signers_ref;
2315                 my $stats_ref;
2316                 my @commit_authors = ();
2317                 my @commit_signers = ();
2318                 my $cmd;
2319
2320                 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2321                 $cmd =~ s/(\$\w+)/$1/eeg;       #substitute variables in $cmd
2322
2323                 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2324                 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2325                 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2326
2327                 push(@signers, @commit_signers);
2328             }
2329         }
2330     }
2331
2332     if ($from_filename) {
2333         if ($output_rolestats) {
2334             my @blame_signers;
2335             if (vcs_is_hg()) {{         # Double brace for last exit
2336                 my $commit_count;
2337                 my @commit_signers = ();
2338                 @commits = uniq(@commits);
2339                 @commits = sort(@commits);
2340                 my $commit = join(" -r ", @commits);
2341                 my $cmd;
2342
2343                 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2344                 $cmd =~ s/(\$\w+)/$1/eeg;       #substitute variables in $cmd
2345
2346                 my @lines = ();
2347
2348                 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2349
2350                 if (!$email_git_penguin_chiefs) {
2351                     @lines = grep(!/${penguin_chiefs}/i, @lines);
2352                 }
2353
2354                 last if !@lines;
2355
2356                 my @authors = ();
2357                 foreach my $line (@lines) {
2358                     if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2359                         my $author = $1;
2360                         $author = deduplicate_email($author);
2361                         push(@authors, $author);
2362                     }
2363                 }
2364
2365                 save_commits_by_author(@lines) if ($interactive);
2366                 save_commits_by_signer(@lines) if ($interactive);
2367
2368                 push(@signers, @authors);
2369             }}
2370             else {
2371                 foreach my $commit (@commits) {
2372                     my $i;
2373                     my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2374                     $cmd =~ s/(\$\w+)/$1/eeg;   #interpolate $cmd
2375                     my @author = vcs_find_author($cmd);
2376                     next if !@author;
2377
2378                     my $formatted_author = deduplicate_email($author[0]);
2379
2380                     my $count = grep(/$commit/, @all_commits);
2381                     for ($i = 0; $i < $count ; $i++) {
2382                         push(@blame_signers, $formatted_author);
2383                     }
2384                 }
2385             }
2386             if (@blame_signers) {
2387                 vcs_assign("authored lines", $total_lines, @blame_signers);
2388             }
2389         }
2390         foreach my $signer (@signers) {
2391             $signer = deduplicate_email($signer);
2392         }
2393         vcs_assign("commits", $total_commits, @signers);
2394     } else {
2395         foreach my $signer (@signers) {
2396             $signer = deduplicate_email($signer);
2397         }
2398         vcs_assign("modified commits", $total_commits, @signers);
2399     }
2400 }
2401
2402 sub vcs_file_exists {
2403     my ($file) = @_;
2404
2405     my $exists;
2406
2407     my $vcs_used = vcs_exists();
2408     return 0 if (!$vcs_used);
2409
2410     my $cmd = $VCS_cmds{"file_exists_cmd"};
2411     $cmd =~ s/(\$\w+)/$1/eeg;           # interpolate $cmd
2412     $cmd .= " 2>&1";
2413     $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
2414
2415     return 0 if ($? != 0);
2416
2417     return $exists;
2418 }
2419
2420 sub vcs_list_files {
2421     my ($file) = @_;
2422
2423     my @lsfiles = ();
2424
2425     my $vcs_used = vcs_exists();
2426     return 0 if (!$vcs_used);
2427
2428     my $cmd = $VCS_cmds{"list_files_cmd"};
2429     $cmd =~ s/(\$\w+)/$1/eeg;   # interpolate $cmd
2430     @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd);
2431
2432     return () if ($? != 0);
2433
2434     return @lsfiles;
2435 }
2436
2437 sub uniq {
2438     my (@parms) = @_;
2439
2440     my %saw;
2441     @parms = grep(!$saw{$_}++, @parms);
2442     return @parms;
2443 }
2444
2445 sub sort_and_uniq {
2446     my (@parms) = @_;
2447
2448     my %saw;
2449     @parms = sort @parms;
2450     @parms = grep(!$saw{$_}++, @parms);
2451     return @parms;
2452 }
2453
2454 sub clean_file_emails {
2455     my (@file_emails) = @_;
2456     my @fmt_emails = ();
2457
2458     foreach my $email (@file_emails) {
2459         $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2460         my ($name, $address) = parse_email($email);
2461         if ($name eq '"[,\.]"') {
2462             $name = "";
2463         }
2464
2465         my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2466         if (@nw > 2) {
2467             my $first = $nw[@nw - 3];
2468             my $middle = $nw[@nw - 2];
2469             my $last = $nw[@nw - 1];
2470
2471             if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2472                  (length($first) == 2 && substr($first, -1) eq ".")) ||
2473                 (length($middle) == 1 ||
2474                  (length($middle) == 2 && substr($middle, -1) eq "."))) {
2475                 $name = "$first $middle $last";
2476             } else {
2477                 $name = "$middle $last";
2478             }
2479         }
2480
2481         if (substr($name, -1) =~ /[,\.]/) {
2482             $name = substr($name, 0, length($name) - 1);
2483         } elsif (substr($name, -2) =~ /[,\.]"/) {
2484             $name = substr($name, 0, length($name) - 2) . '"';
2485         }
2486
2487         if (substr($name, 0, 1) =~ /[,\.]/) {
2488             $name = substr($name, 1, length($name) - 1);
2489         } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2490             $name = '"' . substr($name, 2, length($name) - 2);
2491         }
2492
2493         my $fmt_email = format_email($name, $address, $email_usename);
2494         push(@fmt_emails, $fmt_email);
2495     }
2496     return @fmt_emails;
2497 }
2498
2499 sub merge_email {
2500     my @lines;
2501     my %saw;
2502
2503     for (@_) {
2504         my ($address, $role) = @$_;
2505         if (!$saw{$address}) {
2506             if ($output_roles) {
2507                 push(@lines, "$address ($role)");
2508             } else {
2509                 push(@lines, $address);
2510             }
2511             $saw{$address} = 1;
2512         }
2513     }
2514
2515     return @lines;
2516 }
2517
2518 sub output {
2519     my (@parms) = @_;
2520
2521     if ($output_multiline) {
2522         foreach my $line (@parms) {
2523             print("${line}\n");
2524         }
2525     } else {
2526         print(join($output_separator, @parms));
2527         print("\n");
2528     }
2529 }
2530
2531 my $rfc822re;
2532
2533 sub make_rfc822re {
2534 #   Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2535 #   comment.  We must allow for rfc822_lwsp (or comments) after each of these.
2536 #   This regexp will only work on addresses which have had comments stripped
2537 #   and replaced with rfc822_lwsp.
2538
2539     my $specials = '()<>@,;:\\\\".\\[\\]';
2540     my $controls = '\\000-\\037\\177';
2541
2542     my $dtext = "[^\\[\\]\\r\\\\]";
2543     my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2544
2545     my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2546
2547 #   Use zero-width assertion to spot the limit of an atom.  A simple
2548 #   $rfc822_lwsp* causes the regexp engine to hang occasionally.
2549     my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2550     my $word = "(?:$atom|$quoted_string)";
2551     my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2552
2553     my $sub_domain = "(?:$atom|$domain_literal)";
2554     my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2555
2556     my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2557
2558     my $phrase = "$word*";
2559     my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2560     my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2561     my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2562
2563     my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2564     my $address = "(?:$mailbox|$group)";
2565
2566     return "$rfc822_lwsp*$address";
2567 }
2568
2569 sub rfc822_strip_comments {
2570     my $s = shift;
2571 #   Recursively remove comments, and replace with a single space.  The simpler
2572 #   regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2573 #   chars in atoms, for example.
2574
2575     while ($s =~ s/^((?:[^"\\]|\\.)*
2576                     (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2577                     \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2578     return $s;
2579 }
2580
2581 #   valid: returns true if the parameter is an RFC822 valid address
2582 #
2583 sub rfc822_valid {
2584     my $s = rfc822_strip_comments(shift);
2585
2586     if (!$rfc822re) {
2587         $rfc822re = make_rfc822re();
2588     }
2589
2590     return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2591 }
2592
2593 #   validlist: In scalar context, returns true if the parameter is an RFC822
2594 #              valid list of addresses.
2595 #
2596 #              In list context, returns an empty list on failure (an invalid
2597 #              address was found); otherwise a list whose first element is the
2598 #              number of addresses found and whose remaining elements are the
2599 #              addresses.  This is needed to disambiguate failure (invalid)
2600 #              from success with no addresses found, because an empty string is
2601 #              a valid list.
2602
2603 sub rfc822_validlist {
2604     my $s = rfc822_strip_comments(shift);
2605
2606     if (!$rfc822re) {
2607         $rfc822re = make_rfc822re();
2608     }
2609     # * null list items are valid according to the RFC
2610     # * the '1' business is to aid in distinguishing failure from no results
2611
2612     my @r;
2613     if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2614         $s =~ m/^$rfc822_char*$/) {
2615         while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2616             push(@r, $1);
2617         }
2618         return wantarray ? (scalar(@r), @r) : 1;
2619     }
2620     return wantarray ? () : 0;
2621 }