aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/get_maintainer.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/get_maintainer.pl')
-rwxr-xr-xscripts/get_maintainer.pl746
1 files changed, 561 insertions, 185 deletions
diff --git a/scripts/get_maintainer.pl b/scripts/get_maintainer.pl
index 81a67a458e78..6f97a13bcee4 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.21'; 16my $V = '0.23';
17 17
18use Getopt::Long qw(:config no_auto_abbrev); 18use Getopt::Long qw(:config no_auto_abbrev);
19 19
@@ -23,21 +23,26 @@ my $email_usename = 1;
23my $email_maintainer = 1; 23my $email_maintainer = 1;
24my $email_list = 1; 24my $email_list = 1;
25my $email_subscriber_list = 0; 25my $email_subscriber_list = 0;
26my $email_git = 1;
27my $email_git_penguin_chiefs = 0; 26my $email_git_penguin_chiefs = 0;
27my $email_git = 1;
28my $email_git_blame = 0;
28my $email_git_min_signatures = 1; 29my $email_git_min_signatures = 1;
29my $email_git_max_maintainers = 5; 30my $email_git_max_maintainers = 5;
30my $email_git_min_percent = 5; 31my $email_git_min_percent = 5;
31my $email_git_since = "1-year-ago"; 32my $email_git_since = "1-year-ago";
32my $email_git_blame = 0; 33my $email_hg_since = "-365";
33my $email_remove_duplicates = 1; 34my $email_remove_duplicates = 1;
34my $output_multiline = 1; 35my $output_multiline = 1;
35my $output_separator = ", "; 36my $output_separator = ", ";
37my $output_roles = 0;
38my $output_rolestats = 0;
36my $scm = 0; 39my $scm = 0;
37my $web = 0; 40my $web = 0;
38my $subsystem = 0; 41my $subsystem = 0;
39my $status = 0; 42my $status = 0;
40my $keywords = 1; 43my $keywords = 1;
44my $sections = 0;
45my $file_emails = 0;
41my $from_filename = 0; 46my $from_filename = 0;
42my $pattern_depth = 0; 47my $pattern_depth = 0;
43my $version = 0; 48my $version = 0;
@@ -64,21 +69,52 @@ my $penguin_chiefs = "\(" . join("|",@penguin_chief_names) . "\)";
64my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])"; 69my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
65my $rfc822_char = '[\\000-\\377]'; 70my $rfc822_char = '[\\000-\\377]';
66 71
72# VCS command support: class-like functions and strings
73
74my %VCS_cmds;
75
76my %VCS_cmds_git = (
77 "execute_cmd" => \&git_execute_cmd,
78 "available" => '(which("git") ne "") && (-d ".git")',
79 "find_signers_cmd" => "git log --no-color --since=\$email_git_since -- \$file",
80 "find_commit_signers_cmd" => "git log --no-color -1 \$commit",
81 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
82 "blame_file_cmd" => "git blame -l \$file",
83 "commit_pattern" => "^commit [0-9a-f]{40,40}",
84 "blame_commit_pattern" => "^([0-9a-f]+) "
85);
86
87my %VCS_cmds_hg = (
88 "execute_cmd" => \&hg_execute_cmd,
89 "available" => '(which("hg") ne "") && (-d ".hg")',
90 "find_signers_cmd" =>
91 "hg log --date=\$email_hg_since" .
92 " --template='commit {node}\\n{desc}\\n' -- \$file",
93 "find_commit_signers_cmd" => "hg log --template='{desc}\\n' -r \$commit",
94 "blame_range_cmd" => "", # not supported
95 "blame_file_cmd" => "hg blame -c \$file",
96 "commit_pattern" => "^commit [0-9a-f]{40,40}",
97 "blame_commit_pattern" => "^([0-9a-f]+):"
98);
99
67if (!GetOptions( 100if (!GetOptions(
68 'email!' => \$email, 101 'email!' => \$email,
69 'git!' => \$email_git, 102 'git!' => \$email_git,
103 'git-blame!' => \$email_git_blame,
70 'git-chief-penguins!' => \$email_git_penguin_chiefs, 104 'git-chief-penguins!' => \$email_git_penguin_chiefs,
71 'git-min-signatures=i' => \$email_git_min_signatures, 105 'git-min-signatures=i' => \$email_git_min_signatures,
72 'git-max-maintainers=i' => \$email_git_max_maintainers, 106 'git-max-maintainers=i' => \$email_git_max_maintainers,
73 'git-min-percent=i' => \$email_git_min_percent, 107 'git-min-percent=i' => \$email_git_min_percent,
74 'git-since=s' => \$email_git_since, 108 'git-since=s' => \$email_git_since,
75 'git-blame!' => \$email_git_blame, 109 'hg-since=s' => \$email_hg_since,
76 'remove-duplicates!' => \$email_remove_duplicates, 110 'remove-duplicates!' => \$email_remove_duplicates,
77 'm!' => \$email_maintainer, 111 'm!' => \$email_maintainer,
78 'n!' => \$email_usename, 112 'n!' => \$email_usename,
79 'l!' => \$email_list, 113 'l!' => \$email_list,
80 's!' => \$email_subscriber_list, 114 's!' => \$email_subscriber_list,
81 'multiline!' => \$output_multiline, 115 'multiline!' => \$output_multiline,
116 'roles!' => \$output_roles,
117 'rolestats!' => \$output_rolestats,
82 'separator=s' => \$output_separator, 118 'separator=s' => \$output_separator,
83 'subsystem!' => \$subsystem, 119 'subsystem!' => \$subsystem,
84 'status!' => \$status, 120 'status!' => \$status,
@@ -86,12 +122,13 @@ if (!GetOptions(
86 'web!' => \$web, 122 'web!' => \$web,
87 'pattern-depth=i' => \$pattern_depth, 123 'pattern-depth=i' => \$pattern_depth,
88 'k|keywords!' => \$keywords, 124 'k|keywords!' => \$keywords,
125 'sections!' => \$sections,
126 'fe|file-emails!' => \$file_emails,
89 'f|file' => \$from_filename, 127 'f|file' => \$from_filename,
90 'v|version' => \$version, 128 'v|version' => \$version,
91 'h|help' => \$help, 129 'h|help|usage' => \$help,
92 )) { 130 )) {
93 usage(); 131 die "$P: invalid argument - use --help if necessary\n";
94 die "$P: invalid argument\n";
95} 132}
96 133
97if ($help != 0) { 134if ($help != 0) {
@@ -104,25 +141,37 @@ if ($version != 0) {
104 exit 0; 141 exit 0;
105} 142}
106 143
107if ($#ARGV < 0) { 144if (-t STDIN && !@ARGV) {
108 usage(); 145 # We're talking to a terminal, but have no command line arguments.
109 die "$P: argument missing: patchfile or -f file please\n"; 146 die "$P: missing patchfile or -f file - use --help if necessary\n";
110} 147}
111 148
112if ($output_separator ne ", ") { 149if ($output_separator ne ", ") {
113 $output_multiline = 0; 150 $output_multiline = 0;
114} 151}
115 152
116my $selections = $email + $scm + $status + $subsystem + $web; 153if ($output_rolestats) {
117if ($selections == 0) { 154 $output_roles = 1;
118 usage(); 155}
119 die "$P: Missing required option: email, scm, status, subsystem or web\n"; 156
157if ($sections) {
158 $email = 0;
159 $email_list = 0;
160 $scm = 0;
161 $status = 0;
162 $subsystem = 0;
163 $web = 0;
164 $keywords = 0;
165} else {
166 my $selections = $email + $scm + $status + $subsystem + $web;
167 if ($selections == 0) {
168 die "$P: Missing required option: email, scm, status, subsystem or web\n";
169 }
120} 170}
121 171
122if ($email && 172if ($email &&
123 ($email_maintainer + $email_list + $email_subscriber_list + 173 ($email_maintainer + $email_list + $email_subscriber_list +
124 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) { 174 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
125 usage();
126 die "$P: Please select at least 1 email option\n"; 175 die "$P: Please select at least 1 email option\n";
127} 176}
128 177
@@ -136,8 +185,9 @@ if (!top_of_kernel_tree($lk_path)) {
136my @typevalue = (); 185my @typevalue = ();
137my %keyword_hash; 186my %keyword_hash;
138 187
139open(MAINT, "<${lk_path}MAINTAINERS") || die "$P: Can't open MAINTAINERS\n"; 188open (my $maint, '<', "${lk_path}MAINTAINERS")
140while (<MAINT>) { 189 or die "$P: Can't open MAINTAINERS: $!\n";
190while (<$maint>) {
141 my $line = $_; 191 my $line = $_;
142 192
143 if ($line =~ m/^(\C):\s*(.*)/) { 193 if ($line =~ m/^(\C):\s*(.*)/) {
@@ -162,20 +212,21 @@ while (<MAINT>) {
162 push(@typevalue, $line); 212 push(@typevalue, $line);
163 } 213 }
164} 214}
165close(MAINT); 215close($maint);
166 216
167my %mailmap; 217my %mailmap;
168 218
169if ($email_remove_duplicates) { 219if ($email_remove_duplicates) {
170 open(MAILMAP, "<${lk_path}.mailmap") || warn "$P: Can't open .mailmap\n"; 220 open(my $mailmap, '<', "${lk_path}.mailmap")
171 while (<MAILMAP>) { 221 or warn "$P: Can't open .mailmap: $!\n";
222 while (<$mailmap>) {
172 my $line = $_; 223 my $line = $_;
173 224
174 next if ($line =~ m/^\s*#/); 225 next if ($line =~ m/^\s*#/);
175 next if ($line =~ m/^\s*$/); 226 next if ($line =~ m/^\s*$/);
176 227
177 my ($name, $address) = parse_email($line); 228 my ($name, $address) = parse_email($line);
178 $line = format_email($name, $address); 229 $line = format_email($name, $address, $email_usename);
179 230
180 next if ($line =~ m/^\s*$/); 231 next if ($line =~ m/^\s*$/);
181 232
@@ -187,7 +238,7 @@ if ($email_remove_duplicates) {
187 $mailmap{$name} = \@arr; 238 $mailmap{$name} = \@arr;
188 } 239 }
189 } 240 }
190 close(MAILMAP); 241 close($mailmap);
191} 242}
192 243
193## use the filenames on the command line or find the filenames in the patchfiles 244## use the filenames on the command line or find the filenames in the patchfiles
@@ -195,33 +246,47 @@ if ($email_remove_duplicates) {
195my @files = (); 246my @files = ();
196my @range = (); 247my @range = ();
197my @keyword_tvi = (); 248my @keyword_tvi = ();
249my @file_emails = ();
250
251if (!@ARGV) {
252 push(@ARGV, "&STDIN");
253}
198 254
199foreach my $file (@ARGV) { 255foreach my $file (@ARGV) {
200 ##if $file is a directory and it lacks a trailing slash, add one 256 if ($file ne "&STDIN") {
201 if ((-d $file)) { 257 ##if $file is a directory and it lacks a trailing slash, add one
202 $file =~ s@([^/])$@$1/@; 258 if ((-d $file)) {
203 } elsif (!(-f $file)) { 259 $file =~ s@([^/])$@$1/@;
204 die "$P: file '${file}' not found\n"; 260 } elsif (!(-f $file)) {
261 die "$P: file '${file}' not found\n";
262 }
205 } 263 }
206 if ($from_filename) { 264 if ($from_filename) {
207 push(@files, $file); 265 push(@files, $file);
208 if (-f $file && $keywords) { 266 if (-f $file && ($keywords || $file_emails)) {
209 open(FILE, "<$file") or die "$P: Can't open ${file}\n"; 267 open(my $f, '<', $file)
210 while (<FILE>) { 268 or die "$P: Can't open $file: $!\n";
211 my $patch_line = $_; 269 my $text = do { local($/) ; <$f> };
270 close($f);
271 if ($keywords) {
212 foreach my $line (keys %keyword_hash) { 272 foreach my $line (keys %keyword_hash) {
213 if ($patch_line =~ m/^.*$keyword_hash{$line}/x) { 273 if ($text =~ m/$keyword_hash{$line}/x) {
214 push(@keyword_tvi, $line); 274 push(@keyword_tvi, $line);
215 } 275 }
216 } 276 }
217 } 277 }
218 close(FILE); 278 if ($file_emails) {
279 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;
280 push(@file_emails, clean_file_emails(@poss_addr));
281 }
219 } 282 }
220 } else { 283 } else {
221 my $file_cnt = @files; 284 my $file_cnt = @files;
222 my $lastfile; 285 my $lastfile;
223 open(PATCH, "<$file") or die "$P: Can't open ${file}\n"; 286
224 while (<PATCH>) { 287 open(my $patch, "< $file")
288 or die "$P: Can't open $file: $!\n";
289 while (<$patch>) {
225 my $patch_line = $_; 290 my $patch_line = $_;
226 if (m/^\+\+\+\s+(\S+)/) { 291 if (m/^\+\+\+\s+(\S+)/) {
227 my $filename = $1; 292 my $filename = $1;
@@ -241,7 +306,8 @@ foreach my $file (@ARGV) {
241 } 306 }
242 } 307 }
243 } 308 }
244 close(PATCH); 309 close($patch);
310
245 if ($file_cnt == @files) { 311 if ($file_cnt == @files) {
246 warn "$P: file '${file}' doesn't appear to be a patch. " 312 warn "$P: file '${file}' doesn't appear to be a patch. "
247 . "Add -f to options?\n"; 313 . "Add -f to options?\n";
@@ -250,6 +316,8 @@ foreach my $file (@ARGV) {
250 } 316 }
251} 317}
252 318
319@file_emails = uniq(@file_emails);
320
253my @email_to = (); 321my @email_to = ();
254my @list_to = (); 322my @list_to = ();
255my @scm = (); 323my @scm = ();
@@ -261,54 +329,81 @@ my @status = ();
261 329
262foreach my $file (@files) { 330foreach my $file (@files) {
263 331
264#Do not match excluded file patterns 332 my %hash;
265 333 my $tvi = find_first_section();
266 my $exclude = 0; 334 while ($tvi < @typevalue) {
267 foreach my $line (@typevalue) { 335 my $start = find_starting_index($tvi);
268 if ($line =~ m/^(\C):\s*(.*)/) { 336 my $end = find_ending_index($tvi);
269 my $type = $1; 337 my $exclude = 0;
270 my $value = $2; 338 my $i;
271 if ($type eq 'X') { 339
272 if (file_match_pattern($file, $value)) { 340 #Do not match excluded file patterns
273 $exclude = 1;
274 last;
275 }
276 }
277 }
278 }
279 341
280 if (!$exclude) { 342 for ($i = $start; $i < $end; $i++) {
281 my $tvi = 0; 343 my $line = $typevalue[$i];
282 my %hash;
283 foreach my $line (@typevalue) {
284 if ($line =~ m/^(\C):\s*(.*)/) { 344 if ($line =~ m/^(\C):\s*(.*)/) {
285 my $type = $1; 345 my $type = $1;
286 my $value = $2; 346 my $value = $2;
287 if ($type eq 'F') { 347 if ($type eq 'X') {
288 if (file_match_pattern($file, $value)) { 348 if (file_match_pattern($file, $value)) {
289 my $value_pd = ($value =~ tr@/@@); 349 $exclude = 1;
290 my $file_pd = ($file =~ tr@/@@); 350 last;
291 $value_pd++ if (substr($value,-1,1) ne "/");
292 if ($pattern_depth == 0 ||
293 (($file_pd - $value_pd) < $pattern_depth)) {
294 $hash{$tvi} = $value_pd;
295 }
296 } 351 }
297 } 352 }
298 } 353 }
299 $tvi++;
300 } 354 }
301 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { 355
302 add_categories($line); 356 if (!$exclude) {
357 for ($i = $start; $i < $end; $i++) {
358 my $line = $typevalue[$i];
359 if ($line =~ m/^(\C):\s*(.*)/) {
360 my $type = $1;
361 my $value = $2;
362 if ($type eq 'F') {
363 if (file_match_pattern($file, $value)) {
364 my $value_pd = ($value =~ tr@/@@);
365 my $file_pd = ($file =~ tr@/@@);
366 $value_pd++ if (substr($value,-1,1) ne "/");
367 if ($pattern_depth == 0 ||
368 (($file_pd - $value_pd) < $pattern_depth)) {
369 $hash{$tvi} = $value_pd;
370 }
371 }
372 }
373 }
374 }
303 } 375 }
376
377 $tvi = $end + 1;
378 }
379
380 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
381 add_categories($line);
382 if ($sections) {
383 my $i;
384 my $start = find_starting_index($line);
385 my $end = find_ending_index($line);
386 for ($i = $start; $i < $end; $i++) {
387 my $line = $typevalue[$i];
388 if ($line =~ /^[FX]:/) { ##Restore file patterns
389 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
390 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
391 $line =~ s/\\\./\./g; ##Convert \. to .
392 $line =~ s/\.\*/\*/g; ##Convert .* to *
393 }
394 $line =~ s/^([A-Z]):/$1:\t/g;
395 print("$line\n");
396 }
397 print("\n");
398 }
304 } 399 }
305 400
306 if ($email && $email_git) { 401 if ($email && $email_git) {
307 recent_git_signoffs($file); 402 vcs_file_signoffs($file);
308 } 403 }
309 404
310 if ($email && $email_git_blame) { 405 if ($email && $email_git_blame) {
311 git_assign_blame($file); 406 vcs_file_blame($file);
312 } 407 }
313} 408}
314 409
@@ -324,14 +419,22 @@ if ($email) {
324 if ($chief =~ m/^(.*):(.*)/) { 419 if ($chief =~ m/^(.*):(.*)/) {
325 my $email_address; 420 my $email_address;
326 421
327 $email_address = format_email($1, $2); 422 $email_address = format_email($1, $2, $email_usename);
328 if ($email_git_penguin_chiefs) { 423 if ($email_git_penguin_chiefs) {
329 push(@email_to, $email_address); 424 push(@email_to, [$email_address, 'chief penguin']);
330 } else { 425 } else {
331 @email_to = grep(!/${email_address}/, @email_to); 426 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
332 } 427 }
333 } 428 }
334 } 429 }
430
431 foreach my $email (@file_emails) {
432 my ($name, $address) = parse_email($email);
433
434 my $tmp_email = format_email($name, $address, $email_usename);
435 push_email_address($tmp_email, '');
436 add_role($tmp_email, 'in file');
437 }
335} 438}
336 439
337if ($email || $email_list) { 440if ($email || $email_list) {
@@ -342,7 +445,7 @@ if ($email || $email_list) {
342 if ($email_list) { 445 if ($email_list) {
343 @to = (@to, @list_to); 446 @to = (@to, @list_to);
344 } 447 }
345 output(uniq(@to)); 448 output(merge_email(@to));
346} 449}
347 450
348if ($scm) { 451if ($scm) {
@@ -398,13 +501,17 @@ MAINTAINER field selection options:
398 --git-min-signatures => number of signatures required (default: 1) 501 --git-min-signatures => number of signatures required (default: 1)
399 --git-max-maintainers => maximum maintainers to add (default: 5) 502 --git-max-maintainers => maximum maintainers to add (default: 5)
400 --git-min-percent => minimum percentage of commits required (default: 5) 503 --git-min-percent => minimum percentage of commits required (default: 5)
401 --git-since => git history to use (default: 1-year-ago)
402 --git-blame => use git blame to find modified commits for patch or file 504 --git-blame => use git blame to find modified commits for patch or file
505 --git-since => git history to use (default: 1-year-ago)
506 --hg-since => hg history to use (default: -365)
403 --m => include maintainer(s) if any 507 --m => include maintainer(s) if any
404 --n => include name 'Full Name <addr\@domain.tld>' 508 --n => include name 'Full Name <addr\@domain.tld>'
405 --l => include list(s) if any 509 --l => include list(s) if any
406 --s => include subscriber only list(s) if any 510 --s => include subscriber only list(s) if any
407 --remove-duplicates => minimize duplicate email names/addresses 511 --remove-duplicates => minimize duplicate email names/addresses
512 --roles => show roles (status:subsystem, git-signer, list, etc...)
513 --rolestats => show roles and statistics (commits/total_commits, %)
514 --file-emails => add email addresses found in -f file (default: 0 (off))
408 --scm => print SCM tree(s) if any 515 --scm => print SCM tree(s) if any
409 --status => print status if any 516 --status => print status if any
410 --subsystem => print subsystem name if any 517 --subsystem => print subsystem name if any
@@ -418,6 +525,7 @@ Output type options:
418Other options: 525Other options:
419 --pattern-depth => Number of pattern directory traversals (default: 0 (all)) 526 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
420 --keywords => scan patch for keywords (default: 1 (on)) 527 --keywords => scan patch for keywords (default: 1 (on))
528 --sections => print the entire subsystem sections with pattern matches
421 --version => show version 529 --version => show version
422 --help => show this help information 530 --help => show this help information
423 531
@@ -430,11 +538,24 @@ Notes:
430 directory are examined as git recurses directories. 538 directory are examined as git recurses directories.
431 Any specified X: (exclude) pattern matches are _not_ ignored. 539 Any specified X: (exclude) pattern matches are _not_ ignored.
432 Used with "--nogit", directory is used as a pattern match, 540 Used with "--nogit", directory is used as a pattern match,
433 no individual file within the directory or subdirectory 541 no individual file within the directory or subdirectory
434 is matched. 542 is matched.
435 Used with "--git-blame", does not iterate all files in directory 543 Used with "--git-blame", does not iterate all files in directory
436 Using "--git-blame" is slow and may add old committers and authors 544 Using "--git-blame" is slow and may add old committers and authors
437 that are no longer active maintainers to the output. 545 that are no longer active maintainers to the output.
546 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
547 other automated tools that expect only ["name"] <email address>
548 may not work because of additional output after <email address>.
549 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
550 not the percentage of the entire file authored. # of commits is
551 not a good measure of amount of code authored. 1 major commit may
552 contain a thousand lines, 5 trivial commits may modify a single line.
553 If git is not installed, but mercurial (hg) is installed and an .hg
554 repository exists, the following options apply to mercurial:
555 --git,
556 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
557 --git-blame
558 Use --hg-since not --git-since to control date selection
438EOT 559EOT
439} 560}
440 561
@@ -484,7 +605,7 @@ sub parse_email {
484 $name =~ s/^\"|\"$//g; 605 $name =~ s/^\"|\"$//g;
485 $address =~ s/^\s+|\s+$//g; 606 $address =~ s/^\s+|\s+$//g;
486 607
487 if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars 608 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
488 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes 609 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
489 $name = "\"$name\""; 610 $name = "\"$name\"";
490 } 611 }
@@ -493,7 +614,7 @@ sub parse_email {
493} 614}
494 615
495sub format_email { 616sub format_email {
496 my ($name, $address) = @_; 617 my ($name, $address, $usename) = @_;
497 618
498 my $formatted_email; 619 my $formatted_email;
499 620
@@ -501,16 +622,16 @@ sub format_email {
501 $name =~ s/^\"|\"$//g; 622 $name =~ s/^\"|\"$//g;
502 $address =~ s/^\s+|\s+$//g; 623 $address =~ s/^\s+|\s+$//g;
503 624
504 if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars 625 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
505 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes 626 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
506 $name = "\"$name\""; 627 $name = "\"$name\"";
507 } 628 }
508 629
509 if ($email_usename) { 630 if ($usename) {
510 if ("$name" eq "") { 631 if ("$name" eq "") {
511 $formatted_email = "$address"; 632 $formatted_email = "$address";
512 } else { 633 } else {
513 $formatted_email = "$name <${address}>"; 634 $formatted_email = "$name <$address>";
514 } 635 }
515 } else { 636 } else {
516 $formatted_email = $address; 637 $formatted_email = $address;
@@ -519,6 +640,20 @@ sub format_email {
519 return $formatted_email; 640 return $formatted_email;
520} 641}
521 642
643sub find_first_section {
644 my $index = 0;
645
646 while ($index < @typevalue) {
647 my $tv = $typevalue[$index];
648 if (($tv =~ m/^(\C):\s*(.*)/)) {
649 last;
650 }
651 $index++;
652 }
653
654 return $index;
655}
656
522sub find_starting_index { 657sub find_starting_index {
523 my ($index) = @_; 658 my ($index) = @_;
524 659
@@ -547,6 +682,71 @@ sub find_ending_index {
547 return $index; 682 return $index;
548} 683}
549 684
685sub get_maintainer_role {
686 my ($index) = @_;
687
688 my $i;
689 my $start = find_starting_index($index);
690 my $end = find_ending_index($index);
691
692 my $role;
693 my $subsystem = $typevalue[$start];
694 if (length($subsystem) > 20) {
695 $subsystem = substr($subsystem, 0, 17);
696 $subsystem =~ s/\s*$//;
697 $subsystem = $subsystem . "...";
698 }
699
700 for ($i = $start + 1; $i < $end; $i++) {
701 my $tv = $typevalue[$i];
702 if ($tv =~ m/^(\C):\s*(.*)/) {
703 my $ptype = $1;
704 my $pvalue = $2;
705 if ($ptype eq "S") {
706 $role = $pvalue;
707 }
708 }
709 }
710
711 $role = lc($role);
712 if ($role eq "supported") {
713 $role = "supporter";
714 } elsif ($role eq "maintained") {
715 $role = "maintainer";
716 } elsif ($role eq "odd fixes") {
717 $role = "odd fixer";
718 } elsif ($role eq "orphan") {
719 $role = "orphan minder";
720 } elsif ($role eq "obsolete") {
721 $role = "obsolete minder";
722 } elsif ($role eq "buried alive in reporters") {
723 $role = "chief penguin";
724 }
725
726 return $role . ":" . $subsystem;
727}
728
729sub get_list_role {
730 my ($index) = @_;
731
732 my $i;
733 my $start = find_starting_index($index);
734 my $end = find_ending_index($index);
735
736 my $subsystem = $typevalue[$start];
737 if (length($subsystem) > 20) {
738 $subsystem = substr($subsystem, 0, 17);
739 $subsystem =~ s/\s*$//;
740 $subsystem = $subsystem . "...";
741 }
742
743 if ($subsystem eq "THE REST") {
744 $subsystem = "";
745 }
746
747 return $subsystem;
748}
749
550sub add_categories { 750sub add_categories {
551 my ($index) = @_; 751 my ($index) = @_;
552 752
@@ -564,17 +764,22 @@ sub add_categories {
564 if ($ptype eq "L") { 764 if ($ptype eq "L") {
565 my $list_address = $pvalue; 765 my $list_address = $pvalue;
566 my $list_additional = ""; 766 my $list_additional = "";
767 my $list_role = get_list_role($i);
768
769 if ($list_role ne "") {
770 $list_role = ":" . $list_role;
771 }
567 if ($list_address =~ m/([^\s]+)\s+(.*)$/) { 772 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
568 $list_address = $1; 773 $list_address = $1;
569 $list_additional = $2; 774 $list_additional = $2;
570 } 775 }
571 if ($list_additional =~ m/subscribers-only/) { 776 if ($list_additional =~ m/subscribers-only/) {
572 if ($email_subscriber_list) { 777 if ($email_subscriber_list) {
573 push(@list_to, $list_address); 778 push(@list_to, [$list_address, "subscriber list${list_role}"]);
574 } 779 }
575 } else { 780 } else {
576 if ($email_list) { 781 if ($email_list) {
577 push(@list_to, $list_address); 782 push(@list_to, [$list_address, "open list${list_role}"]);
578 } 783 }
579 } 784 }
580 } elsif ($ptype eq "M") { 785 } elsif ($ptype eq "M") {
@@ -585,13 +790,14 @@ sub add_categories {
585 if ($tv =~ m/^(\C):\s*(.*)/) { 790 if ($tv =~ m/^(\C):\s*(.*)/) {
586 if ($1 eq "P") { 791 if ($1 eq "P") {
587 $name = $2; 792 $name = $2;
588 $pvalue = format_email($name, $address); 793 $pvalue = format_email($name, $address, $email_usename);
589 } 794 }
590 } 795 }
591 } 796 }
592 } 797 }
593 if ($email_maintainer) { 798 if ($email_maintainer) {
594 push_email_addresses($pvalue); 799 my $role = get_maintainer_role($i);
800 push_email_addresses($pvalue, $role);
595 } 801 }
596 } elsif ($ptype eq "T") { 802 } elsif ($ptype eq "T") {
597 push(@scm, $pvalue); 803 push(@scm, $pvalue);
@@ -618,7 +824,7 @@ sub email_inuse {
618} 824}
619 825
620sub push_email_address { 826sub push_email_address {
621 my ($line) = @_; 827 my ($line, $role) = @_;
622 828
623 my ($name, $address) = parse_email($line); 829 my ($name, $address) = parse_email($line);
624 830
@@ -627,9 +833,9 @@ sub push_email_address {
627 } 833 }
628 834
629 if (!$email_remove_duplicates) { 835 if (!$email_remove_duplicates) {
630 push(@email_to, format_email($name, $address)); 836 push(@email_to, [format_email($name, $address, $email_usename), $role]);
631 } elsif (!email_inuse($name, $address)) { 837 } elsif (!email_inuse($name, $address)) {
632 push(@email_to, format_email($name, $address)); 838 push(@email_to, [format_email($name, $address, $email_usename), $role]);
633 $email_hash_name{$name}++; 839 $email_hash_name{$name}++;
634 $email_hash_address{$address}++; 840 $email_hash_address{$address}++;
635 } 841 }
@@ -638,24 +844,56 @@ sub push_email_address {
638} 844}
639 845
640sub push_email_addresses { 846sub push_email_addresses {
641 my ($address) = @_; 847 my ($address, $role) = @_;
642 848
643 my @address_list = (); 849 my @address_list = ();
644 850
645 if (rfc822_valid($address)) { 851 if (rfc822_valid($address)) {
646 push_email_address($address); 852 push_email_address($address, $role);
647 } elsif (@address_list = rfc822_validlist($address)) { 853 } elsif (@address_list = rfc822_validlist($address)) {
648 my $array_count = shift(@address_list); 854 my $array_count = shift(@address_list);
649 while (my $entry = shift(@address_list)) { 855 while (my $entry = shift(@address_list)) {
650 push_email_address($entry); 856 push_email_address($entry, $role);
651 } 857 }
652 } else { 858 } else {
653 if (!push_email_address($address)) { 859 if (!push_email_address($address, $role)) {
654 warn("Invalid MAINTAINERS address: '" . $address . "'\n"); 860 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
655 } 861 }
656 } 862 }
657} 863}
658 864
865sub add_role {
866 my ($line, $role) = @_;
867
868 my ($name, $address) = parse_email($line);
869 my $email = format_email($name, $address, $email_usename);
870
871 foreach my $entry (@email_to) {
872 if ($email_remove_duplicates) {
873 my ($entry_name, $entry_address) = parse_email($entry->[0]);
874 if (($name eq $entry_name || $address eq $entry_address)
875 && ($role eq "" || !($entry->[1] =~ m/$role/))
876 ) {
877 if ($entry->[1] eq "") {
878 $entry->[1] = "$role";
879 } else {
880 $entry->[1] = "$entry->[1],$role";
881 }
882 }
883 } else {
884 if ($email eq $entry->[0]
885 && ($role eq "" || !($entry->[1] =~ m/$role/))
886 ) {
887 if ($entry->[1] eq "") {
888 $entry->[1] = "$role";
889 } else {
890 $entry->[1] = "$entry->[1],$role";
891 }
892 }
893 }
894 }
895}
896
659sub which { 897sub which {
660 my ($bin) = @_; 898 my ($bin) = @_;
661 899
@@ -669,7 +907,7 @@ sub which {
669} 907}
670 908
671sub mailmap { 909sub mailmap {
672 my @lines = @_; 910 my (@lines) = @_;
673 my %hash; 911 my %hash;
674 912
675 foreach my $line (@lines) { 913 foreach my $line (@lines) {
@@ -678,14 +916,14 @@ sub mailmap {
678 $hash{$name} = $address; 916 $hash{$name} = $address;
679 } elsif ($address ne $hash{$name}) { 917 } elsif ($address ne $hash{$name}) {
680 $address = $hash{$name}; 918 $address = $hash{$name};
681 $line = format_email($name, $address); 919 $line = format_email($name, $address, $email_usename);
682 } 920 }
683 if (exists($mailmap{$name})) { 921 if (exists($mailmap{$name})) {
684 my $obj = $mailmap{$name}; 922 my $obj = $mailmap{$name};
685 foreach my $map_address (@$obj) { 923 foreach my $map_address (@$obj) {
686 if (($map_address eq $address) && 924 if (($map_address eq $address) &&
687 ($map_address ne $hash{$name})) { 925 ($map_address ne $hash{$name})) {
688 $line = format_email($name, $hash{$name}); 926 $line = format_email($name, $hash{$name}, $email_usename);
689 } 927 }
690 } 928 }
691 } 929 }
@@ -694,34 +932,38 @@ sub mailmap {
694 return @lines; 932 return @lines;
695} 933}
696 934
697sub recent_git_signoffs { 935sub git_execute_cmd {
698 my ($file) = @_; 936 my ($cmd) = @_;
699
700 my $sign_offs = "";
701 my $cmd = "";
702 my $output = "";
703 my $count = 0;
704 my @lines = (); 937 my @lines = ();
705 my %hash;
706 my $total_sign_offs;
707 938
708 if (which("git") eq "") { 939 my $output = `$cmd`;
709 warn("$P: git not found. Add --nogit to options?\n"); 940 $output =~ s/^\s*//gm;
710 return; 941 @lines = split("\n", $output);
711 }
712 if (!(-d ".git")) {
713 warn("$P: .git directory not found. Use a git repository for better results.\n");
714 warn("$P: perhaps 'git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git'\n");
715 return;
716 }
717 942
718 $cmd = "git log --since=${email_git_since} -- ${file}"; 943 return @lines;
944}
719 945
720 $output = `${cmd}`; 946sub hg_execute_cmd {
721 $output =~ s/^\s*//gm; 947 my ($cmd) = @_;
948 my @lines = ();
722 949
950 my $output = `$cmd`;
723 @lines = split("\n", $output); 951 @lines = split("\n", $output);
724 952
953 return @lines;
954}
955
956sub vcs_find_signers {
957 my ($cmd) = @_;
958 my @lines = ();
959 my $commits;
960
961 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
962
963 my $pattern = $VCS_cmds{"commit_pattern"};
964
965 $commits = grep(/$pattern/, @lines); # of commits
966
725 @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines); 967 @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines);
726 if (!$email_git_penguin_chiefs) { 968 if (!$email_git_penguin_chiefs) {
727 @lines = grep(!/${penguin_chiefs}/i, @lines); 969 @lines = grep(!/${penguin_chiefs}/i, @lines);
@@ -729,111 +971,183 @@ sub recent_git_signoffs {
729 # cut -f2- -d":" 971 # cut -f2- -d":"
730 s/.*:\s*(.+)\s*/$1/ for (@lines); 972 s/.*:\s*(.+)\s*/$1/ for (@lines);
731 973
732 $total_sign_offs = @lines; 974## Reformat email addresses (with names) to avoid badly written signatures
733 975
734 if ($email_remove_duplicates) { 976 foreach my $line (@lines) {
735 @lines = mailmap(@lines); 977 my ($name, $address) = parse_email($line);
978 $line = format_email($name, $address, 1);
736 } 979 }
737 980
738 @lines = sort(@lines); 981 return ($commits, @lines);
739
740 # uniq -c
741 $hash{$_}++ for @lines;
742
743 # sort -rn
744 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
745 my $sign_offs = $hash{$line};
746 $count++;
747 last if ($sign_offs < $email_git_min_signatures ||
748 $count > $email_git_max_maintainers ||
749 $sign_offs * 100 / $total_sign_offs < $email_git_min_percent);
750 push_email_address($line);
751 }
752} 982}
753 983
754sub save_commits { 984sub vcs_save_commits {
755 my ($cmd, @commits) = @_; 985 my ($cmd) = @_;
756 my $output;
757 my @lines = (); 986 my @lines = ();
987 my @commits = ();
758 988
759 $output = `${cmd}`; 989 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
760 990
761 @lines = split("\n", $output);
762 foreach my $line (@lines) { 991 foreach my $line (@lines) {
763 if ($line =~ m/^(\w+) /) { 992 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
764 push (@commits, $1); 993 push(@commits, $1);
765 } 994 }
766 } 995 }
996
767 return @commits; 997 return @commits;
768} 998}
769 999
770sub git_assign_blame { 1000sub vcs_blame {
771 my ($file) = @_; 1001 my ($file) = @_;
772
773 my @lines = ();
774 my @commits = ();
775 my $cmd; 1002 my $cmd;
776 my $output; 1003 my @commits = ();
777 my %hash; 1004
778 my $total_sign_offs; 1005 return @commits if (!(-f $file));
779 my $count; 1006
1007 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1008 my @all_commits = ();
1009
1010 $cmd = $VCS_cmds{"blame_file_cmd"};
1011 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1012 @all_commits = vcs_save_commits($cmd);
780 1013
781 if (@range) {
782 foreach my $file_range_diff (@range) { 1014 foreach my $file_range_diff (@range) {
783 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/)); 1015 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
784 my $diff_file = $1; 1016 my $diff_file = $1;
785 my $diff_start = $2; 1017 my $diff_start = $2;
786 my $diff_length = $3; 1018 my $diff_length = $3;
787 next if (!("$file" eq "$diff_file")); 1019 next if ("$file" ne "$diff_file");
788 $cmd = "git blame -l -L $diff_start,+$diff_length $file"; 1020 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
789 @commits = save_commits($cmd, @commits); 1021 push(@commits, $all_commits[$i]);
1022 }
790 } 1023 }
791 } else { 1024 } elsif (@range) {
792 if (-f $file) { 1025 foreach my $file_range_diff (@range) {
793 $cmd = "git blame -l $file"; 1026 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
794 @commits = save_commits($cmd, @commits); 1027 my $diff_file = $1;
1028 my $diff_start = $2;
1029 my $diff_length = $3;
1030 next if ("$file" ne "$diff_file");
1031 $cmd = $VCS_cmds{"blame_range_cmd"};
1032 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1033 push(@commits, vcs_save_commits($cmd));
795 } 1034 }
1035 } else {
1036 $cmd = $VCS_cmds{"blame_file_cmd"};
1037 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1038 @commits = vcs_save_commits($cmd);
796 } 1039 }
797 1040
798 $total_sign_offs = 0; 1041 return @commits;
799 @commits = uniq(@commits); 1042}
800 foreach my $commit (@commits) {
801 $cmd = "git log -1 ${commit}";
802 1043
803 $output = `${cmd}`; 1044my $printed_novcs = 0;
804 $output =~ s/^\s*//gm; 1045sub vcs_exists {
805 @lines = split("\n", $output); 1046 %VCS_cmds = %VCS_cmds_git;
1047 return 1 if eval $VCS_cmds{"available"};
1048 %VCS_cmds = %VCS_cmds_hg;
1049 return 1 if eval $VCS_cmds{"available"};
1050 %VCS_cmds = ();
1051 if (!$printed_novcs) {
1052 warn("$P: No supported VCS found. Add --nogit to options?\n");
1053 warn("Using a git repository produces better results.\n");
1054 warn("Try Linus Torvalds' latest git repository using:\n");
1055 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
1056 $printed_novcs = 1;
1057 }
1058 return 0;
1059}
806 1060
807 @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines); 1061sub vcs_assign {
808 if (!$email_git_penguin_chiefs) { 1062 my ($role, $divisor, @lines) = @_;
809 @lines = grep(!/${penguin_chiefs}/i, @lines);
810 }
811 1063
812 # cut -f2- -d":" 1064 my %hash;
813 s/.*:\s*(.+)\s*/$1/ for (@lines); 1065 my $count = 0;
814 1066
815 $total_sign_offs += @lines; 1067 return if (@lines <= 0);
816 1068
817 if ($email_remove_duplicates) { 1069 if ($divisor <= 0) {
818 @lines = mailmap(@lines); 1070 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
819 } 1071 $divisor = 1;
1072 }
820 1073
821 $hash{$_}++ for @lines; 1074 if ($email_remove_duplicates) {
1075 @lines = mailmap(@lines);
822 } 1076 }
823 1077
824 $count = 0; 1078 @lines = sort(@lines);
1079
1080 # uniq -c
1081 $hash{$_}++ for @lines;
1082
1083 # sort -rn
825 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { 1084 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
826 my $sign_offs = $hash{$line}; 1085 my $sign_offs = $hash{$line};
1086 my $percent = $sign_offs * 100 / $divisor;
1087
1088 $percent = 100 if ($percent > 100);
827 $count++; 1089 $count++;
828 last if ($sign_offs < $email_git_min_signatures || 1090 last if ($sign_offs < $email_git_min_signatures ||
829 $count > $email_git_max_maintainers || 1091 $count > $email_git_max_maintainers ||
830 $sign_offs * 100 / $total_sign_offs < $email_git_min_percent); 1092 $percent < $email_git_min_percent);
831 push_email_address($line); 1093 push_email_address($line, '');
1094 if ($output_rolestats) {
1095 my $fmt_percent = sprintf("%.0f", $percent);
1096 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1097 } else {
1098 add_role($line, $role);
1099 }
1100 }
1101}
1102
1103sub vcs_file_signoffs {
1104 my ($file) = @_;
1105
1106 my @signers = ();
1107 my $commits;
1108
1109 return if (!vcs_exists());
1110
1111 my $cmd = $VCS_cmds{"find_signers_cmd"};
1112 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1113
1114 ($commits, @signers) = vcs_find_signers($cmd);
1115 vcs_assign("commit_signer", $commits, @signers);
1116}
1117
1118sub vcs_file_blame {
1119 my ($file) = @_;
1120
1121 my @signers = ();
1122 my @commits = ();
1123 my $total_commits;
1124
1125 return if (!vcs_exists());
1126
1127 @commits = vcs_blame($file);
1128 @commits = uniq(@commits);
1129 $total_commits = @commits;
1130
1131 foreach my $commit (@commits) {
1132 my $commit_count;
1133 my @commit_signers = ();
1134
1135 my $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1136 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1137
1138 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1139 push(@signers, @commit_signers);
1140 }
1141
1142 if ($from_filename) {
1143 vcs_assign("commits", $total_commits, @signers);
1144 } else {
1145 vcs_assign("modified commits", $total_commits, @signers);
832 } 1146 }
833} 1147}
834 1148
835sub uniq { 1149sub uniq {
836 my @parms = @_; 1150 my (@parms) = @_;
837 1151
838 my %saw; 1152 my %saw;
839 @parms = grep(!$saw{$_}++, @parms); 1153 @parms = grep(!$saw{$_}++, @parms);
@@ -841,7 +1155,7 @@ sub uniq {
841} 1155}
842 1156
843sub sort_and_uniq { 1157sub sort_and_uniq {
844 my @parms = @_; 1158 my (@parms) = @_;
845 1159
846 my %saw; 1160 my %saw;
847 @parms = sort @parms; 1161 @parms = sort @parms;
@@ -849,8 +1163,72 @@ sub sort_and_uniq {
849 return @parms; 1163 return @parms;
850} 1164}
851 1165
1166sub clean_file_emails {
1167 my (@file_emails) = @_;
1168 my @fmt_emails = ();
1169
1170 foreach my $email (@file_emails) {
1171 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
1172 my ($name, $address) = parse_email($email);
1173 if ($name eq '"[,\.]"') {
1174 $name = "";
1175 }
1176
1177 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
1178 if (@nw > 2) {
1179 my $first = $nw[@nw - 3];
1180 my $middle = $nw[@nw - 2];
1181 my $last = $nw[@nw - 1];
1182
1183 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
1184 (length($first) == 2 && substr($first, -1) eq ".")) ||
1185 (length($middle) == 1 ||
1186 (length($middle) == 2 && substr($middle, -1) eq "."))) {
1187 $name = "$first $middle $last";
1188 } else {
1189 $name = "$middle $last";
1190 }
1191 }
1192
1193 if (substr($name, -1) =~ /[,\.]/) {
1194 $name = substr($name, 0, length($name) - 1);
1195 } elsif (substr($name, -2) =~ /[,\.]"/) {
1196 $name = substr($name, 0, length($name) - 2) . '"';
1197 }
1198
1199 if (substr($name, 0, 1) =~ /[,\.]/) {
1200 $name = substr($name, 1, length($name) - 1);
1201 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
1202 $name = '"' . substr($name, 2, length($name) - 2);
1203 }
1204
1205 my $fmt_email = format_email($name, $address, $email_usename);
1206 push(@fmt_emails, $fmt_email);
1207 }
1208 return @fmt_emails;
1209}
1210
1211sub merge_email {
1212 my @lines;
1213 my %saw;
1214
1215 for (@_) {
1216 my ($address, $role) = @$_;
1217 if (!$saw{$address}) {
1218 if ($output_roles) {
1219 push(@lines, "$address ($role)");
1220 } else {
1221 push(@lines, $address);
1222 }
1223 $saw{$address} = 1;
1224 }
1225 }
1226
1227 return @lines;
1228}
1229
852sub output { 1230sub output {
853 my @parms = @_; 1231 my (@parms) = @_;
854 1232
855 if ($output_multiline) { 1233 if ($output_multiline) {
856 foreach my $line (@parms) { 1234 foreach my $line (@parms) {
@@ -914,7 +1292,7 @@ sub rfc822_strip_comments {
914 1292
915# valid: returns true if the parameter is an RFC822 valid address 1293# valid: returns true if the parameter is an RFC822 valid address
916# 1294#
917sub rfc822_valid ($) { 1295sub rfc822_valid {
918 my $s = rfc822_strip_comments(shift); 1296 my $s = rfc822_strip_comments(shift);
919 1297
920 if (!$rfc822re) { 1298 if (!$rfc822re) {
@@ -934,7 +1312,7 @@ sub rfc822_valid ($) {
934# from success with no addresses found, because an empty string is 1312# from success with no addresses found, because an empty string is
935# a valid list. 1313# a valid list.
936 1314
937sub rfc822_validlist ($) { 1315sub rfc822_validlist {
938 my $s = rfc822_strip_comments(shift); 1316 my $s = rfc822_strip_comments(shift);
939 1317
940 if (!$rfc822re) { 1318 if (!$rfc822re) {
@@ -947,11 +1325,9 @@ sub rfc822_validlist ($) {
947 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so && 1325 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
948 $s =~ m/^$rfc822_char*$/) { 1326 $s =~ m/^$rfc822_char*$/) {
949 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) { 1327 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
950 push @r, $1; 1328 push(@r, $1);
951 } 1329 }
952 return wantarray ? (scalar(@r), @r) : 1; 1330 return wantarray ? (scalar(@r), @r) : 1;
953 } 1331 }
954 else { 1332 return wantarray ? () : 0;
955 return wantarray ? () : 0;
956 }
957} 1333}