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