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