OSDN Git Service

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