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