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