aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/get_maintainer.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/get_maintainer.pl')
-rwxr-xr-xscripts/get_maintainer.pl1406
1 files changed, 1166 insertions, 240 deletions
diff --git a/scripts/get_maintainer.pl b/scripts/get_maintainer.pl
index 090f2483970..139e0fff8e3 100755
--- a/scripts/get_maintainer.pl
+++ b/scripts/get_maintainer.pl
@@ -13,7 +13,7 @@
13use strict; 13use strict;
14 14
15my $P = $0; 15my $P = $0;
16my $V = '0.23'; 16my $V = '0.26';
17 17
18use Getopt::Long qw(:config no_auto_abbrev); 18use Getopt::Long qw(:config no_auto_abbrev);
19 19
@@ -24,34 +24,46 @@ my $email_maintainer = 1;
24my $email_list = 1; 24my $email_list = 1;
25my $email_subscriber_list = 0; 25my $email_subscriber_list = 0;
26my $email_git_penguin_chiefs = 0; 26my $email_git_penguin_chiefs = 0;
27my $email_git = 1; 27my $email_git = 0;
28my $email_git_all_signature_types = 0;
28my $email_git_blame = 0; 29my $email_git_blame = 0;
30my $email_git_blame_signatures = 1;
31my $email_git_fallback = 1;
29my $email_git_min_signatures = 1; 32my $email_git_min_signatures = 1;
30my $email_git_max_maintainers = 5; 33my $email_git_max_maintainers = 5;
31my $email_git_min_percent = 5; 34my $email_git_min_percent = 5;
32my $email_git_since = "1-year-ago"; 35my $email_git_since = "1-year-ago";
33my $email_hg_since = "-365"; 36my $email_hg_since = "-365";
37my $interactive = 0;
34my $email_remove_duplicates = 1; 38my $email_remove_duplicates = 1;
39my $email_use_mailmap = 1;
35my $output_multiline = 1; 40my $output_multiline = 1;
36my $output_separator = ", "; 41my $output_separator = ", ";
37my $output_roles = 0; 42my $output_roles = 0;
38my $output_rolestats = 0; 43my $output_rolestats = 1;
39my $scm = 0; 44my $scm = 0;
40my $web = 0; 45my $web = 0;
41my $subsystem = 0; 46my $subsystem = 0;
42my $status = 0; 47my $status = 0;
43my $keywords = 1; 48my $keywords = 1;
49my $sections = 0;
50my $file_emails = 0;
44my $from_filename = 0; 51my $from_filename = 0;
45my $pattern_depth = 0; 52my $pattern_depth = 0;
46my $version = 0; 53my $version = 0;
47my $help = 0; 54my $help = 0;
48 55
56my $vcs_used = 0;
57
49my $exit = 0; 58my $exit = 0;
50 59
60my %commit_author_hash;
61my %commit_signer_hash;
62
51my @penguin_chief = (); 63my @penguin_chief = ();
52push(@penguin_chief,"Linus Torvalds:torvalds\@linux-foundation.org"); 64push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
53#Andrew wants in on most everything - 2009/01/14 65#Andrew wants in on most everything - 2009/01/14
54#push(@penguin_chief,"Andrew Morton:akpm\@linux-foundation.org"); 66#push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
55 67
56my @penguin_chief_names = (); 68my @penguin_chief_names = ();
57foreach my $chief (@penguin_chief) { 69foreach my $chief (@penguin_chief) {
@@ -61,7 +73,15 @@ foreach my $chief (@penguin_chief) {
61 push(@penguin_chief_names, $chief_name); 73 push(@penguin_chief_names, $chief_name);
62 } 74 }
63} 75}
64my $penguin_chiefs = "\(" . join("|",@penguin_chief_names) . "\)"; 76my $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
81my @signature_tags = ();
82push(@signature_tags, "Signed-off-by:");
83push(@signature_tags, "Reviewed-by:");
84push(@signature_tags, "Acked-by:");
65 85
66# rfc822 email address - preloaded methods go here. 86# rfc822 email address - preloaded methods go here.
67my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])"; 87my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
@@ -74,38 +94,106 @@ my %VCS_cmds;
74my %VCS_cmds_git = ( 94my %VCS_cmds_git = (
75 "execute_cmd" => \&git_execute_cmd, 95 "execute_cmd" => \&git_execute_cmd,
76 "available" => '(which("git") ne "") && (-d ".git")', 96 "available" => '(which("git") ne "") && (-d ".git")',
77 "find_signers_cmd" => "git log --since=\$email_git_since -- \$file", 97 "find_signers_cmd" =>
78 "find_commit_signers_cmd" => "git log -1 \$commit", 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",
79 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file", 120 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
80 "blame_file_cmd" => "git blame -l \$file", 121 "blame_file_cmd" => "git blame -l \$file",
81 "commit_pattern" => "^commit [0-9a-f]{40,40}", 122 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
82 "blame_commit_pattern" => "^([0-9a-f]+) " 123 "blame_commit_pattern" => "^([0-9a-f]+) ",
124 "author_pattern" => "^GitAuthor: (.*)",
125 "subject_pattern" => "^GitSubject: (.*)",
83); 126);
84 127
85my %VCS_cmds_hg = ( 128my %VCS_cmds_hg = (
86 "execute_cmd" => \&hg_execute_cmd, 129 "execute_cmd" => \&hg_execute_cmd,
87 "available" => '(which("hg") ne "") && (-d ".hg")', 130 "available" => '(which("hg") ne "") && (-d ".hg")',
88 "find_signers_cmd" => 131 "find_signers_cmd" =>
89 "hg log --date=\$email_hg_since" . 132 "hg log --date=\$email_hg_since " .
90 " --template='commit {node}\\n{desc}\\n' -- \$file", 133 "--template='HgCommit: {node}\\n" .
91 "find_commit_signers_cmd" => "hg log --template='{desc}\\n' -r \$commit", 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",
92 "blame_range_cmd" => "", # not supported 147 "blame_range_cmd" => "", # not supported
93 "blame_file_cmd" => "hg blame -c \$file", 148 "blame_file_cmd" => "hg blame -n \$file",
94 "commit_pattern" => "^commit [0-9a-f]{40,40}", 149 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
95 "blame_commit_pattern" => "^([0-9a-f]+):" 150 "blame_commit_pattern" => "^([ 0-9a-f]+):",
151 "author_pattern" => "^HgAuthor: (.*)",
152 "subject_pattern" => "^HgSubject: (.*)",
96); 153);
97 154
155my $conf = which_conf(".get_maintainer.conf");
156if (-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
98if (!GetOptions( 181if (!GetOptions(
99 'email!' => \$email, 182 'email!' => \$email,
100 'git!' => \$email_git, 183 'git!' => \$email_git,
184 'git-all-signature-types!' => \$email_git_all_signature_types,
101 'git-blame!' => \$email_git_blame, 185 'git-blame!' => \$email_git_blame,
186 'git-blame-signatures!' => \$email_git_blame_signatures,
187 'git-fallback!' => \$email_git_fallback,
102 'git-chief-penguins!' => \$email_git_penguin_chiefs, 188 'git-chief-penguins!' => \$email_git_penguin_chiefs,
103 'git-min-signatures=i' => \$email_git_min_signatures, 189 'git-min-signatures=i' => \$email_git_min_signatures,
104 'git-max-maintainers=i' => \$email_git_max_maintainers, 190 'git-max-maintainers=i' => \$email_git_max_maintainers,
105 'git-min-percent=i' => \$email_git_min_percent, 191 'git-min-percent=i' => \$email_git_min_percent,
106 'git-since=s' => \$email_git_since, 192 'git-since=s' => \$email_git_since,
107 'hg-since=s' => \$email_hg_since, 193 'hg-since=s' => \$email_hg_since,
194 'i|interactive!' => \$interactive,
108 'remove-duplicates!' => \$email_remove_duplicates, 195 'remove-duplicates!' => \$email_remove_duplicates,
196 'mailmap!' => \$email_use_mailmap,
109 'm!' => \$email_maintainer, 197 'm!' => \$email_maintainer,
110 'n!' => \$email_usename, 198 'n!' => \$email_usename,
111 'l!' => \$email_list, 199 'l!' => \$email_list,
@@ -120,9 +208,11 @@ if (!GetOptions(
120 'web!' => \$web, 208 'web!' => \$web,
121 'pattern-depth=i' => \$pattern_depth, 209 'pattern-depth=i' => \$pattern_depth,
122 'k|keywords!' => \$keywords, 210 'k|keywords!' => \$keywords,
211 'sections!' => \$sections,
212 'fe|file-emails!' => \$file_emails,
123 'f|file' => \$from_filename, 213 'f|file' => \$from_filename,
124 'v|version' => \$version, 214 'v|version' => \$version,
125 'h|help' => \$help, 215 'h|help|usage' => \$help,
126 )) { 216 )) {
127 die "$P: invalid argument - use --help if necessary\n"; 217 die "$P: invalid argument - use --help if necessary\n";
128} 218}
@@ -137,29 +227,34 @@ if ($version != 0) {
137 exit 0; 227 exit 0;
138} 228}
139 229
140if ($#ARGV < 0) { 230if (-t STDIN && !@ARGV) {
141 usage(); 231 # We're talking to a terminal, but have no command line arguments.
142 die "$P: argument missing: patchfile or -f file please\n"; 232 die "$P: missing patchfile or -f file - use --help if necessary\n";
143}
144
145if ($output_separator ne ", ") {
146 $output_multiline = 0;
147} 233}
148 234
149if ($output_rolestats) { 235$output_multiline = 0 if ($output_separator ne ", ");
150 $output_roles = 1; 236$output_rolestats = 1 if ($interactive);
151} 237$output_roles = 1 if ($output_rolestats);
152 238
153my $selections = $email + $scm + $status + $subsystem + $web; 239if ($sections) {
154if ($selections == 0) { 240 $email = 0;
155 usage(); 241 $email_list = 0;
156 die "$P: Missing required option: email, scm, status, subsystem or web\n"; 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 }
157} 253}
158 254
159if ($email && 255if ($email &&
160 ($email_maintainer + $email_list + $email_subscriber_list + 256 ($email_maintainer + $email_list + $email_subscriber_list +
161 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) { 257 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
162 usage();
163 die "$P: Please select at least 1 email option\n"; 258 die "$P: Please select at least 1 email option\n";
164} 259}
165 260
@@ -173,8 +268,9 @@ if (!top_of_kernel_tree($lk_path)) {
173my @typevalue = (); 268my @typevalue = ();
174my %keyword_hash; 269my %keyword_hash;
175 270
176open(MAINT, "<${lk_path}MAINTAINERS") || die "$P: Can't open MAINTAINERS\n"; 271open (my $maint, '<', "${lk_path}MAINTAINERS")
177while (<MAINT>) { 272 or die "$P: Can't open MAINTAINERS: $!\n";
273while (<$maint>) {
178 my $line = $_; 274 my $line = $_;
179 275
180 if ($line =~ m/^(\C):\s*(.*)/) { 276 if ($line =~ m/^(\C):\s*(.*)/) {
@@ -199,32 +295,84 @@ while (<MAINT>) {
199 push(@typevalue, $line); 295 push(@typevalue, $line);
200 } 296 }
201} 297}
202close(MAINT); 298close($maint);
203 299
204my %mailmap;
205 300
206if ($email_remove_duplicates) { 301#
207 open(MAILMAP, "<${lk_path}.mailmap") || warn "$P: Can't open .mailmap\n"; 302# Read mail address map
208 while (<MAILMAP>) { 303#
209 my $line = $_;
210
211 next if ($line =~ m/^\s*#/);
212 next if ($line =~ m/^\s*$/);
213
214 my ($name, $address) = parse_email($line);
215 $line = format_email($name, $address, $email_usename);
216
217 next if ($line =~ m/^\s*$/);
218 304
219 if (exists($mailmap{$name})) { 305my $mailmap;
220 my $obj = $mailmap{$name}; 306
221 push(@$obj, $address); 307read_mailmap();
222 } else { 308
223 my @arr = ($address); 309sub read_mailmap {
224 $mailmap{$name} = \@arr; 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;
225 } 373 }
226 } 374 }
227 close(MAILMAP); 375 close($mailmap_file);
228} 376}
229 377
230## use the filenames on the command line or find the filenames in the patchfiles 378## use the filenames on the command line or find the filenames in the patchfiles
@@ -232,31 +380,47 @@ if ($email_remove_duplicates) {
232my @files = (); 380my @files = ();
233my @range = (); 381my @range = ();
234my @keyword_tvi = (); 382my @keyword_tvi = ();
383my @file_emails = ();
384
385if (!@ARGV) {
386 push(@ARGV, "&STDIN");
387}
235 388
236foreach my $file (@ARGV) { 389foreach my $file (@ARGV) {
237 ##if $file is a directory and it lacks a trailing slash, add one 390 if ($file ne "&STDIN") {
238 if ((-d $file)) { 391 ##if $file is a directory and it lacks a trailing slash, add one
239 $file =~ s@([^/])$@$1/@; 392 if ((-d $file)) {
240 } elsif (!(-f $file)) { 393 $file =~ s@([^/])$@$1/@;
241 die "$P: file '${file}' not found\n"; 394 } elsif (!(-f $file)) {
395 die "$P: file '${file}' not found\n";
396 }
242 } 397 }
243 if ($from_filename) { 398 if ($from_filename) {
244 push(@files, $file); 399 push(@files, $file);
245 if (-f $file && $keywords) { 400 if ($file ne "MAINTAINERS" && -f $file && ($keywords || $file_emails)) {
246 open(FILE, "<$file") or die "$P: Can't open ${file}\n"; 401 open(my $f, '<', $file)
247 my $text = do { local($/) ; <FILE> }; 402 or die "$P: Can't open $file: $!\n";
248 foreach my $line (keys %keyword_hash) { 403 my $text = do { local($/) ; <$f> };
249 if ($text =~ m/$keyword_hash{$line}/x) { 404 close($f);
250 push(@keyword_tvi, $line); 405 if ($keywords) {
406 foreach my $line (keys %keyword_hash) {
407 if ($text =~ m/$keyword_hash{$line}/x) {
408 push(@keyword_tvi, $line);
409 }
251 } 410 }
252 } 411 }
253 close(FILE); 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 }
254 } 416 }
255 } else { 417 } else {
256 my $file_cnt = @files; 418 my $file_cnt = @files;
257 my $lastfile; 419 my $lastfile;
258 open(PATCH, "<$file") or die "$P: Can't open ${file}\n"; 420
259 while (<PATCH>) { 421 open(my $patch, "< $file")
422 or die "$P: Can't open $file: $!\n";
423 while (<$patch>) {
260 my $patch_line = $_; 424 my $patch_line = $_;
261 if (m/^\+\+\+\s+(\S+)/) { 425 if (m/^\+\+\+\s+(\S+)/) {
262 my $filename = $1; 426 my $filename = $1;
@@ -276,7 +440,8 @@ foreach my $file (@ARGV) {
276 } 440 }
277 } 441 }
278 } 442 }
279 close(PATCH); 443 close($patch);
444
280 if ($file_cnt == @files) { 445 if ($file_cnt == @files) {
281 warn "$P: file '${file}' doesn't appear to be a patch. " 446 warn "$P: file '${file}' doesn't appear to be a patch. "
282 . "Add -f to options?\n"; 447 . "Add -f to options?\n";
@@ -285,133 +450,247 @@ foreach my $file (@ARGV) {
285 } 450 }
286} 451}
287 452
453@file_emails = uniq(@file_emails);
454
455my %email_hash_name;
456my %email_hash_address;
288my @email_to = (); 457my @email_to = ();
458my %hash_list_to;
289my @list_to = (); 459my @list_to = ();
290my @scm = (); 460my @scm = ();
291my @web = (); 461my @web = ();
292my @subsystem = (); 462my @subsystem = ();
293my @status = (); 463my @status = ();
464my %deduplicate_name_hash = ();
465my %deduplicate_address_hash = ();
466my $signature_pattern;
294 467
295# Find responsible parties 468my @maintainers = get_maintainers();
296 469
297foreach my $file (@files) { 470if (@maintainers) {
471 @maintainers = merge_email(@maintainers);
472 output(@maintainers);
473}
298 474
299 my %hash; 475if ($scm) {
300 my $tvi = find_first_section(); 476 @scm = uniq(@scm);
301 while ($tvi < @typevalue) { 477 output(@scm);
302 my $start = find_starting_index($tvi); 478}
303 my $end = find_ending_index($tvi); 479
304 my $exclude = 0; 480if ($status) {
305 my $i; 481 @status = uniq(@status);
306 482 output(@status);
307 #Do not match excluded file patterns 483}
308 484
309 for ($i = $start; $i < $end; $i++) { 485if ($subsystem) {
310 my $line = $typevalue[$i]; 486 @subsystem = uniq(@subsystem);
311 if ($line =~ m/^(\C):\s*(.*)/) { 487 output(@subsystem);
312 my $type = $1; 488}
313 my $value = $2; 489
314 if ($type eq 'X') { 490if ($web) {
315 if (file_match_pattern($file, $value)) { 491 @web = uniq(@web);
316 $exclude = 1; 492 output(@web);
317 } 493}
494
495exit($exit);
496
497sub range_is_maintained {
498 my ($start, $end) = @_;
499
500 for (my $i = $start; $i < $end; $i++) {
501 my $line = $typevalue[$i];
502 if ($line =~ m/^(\C):\s*(.*)/) {
503 my $type = $1;
504 my $value = $2;
505 if ($type eq 'S') {
506 if ($value =~ /(maintain|support)/i) {
507 return 1;
318 } 508 }
319 } 509 }
320 } 510 }
511 }
512 return 0;
513}
514
515sub range_has_maintainer {
516 my ($start, $end) = @_;
517
518 for (my $i = $start; $i < $end; $i++) {
519 my $line = $typevalue[$i];
520 if ($line =~ m/^(\C):\s*(.*)/) {
521 my $type = $1;
522 my $value = $2;
523 if ($type eq 'M') {
524 return 1;
525 }
526 }
527 }
528 return 0;
529}
530
531sub get_maintainers {
532 %email_hash_name = ();
533 %email_hash_address = ();
534 %commit_author_hash = ();
535 %commit_signer_hash = ();
536 @email_to = ();
537 %hash_list_to = ();
538 @list_to = ();
539 @scm = ();
540 @web = ();
541 @subsystem = ();
542 @status = ();
543 %deduplicate_name_hash = ();
544 %deduplicate_address_hash = ();
545 if ($email_git_all_signature_types) {
546 $signature_pattern = "(.+?)[Bb][Yy]:";
547 } else {
548 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
549 }
550
551 # Find responsible parties
552
553 my %exact_pattern_match_hash = ();
554
555 foreach my $file (@files) {
556
557 my %hash;
558 my $tvi = find_first_section();
559 while ($tvi < @typevalue) {
560 my $start = find_starting_index($tvi);
561 my $end = find_ending_index($tvi);
562 my $exclude = 0;
563 my $i;
564
565 #Do not match excluded file patterns
321 566
322 if (!$exclude) {
323 for ($i = $start; $i < $end; $i++) { 567 for ($i = $start; $i < $end; $i++) {
324 my $line = $typevalue[$i]; 568 my $line = $typevalue[$i];
325 if ($line =~ m/^(\C):\s*(.*)/) { 569 if ($line =~ m/^(\C):\s*(.*)/) {
326 my $type = $1; 570 my $type = $1;
327 my $value = $2; 571 my $value = $2;
328 if ($type eq 'F') { 572 if ($type eq 'X') {
329 if (file_match_pattern($file, $value)) { 573 if (file_match_pattern($file, $value)) {
330 my $value_pd = ($value =~ tr@/@@); 574 $exclude = 1;
331 my $file_pd = ($file =~ tr@/@@); 575 last;
332 $value_pd++ if (substr($value,-1,1) ne "/"); 576 }
333 if ($pattern_depth == 0 || 577 }
334 (($file_pd - $value_pd) < $pattern_depth)) { 578 }
335 $hash{$tvi} = $value_pd; 579 }
580
581 if (!$exclude) {
582 for ($i = $start; $i < $end; $i++) {
583 my $line = $typevalue[$i];
584 if ($line =~ m/^(\C):\s*(.*)/) {
585 my $type = $1;
586 my $value = $2;
587 if ($type eq 'F') {
588 if (file_match_pattern($file, $value)) {
589 my $value_pd = ($value =~ tr@/@@);
590 my $file_pd = ($file =~ tr@/@@);
591 $value_pd++ if (substr($value,-1,1) ne "/");
592 $value_pd = -1 if ($value =~ /^\.\*/);
593 if ($value_pd >= $file_pd &&
594 range_is_maintained($start, $end) &&
595 range_has_maintainer($start, $end)) {
596 $exact_pattern_match_hash{$file} = 1;
597 }
598 if ($pattern_depth == 0 ||
599 (($file_pd - $value_pd) < $pattern_depth)) {
600 $hash{$tvi} = $value_pd;
601 }
336 } 602 }
337 } 603 }
338 } 604 }
339 } 605 }
340 } 606 }
607 $tvi = $end + 1;
341 } 608 }
342 609
343 $tvi += ($end - $start); 610 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
344 611 add_categories($line);
612 if ($sections) {
613 my $i;
614 my $start = find_starting_index($line);
615 my $end = find_ending_index($line);
616 for ($i = $start; $i < $end; $i++) {
617 my $line = $typevalue[$i];
618 if ($line =~ /^[FX]:/) { ##Restore file patterns
619 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
620 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
621 $line =~ s/\\\./\./g; ##Convert \. to .
622 $line =~ s/\.\*/\*/g; ##Convert .* to *
623 }
624 $line =~ s/^([A-Z]):/$1:\t/g;
625 print("$line\n");
626 }
627 print("\n");
628 }
629 }
345 } 630 }
346 631
347 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { 632 if ($keywords) {
348 add_categories($line); 633 @keyword_tvi = sort_and_uniq(@keyword_tvi);
634 foreach my $line (@keyword_tvi) {
635 add_categories($line);
636 }
349 } 637 }
350 638
351 if ($email && $email_git) { 639 foreach my $email (@email_to, @list_to) {
352 vcs_file_signoffs($file); 640 $email->[0] = deduplicate_email($email->[0]);
353 } 641 }
354 642
355 if ($email && $email_git_blame) { 643 foreach my $file (@files) {
356 vcs_file_blame($file); 644 if ($email &&
357 } 645 ($email_git || ($email_git_fallback &&
358} 646 !$exact_pattern_match_hash{$file}))) {
359 647 vcs_file_signoffs($file);
360if ($keywords) { 648 }
361 @keyword_tvi = sort_and_uniq(@keyword_tvi); 649 if ($email && $email_git_blame) {
362 foreach my $line (@keyword_tvi) { 650 vcs_file_blame($file);
363 add_categories($line); 651 }
364 } 652 }
365}
366 653
367if ($email) { 654 if ($email) {
368 foreach my $chief (@penguin_chief) { 655 foreach my $chief (@penguin_chief) {
369 if ($chief =~ m/^(.*):(.*)/) { 656 if ($chief =~ m/^(.*):(.*)/) {
370 my $email_address; 657 my $email_address;
371 658
372 $email_address = format_email($1, $2, $email_usename); 659 $email_address = format_email($1, $2, $email_usename);
373 if ($email_git_penguin_chiefs) { 660 if ($email_git_penguin_chiefs) {
374 push(@email_to, [$email_address, 'chief penguin']); 661 push(@email_to, [$email_address, 'chief penguin']);
375 } else { 662 } else {
376 @email_to = grep($_->[0] !~ /${email_address}/, @email_to); 663 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
664 }
377 } 665 }
378 } 666 }
667
668 foreach my $email (@file_emails) {
669 my ($name, $address) = parse_email($email);
670
671 my $tmp_email = format_email($name, $address, $email_usename);
672 push_email_address($tmp_email, '');
673 add_role($tmp_email, 'in file');
674 }
379 } 675 }
380}
381 676
382if ($email || $email_list) {
383 my @to = (); 677 my @to = ();
384 if ($email) { 678 if ($email || $email_list) {
385 @to = (@to, @email_to); 679 if ($email) {
386 } 680 @to = (@to, @email_to);
387 if ($email_list) { 681 }
388 @to = (@to, @list_to); 682 if ($email_list) {
683 @to = (@to, @list_to);
684 }
389 } 685 }
390 output(merge_email(@to));
391}
392
393if ($scm) {
394 @scm = uniq(@scm);
395 output(@scm);
396}
397 686
398if ($status) { 687 if ($interactive) {
399 @status = uniq(@status); 688 @to = interactive_get_maintainers(\@to);
400 output(@status); 689 }
401}
402
403if ($subsystem) {
404 @subsystem = uniq(@subsystem);
405 output(@subsystem);
406}
407 690
408if ($web) { 691 return @to;
409 @web = uniq(@web);
410 output(@web);
411} 692}
412 693
413exit($exit);
414
415sub file_match_pattern { 694sub file_match_pattern {
416 my ($file, $pattern) = @_; 695 my ($file, $pattern) = @_;
417 if (substr($pattern, -1) eq "/") { 696 if (substr($pattern, -1) eq "/") {
@@ -439,13 +718,17 @@ version: $V
439MAINTAINER field selection options: 718MAINTAINER field selection options:
440 --email => print email address(es) if any 719 --email => print email address(es) if any
441 --git => include recent git \*-by: signers 720 --git => include recent git \*-by: signers
721 --git-all-signature-types => include signers regardless of signature type
722 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
723 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
442 --git-chief-penguins => include ${penguin_chiefs} 724 --git-chief-penguins => include ${penguin_chiefs}
443 --git-min-signatures => number of signatures required (default: 1) 725 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
444 --git-max-maintainers => maximum maintainers to add (default: 5) 726 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
445 --git-min-percent => minimum percentage of commits required (default: 5) 727 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
446 --git-blame => use git blame to find modified commits for patch or file 728 --git-blame => use git blame to find modified commits for patch or file
447 --git-since => git history to use (default: 1-year-ago) 729 --git-since => git history to use (default: $email_git_since)
448 --hg-since => hg history to use (default: -365) 730 --hg-since => hg history to use (default: $email_hg_since)
731 --interactive => display a menu (mostly useful if used with the --git option)
449 --m => include maintainer(s) if any 732 --m => include maintainer(s) if any
450 --n => include name 'Full Name <addr\@domain.tld>' 733 --n => include name 'Full Name <addr\@domain.tld>'
451 --l => include list(s) if any 734 --l => include list(s) if any
@@ -453,6 +736,7 @@ MAINTAINER field selection options:
453 --remove-duplicates => minimize duplicate email names/addresses 736 --remove-duplicates => minimize duplicate email names/addresses
454 --roles => show roles (status:subsystem, git-signer, list, etc...) 737 --roles => show roles (status:subsystem, git-signer, list, etc...)
455 --rolestats => show roles and statistics (commits/total_commits, %) 738 --rolestats => show roles and statistics (commits/total_commits, %)
739 --file-emails => add email addresses found in -f file (default: 0 (off))
456 --scm => print SCM tree(s) if any 740 --scm => print SCM tree(s) if any
457 --status => print status if any 741 --status => print status if any
458 --subsystem => print subsystem name if any 742 --subsystem => print subsystem name if any
@@ -465,12 +749,15 @@ Output type options:
465 749
466Other options: 750Other options:
467 --pattern-depth => Number of pattern directory traversals (default: 0 (all)) 751 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
468 --keywords => scan patch for keywords (default: 1 (on)) 752 --keywords => scan patch for keywords (default: $keywords)
753 --sections => print all of the subsystem sections with pattern matches
754 --mailmap => use .mailmap file (default: $email_use_mailmap)
469 --version => show version 755 --version => show version
470 --help => show this help information 756 --help => show this help information
471 757
472Default options: 758Default options:
473 [--email --git --m --n --l --multiline --pattern-depth=0 --remove-duplicates] 759 [--email --nogit --git-fallback --m --n --l --multiline -pattern-depth=0
760 --remove-duplicates --rolestats]
474 761
475Notes: 762Notes:
476 Using "-f directory" may give unexpected results: 763 Using "-f directory" may give unexpected results:
@@ -496,34 +783,39 @@ Notes:
496 --git-min-signatures, --git-max-maintainers, --git-min-percent, and 783 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
497 --git-blame 784 --git-blame
498 Use --hg-since not --git-since to control date selection 785 Use --hg-since not --git-since to control date selection
786 File ".get_maintainer.conf", if it exists in the linux kernel source root
787 directory, can change whatever get_maintainer defaults are desired.
788 Entries in this file can be any command line argument.
789 This file is prepended to any additional command line arguments.
790 Multiple lines and # comments are allowed.
499EOT 791EOT
500} 792}
501 793
502sub top_of_kernel_tree { 794sub top_of_kernel_tree {
503 my ($lk_path) = @_; 795 my ($lk_path) = @_;
504 796
505 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") { 797 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
506 $lk_path .= "/"; 798 $lk_path .= "/";
507 } 799 }
508 if ( (-f "${lk_path}COPYING") 800 if ( (-f "${lk_path}COPYING")
509 && (-f "${lk_path}CREDITS") 801 && (-f "${lk_path}CREDITS")
510 && (-f "${lk_path}Kbuild") 802 && (-f "${lk_path}Kbuild")
511 && (-f "${lk_path}MAINTAINERS") 803 && (-f "${lk_path}MAINTAINERS")
512 && (-f "${lk_path}Makefile") 804 && (-f "${lk_path}Makefile")
513 && (-f "${lk_path}README") 805 && (-f "${lk_path}README")
514 && (-d "${lk_path}Documentation") 806 && (-d "${lk_path}Documentation")
515 && (-d "${lk_path}arch") 807 && (-d "${lk_path}arch")
516 && (-d "${lk_path}include") 808 && (-d "${lk_path}include")
517 && (-d "${lk_path}drivers") 809 && (-d "${lk_path}drivers")
518 && (-d "${lk_path}fs") 810 && (-d "${lk_path}fs")
519 && (-d "${lk_path}init") 811 && (-d "${lk_path}init")
520 && (-d "${lk_path}ipc") 812 && (-d "${lk_path}ipc")
521 && (-d "${lk_path}kernel") 813 && (-d "${lk_path}kernel")
522 && (-d "${lk_path}lib") 814 && (-d "${lk_path}lib")
523 && (-d "${lk_path}scripts")) { 815 && (-d "${lk_path}scripts")) {
524 return 1; 816 return 1;
525 } 817 }
526 return 0; 818 return 0;
527} 819}
528 820
529sub parse_email { 821sub parse_email {
@@ -545,7 +837,7 @@ sub parse_email {
545 $name =~ s/^\"|\"$//g; 837 $name =~ s/^\"|\"$//g;
546 $address =~ s/^\s+|\s+$//g; 838 $address =~ s/^\s+|\s+$//g;
547 839
548 if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars 840 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
549 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes 841 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
550 $name = "\"$name\""; 842 $name = "\"$name\"";
551 } 843 }
@@ -562,7 +854,7 @@ sub format_email {
562 $name =~ s/^\"|\"$//g; 854 $name =~ s/^\"|\"$//g;
563 $address =~ s/^\s+|\s+$//g; 855 $address =~ s/^\s+|\s+$//g;
564 856
565 if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars 857 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
566 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes 858 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
567 $name = "\"$name\""; 859 $name = "\"$name\"";
568 } 860 }
@@ -715,11 +1007,19 @@ sub add_categories {
715 } 1007 }
716 if ($list_additional =~ m/subscribers-only/) { 1008 if ($list_additional =~ m/subscribers-only/) {
717 if ($email_subscriber_list) { 1009 if ($email_subscriber_list) {
718 push(@list_to, [$list_address, "subscriber list${list_role}"]); 1010 if (!$hash_list_to{lc($list_address)}) {
1011 $hash_list_to{lc($list_address)} = 1;
1012 push(@list_to, [$list_address,
1013 "subscriber list${list_role}"]);
1014 }
719 } 1015 }
720 } else { 1016 } else {
721 if ($email_list) { 1017 if ($email_list) {
722 push(@list_to, [$list_address, "open list${list_role}"]); 1018 if (!$hash_list_to{lc($list_address)}) {
1019 $hash_list_to{lc($list_address)} = 1;
1020 push(@list_to, [$list_address,
1021 "open list${list_role}"]);
1022 }
723 } 1023 }
724 } 1024 }
725 } elsif ($ptype eq "M") { 1025 } elsif ($ptype eq "M") {
@@ -750,15 +1050,12 @@ sub add_categories {
750 } 1050 }
751} 1051}
752 1052
753my %email_hash_name;
754my %email_hash_address;
755
756sub email_inuse { 1053sub email_inuse {
757 my ($name, $address) = @_; 1054 my ($name, $address) = @_;
758 1055
759 return 1 if (($name eq "") && ($address eq "")); 1056 return 1 if (($name eq "") && ($address eq ""));
760 return 1 if (($name ne "") && exists($email_hash_name{$name})); 1057 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
761 return 1 if (($address ne "") && exists($email_hash_address{$address})); 1058 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
762 1059
763 return 0; 1060 return 0;
764} 1061}
@@ -776,8 +1073,8 @@ sub push_email_address {
776 push(@email_to, [format_email($name, $address, $email_usename), $role]); 1073 push(@email_to, [format_email($name, $address, $email_usename), $role]);
777 } elsif (!email_inuse($name, $address)) { 1074 } elsif (!email_inuse($name, $address)) {
778 push(@email_to, [format_email($name, $address, $email_usename), $role]); 1075 push(@email_to, [format_email($name, $address, $email_usename), $role]);
779 $email_hash_name{$name}++; 1076 $email_hash_name{lc($name)}++ if ($name ne "");
780 $email_hash_address{$address}++; 1077 $email_hash_address{lc($address)}++;
781 } 1078 }
782 1079
783 return 1; 1080 return 1;
@@ -811,7 +1108,9 @@ sub add_role {
811 foreach my $entry (@email_to) { 1108 foreach my $entry (@email_to) {
812 if ($email_remove_duplicates) { 1109 if ($email_remove_duplicates) {
813 my ($entry_name, $entry_address) = parse_email($entry->[0]); 1110 my ($entry_name, $entry_address) = parse_email($entry->[0]);
814 if ($name eq $entry_name || $address eq $entry_address) { 1111 if (($name eq $entry_name || $address eq $entry_address)
1112 && ($role eq "" || !($entry->[1] =~ m/$role/))
1113 ) {
815 if ($entry->[1] eq "") { 1114 if ($entry->[1] eq "") {
816 $entry->[1] = "$role"; 1115 $entry->[1] = "$role";
817 } else { 1116 } else {
@@ -819,7 +1118,9 @@ sub add_role {
819 } 1118 }
820 } 1119 }
821 } else { 1120 } else {
822 if ($email eq $entry->[0]) { 1121 if ($email eq $entry->[0]
1122 && ($role eq "" || !($entry->[1] =~ m/$role/))
1123 ) {
823 if ($entry->[1] eq "") { 1124 if ($entry->[1] eq "") {
824 $entry->[1] = "$role"; 1125 $entry->[1] = "$role";
825 } else { 1126 } else {
@@ -842,30 +1143,69 @@ sub which {
842 return ""; 1143 return "";
843} 1144}
844 1145
845sub mailmap { 1146sub which_conf {
846 my (@lines) = @_; 1147 my ($conf) = @_;
847 my %hash;
848 1148
849 foreach my $line (@lines) { 1149 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
850 my ($name, $address) = parse_email($line); 1150 if (-e "$path/$conf") {
851 if (!exists($hash{$name})) { 1151 return "$path/$conf";
852 $hash{$name} = $address;
853 } elsif ($address ne $hash{$name}) {
854 $address = $hash{$name};
855 $line = format_email($name, $address, $email_usename);
856 } 1152 }
857 if (exists($mailmap{$name})) { 1153 }
858 my $obj = $mailmap{$name}; 1154
859 foreach my $map_address (@$obj) { 1155 return "";
860 if (($map_address eq $address) && 1156}
861 ($map_address ne $hash{$name})) { 1157
862 $line = format_email($name, $hash{$name}, $email_usename); 1158sub mailmap_email {
863 } 1159 my ($line) = @_;
864 } 1160
1161 my ($name, $address) = parse_email($line);
1162 my $email = format_email($name, $address, 1);
1163 my $real_name = $name;
1164 my $real_address = $address;
1165
1166 if (exists $mailmap->{names}->{$email} ||
1167 exists $mailmap->{addresses}->{$email}) {
1168 if (exists $mailmap->{names}->{$email}) {
1169 $real_name = $mailmap->{names}->{$email};
865 } 1170 }
1171 if (exists $mailmap->{addresses}->{$email}) {
1172 $real_address = $mailmap->{addresses}->{$email};
1173 }
1174 } else {
1175 if (exists $mailmap->{names}->{$address}) {
1176 $real_name = $mailmap->{names}->{$address};
1177 }
1178 if (exists $mailmap->{addresses}->{$address}) {
1179 $real_address = $mailmap->{addresses}->{$address};
1180 }
1181 }
1182 return format_email($real_name, $real_address, 1);
1183}
1184
1185sub mailmap {
1186 my (@addresses) = @_;
1187
1188 my @mapped_emails = ();
1189 foreach my $line (@addresses) {
1190 push(@mapped_emails, mailmap_email($line));
866 } 1191 }
1192 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1193 return @mapped_emails;
1194}
867 1195
868 return @lines; 1196sub merge_by_realname {
1197 my %address_map;
1198 my (@emails) = @_;
1199
1200 foreach my $email (@emails) {
1201 my ($name, $address) = parse_email($email);
1202 if (exists $address_map{$name}) {
1203 $address = $address_map{$name};
1204 $email = format_email($name, $address, 1);
1205 } else {
1206 $address_map{$name} = $address;
1207 }
1208 }
869} 1209}
870 1210
871sub git_execute_cmd { 1211sub git_execute_cmd {
@@ -889,10 +1229,30 @@ sub hg_execute_cmd {
889 return @lines; 1229 return @lines;
890} 1230}
891 1231
1232sub extract_formatted_signatures {
1233 my (@signature_lines) = @_;
1234
1235 my @type = @signature_lines;
1236
1237 s/\s*(.*):.*/$1/ for (@type);
1238
1239 # cut -f2- -d":"
1240 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1241
1242## Reformat email addresses (with names) to avoid badly written signatures
1243
1244 foreach my $signer (@signature_lines) {
1245 $signer = deduplicate_email($signer);
1246 }
1247
1248 return (\@type, \@signature_lines);
1249}
1250
892sub vcs_find_signers { 1251sub vcs_find_signers {
893 my ($cmd) = @_; 1252 my ($cmd) = @_;
894 my @lines = ();
895 my $commits; 1253 my $commits;
1254 my @lines = ();
1255 my @signatures = ();
896 1256
897 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd); 1257 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
898 1258
@@ -900,21 +1260,48 @@ sub vcs_find_signers {
900 1260
901 $commits = grep(/$pattern/, @lines); # of commits 1261 $commits = grep(/$pattern/, @lines); # of commits
902 1262
903 @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines); 1263 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1264
1265 return (0, @signatures) if !@signatures;
1266
1267 save_commits_by_author(@lines) if ($interactive);
1268 save_commits_by_signer(@lines) if ($interactive);
1269
1270 if (!$email_git_penguin_chiefs) {
1271 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1272 }
1273
1274 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1275
1276 return ($commits, @$signers_ref);
1277}
1278
1279sub vcs_find_author {
1280 my ($cmd) = @_;
1281 my @lines = ();
1282
1283 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1284
904 if (!$email_git_penguin_chiefs) { 1285 if (!$email_git_penguin_chiefs) {
905 @lines = grep(!/${penguin_chiefs}/i, @lines); 1286 @lines = grep(!/${penguin_chiefs}/i, @lines);
906 } 1287 }
907 # cut -f2- -d":"
908 s/.*:\s*(.+)\s*/$1/ for (@lines);
909 1288
910## Reformat email addresses (with names) to avoid badly written signatures 1289 return @lines if !@lines;
911 1290
1291 my @authors = ();
912 foreach my $line (@lines) { 1292 foreach my $line (@lines) {
913 my ($name, $address) = parse_email($line); 1293 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
914 $line = format_email($name, $address, 1); 1294 my $author = $1;
1295 my ($name, $address) = parse_email($author);
1296 $author = format_email($name, $address, 1);
1297 push(@authors, $author);
1298 }
915 } 1299 }
916 1300
917 return ($commits, @lines); 1301 save_commits_by_author(@lines) if ($interactive);
1302 save_commits_by_signer(@lines) if ($interactive);
1303
1304 return @authors;
918} 1305}
919 1306
920sub vcs_save_commits { 1307sub vcs_save_commits {
@@ -974,6 +1361,10 @@ sub vcs_blame {
974 @commits = vcs_save_commits($cmd); 1361 @commits = vcs_save_commits($cmd);
975 } 1362 }
976 1363
1364 foreach my $commit (@commits) {
1365 $commit =~ s/^\^//g;
1366 }
1367
977 return @commits; 1368 return @commits;
978} 1369}
979 1370
@@ -982,7 +1373,7 @@ sub vcs_exists {
982 %VCS_cmds = %VCS_cmds_git; 1373 %VCS_cmds = %VCS_cmds_git;
983 return 1 if eval $VCS_cmds{"available"}; 1374 return 1 if eval $VCS_cmds{"available"};
984 %VCS_cmds = %VCS_cmds_hg; 1375 %VCS_cmds = %VCS_cmds_hg;
985 return 1 if eval $VCS_cmds{"available"}; 1376 return 2 if eval $VCS_cmds{"available"};
986 %VCS_cmds = (); 1377 %VCS_cmds = ();
987 if (!$printed_novcs) { 1378 if (!$printed_novcs) {
988 warn("$P: No supported VCS found. Add --nogit to options?\n"); 1379 warn("$P: No supported VCS found. Add --nogit to options?\n");
@@ -994,6 +1385,405 @@ sub vcs_exists {
994 return 0; 1385 return 0;
995} 1386}
996 1387
1388sub vcs_is_git {
1389 vcs_exists();
1390 return $vcs_used == 1;
1391}
1392
1393sub vcs_is_hg {
1394 return $vcs_used == 2;
1395}
1396
1397sub interactive_get_maintainers {
1398 my ($list_ref) = @_;
1399 my @list = @$list_ref;
1400
1401 vcs_exists();
1402
1403 my %selected;
1404 my %authored;
1405 my %signed;
1406 my $count = 0;
1407 my $maintained = 0;
1408 foreach my $entry (@list) {
1409 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1410 $selected{$count} = 1;
1411 $authored{$count} = 0;
1412 $signed{$count} = 0;
1413 $count++;
1414 }
1415
1416 #menu loop
1417 my $done = 0;
1418 my $print_options = 0;
1419 my $redraw = 1;
1420 while (!$done) {
1421 $count = 0;
1422 if ($redraw) {
1423 printf STDERR "\n%1s %2s %-65s",
1424 "*", "#", "email/list and role:stats";
1425 if ($email_git ||
1426 ($email_git_fallback && !$maintained) ||
1427 $email_git_blame) {
1428 print STDERR "auth sign";
1429 }
1430 print STDERR "\n";
1431 foreach my $entry (@list) {
1432 my $email = $entry->[0];
1433 my $role = $entry->[1];
1434 my $sel = "";
1435 $sel = "*" if ($selected{$count});
1436 my $commit_author = $commit_author_hash{$email};
1437 my $commit_signer = $commit_signer_hash{$email};
1438 my $authored = 0;
1439 my $signed = 0;
1440 $authored++ for (@{$commit_author});
1441 $signed++ for (@{$commit_signer});
1442 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1443 printf STDERR "%4d %4d", $authored, $signed
1444 if ($authored > 0 || $signed > 0);
1445 printf STDERR "\n %s\n", $role;
1446 if ($authored{$count}) {
1447 my $commit_author = $commit_author_hash{$email};
1448 foreach my $ref (@{$commit_author}) {
1449 print STDERR " Author: @{$ref}[1]\n";
1450 }
1451 }
1452 if ($signed{$count}) {
1453 my $commit_signer = $commit_signer_hash{$email};
1454 foreach my $ref (@{$commit_signer}) {
1455 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1456 }
1457 }
1458
1459 $count++;
1460 }
1461 }
1462 my $date_ref = \$email_git_since;
1463 $date_ref = \$email_hg_since if (vcs_is_hg());
1464 if ($print_options) {
1465 $print_options = 0;
1466 if (vcs_exists()) {
1467 print STDERR <<EOT
1468
1469Version Control options:
1470g use git history [$email_git]
1471gf use git-fallback [$email_git_fallback]
1472b use git blame [$email_git_blame]
1473bs use blame signatures [$email_git_blame_signatures]
1474c# minimum commits [$email_git_min_signatures]
1475%# min percent [$email_git_min_percent]
1476d# history to use [$$date_ref]
1477x# max maintainers [$email_git_max_maintainers]
1478t all signature types [$email_git_all_signature_types]
1479m use .mailmap [$email_use_mailmap]
1480EOT
1481 }
1482 print STDERR <<EOT
1483
1484Additional options:
14850 toggle all
1486tm toggle maintainers
1487tg toggle git entries
1488tl toggle open list entries
1489ts toggle subscriber list entries
1490f emails in file [$file_emails]
1491k keywords in file [$keywords]
1492r remove duplicates [$email_remove_duplicates]
1493p# pattern match depth [$pattern_depth]
1494EOT
1495 }
1496 print STDERR
1497"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1498
1499 my $input = <STDIN>;
1500 chomp($input);
1501
1502 $redraw = 1;
1503 my $rerun = 0;
1504 my @wish = split(/[, ]+/, $input);
1505 foreach my $nr (@wish) {
1506 $nr = lc($nr);
1507 my $sel = substr($nr, 0, 1);
1508 my $str = substr($nr, 1);
1509 my $val = 0;
1510 $val = $1 if $str =~ /^(\d+)$/;
1511
1512 if ($sel eq "y") {
1513 $interactive = 0;
1514 $done = 1;
1515 $output_rolestats = 0;
1516 $output_roles = 0;
1517 last;
1518 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1519 $selected{$nr - 1} = !$selected{$nr - 1};
1520 } elsif ($sel eq "*" || $sel eq '^') {
1521 my $toggle = 0;
1522 $toggle = 1 if ($sel eq '*');
1523 for (my $i = 0; $i < $count; $i++) {
1524 $selected{$i} = $toggle;
1525 }
1526 } elsif ($sel eq "0") {
1527 for (my $i = 0; $i < $count; $i++) {
1528 $selected{$i} = !$selected{$i};
1529 }
1530 } elsif ($sel eq "t") {
1531 if (lc($str) eq "m") {
1532 for (my $i = 0; $i < $count; $i++) {
1533 $selected{$i} = !$selected{$i}
1534 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1535 }
1536 } elsif (lc($str) eq "g") {
1537 for (my $i = 0; $i < $count; $i++) {
1538 $selected{$i} = !$selected{$i}
1539 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1540 }
1541 } elsif (lc($str) eq "l") {
1542 for (my $i = 0; $i < $count; $i++) {
1543 $selected{$i} = !$selected{$i}
1544 if ($list[$i]->[1] =~ /^(open list)/i);
1545 }
1546 } elsif (lc($str) eq "s") {
1547 for (my $i = 0; $i < $count; $i++) {
1548 $selected{$i} = !$selected{$i}
1549 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1550 }
1551 }
1552 } elsif ($sel eq "a") {
1553 if ($val > 0 && $val <= $count) {
1554 $authored{$val - 1} = !$authored{$val - 1};
1555 } elsif ($str eq '*' || $str eq '^') {
1556 my $toggle = 0;
1557 $toggle = 1 if ($str eq '*');
1558 for (my $i = 0; $i < $count; $i++) {
1559 $authored{$i} = $toggle;
1560 }
1561 }
1562 } elsif ($sel eq "s") {
1563 if ($val > 0 && $val <= $count) {
1564 $signed{$val - 1} = !$signed{$val - 1};
1565 } elsif ($str eq '*' || $str eq '^') {
1566 my $toggle = 0;
1567 $toggle = 1 if ($str eq '*');
1568 for (my $i = 0; $i < $count; $i++) {
1569 $signed{$i} = $toggle;
1570 }
1571 }
1572 } elsif ($sel eq "o") {
1573 $print_options = 1;
1574 $redraw = 1;
1575 } elsif ($sel eq "g") {
1576 if ($str eq "f") {
1577 bool_invert(\$email_git_fallback);
1578 } else {
1579 bool_invert(\$email_git);
1580 }
1581 $rerun = 1;
1582 } elsif ($sel eq "b") {
1583 if ($str eq "s") {
1584 bool_invert(\$email_git_blame_signatures);
1585 } else {
1586 bool_invert(\$email_git_blame);
1587 }
1588 $rerun = 1;
1589 } elsif ($sel eq "c") {
1590 if ($val > 0) {
1591 $email_git_min_signatures = $val;
1592 $rerun = 1;
1593 }
1594 } elsif ($sel eq "x") {
1595 if ($val > 0) {
1596 $email_git_max_maintainers = $val;
1597 $rerun = 1;
1598 }
1599 } elsif ($sel eq "%") {
1600 if ($str ne "" && $val >= 0) {
1601 $email_git_min_percent = $val;
1602 $rerun = 1;
1603 }
1604 } elsif ($sel eq "d") {
1605 if (vcs_is_git()) {
1606 $email_git_since = $str;
1607 } elsif (vcs_is_hg()) {
1608 $email_hg_since = $str;
1609 }
1610 $rerun = 1;
1611 } elsif ($sel eq "t") {
1612 bool_invert(\$email_git_all_signature_types);
1613 $rerun = 1;
1614 } elsif ($sel eq "f") {
1615 bool_invert(\$file_emails);
1616 $rerun = 1;
1617 } elsif ($sel eq "r") {
1618 bool_invert(\$email_remove_duplicates);
1619 $rerun = 1;
1620 } elsif ($sel eq "m") {
1621 bool_invert(\$email_use_mailmap);
1622 read_mailmap();
1623 $rerun = 1;
1624 } elsif ($sel eq "k") {
1625 bool_invert(\$keywords);
1626 $rerun = 1;
1627 } elsif ($sel eq "p") {
1628 if ($str ne "" && $val >= 0) {
1629 $pattern_depth = $val;
1630 $rerun = 1;
1631 }
1632 } elsif ($sel eq "h" || $sel eq "?") {
1633 print STDERR <<EOT
1634
1635Interactive mode allows you to select the various maintainers, submitters,
1636commit signers and mailing lists that could be CC'd on a patch.
1637
1638Any *'d entry is selected.
1639
1640If you have git or hg installed, you can choose to summarize the commit
1641history of files in the patch. Also, each line of the current file can
1642be matched to its commit author and that commits signers with blame.
1643
1644Various knobs exist to control the length of time for active commit
1645tracking, the maximum number of commit authors and signers to add,
1646and such.
1647
1648Enter selections at the prompt until you are satisfied that the selected
1649maintainers are appropriate. You may enter multiple selections separated
1650by either commas or spaces.
1651
1652EOT
1653 } else {
1654 print STDERR "invalid option: '$nr'\n";
1655 $redraw = 0;
1656 }
1657 }
1658 if ($rerun) {
1659 print STDERR "git-blame can be very slow, please have patience..."
1660 if ($email_git_blame);
1661 goto &get_maintainers;
1662 }
1663 }
1664
1665 #drop not selected entries
1666 $count = 0;
1667 my @new_emailto = ();
1668 foreach my $entry (@list) {
1669 if ($selected{$count}) {
1670 push(@new_emailto, $list[$count]);
1671 }
1672 $count++;
1673 }
1674 return @new_emailto;
1675}
1676
1677sub bool_invert {
1678 my ($bool_ref) = @_;
1679
1680 if ($$bool_ref) {
1681 $$bool_ref = 0;
1682 } else {
1683 $$bool_ref = 1;
1684 }
1685}
1686
1687sub deduplicate_email {
1688 my ($email) = @_;
1689
1690 my $matched = 0;
1691 my ($name, $address) = parse_email($email);
1692 $email = format_email($name, $address, 1);
1693 $email = mailmap_email($email);
1694
1695 return $email if (!$email_remove_duplicates);
1696
1697 ($name, $address) = parse_email($email);
1698
1699 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1700 $name = $deduplicate_name_hash{lc($name)}->[0];
1701 $address = $deduplicate_name_hash{lc($name)}->[1];
1702 $matched = 1;
1703 } elsif ($deduplicate_address_hash{lc($address)}) {
1704 $name = $deduplicate_address_hash{lc($address)}->[0];
1705 $address = $deduplicate_address_hash{lc($address)}->[1];
1706 $matched = 1;
1707 }
1708 if (!$matched) {
1709 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1710 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1711 }
1712 $email = format_email($name, $address, 1);
1713 $email = mailmap_email($email);
1714 return $email;
1715}
1716
1717sub save_commits_by_author {
1718 my (@lines) = @_;
1719
1720 my @authors = ();
1721 my @commits = ();
1722 my @subjects = ();
1723
1724 foreach my $line (@lines) {
1725 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1726 my $author = $1;
1727 $author = deduplicate_email($author);
1728 push(@authors, $author);
1729 }
1730 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1731 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1732 }
1733
1734 for (my $i = 0; $i < @authors; $i++) {
1735 my $exists = 0;
1736 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1737 if (@{$ref}[0] eq $commits[$i] &&
1738 @{$ref}[1] eq $subjects[$i]) {
1739 $exists = 1;
1740 last;
1741 }
1742 }
1743 if (!$exists) {
1744 push(@{$commit_author_hash{$authors[$i]}},
1745 [ ($commits[$i], $subjects[$i]) ]);
1746 }
1747 }
1748}
1749
1750sub save_commits_by_signer {
1751 my (@lines) = @_;
1752
1753 my $commit = "";
1754 my $subject = "";
1755
1756 foreach my $line (@lines) {
1757 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1758 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1759 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1760 my @signatures = ($line);
1761 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1762 my @types = @$types_ref;
1763 my @signers = @$signers_ref;
1764
1765 my $type = $types[0];
1766 my $signer = $signers[0];
1767
1768 $signer = deduplicate_email($signer);
1769
1770 my $exists = 0;
1771 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1772 if (@{$ref}[0] eq $commit &&
1773 @{$ref}[1] eq $subject &&
1774 @{$ref}[2] eq $type) {
1775 $exists = 1;
1776 last;
1777 }
1778 }
1779 if (!$exists) {
1780 push(@{$commit_signer_hash{$signer}},
1781 [ ($commit, $subject, $type) ]);
1782 }
1783 }
1784 }
1785}
1786
997sub vcs_assign { 1787sub vcs_assign {
998 my ($role, $divisor, @lines) = @_; 1788 my ($role, $divisor, @lines) = @_;
999 1789
@@ -1007,9 +1797,9 @@ sub vcs_assign {
1007 $divisor = 1; 1797 $divisor = 1;
1008 } 1798 }
1009 1799
1010 if ($email_remove_duplicates) { 1800 @lines = mailmap(@lines);
1011 @lines = mailmap(@lines); 1801
1012 } 1802 return if (@lines <= 0);
1013 1803
1014 @lines = sort(@lines); 1804 @lines = sort(@lines);
1015 1805
@@ -1042,12 +1832,18 @@ sub vcs_file_signoffs {
1042 my @signers = (); 1832 my @signers = ();
1043 my $commits; 1833 my $commits;
1044 1834
1045 return if (!vcs_exists()); 1835 $vcs_used = vcs_exists();
1836 return if (!$vcs_used);
1046 1837
1047 my $cmd = $VCS_cmds{"find_signers_cmd"}; 1838 my $cmd = $VCS_cmds{"find_signers_cmd"};
1048 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd 1839 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1049 1840
1050 ($commits, @signers) = vcs_find_signers($cmd); 1841 ($commits, @signers) = vcs_find_signers($cmd);
1842
1843 foreach my $signer (@signers) {
1844 $signer = deduplicate_email($signer);
1845 }
1846
1051 vcs_assign("commit_signer", $commits, @signers); 1847 vcs_assign("commit_signer", $commits, @signers);
1052} 1848}
1053 1849
@@ -1055,29 +1851,114 @@ sub vcs_file_blame {
1055 my ($file) = @_; 1851 my ($file) = @_;
1056 1852
1057 my @signers = (); 1853 my @signers = ();
1854 my @all_commits = ();
1058 my @commits = (); 1855 my @commits = ();
1059 my $total_commits; 1856 my $total_commits;
1857 my $total_lines;
1060 1858
1061 return if (!vcs_exists()); 1859 $vcs_used = vcs_exists();
1860 return if (!$vcs_used);
1062 1861
1063 @commits = vcs_blame($file); 1862 @all_commits = vcs_blame($file);
1064 @commits = uniq(@commits); 1863 @commits = uniq(@all_commits);
1065 $total_commits = @commits; 1864 $total_commits = @commits;
1865 $total_lines = @all_commits;
1066 1866
1067 foreach my $commit (@commits) { 1867 if ($email_git_blame_signatures) {
1068 my $commit_count; 1868 if (vcs_is_hg()) {
1069 my @commit_signers = (); 1869 my $commit_count;
1870 my @commit_signers = ();
1871 my $commit = join(" -r ", @commits);
1872 my $cmd;
1873
1874 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1875 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1876
1877 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1878
1879 push(@signers, @commit_signers);
1880 } else {
1881 foreach my $commit (@commits) {
1882 my $commit_count;
1883 my @commit_signers = ();
1884 my $cmd;
1885
1886 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1887 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1070 1888
1071 my $cmd = $VCS_cmds{"find_commit_signers_cmd"}; 1889 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1072 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1073 1890
1074 ($commit_count, @commit_signers) = vcs_find_signers($cmd); 1891 push(@signers, @commit_signers);
1075 push(@signers, @commit_signers); 1892 }
1893 }
1076 } 1894 }
1077 1895
1078 if ($from_filename) { 1896 if ($from_filename) {
1897 if ($output_rolestats) {
1898 my @blame_signers;
1899 if (vcs_is_hg()) {{ # Double brace for last exit
1900 my $commit_count;
1901 my @commit_signers = ();
1902 @commits = uniq(@commits);
1903 @commits = sort(@commits);
1904 my $commit = join(" -r ", @commits);
1905 my $cmd;
1906
1907 $cmd = $VCS_cmds{"find_commit_author_cmd"};
1908 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1909
1910 my @lines = ();
1911
1912 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1913
1914 if (!$email_git_penguin_chiefs) {
1915 @lines = grep(!/${penguin_chiefs}/i, @lines);
1916 }
1917
1918 last if !@lines;
1919
1920 my @authors = ();
1921 foreach my $line (@lines) {
1922 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1923 my $author = $1;
1924 $author = deduplicate_email($author);
1925 push(@authors, $author);
1926 }
1927 }
1928
1929 save_commits_by_author(@lines) if ($interactive);
1930 save_commits_by_signer(@lines) if ($interactive);
1931
1932 push(@signers, @authors);
1933 }}
1934 else {
1935 foreach my $commit (@commits) {
1936 my $i;
1937 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1938 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1939 my @author = vcs_find_author($cmd);
1940 next if !@author;
1941
1942 my $formatted_author = deduplicate_email($author[0]);
1943
1944 my $count = grep(/$commit/, @all_commits);
1945 for ($i = 0; $i < $count ; $i++) {
1946 push(@blame_signers, $formatted_author);
1947 }
1948 }
1949 }
1950 if (@blame_signers) {
1951 vcs_assign("authored lines", $total_lines, @blame_signers);
1952 }
1953 }
1954 foreach my $signer (@signers) {
1955 $signer = deduplicate_email($signer);
1956 }
1079 vcs_assign("commits", $total_commits, @signers); 1957 vcs_assign("commits", $total_commits, @signers);
1080 } else { 1958 } else {
1959 foreach my $signer (@signers) {
1960 $signer = deduplicate_email($signer);
1961 }
1081 vcs_assign("modified commits", $total_commits, @signers); 1962 vcs_assign("modified commits", $total_commits, @signers);
1082 } 1963 }
1083} 1964}
@@ -1099,6 +1980,51 @@ sub sort_and_uniq {
1099 return @parms; 1980 return @parms;
1100} 1981}
1101 1982
1983sub clean_file_emails {
1984 my (@file_emails) = @_;
1985 my @fmt_emails = ();
1986
1987 foreach my $email (@file_emails) {
1988 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
1989 my ($name, $address) = parse_email($email);
1990 if ($name eq '"[,\.]"') {
1991 $name = "";
1992 }
1993
1994 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
1995 if (@nw > 2) {
1996 my $first = $nw[@nw - 3];
1997 my $middle = $nw[@nw - 2];
1998 my $last = $nw[@nw - 1];
1999
2000 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2001 (length($first) == 2 && substr($first, -1) eq ".")) ||
2002 (length($middle) == 1 ||
2003 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2004 $name = "$first $middle $last";
2005 } else {
2006 $name = "$middle $last";
2007 }
2008 }
2009
2010 if (substr($name, -1) =~ /[,\.]/) {
2011 $name = substr($name, 0, length($name) - 1);
2012 } elsif (substr($name, -2) =~ /[,\.]"/) {
2013 $name = substr($name, 0, length($name) - 2) . '"';
2014 }
2015
2016 if (substr($name, 0, 1) =~ /[,\.]/) {
2017 $name = substr($name, 1, length($name) - 1);
2018 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2019 $name = '"' . substr($name, 2, length($name) - 2);
2020 }
2021
2022 my $fmt_email = format_email($name, $address, $email_usename);
2023 push(@fmt_emails, $fmt_email);
2024 }
2025 return @fmt_emails;
2026}
2027
1102sub merge_email { 2028sub merge_email {
1103 my @lines; 2029 my @lines;
1104 my %saw; 2030 my %saw;
@@ -1183,7 +2109,7 @@ sub rfc822_strip_comments {
1183 2109
1184# valid: returns true if the parameter is an RFC822 valid address 2110# valid: returns true if the parameter is an RFC822 valid address
1185# 2111#
1186sub rfc822_valid ($) { 2112sub rfc822_valid {
1187 my $s = rfc822_strip_comments(shift); 2113 my $s = rfc822_strip_comments(shift);
1188 2114
1189 if (!$rfc822re) { 2115 if (!$rfc822re) {
@@ -1203,7 +2129,7 @@ sub rfc822_valid ($) {
1203# from success with no addresses found, because an empty string is 2129# from success with no addresses found, because an empty string is
1204# a valid list. 2130# a valid list.
1205 2131
1206sub rfc822_validlist ($) { 2132sub rfc822_validlist {
1207 my $s = rfc822_strip_comments(shift); 2133 my $s = rfc822_strip_comments(shift);
1208 2134
1209 if (!$rfc822re) { 2135 if (!$rfc822re) {