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