mb/dell/snb_ivb_latitude/*/hda_verb.c: Use AZALIA_PIN_DESC macro
[coreboot.git] / util / scripts / get_maintainer.pl
blobe3fe35c6a03ffbcea260ded14975714b0b49b58f
1 #!/usr/bin/env perl
2 # (c) 2007, Joe Perches <joe@perches.com>
3 # created from checkpatch.pl
5 # Print selected MAINTAINERS information for
6 # the files modified in a patch or for a file
8 # usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
9 # perl scripts/get_maintainer.pl [OPTIONS] -f <file>
11 # SPDX-License-Identifier: GPL-2.0-only
13 use strict;
14 use warnings;
16 my $P = $0;
17 my $V = '0.26';
19 use Getopt::Long qw(:config no_auto_abbrev);
20 use Cwd;
22 my $cur_path = fastgetcwd() . '/';
23 my $lk_path = "./";
24 my $email = 1;
25 my $email_usename = 1;
26 my $email_maintainer = 1;
27 my $email_reviewer = 1;
28 my $email_list = 1;
29 my $email_subscriber_list = 0;
30 my $email_git_penguin_chiefs = 0;
31 my $email_git = 0;
32 my $email_git_all_signature_types = 0;
33 my $email_git_blame = 0;
34 my $email_git_blame_signatures = 1;
35 my $email_git_fallback = 1;
36 my $email_git_min_signatures = 1;
37 my $email_git_max_maintainers = 5;
38 my $email_git_min_percent = 5;
39 my $email_git_since = "1-year-ago";
40 my $email_hg_since = "-365";
41 my $interactive = 0;
42 my $email_remove_duplicates = 1;
43 my $email_use_mailmap = 1;
44 my $output_multiline = 1;
45 my $output_separator = ", ";
46 my $output_roles = 0;
47 my $output_rolestats = 1;
48 my $output_section_maxlen = 50;
49 my $scm = 0;
50 my $web = 0;
51 my $subsystem = 0;
52 my $status = 0;
53 my $letters = "";
54 my $keywords = 1;
55 my $sections = 0;
56 my $file_emails = 0;
57 my $from_filename = 0;
58 my $pattern_depth = 0;
59 my $version = 0;
60 my $help = 0;
62 my $vcs_used = 0;
64 my $exit = 0;
66 my %commit_author_hash;
67 my %commit_signer_hash;
69 my @penguin_chief = ();
70 push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
71 #Andrew wants in on most everything - 2009/01/14
72 #push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
74 my @penguin_chief_names = ();
75 foreach my $chief (@penguin_chief) {
76 if ($chief =~ m/^(.*):(.*)/) {
77 my $chief_name = $1;
78 my $chief_addr = $2;
79 push(@penguin_chief_names, $chief_name);
82 my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
84 # Signature types of people who are either
85 # a) responsible for the code in question, or
86 # b) familiar enough with it to give relevant feedback
87 my @signature_tags = ();
88 push(@signature_tags, "Signed-off-by:");
89 push(@signature_tags, "Reviewed-by:");
90 push(@signature_tags, "Acked-by:");
92 my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
94 # rfc822 email address - preloaded methods go here.
95 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
96 my $rfc822_char = '[\\000-\\377]';
98 # VCS command support: class-like functions and strings
100 my %VCS_cmds;
102 my %VCS_cmds_git = (
103 "execute_cmd" => \&git_execute_cmd,
104 "available" => '(which("git") ne "") && (-e ".git")',
105 "find_signers_cmd" =>
106 "git log --no-color --follow --since=\$email_git_since " .
107 '--numstat --no-merges ' .
108 '--format="GitCommit: %H%n' .
109 'GitAuthor: %an <%ae>%n' .
110 'GitDate: %aD%n' .
111 'GitSubject: %s%n' .
112 '%b%n"' .
113 " -- \$file",
114 "find_commit_signers_cmd" =>
115 "git log --no-color " .
116 '--numstat ' .
117 '--format="GitCommit: %H%n' .
118 'GitAuthor: %an <%ae>%n' .
119 'GitDate: %aD%n' .
120 'GitSubject: %s%n' .
121 '%b%n"' .
122 " -1 \$commit",
123 "find_commit_author_cmd" =>
124 "git log --no-color " .
125 '--numstat ' .
126 '--format="GitCommit: %H%n' .
127 'GitAuthor: %an <%ae>%n' .
128 'GitDate: %aD%n' .
129 'GitSubject: %s%n"' .
130 " -1 \$commit",
131 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
132 "blame_file_cmd" => "git blame -l \$file",
133 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
134 "blame_commit_pattern" => "^([0-9a-f]+) ",
135 "author_pattern" => "^GitAuthor: (.*)",
136 "subject_pattern" => "^GitSubject: (.*)",
137 "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
138 "file_exists_cmd" => "git ls-files \$file",
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 "file_exists_cmd" => "hg files \$file",
170 my $conf = which_conf(".get_maintainer.conf");
171 if (-f $conf) {
172 my @conf_args;
173 open(my $conffile, '<', "$conf")
174 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
176 while (<$conffile>) {
177 my $line = $_;
179 $line =~ s/\s*\n?$//g;
180 $line =~ s/^\s*//g;
181 $line =~ s/\s+/ /g;
183 next if ($line =~ m/^\s*#/);
184 next if ($line =~ m/^\s*$/);
186 my @words = split(" ", $line);
187 foreach my $word (@words) {
188 last if ($word =~ m/^#/);
189 push (@conf_args, $word);
192 close($conffile);
193 unshift(@ARGV, @conf_args) if @conf_args;
196 my @ignore_emails = ();
197 my $ignore_file = which_conf(".get_maintainer.ignore");
198 if (-f $ignore_file) {
199 open(my $ignore, '<', "$ignore_file")
200 or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n";
201 while (<$ignore>) {
202 my $line = $_;
204 $line =~ s/\s*\n?$//;
205 $line =~ s/^\s*//;
206 $line =~ s/\s+$//;
207 $line =~ s/#.*$//;
209 next if ($line =~ m/^\s*$/);
210 if (rfc822_valid($line)) {
211 push(@ignore_emails, $line);
214 close($ignore);
217 if (!GetOptions(
218 'email!' => \$email,
219 'git!' => \$email_git,
220 'git-all-signature-types!' => \$email_git_all_signature_types,
221 'git-blame!' => \$email_git_blame,
222 'git-blame-signatures!' => \$email_git_blame_signatures,
223 'git-fallback!' => \$email_git_fallback,
224 'git-chief-penguins!' => \$email_git_penguin_chiefs,
225 'git-min-signatures=i' => \$email_git_min_signatures,
226 'git-max-maintainers=i' => \$email_git_max_maintainers,
227 'git-min-percent=i' => \$email_git_min_percent,
228 'git-since=s' => \$email_git_since,
229 'hg-since=s' => \$email_hg_since,
230 'i|interactive!' => \$interactive,
231 'remove-duplicates!' => \$email_remove_duplicates,
232 'mailmap!' => \$email_use_mailmap,
233 'm!' => \$email_maintainer,
234 'r!' => \$email_reviewer,
235 'n!' => \$email_usename,
236 'l!' => \$email_list,
237 's!' => \$email_subscriber_list,
238 'multiline!' => \$output_multiline,
239 'roles!' => \$output_roles,
240 'rolestats!' => \$output_rolestats,
241 'separator=s' => \$output_separator,
242 'subsystem!' => \$subsystem,
243 'status!' => \$status,
244 'scm!' => \$scm,
245 'web!' => \$web,
246 'letters=s' => \$letters,
247 'pattern-depth=i' => \$pattern_depth,
248 'k|keywords!' => \$keywords,
249 'sections!' => \$sections,
250 'fe|file-emails!' => \$file_emails,
251 'f|file' => \$from_filename,
252 'v|version' => \$version,
253 'h|help|usage' => \$help,
254 )) {
255 die "$P: invalid argument - use --help if necessary\n";
258 if ($help != 0) {
259 usage();
260 exit 0;
263 if ($version != 0) {
264 print("${P} ${V}\n");
265 exit 0;
268 if (-t STDIN && !@ARGV) {
269 # We're talking to a terminal, but have no command line arguments.
270 die "$P: missing patchfile or -f file - use --help if necessary\n";
273 $output_multiline = 0 if ($output_separator ne ", ");
274 $output_rolestats = 1 if ($interactive);
275 $output_roles = 1 if ($output_rolestats);
277 if ($sections || $letters ne "") {
278 $sections = 1;
279 $email = 0;
280 $email_list = 0;
281 $scm = 0;
282 $status = 0;
283 $subsystem = 0;
284 $web = 0;
285 $keywords = 0;
286 $interactive = 0;
287 } else {
288 my $selections = $email + $scm + $status + $subsystem + $web;
289 if ($selections == 0) {
290 die "$P: Missing required option: email, scm, status, subsystem or web\n";
294 if ($email &&
295 ($email_maintainer + $email_reviewer +
296 $email_list + $email_subscriber_list +
297 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
298 die "$P: Please select at least 1 email option\n";
301 ## Read MAINTAINERS for type/value pairs
303 my @typevalue = ();
304 my %keyword_hash;
306 open (my $maint, '<', "${lk_path}MAINTAINERS")
307 or die "$P: Can't open MAINTAINERS: $!\n";
308 while (<$maint>) {
309 my $line = $_;
311 if ($line =~ m/^([A-Z]):\s*(.*)/) {
312 my $type = $1;
313 my $value = $2;
315 ##Filename pattern matching
316 if ($type eq "F" || $type eq "X") {
317 $value =~ s@\.@\\\.@g; ##Convert . to \.
318 $value =~ s/\*/\.\*/g; ##Convert * to .*
319 $value =~ s/\?/\./g; ##Convert ? to .
320 ##if pattern is a directory and it lacks a trailing slash, add one
321 if ((-d $value)) {
322 $value =~ s@([^/])$@$1/@;
324 } elsif ($type eq "K") {
325 $keyword_hash{@typevalue} = $value;
327 push(@typevalue, "$type:$value");
328 } elsif (!/^(\s)*$/) {
329 $line =~ s/\n$//g;
330 push(@typevalue, $line);
333 close($maint);
337 # Read mail address map
340 my $mailmap;
342 read_mailmap();
344 sub read_mailmap {
345 $mailmap = {
346 names => {},
347 addresses => {}
350 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
352 open(my $mailmap_file, '<', "${lk_path}.mailmap")
353 or warn "$P: Can't open .mailmap: $!\n";
355 while (<$mailmap_file>) {
356 s/#.*$//; #strip comments
357 s/^\s+|\s+$//g; #trim
359 next if (/^\s*$/); #skip empty lines
360 #entries have one of the following formats:
361 # name1 <mail1>
362 # <mail1> <mail2>
363 # name1 <mail1> <mail2>
364 # name1 <mail1> name2 <mail2>
365 # (see man git-shortlog)
367 if (/^([^<]+)<([^>]+)>$/) {
368 my $real_name = $1;
369 my $address = $2;
371 $real_name =~ s/\s+$//;
372 ($real_name, $address) = parse_email("$real_name <$address>");
373 $mailmap->{names}->{$address} = $real_name;
375 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
376 my $real_address = $1;
377 my $wrong_address = $2;
379 $mailmap->{addresses}->{$wrong_address} = $real_address;
381 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
382 my $real_name = $1;
383 my $real_address = $2;
384 my $wrong_address = $3;
386 $real_name =~ s/\s+$//;
387 ($real_name, $real_address) =
388 parse_email("$real_name <$real_address>");
389 $mailmap->{names}->{$wrong_address} = $real_name;
390 $mailmap->{addresses}->{$wrong_address} = $real_address;
392 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
393 my $real_name = $1;
394 my $real_address = $2;
395 my $wrong_name = $3;
396 my $wrong_address = $4;
398 $real_name =~ s/\s+$//;
399 ($real_name, $real_address) =
400 parse_email("$real_name <$real_address>");
402 $wrong_name =~ s/\s+$//;
403 ($wrong_name, $wrong_address) =
404 parse_email("$wrong_name <$wrong_address>");
406 my $wrong_email = format_email($wrong_name, $wrong_address, 1);
407 $mailmap->{names}->{$wrong_email} = $real_name;
408 $mailmap->{addresses}->{$wrong_email} = $real_address;
411 close($mailmap_file);
414 ## use the filenames on the command line or find the filenames in the patchfiles
416 my @files = ();
417 my @range = ();
418 my @keyword_tvi = ();
419 my @file_emails = ();
421 if (!@ARGV) {
422 push(@ARGV, "&STDIN");
425 foreach my $file (@ARGV) {
426 if ($file ne "&STDIN") {
427 ##if $file is a directory and it lacks a trailing slash, add one
428 if ((-d $file)) {
429 $file =~ s@([^/])$@$1/@;
430 } elsif (!(-f $file)) {
431 die "$P: file '${file}' not found\n";
434 if ($from_filename || ($file ne "&STDIN" && vcs_file_exists($file))) {
435 $file =~ s/^\Q${cur_path}\E//; #strip any absolute path
436 $file =~ s/^\Q${lk_path}\E//; #or the path to the lk tree
437 push(@files, $file);
438 if ($file ne "MAINTAINERS" && -f $file && ($keywords || $file_emails)) {
439 open(my $f, '<', $file)
440 or die "$P: Can't open $file: $!\n";
441 my $text = do { local($/) ; <$f> };
442 close($f);
443 if ($keywords) {
444 foreach my $line (keys %keyword_hash) {
445 if ($text =~ m/$keyword_hash{$line}/x) {
446 push(@keyword_tvi, $line);
450 if ($file_emails) {
451 my @poss_addr = $text =~ m$[A-Za--ÿ\"\' \,\.\+-]*\s*[\,]*\s*[\(\<\{]{0,1}[A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+\.[A-Za-z0-9]+[\)\>\}]{0,1}$g;
452 push(@file_emails, clean_file_emails(@poss_addr));
455 } else {
456 my $file_cnt = @files;
457 my $lastfile;
459 open(my $patch, "< $file")
460 or die "$P: Can't open $file: $!\n";
462 # We can check arbitrary information before the patch
463 # like the commit message, mail headers, etc...
464 # This allows us to match arbitrary keywords against any part
465 # of a git format-patch generated file (subject tags, etc...)
467 my $patch_prefix = ""; #Parsing the intro
469 while (<$patch>) {
470 my $patch_line = $_;
471 if (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
472 my $filename = $1;
473 $filename =~ s@^[^/]*/@@;
474 $filename =~ s@\n@@;
475 $lastfile = $filename;
476 push(@files, $filename);
477 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
478 } elsif (m/^\@\@ -(\d+),(\d+)/) {
479 if ($email_git_blame) {
480 push(@range, "$lastfile:$1:$2");
482 } elsif ($keywords) {
483 foreach my $line (keys %keyword_hash) {
484 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
485 push(@keyword_tvi, $line);
490 close($patch);
492 if ($file_cnt == @files) {
493 warn "$P: file '${file}' doesn't appear to be a patch. "
494 . "Add -f to options?\n";
496 @files = sort_and_uniq(@files);
500 @file_emails = uniq(@file_emails);
502 my %email_hash_name;
503 my %email_hash_address;
504 my @email_to = ();
505 my %hash_list_to;
506 my @list_to = ();
507 my @scm = ();
508 my @web = ();
509 my @subsystem = ();
510 my @status = ();
511 my %deduplicate_name_hash = ();
512 my %deduplicate_address_hash = ();
514 my @maintainers = get_maintainers();
516 if (@maintainers) {
517 @maintainers = merge_email(@maintainers);
518 output(@maintainers);
521 if ($scm) {
522 @scm = uniq(@scm);
523 output(@scm);
526 if ($status) {
527 @status = uniq(@status);
528 output(@status);
531 if ($subsystem) {
532 @subsystem = uniq(@subsystem);
533 output(@subsystem);
536 if ($web) {
537 @web = uniq(@web);
538 output(@web);
541 exit($exit);
543 sub ignore_email_address {
544 my ($address) = @_;
546 foreach my $ignore (@ignore_emails) {
547 return 1 if ($ignore eq $address);
550 return 0;
553 sub range_is_maintained {
554 my ($start, $end) = @_;
556 for (my $i = $start; $i < $end; $i++) {
557 my $line = $typevalue[$i];
558 if ($line =~ m/^([A-Z]):\s*(.*)/) {
559 my $type = $1;
560 my $value = $2;
561 if ($type eq 'S') {
562 if ($value =~ /(maintain|support)/i) {
563 return 1;
568 return 0;
571 sub range_has_maintainer {
572 my ($start, $end) = @_;
574 for (my $i = $start; $i < $end; $i++) {
575 my $line = $typevalue[$i];
576 if ($line =~ m/^([A-Z]):\s*(.*)/) {
577 my $type = $1;
578 my $value = $2;
579 if ($type eq 'M') {
580 return 1;
584 return 0;
587 sub get_maintainers {
588 %email_hash_name = ();
589 %email_hash_address = ();
590 %commit_author_hash = ();
591 %commit_signer_hash = ();
592 @email_to = ();
593 %hash_list_to = ();
594 @list_to = ();
595 @scm = ();
596 @web = ();
597 @subsystem = ();
598 @status = ();
599 %deduplicate_name_hash = ();
600 %deduplicate_address_hash = ();
601 if ($email_git_all_signature_types) {
602 $signature_pattern = "(.+?)[Bb][Yy]:";
603 } else {
604 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
607 # Find responsible parties
609 my %exact_pattern_match_hash = ();
611 foreach my $file (@files) {
613 my %hash;
614 my $tvi = find_first_section();
615 while ($tvi < @typevalue) {
616 my $start = find_starting_index($tvi);
617 my $end = find_ending_index($tvi);
618 my $exclude = 0;
619 my $i;
621 #Do not match excluded file patterns
623 for ($i = $start; $i < $end; $i++) {
624 my $line = $typevalue[$i];
625 if ($line =~ m/^([A-Z]):\s*(.*)/) {
626 my $type = $1;
627 my $value = $2;
628 if ($type eq 'X') {
629 if (file_match_pattern($file, $value)) {
630 $exclude = 1;
631 last;
637 if (!$exclude) {
638 for ($i = $start; $i < $end; $i++) {
639 my $line = $typevalue[$i];
640 if ($line =~ m/^([A-Z]):\s*(.*)/) {
641 my $type = $1;
642 my $value = $2;
643 if ($type eq 'F') {
644 if (file_match_pattern($file, $value)) {
645 my $value_pd = ($value =~ tr@/@@);
646 my $file_pd = ($file =~ tr@/@@);
647 $value_pd++ if (substr($value,-1,1) ne "/");
648 $value_pd = -1 if ($value =~ /^\.\*/);
649 if ($value_pd >= $file_pd &&
650 range_is_maintained($start, $end) &&
651 range_has_maintainer($start, $end)) {
652 $exact_pattern_match_hash{$file} = 1;
654 if ($pattern_depth == 0 ||
655 (($file_pd - $value_pd) < $pattern_depth)) {
656 $hash{$tvi} = $value_pd;
659 } elsif ($type eq 'N') {
660 if ($file =~ m/$value/x) {
661 $hash{$tvi} = 0;
667 $tvi = $end + 1;
670 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
671 add_categories($line);
672 if ($sections) {
673 my $i;
674 my $start = find_starting_index($line);
675 my $end = find_ending_index($line);
676 for ($i = $start; $i < $end; $i++) {
677 my $line = $typevalue[$i];
678 if ($line =~ /^[FX]:/) { ##Restore file patterns
679 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
680 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
681 $line =~ s/\\\./\./g; ##Convert \. to .
682 $line =~ s/\.\*/\*/g; ##Convert .* to *
684 my $count = $line =~ s/^([A-Z]):/$1:\t/g;
685 if ($letters eq "" || (!$count || $letters =~ /$1/i)) {
686 print("$line\n");
689 print("\n");
694 if ($keywords) {
695 @keyword_tvi = sort_and_uniq(@keyword_tvi);
696 foreach my $line (@keyword_tvi) {
697 add_categories($line);
701 foreach my $email (@email_to, @list_to) {
702 $email->[0] = deduplicate_email($email->[0]);
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);
711 if ($email && $email_git_blame) {
712 vcs_file_blame($file);
716 if ($email) {
717 foreach my $chief (@penguin_chief) {
718 if ($chief =~ m/^(.*):(.*)/) {
719 my $email_address;
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);
730 foreach my $email (@file_emails) {
731 my ($name, $address) = parse_email($email);
733 my $tmp_email = format_email($name, $address, $email_usename);
734 push_email_address($tmp_email, '');
735 add_role($tmp_email, 'in file');
739 my @to = ();
740 if ($email || $email_list) {
741 if ($email) {
742 @to = (@to, @email_to);
744 if ($email_list) {
745 @to = (@to, @list_to);
749 if ($interactive) {
750 @to = interactive_get_maintainers(\@to);
753 return @to;
756 sub file_match_pattern {
757 my ($file, $pattern) = @_;
758 if (substr($pattern, -1) eq "/") {
759 if ($file =~ m@^$pattern@) {
760 return 1;
762 } else {
763 if ($file =~ m@^$pattern@) {
764 my $s1 = ($file =~ tr@/@@);
765 my $s2 = ($pattern =~ tr@/@@);
766 if ($s1 == $s2) {
767 return 1;
771 return 0;
774 sub usage {
775 print <<EOT;
776 usage: $P [options] patchfile
777 $P [options] -f file|directory
778 version: $V
780 MAINTAINER 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 MAINTAINERS 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
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
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 --letters => print all matching 'letter' types from all matching sections
819 --mailmap => use .mailmap file (default: $email_use_mailmap)
820 --version => show version
821 --help => show this help information
823 Default options:
824 [--email --nogit --git-fallback --m --r --n --l --multiline --pattern-depth=0
825 --remove-duplicates --rolestats]
827 Notes:
828 Using "-f directory" may give unexpected results:
829 Used with "--git", git signators for _all_ files in and below
830 directory are examined as git recurses directories.
831 Any specified X: (exclude) pattern matches are _not_ ignored.
832 Used with "--nogit", directory is used as a pattern match,
833 no individual file within the directory or subdirectory
834 is matched.
835 Used with "--git-blame", does not iterate all files in directory
836 Using "--git-blame" is slow and may add old committers and authors
837 that are no longer active maintainers to the output.
838 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
839 other automated tools that expect only ["name"] <email address>
840 may not work because of additional output after <email address>.
841 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
842 not the percentage of the entire file authored. # of commits is
843 not a good measure of amount of code authored. 1 major commit may
844 contain a thousand lines, 5 trivial commits may modify a single line.
845 If git is not installed, but mercurial (hg) is installed and an .hg
846 repository exists, the following options apply to mercurial:
847 --git,
848 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
849 --git-blame
850 Use --hg-since not --git-since to control date selection
851 File ".get_maintainer.conf", if it exists in the linux kernel source root
852 directory, can change whatever get_maintainer defaults are desired.
853 Entries in this file can be any command line argument.
854 This file is prepended to any additional command line arguments.
855 Multiple lines and # comments are allowed.
856 Most options have both positive and negative forms.
857 The negative forms for --<foo> are --no<foo> and --no-<foo>.
862 sub top_of_kernel_tree {
863 my ($lk_path) = @_;
865 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
866 $lk_path .= "/";
868 if ( (-f "${lk_path}COPYING")
869 && (-f "${lk_path}CREDITS")
870 && (-f "${lk_path}Kbuild")
871 && (-f "${lk_path}MAINTAINERS")
872 && (-f "${lk_path}Makefile")
873 && (-f "${lk_path}README")
874 && (-d "${lk_path}Documentation")
875 && (-d "${lk_path}arch")
876 && (-d "${lk_path}include")
877 && (-d "${lk_path}drivers")
878 && (-d "${lk_path}fs")
879 && (-d "${lk_path}init")
880 && (-d "${lk_path}ipc")
881 && (-d "${lk_path}kernel")
882 && (-d "${lk_path}lib")
883 && (-d "${lk_path}scripts")) {
884 return 1;
886 return 0;
889 sub parse_email {
890 my ($formatted_email) = @_;
892 my $name = "";
893 my $address = "";
895 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
896 $name = $1;
897 $address = $2;
898 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
899 $address = $1;
900 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
901 $address = $1;
904 $name =~ s/^\s+|\s+$//g;
905 $name =~ s/^\"|\"$//g;
906 $address =~ s/^\s+|\s+$//g;
908 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
909 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
910 $name = "\"$name\"";
913 return ($name, $address);
916 sub format_email {
917 my ($name, $address, $usename) = @_;
919 my $formatted_email;
921 $name =~ s/^\s+|\s+$//g;
922 $name =~ s/^\"|\"$//g;
923 $address =~ s/^\s+|\s+$//g;
925 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
926 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
927 $name = "\"$name\"";
930 if ($usename) {
931 if ("$name" eq "") {
932 $formatted_email = "$address";
933 } else {
934 $formatted_email = "$name <$address>";
936 } else {
937 $formatted_email = $address;
940 return $formatted_email;
943 sub find_first_section {
944 my $index = 0;
946 while ($index < @typevalue) {
947 my $tv = $typevalue[$index];
948 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
949 last;
951 $index++;
954 return $index;
957 sub find_starting_index {
958 my ($index) = @_;
960 while ($index > 0) {
961 my $tv = $typevalue[$index];
962 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
963 last;
965 $index--;
968 return $index;
971 sub find_ending_index {
972 my ($index) = @_;
974 while ($index < @typevalue) {
975 my $tv = $typevalue[$index];
976 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
977 last;
979 $index++;
982 return $index;
985 sub get_subsystem_name {
986 my ($index) = @_;
988 my $start = find_starting_index($index);
990 my $subsystem = $typevalue[$start];
991 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
992 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
993 $subsystem =~ s/\s*$//;
994 $subsystem = $subsystem . "...";
996 return $subsystem;
999 sub get_maintainer_role {
1000 my ($index) = @_;
1002 my $i;
1003 my $start = find_starting_index($index);
1004 my $end = find_ending_index($index);
1006 my $role = "unknown";
1007 my $subsystem = get_subsystem_name($index);
1009 for ($i = $start + 1; $i < $end; $i++) {
1010 my $tv = $typevalue[$i];
1011 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1012 my $ptype = $1;
1013 my $pvalue = $2;
1014 if ($ptype eq "S") {
1015 $role = $pvalue;
1020 $role = lc($role);
1021 if ($role eq "supported") {
1022 $role = "supporter";
1023 } elsif ($role eq "maintained") {
1024 $role = "maintainer";
1025 } elsif ($role eq "odd fixes") {
1026 $role = "odd fixer";
1027 } elsif ($role eq "orphan") {
1028 $role = "orphan minder";
1029 } elsif ($role eq "obsolete") {
1030 $role = "obsolete minder";
1031 } elsif ($role eq "buried alive in reporters") {
1032 $role = "chief penguin";
1035 return $role . ":" . $subsystem;
1038 sub get_list_role {
1039 my ($index) = @_;
1041 my $subsystem = get_subsystem_name($index);
1043 if ($subsystem eq "THE REST") {
1044 $subsystem = "";
1047 return $subsystem;
1050 sub add_categories {
1051 my ($index) = @_;
1053 my $i;
1054 my $start = find_starting_index($index);
1055 my $end = find_ending_index($index);
1057 push(@subsystem, $typevalue[$start]);
1059 for ($i = $start + 1; $i < $end; $i++) {
1060 my $tv = $typevalue[$i];
1061 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1062 my $ptype = $1;
1063 my $pvalue = $2;
1064 if ($ptype eq "L") {
1065 my $list_address = $pvalue;
1066 my $list_additional = "";
1067 my $list_role = get_list_role($i);
1069 if ($list_role ne "") {
1070 $list_role = ":" . $list_role;
1072 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1073 $list_address = $1;
1074 $list_additional = $2;
1076 if ($list_additional =~ m/subscribers-only/) {
1077 if ($email_subscriber_list) {
1078 if (!$hash_list_to{lc($list_address)}) {
1079 $hash_list_to{lc($list_address)} = 1;
1080 push(@list_to, [$list_address,
1081 "subscriber list${list_role}"]);
1084 } else {
1085 if ($email_list) {
1086 if (!$hash_list_to{lc($list_address)}) {
1087 $hash_list_to{lc($list_address)} = 1;
1088 if ($list_additional =~ m/moderated/) {
1089 push(@list_to, [$list_address,
1090 "moderated list${list_role}"]);
1091 } else {
1092 push(@list_to, [$list_address,
1093 "open list${list_role}"]);
1098 } elsif ($ptype eq "M") {
1099 my ($name, $address) = parse_email($pvalue);
1100 if ($name eq "") {
1101 if ($i > 0) {
1102 my $tv = $typevalue[$i - 1];
1103 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1104 if ($1 eq "P") {
1105 $name = $2;
1106 $pvalue = format_email($name, $address, $email_usename);
1111 if ($email_maintainer) {
1112 my $role = get_maintainer_role($i);
1113 push_email_addresses($pvalue, $role);
1115 } elsif ($ptype eq "R") {
1116 my ($name, $address) = parse_email($pvalue);
1117 if ($name eq "") {
1118 if ($i > 0) {
1119 my $tv = $typevalue[$i - 1];
1120 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1121 if ($1 eq "P") {
1122 $name = $2;
1123 $pvalue = format_email($name, $address, $email_usename);
1128 if ($email_reviewer) {
1129 my $subsystem = get_subsystem_name($i);
1130 push_email_addresses($pvalue, "reviewer:$subsystem");
1132 } elsif ($ptype eq "T") {
1133 push(@scm, $pvalue);
1134 } elsif ($ptype eq "W") {
1135 push(@web, $pvalue);
1136 } elsif ($ptype eq "S") {
1137 push(@status, $pvalue);
1143 sub email_inuse {
1144 my ($name, $address) = @_;
1146 return 1 if (($name eq "") && ($address eq ""));
1147 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1148 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1150 return 0;
1153 sub push_email_address {
1154 my ($line, $role) = @_;
1156 my ($name, $address) = parse_email($line);
1158 if ($address eq "") {
1159 return 0;
1162 if (!$email_remove_duplicates) {
1163 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1164 } elsif (!email_inuse($name, $address)) {
1165 push(@email_to, [format_email($name, $address, $email_usename), $role]);
1166 $email_hash_name{lc($name)}++ if ($name ne "");
1167 $email_hash_address{lc($address)}++;
1170 return 1;
1173 sub push_email_addresses {
1174 my ($address, $role) = @_;
1176 my @address_list = ();
1178 if (rfc822_valid($address)) {
1179 push_email_address($address, $role);
1180 } elsif (@address_list = rfc822_validlist($address)) {
1181 my $array_count = shift(@address_list);
1182 while (my $entry = shift(@address_list)) {
1183 push_email_address($entry, $role);
1185 } else {
1186 if (!push_email_address($address, $role)) {
1187 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1192 sub add_role {
1193 my ($line, $role) = @_;
1195 my ($name, $address) = parse_email($line);
1196 my $email = format_email($name, $address, $email_usename);
1198 foreach my $entry (@email_to) {
1199 if ($email_remove_duplicates) {
1200 my ($entry_name, $entry_address) = parse_email($entry->[0]);
1201 if (($name eq $entry_name || $address eq $entry_address)
1202 && ($role eq "" || !($entry->[1] =~ m/$role/))
1204 if ($entry->[1] eq "") {
1205 $entry->[1] = "$role";
1206 } else {
1207 $entry->[1] = "$entry->[1],$role";
1210 } else {
1211 if ($email eq $entry->[0]
1212 && ($role eq "" || !($entry->[1] =~ m/$role/))
1214 if ($entry->[1] eq "") {
1215 $entry->[1] = "$role";
1216 } else {
1217 $entry->[1] = "$entry->[1],$role";
1224 sub which {
1225 my ($bin) = @_;
1227 foreach my $path (split(/:/, $ENV{PATH})) {
1228 if (-e "$path/$bin") {
1229 return "$path/$bin";
1233 return "";
1236 sub which_conf {
1237 my ($conf) = @_;
1239 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1240 if (-e "$path/$conf") {
1241 return "$path/$conf";
1245 return "";
1248 sub mailmap_email {
1249 my ($line) = @_;
1251 my ($name, $address) = parse_email($line);
1252 my $email = format_email($name, $address, 1);
1253 my $real_name = $name;
1254 my $real_address = $address;
1256 if (exists $mailmap->{names}->{$email} ||
1257 exists $mailmap->{addresses}->{$email}) {
1258 if (exists $mailmap->{names}->{$email}) {
1259 $real_name = $mailmap->{names}->{$email};
1261 if (exists $mailmap->{addresses}->{$email}) {
1262 $real_address = $mailmap->{addresses}->{$email};
1264 } else {
1265 if (exists $mailmap->{names}->{$address}) {
1266 $real_name = $mailmap->{names}->{$address};
1268 if (exists $mailmap->{addresses}->{$address}) {
1269 $real_address = $mailmap->{addresses}->{$address};
1272 return format_email($real_name, $real_address, 1);
1275 sub mailmap {
1276 my (@addresses) = @_;
1278 my @mapped_emails = ();
1279 foreach my $line (@addresses) {
1280 push(@mapped_emails, mailmap_email($line));
1282 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1283 return @mapped_emails;
1286 sub merge_by_realname {
1287 my %address_map;
1288 my (@emails) = @_;
1290 foreach my $email (@emails) {
1291 my ($name, $address) = parse_email($email);
1292 if (exists $address_map{$name}) {
1293 $address = $address_map{$name};
1294 $email = format_email($name, $address, 1);
1295 } else {
1296 $address_map{$name} = $address;
1301 sub git_execute_cmd {
1302 my ($cmd) = @_;
1303 my @lines = ();
1305 my $output = `$cmd`;
1306 $output =~ s/^\s*//gm;
1307 @lines = split("\n", $output);
1309 return @lines;
1312 sub hg_execute_cmd {
1313 my ($cmd) = @_;
1314 my @lines = ();
1316 my $output = `$cmd`;
1317 @lines = split("\n", $output);
1319 return @lines;
1322 sub extract_formatted_signatures {
1323 my (@signature_lines) = @_;
1325 my @type = @signature_lines;
1327 s/\s*(.*):.*/$1/ for (@type);
1329 # cut -f2- -d":"
1330 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1332 ## Reformat email addresses (with names) to avoid badly written signatures
1334 foreach my $signer (@signature_lines) {
1335 $signer = deduplicate_email($signer);
1338 return (\@type, \@signature_lines);
1341 sub vcs_find_signers {
1342 my ($cmd, $file) = @_;
1343 my $commits;
1344 my @lines = ();
1345 my @signatures = ();
1346 my @authors = ();
1347 my @stats = ();
1349 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1351 my $pattern = $VCS_cmds{"commit_pattern"};
1352 my $author_pattern = $VCS_cmds{"author_pattern"};
1353 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1355 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
1357 $commits = grep(/$pattern/, @lines); # of commits
1359 @authors = grep(/$author_pattern/, @lines);
1360 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1361 @stats = grep(/$stat_pattern/, @lines);
1363 # print("stats: <@stats>\n");
1365 return (0, \@signatures, \@authors, \@stats) if !@signatures;
1367 save_commits_by_author(@lines) if ($interactive);
1368 save_commits_by_signer(@lines) if ($interactive);
1370 if (!$email_git_penguin_chiefs) {
1371 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1374 my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
1375 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1377 return ($commits, $signers_ref, $authors_ref, \@stats);
1380 sub vcs_find_author {
1381 my ($cmd) = @_;
1382 my @lines = ();
1384 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1386 if (!$email_git_penguin_chiefs) {
1387 @lines = grep(!/${penguin_chiefs}/i, @lines);
1390 return @lines if !@lines;
1392 my @authors = ();
1393 foreach my $line (@lines) {
1394 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1395 my $author = $1;
1396 my ($name, $address) = parse_email($author);
1397 $author = format_email($name, $address, 1);
1398 push(@authors, $author);
1402 save_commits_by_author(@lines) if ($interactive);
1403 save_commits_by_signer(@lines) if ($interactive);
1405 return @authors;
1408 sub vcs_save_commits {
1409 my ($cmd) = @_;
1410 my @lines = ();
1411 my @commits = ();
1413 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1415 foreach my $line (@lines) {
1416 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1417 push(@commits, $1);
1421 return @commits;
1424 sub vcs_blame {
1425 my ($file) = @_;
1426 my $cmd;
1427 my @commits = ();
1429 return @commits if (!(-f $file));
1431 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1432 my @all_commits = ();
1434 $cmd = $VCS_cmds{"blame_file_cmd"};
1435 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1436 @all_commits = vcs_save_commits($cmd);
1438 foreach my $file_range_diff (@range) {
1439 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1440 my $diff_file = $1;
1441 my $diff_start = $2;
1442 my $diff_length = $3;
1443 next if ("$file" ne "$diff_file");
1444 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1445 push(@commits, $all_commits[$i]);
1448 } elsif (@range) {
1449 foreach my $file_range_diff (@range) {
1450 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1451 my $diff_file = $1;
1452 my $diff_start = $2;
1453 my $diff_length = $3;
1454 next if ("$file" ne "$diff_file");
1455 $cmd = $VCS_cmds{"blame_range_cmd"};
1456 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1457 push(@commits, vcs_save_commits($cmd));
1459 } else {
1460 $cmd = $VCS_cmds{"blame_file_cmd"};
1461 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1462 @commits = vcs_save_commits($cmd);
1465 foreach my $commit (@commits) {
1466 $commit =~ s/^\^//g;
1469 return @commits;
1472 my $printed_novcs = 0;
1473 sub vcs_exists {
1474 %VCS_cmds = %VCS_cmds_git;
1475 return 1 if eval $VCS_cmds{"available"};
1476 %VCS_cmds = %VCS_cmds_hg;
1477 return 2 if eval $VCS_cmds{"available"};
1478 %VCS_cmds = ();
1479 if (!$printed_novcs) {
1480 warn("$P: No supported VCS found. Add --nogit to options?\n");
1481 warn("Using a git repository produces better results.\n");
1482 warn("Try Linus Torvalds' latest git repository using:\n");
1483 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
1484 $printed_novcs = 1;
1486 return 0;
1489 sub vcs_is_git {
1490 vcs_exists();
1491 return $vcs_used == 1;
1494 sub vcs_is_hg {
1495 return $vcs_used == 2;
1498 sub interactive_get_maintainers {
1499 my ($list_ref) = @_;
1500 my @list = @$list_ref;
1502 vcs_exists();
1504 my %selected;
1505 my %authored;
1506 my %signed;
1507 my $count = 0;
1508 my $maintained = 0;
1509 foreach my $entry (@list) {
1510 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1511 $selected{$count} = 1;
1512 $authored{$count} = 0;
1513 $signed{$count} = 0;
1514 $count++;
1517 #menu loop
1518 my $done = 0;
1519 my $print_options = 0;
1520 my $redraw = 1;
1521 while (!$done) {
1522 $count = 0;
1523 if ($redraw) {
1524 printf STDERR "\n%1s %2s %-65s",
1525 "*", "#", "email/list and role:stats";
1526 if ($email_git ||
1527 ($email_git_fallback && !$maintained) ||
1528 $email_git_blame) {
1529 print STDERR "auth sign";
1531 print STDERR "\n";
1532 foreach my $entry (@list) {
1533 my $email = $entry->[0];
1534 my $role = $entry->[1];
1535 my $sel = "";
1536 $sel = "*" if ($selected{$count});
1537 my $commit_author = $commit_author_hash{$email};
1538 my $commit_signer = $commit_signer_hash{$email};
1539 my $authored = 0;
1540 my $signed = 0;
1541 $authored++ for (@{$commit_author});
1542 $signed++ for (@{$commit_signer});
1543 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1544 printf STDERR "%4d %4d", $authored, $signed
1545 if ($authored > 0 || $signed > 0);
1546 printf STDERR "\n %s\n", $role;
1547 if ($authored{$count}) {
1548 my $commit_author = $commit_author_hash{$email};
1549 foreach my $ref (@{$commit_author}) {
1550 print STDERR " Author: @{$ref}[1]\n";
1553 if ($signed{$count}) {
1554 my $commit_signer = $commit_signer_hash{$email};
1555 foreach my $ref (@{$commit_signer}) {
1556 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1560 $count++;
1563 my $date_ref = \$email_git_since;
1564 $date_ref = \$email_hg_since if (vcs_is_hg());
1565 if ($print_options) {
1566 $print_options = 0;
1567 if (vcs_exists()) {
1568 print STDERR <<EOT
1570 Version Control options:
1571 g use git history [$email_git]
1572 gf use git-fallback [$email_git_fallback]
1573 b use git blame [$email_git_blame]
1574 bs use blame signatures [$email_git_blame_signatures]
1575 c# minimum commits [$email_git_min_signatures]
1576 %# min percent [$email_git_min_percent]
1577 d# history to use [$$date_ref]
1578 x# max maintainers [$email_git_max_maintainers]
1579 t all signature types [$email_git_all_signature_types]
1580 m use .mailmap [$email_use_mailmap]
1583 print STDERR <<EOT
1585 Additional options:
1586 0 toggle all
1587 tm toggle maintainers
1588 tg toggle git entries
1589 tl toggle open list entries
1590 ts toggle subscriber list entries
1591 f emails in file [$file_emails]
1592 k keywords in file [$keywords]
1593 r remove duplicates [$email_remove_duplicates]
1594 p# pattern match depth [$pattern_depth]
1597 print STDERR
1598 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1600 my $input = <STDIN>;
1601 chomp($input);
1603 $redraw = 1;
1604 my $rerun = 0;
1605 my @wish = split(/[, ]+/, $input);
1606 foreach my $nr (@wish) {
1607 $nr = lc($nr);
1608 my $sel = substr($nr, 0, 1);
1609 my $str = substr($nr, 1);
1610 my $val = 0;
1611 $val = $1 if $str =~ /^(\d+)$/;
1613 if ($sel eq "y") {
1614 $interactive = 0;
1615 $done = 1;
1616 $output_rolestats = 0;
1617 $output_roles = 0;
1618 last;
1619 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1620 $selected{$nr - 1} = !$selected{$nr - 1};
1621 } elsif ($sel eq "*" || $sel eq '^') {
1622 my $toggle = 0;
1623 $toggle = 1 if ($sel eq '*');
1624 for (my $i = 0; $i < $count; $i++) {
1625 $selected{$i} = $toggle;
1627 } elsif ($sel eq "0") {
1628 for (my $i = 0; $i < $count; $i++) {
1629 $selected{$i} = !$selected{$i};
1631 } elsif ($sel eq "t") {
1632 if (lc($str) eq "m") {
1633 for (my $i = 0; $i < $count; $i++) {
1634 $selected{$i} = !$selected{$i}
1635 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1637 } elsif (lc($str) eq "g") {
1638 for (my $i = 0; $i < $count; $i++) {
1639 $selected{$i} = !$selected{$i}
1640 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1642 } elsif (lc($str) eq "l") {
1643 for (my $i = 0; $i < $count; $i++) {
1644 $selected{$i} = !$selected{$i}
1645 if ($list[$i]->[1] =~ /^(open list)/i);
1647 } elsif (lc($str) eq "s") {
1648 for (my $i = 0; $i < $count; $i++) {
1649 $selected{$i} = !$selected{$i}
1650 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1653 } elsif ($sel eq "a") {
1654 if ($val > 0 && $val <= $count) {
1655 $authored{$val - 1} = !$authored{$val - 1};
1656 } elsif ($str eq '*' || $str eq '^') {
1657 my $toggle = 0;
1658 $toggle = 1 if ($str eq '*');
1659 for (my $i = 0; $i < $count; $i++) {
1660 $authored{$i} = $toggle;
1663 } elsif ($sel eq "s") {
1664 if ($val > 0 && $val <= $count) {
1665 $signed{$val - 1} = !$signed{$val - 1};
1666 } elsif ($str eq '*' || $str eq '^') {
1667 my $toggle = 0;
1668 $toggle = 1 if ($str eq '*');
1669 for (my $i = 0; $i < $count; $i++) {
1670 $signed{$i} = $toggle;
1673 } elsif ($sel eq "o") {
1674 $print_options = 1;
1675 $redraw = 1;
1676 } elsif ($sel eq "g") {
1677 if ($str eq "f") {
1678 bool_invert(\$email_git_fallback);
1679 } else {
1680 bool_invert(\$email_git);
1682 $rerun = 1;
1683 } elsif ($sel eq "b") {
1684 if ($str eq "s") {
1685 bool_invert(\$email_git_blame_signatures);
1686 } else {
1687 bool_invert(\$email_git_blame);
1689 $rerun = 1;
1690 } elsif ($sel eq "c") {
1691 if ($val > 0) {
1692 $email_git_min_signatures = $val;
1693 $rerun = 1;
1695 } elsif ($sel eq "x") {
1696 if ($val > 0) {
1697 $email_git_max_maintainers = $val;
1698 $rerun = 1;
1700 } elsif ($sel eq "%") {
1701 if ($str ne "" && $val >= 0) {
1702 $email_git_min_percent = $val;
1703 $rerun = 1;
1705 } elsif ($sel eq "d") {
1706 if (vcs_is_git()) {
1707 $email_git_since = $str;
1708 } elsif (vcs_is_hg()) {
1709 $email_hg_since = $str;
1711 $rerun = 1;
1712 } elsif ($sel eq "t") {
1713 bool_invert(\$email_git_all_signature_types);
1714 $rerun = 1;
1715 } elsif ($sel eq "f") {
1716 bool_invert(\$file_emails);
1717 $rerun = 1;
1718 } elsif ($sel eq "r") {
1719 bool_invert(\$email_remove_duplicates);
1720 $rerun = 1;
1721 } elsif ($sel eq "m") {
1722 bool_invert(\$email_use_mailmap);
1723 read_mailmap();
1724 $rerun = 1;
1725 } elsif ($sel eq "k") {
1726 bool_invert(\$keywords);
1727 $rerun = 1;
1728 } elsif ($sel eq "p") {
1729 if ($str ne "" && $val >= 0) {
1730 $pattern_depth = $val;
1731 $rerun = 1;
1733 } elsif ($sel eq "h" || $sel eq "?") {
1734 print STDERR <<EOT
1736 Interactive mode allows you to select the various maintainers, submitters,
1737 commit signers and mailing lists that could be CC'd on a patch.
1739 Any *'d entry is selected.
1741 If you have git or hg installed, you can choose to summarize the commit
1742 history of files in the patch. Also, each line of the current file can
1743 be matched to its commit author and that commits signers with blame.
1745 Various knobs exist to control the length of time for active commit
1746 tracking, the maximum number of commit authors and signers to add,
1747 and such.
1749 Enter selections at the prompt until you are satisfied that the selected
1750 maintainers are appropriate. You may enter multiple selections separated
1751 by either commas or spaces.
1754 } else {
1755 print STDERR "invalid option: '$nr'\n";
1756 $redraw = 0;
1759 if ($rerun) {
1760 print STDERR "git-blame can be very slow, please have patience..."
1761 if ($email_git_blame);
1762 goto &get_maintainers;
1766 #drop not selected entries
1767 $count = 0;
1768 my @new_emailto = ();
1769 foreach my $entry (@list) {
1770 if ($selected{$count}) {
1771 push(@new_emailto, $list[$count]);
1773 $count++;
1775 return @new_emailto;
1778 sub bool_invert {
1779 my ($bool_ref) = @_;
1781 if ($$bool_ref) {
1782 $$bool_ref = 0;
1783 } else {
1784 $$bool_ref = 1;
1788 sub deduplicate_email {
1789 my ($email) = @_;
1791 my $matched = 0;
1792 my ($name, $address) = parse_email($email);
1793 $email = format_email($name, $address, 1);
1794 $email = mailmap_email($email);
1796 return $email if (!$email_remove_duplicates);
1798 ($name, $address) = parse_email($email);
1800 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1801 $name = $deduplicate_name_hash{lc($name)}->[0];
1802 $address = $deduplicate_name_hash{lc($name)}->[1];
1803 $matched = 1;
1804 } elsif ($deduplicate_address_hash{lc($address)}) {
1805 $name = $deduplicate_address_hash{lc($address)}->[0];
1806 $address = $deduplicate_address_hash{lc($address)}->[1];
1807 $matched = 1;
1809 if (!$matched) {
1810 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1811 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1813 $email = format_email($name, $address, 1);
1814 $email = mailmap_email($email);
1815 return $email;
1818 sub save_commits_by_author {
1819 my (@lines) = @_;
1821 my @authors = ();
1822 my @commits = ();
1823 my @subjects = ();
1825 foreach my $line (@lines) {
1826 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1827 my $author = $1;
1828 $author = deduplicate_email($author);
1829 push(@authors, $author);
1831 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1832 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1835 for (my $i = 0; $i < @authors; $i++) {
1836 my $exists = 0;
1837 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1838 if (@{$ref}[0] eq $commits[$i] &&
1839 @{$ref}[1] eq $subjects[$i]) {
1840 $exists = 1;
1841 last;
1844 if (!$exists) {
1845 push(@{$commit_author_hash{$authors[$i]}},
1846 [ ($commits[$i], $subjects[$i]) ]);
1851 sub save_commits_by_signer {
1852 my (@lines) = @_;
1854 my $commit = "";
1855 my $subject = "";
1857 foreach my $line (@lines) {
1858 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1859 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1860 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1861 my @signatures = ($line);
1862 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1863 my @types = @$types_ref;
1864 my @signers = @$signers_ref;
1866 my $type = $types[0];
1867 my $signer = $signers[0];
1869 $signer = deduplicate_email($signer);
1871 my $exists = 0;
1872 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1873 if (@{$ref}[0] eq $commit &&
1874 @{$ref}[1] eq $subject &&
1875 @{$ref}[2] eq $type) {
1876 $exists = 1;
1877 last;
1880 if (!$exists) {
1881 push(@{$commit_signer_hash{$signer}},
1882 [ ($commit, $subject, $type) ]);
1888 sub vcs_assign {
1889 my ($role, $divisor, @lines) = @_;
1891 my %hash;
1892 my $count = 0;
1894 return if (@lines <= 0);
1896 if ($divisor <= 0) {
1897 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
1898 $divisor = 1;
1901 @lines = mailmap(@lines);
1903 return if (@lines <= 0);
1905 @lines = sort(@lines);
1907 # uniq -c
1908 $hash{$_}++ for @lines;
1910 # sort -rn
1911 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
1912 my $sign_offs = $hash{$line};
1913 my $percent = $sign_offs * 100 / $divisor;
1915 $percent = 100 if ($percent > 100);
1916 next if (ignore_email_address($line));
1917 $count++;
1918 last if ($sign_offs < $email_git_min_signatures ||
1919 $count > $email_git_max_maintainers ||
1920 $percent < $email_git_min_percent);
1921 push_email_address($line, '');
1922 if ($output_rolestats) {
1923 my $fmt_percent = sprintf("%.0f", $percent);
1924 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1925 } else {
1926 add_role($line, $role);
1931 sub vcs_file_signoffs {
1932 my ($file) = @_;
1934 my $authors_ref;
1935 my $signers_ref;
1936 my $stats_ref;
1937 my @authors = ();
1938 my @signers = ();
1939 my @stats = ();
1940 my $commits;
1942 $vcs_used = vcs_exists();
1943 return if (!$vcs_used);
1945 my $cmd = $VCS_cmds{"find_signers_cmd"};
1946 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1948 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
1950 @signers = @{$signers_ref} if defined $signers_ref;
1951 @authors = @{$authors_ref} if defined $authors_ref;
1952 @stats = @{$stats_ref} if defined $stats_ref;
1954 # print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
1956 foreach my $signer (@signers) {
1957 $signer = deduplicate_email($signer);
1960 vcs_assign("commit_signer", $commits, @signers);
1961 vcs_assign("authored", $commits, @authors);
1962 if ($#authors == $#stats) {
1963 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1964 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
1966 my $added = 0;
1967 my $deleted = 0;
1968 for (my $i = 0; $i <= $#stats; $i++) {
1969 if ($stats[$i] =~ /$stat_pattern/) {
1970 $added += $1;
1971 $deleted += $2;
1974 my @tmp_authors = uniq(@authors);
1975 foreach my $author (@tmp_authors) {
1976 $author = deduplicate_email($author);
1978 @tmp_authors = uniq(@tmp_authors);
1979 my @list_added = ();
1980 my @list_deleted = ();
1981 foreach my $author (@tmp_authors) {
1982 my $auth_added = 0;
1983 my $auth_deleted = 0;
1984 for (my $i = 0; $i <= $#stats; $i++) {
1985 if ($author eq deduplicate_email($authors[$i]) &&
1986 $stats[$i] =~ /$stat_pattern/) {
1987 $auth_added += $1;
1988 $auth_deleted += $2;
1991 for (my $i = 0; $i < $auth_added; $i++) {
1992 push(@list_added, $author);
1994 for (my $i = 0; $i < $auth_deleted; $i++) {
1995 push(@list_deleted, $author);
1998 vcs_assign("added_lines", $added, @list_added);
1999 vcs_assign("removed_lines", $deleted, @list_deleted);
2003 sub vcs_file_blame {
2004 my ($file) = @_;
2006 my @signers = ();
2007 my @all_commits = ();
2008 my @commits = ();
2009 my $total_commits;
2010 my $total_lines;
2012 $vcs_used = vcs_exists();
2013 return if (!$vcs_used);
2015 @all_commits = vcs_blame($file);
2016 @commits = uniq(@all_commits);
2017 $total_commits = @commits;
2018 $total_lines = @all_commits;
2020 if ($email_git_blame_signatures) {
2021 if (vcs_is_hg()) {
2022 my $commit_count;
2023 my $commit_authors_ref;
2024 my $commit_signers_ref;
2025 my $stats_ref;
2026 my @commit_authors = ();
2027 my @commit_signers = ();
2028 my $commit = join(" -r ", @commits);
2029 my $cmd;
2031 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2032 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2034 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2035 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2036 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2038 push(@signers, @commit_signers);
2039 } else {
2040 foreach my $commit (@commits) {
2041 my $commit_count;
2042 my $commit_authors_ref;
2043 my $commit_signers_ref;
2044 my $stats_ref;
2045 my @commit_authors = ();
2046 my @commit_signers = ();
2047 my $cmd;
2049 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2050 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2052 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2053 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2054 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2056 push(@signers, @commit_signers);
2061 if ($from_filename) {
2062 if ($output_rolestats) {
2063 my @blame_signers;
2064 if (vcs_is_hg()) {{ # Double brace for last exit
2065 my $commit_count;
2066 my @commit_signers = ();
2067 @commits = uniq(@commits);
2068 @commits = sort(@commits);
2069 my $commit = join(" -r ", @commits);
2070 my $cmd;
2072 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2073 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2075 my @lines = ();
2077 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2079 if (!$email_git_penguin_chiefs) {
2080 @lines = grep(!/${penguin_chiefs}/i, @lines);
2083 last if !@lines;
2085 my @authors = ();
2086 foreach my $line (@lines) {
2087 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2088 my $author = $1;
2089 $author = deduplicate_email($author);
2090 push(@authors, $author);
2094 save_commits_by_author(@lines) if ($interactive);
2095 save_commits_by_signer(@lines) if ($interactive);
2097 push(@signers, @authors);
2099 else {
2100 foreach my $commit (@commits) {
2101 my $i;
2102 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2103 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2104 my @author = vcs_find_author($cmd);
2105 next if !@author;
2107 my $formatted_author = deduplicate_email($author[0]);
2109 my $count = grep(/$commit/, @all_commits);
2110 for ($i = 0; $i < $count ; $i++) {
2111 push(@blame_signers, $formatted_author);
2115 if (@blame_signers) {
2116 vcs_assign("authored lines", $total_lines, @blame_signers);
2119 foreach my $signer (@signers) {
2120 $signer = deduplicate_email($signer);
2122 vcs_assign("commits", $total_commits, @signers);
2123 } else {
2124 foreach my $signer (@signers) {
2125 $signer = deduplicate_email($signer);
2127 vcs_assign("modified commits", $total_commits, @signers);
2131 sub vcs_file_exists {
2132 my ($file) = @_;
2134 my $exists;
2136 my $vcs_used = vcs_exists();
2137 return 0 if (!$vcs_used);
2139 my $cmd = $VCS_cmds{"file_exists_cmd"};
2140 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2141 $cmd .= " 2>&1";
2142 $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
2144 return 0 if ($? != 0);
2146 return $exists;
2149 sub uniq {
2150 my (@parms) = @_;
2152 my %saw;
2153 @parms = grep(!$saw{$_}++, @parms);
2154 return @parms;
2157 sub sort_and_uniq {
2158 my (@parms) = @_;
2160 my %saw;
2161 @parms = sort @parms;
2162 @parms = grep(!$saw{$_}++, @parms);
2163 return @parms;
2166 sub clean_file_emails {
2167 my (@file_emails) = @_;
2168 my @fmt_emails = ();
2170 foreach my $email (@file_emails) {
2171 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2172 my ($name, $address) = parse_email($email);
2173 if ($name eq '"[,\.]"') {
2174 $name = "";
2177 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2178 if (@nw > 2) {
2179 my $first = $nw[@nw - 3];
2180 my $middle = $nw[@nw - 2];
2181 my $last = $nw[@nw - 1];
2183 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2184 (length($first) == 2 && substr($first, -1) eq ".")) ||
2185 (length($middle) == 1 ||
2186 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2187 $name = "$first $middle $last";
2188 } else {
2189 $name = "$middle $last";
2193 if (substr($name, -1) =~ /[,\.]/) {
2194 $name = substr($name, 0, length($name) - 1);
2195 } elsif (substr($name, -2) =~ /[,\.]"/) {
2196 $name = substr($name, 0, length($name) - 2) . '"';
2199 if (substr($name, 0, 1) =~ /[,\.]/) {
2200 $name = substr($name, 1, length($name) - 1);
2201 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2202 $name = '"' . substr($name, 2, length($name) - 2);
2205 my $fmt_email = format_email($name, $address, $email_usename);
2206 push(@fmt_emails, $fmt_email);
2208 return @fmt_emails;
2211 sub merge_email {
2212 my @lines;
2213 my %saw;
2215 for (@_) {
2216 my ($address, $role) = @$_;
2217 if (!$saw{$address}) {
2218 if ($output_roles) {
2219 push(@lines, "$address ($role)");
2220 } else {
2221 push(@lines, $address);
2223 $saw{$address} = 1;
2227 return @lines;
2230 sub output {
2231 my (@parms) = @_;
2233 if ($output_multiline) {
2234 foreach my $line (@parms) {
2235 print("${line}\n");
2237 } else {
2238 print(join($output_separator, @parms));
2239 print("\n");
2243 my $rfc822re;
2245 sub make_rfc822re {
2246 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2247 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
2248 # This regexp will only work on addresses which have had comments stripped
2249 # and replaced with rfc822_lwsp.
2251 my $specials = '()<>@,;:\\\\".\\[\\]';
2252 my $controls = '\\000-\\037\\177';
2254 my $dtext = "[^\\[\\]\\r\\\\]";
2255 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2257 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2259 # Use zero-width assertion to spot the limit of an atom. A simple
2260 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
2261 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2262 my $word = "(?:$atom|$quoted_string)";
2263 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2265 my $sub_domain = "(?:$atom|$domain_literal)";
2266 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2268 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2270 my $phrase = "$word*";
2271 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2272 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2273 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2275 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2276 my $address = "(?:$mailbox|$group)";
2278 return "$rfc822_lwsp*$address";
2281 sub rfc822_strip_comments {
2282 my $s = shift;
2283 # Recursively remove comments, and replace with a single space. The simpler
2284 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2285 # chars in atoms, for example.
2287 while ($s =~ s/^((?:[^"\\]|\\.)*
2288 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2289 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2290 return $s;
2293 # valid: returns true if the parameter is an RFC822 valid address
2295 sub rfc822_valid {
2296 my $s = rfc822_strip_comments(shift);
2298 if (!$rfc822re) {
2299 $rfc822re = make_rfc822re();
2302 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2305 # validlist: In scalar context, returns true if the parameter is an RFC822
2306 # valid list of addresses.
2308 # In list context, returns an empty list on failure (an invalid
2309 # address was found); otherwise a list whose first element is the
2310 # number of addresses found and whose remaining elements are the
2311 # addresses. This is needed to disambiguate failure (invalid)
2312 # from success with no addresses found, because an empty string is
2313 # a valid list.
2315 sub rfc822_validlist {
2316 my $s = rfc822_strip_comments(shift);
2318 if (!$rfc822re) {
2319 $rfc822re = make_rfc822re();
2321 # * null list items are valid according to the RFC
2322 # * the '1' business is to aid in distinguishing failure from no results
2324 my @r;
2325 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2326 $s =~ m/^$rfc822_char*$/) {
2327 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2328 push(@r, $1);
2330 return wantarray ? (scalar(@r), @r) : 1;
2332 return wantarray ? () : 0;