xref: /openbmc/linux/scripts/get_maintainer.pl (revision 22fd411a)
1#!/usr/bin/perl -w
2# (c) 2007, Joe Perches <joe@perches.com>
3#           created from checkpatch.pl
4#
5# Print selected MAINTAINERS information for
6# the files modified in a patch or for a file
7#
8# usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
9#        perl scripts/get_maintainer.pl [OPTIONS] -f <file>
10#
11# Licensed under the terms of the GNU GPL License version 2
12
13use strict;
14
15my $P = $0;
16my $V = '0.26';
17
18use Getopt::Long qw(:config no_auto_abbrev);
19
20my $lk_path = "./";
21my $email = 1;
22my $email_usename = 1;
23my $email_maintainer = 1;
24my $email_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 = 1;
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 range_is_maintained {
498    my ($start, $end) = @_;
499
500    for (my $i = $start; $i < $end; $i++) {
501	my $line = $typevalue[$i];
502	if ($line =~ m/^(\C):\s*(.*)/) {
503	    my $type = $1;
504	    my $value = $2;
505	    if ($type eq 'S') {
506		if ($value =~ /(maintain|support)/i) {
507		    return 1;
508		}
509	    }
510	}
511    }
512    return 0;
513}
514
515sub range_has_maintainer {
516    my ($start, $end) = @_;
517
518    for (my $i = $start; $i < $end; $i++) {
519	my $line = $typevalue[$i];
520	if ($line =~ m/^(\C):\s*(.*)/) {
521	    my $type = $1;
522	    my $value = $2;
523	    if ($type eq 'M') {
524		return 1;
525	    }
526	}
527    }
528    return 0;
529}
530
531sub get_maintainers {
532    %email_hash_name = ();
533    %email_hash_address = ();
534    %commit_author_hash = ();
535    %commit_signer_hash = ();
536    @email_to = ();
537    %hash_list_to = ();
538    @list_to = ();
539    @scm = ();
540    @web = ();
541    @subsystem = ();
542    @status = ();
543    %deduplicate_name_hash = ();
544    %deduplicate_address_hash = ();
545    if ($email_git_all_signature_types) {
546	$signature_pattern = "(.+?)[Bb][Yy]:";
547    } else {
548	$signature_pattern = "\(" . join("|", @signature_tags) . "\)";
549    }
550
551    # Find responsible parties
552
553    my %exact_pattern_match_hash = ();
554
555    foreach my $file (@files) {
556
557	my %hash;
558	my $tvi = find_first_section();
559	while ($tvi < @typevalue) {
560	    my $start = find_starting_index($tvi);
561	    my $end = find_ending_index($tvi);
562	    my $exclude = 0;
563	    my $i;
564
565	    #Do not match excluded file patterns
566
567	    for ($i = $start; $i < $end; $i++) {
568		my $line = $typevalue[$i];
569		if ($line =~ m/^(\C):\s*(.*)/) {
570		    my $type = $1;
571		    my $value = $2;
572		    if ($type eq 'X') {
573			if (file_match_pattern($file, $value)) {
574			    $exclude = 1;
575			    last;
576			}
577		    }
578		}
579	    }
580
581	    if (!$exclude) {
582		for ($i = $start; $i < $end; $i++) {
583		    my $line = $typevalue[$i];
584		    if ($line =~ m/^(\C):\s*(.*)/) {
585			my $type = $1;
586			my $value = $2;
587			if ($type eq 'F') {
588			    if (file_match_pattern($file, $value)) {
589				my $value_pd = ($value =~ tr@/@@);
590				my $file_pd = ($file  =~ tr@/@@);
591				$value_pd++ if (substr($value,-1,1) ne "/");
592				$value_pd = -1 if ($value =~ /^\.\*/);
593				if ($value_pd >= $file_pd &&
594				    range_is_maintained($start, $end) &&
595				    range_has_maintainer($start, $end)) {
596				    $exact_pattern_match_hash{$file} = 1;
597				}
598				if ($pattern_depth == 0 ||
599				    (($file_pd - $value_pd) < $pattern_depth)) {
600				    $hash{$tvi} = $value_pd;
601				}
602			    }
603			}
604		    }
605		}
606	    }
607	    $tvi = $end + 1;
608	}
609
610	foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
611	    add_categories($line);
612	    if ($sections) {
613		my $i;
614		my $start = find_starting_index($line);
615		my $end = find_ending_index($line);
616		for ($i = $start; $i < $end; $i++) {
617		    my $line = $typevalue[$i];
618		    if ($line =~ /^[FX]:/) {		##Restore file patterns
619			$line =~ s/([^\\])\.([^\*])/$1\?$2/g;
620			$line =~ s/([^\\])\.$/$1\?/g;	##Convert . back to ?
621			$line =~ s/\\\./\./g;       	##Convert \. to .
622			$line =~ s/\.\*/\*/g;       	##Convert .* to *
623		    }
624		    $line =~ s/^([A-Z]):/$1:\t/g;
625		    print("$line\n");
626		}
627		print("\n");
628	    }
629	}
630    }
631
632    if ($keywords) {
633	@keyword_tvi = sort_and_uniq(@keyword_tvi);
634	foreach my $line (@keyword_tvi) {
635	    add_categories($line);
636	}
637    }
638
639    foreach my $email (@email_to, @list_to) {
640	$email->[0] = deduplicate_email($email->[0]);
641    }
642
643    foreach my $file (@files) {
644	if ($email &&
645	    ($email_git || ($email_git_fallback &&
646			    !$exact_pattern_match_hash{$file}))) {
647	    vcs_file_signoffs($file);
648	}
649	if ($email && $email_git_blame) {
650	    vcs_file_blame($file);
651	}
652    }
653
654    if ($email) {
655	foreach my $chief (@penguin_chief) {
656	    if ($chief =~ m/^(.*):(.*)/) {
657		my $email_address;
658
659		$email_address = format_email($1, $2, $email_usename);
660		if ($email_git_penguin_chiefs) {
661		    push(@email_to, [$email_address, 'chief penguin']);
662		} else {
663		    @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
664		}
665	    }
666	}
667
668	foreach my $email (@file_emails) {
669	    my ($name, $address) = parse_email($email);
670
671	    my $tmp_email = format_email($name, $address, $email_usename);
672	    push_email_address($tmp_email, '');
673	    add_role($tmp_email, 'in file');
674	}
675    }
676
677    my @to = ();
678    if ($email || $email_list) {
679	if ($email) {
680	    @to = (@to, @email_to);
681	}
682	if ($email_list) {
683	    @to = (@to, @list_to);
684	}
685    }
686
687    if ($interactive) {
688	@to = interactive_get_maintainers(\@to);
689    }
690
691    return @to;
692}
693
694sub file_match_pattern {
695    my ($file, $pattern) = @_;
696    if (substr($pattern, -1) eq "/") {
697	if ($file =~ m@^$pattern@) {
698	    return 1;
699	}
700    } else {
701	if ($file =~ m@^$pattern@) {
702	    my $s1 = ($file =~ tr@/@@);
703	    my $s2 = ($pattern =~ tr@/@@);
704	    if ($s1 == $s2) {
705		return 1;
706	    }
707	}
708    }
709    return 0;
710}
711
712sub usage {
713    print <<EOT;
714usage: $P [options] patchfile
715       $P [options] -f file|directory
716version: $V
717
718MAINTAINER field selection options:
719  --email => print email address(es) if any
720    --git => include recent git \*-by: signers
721    --git-all-signature-types => include signers regardless of signature type
722        or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
723    --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
724    --git-chief-penguins => include ${penguin_chiefs}
725    --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
726    --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
727    --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
728    --git-blame => use git blame to find modified commits for patch or file
729    --git-since => git history to use (default: $email_git_since)
730    --hg-since => hg history to use (default: $email_hg_since)
731    --interactive => display a menu (mostly useful if used with the --git option)
732    --m => include maintainer(s) if any
733    --n => include name 'Full Name <addr\@domain.tld>'
734    --l => include list(s) if any
735    --s => include subscriber only list(s) if any
736    --remove-duplicates => minimize duplicate email names/addresses
737    --roles => show roles (status:subsystem, git-signer, list, etc...)
738    --rolestats => show roles and statistics (commits/total_commits, %)
739    --file-emails => add email addresses found in -f file (default: 0 (off))
740  --scm => print SCM tree(s) if any
741  --status => print status if any
742  --subsystem => print subsystem name if any
743  --web => print website(s) if any
744
745Output type options:
746  --separator [, ] => separator for multiple entries on 1 line
747    using --separator also sets --nomultiline if --separator is not [, ]
748  --multiline => print 1 entry per line
749
750Other options:
751  --pattern-depth => Number of pattern directory traversals (default: 0 (all))
752  --keywords => scan patch for keywords (default: $keywords)
753  --sections => print all of the subsystem sections with pattern matches
754  --mailmap => use .mailmap file (default: $email_use_mailmap)
755  --version => show version
756  --help => show this help information
757
758Default options:
759  [--email --nogit --git-fallback --m --n --l --multiline -pattern-depth=0
760   --remove-duplicates --rolestats]
761
762Notes:
763  Using "-f directory" may give unexpected results:
764      Used with "--git", git signators for _all_ files in and below
765          directory are examined as git recurses directories.
766          Any specified X: (exclude) pattern matches are _not_ ignored.
767      Used with "--nogit", directory is used as a pattern match,
768          no individual file within the directory or subdirectory
769          is matched.
770      Used with "--git-blame", does not iterate all files in directory
771  Using "--git-blame" is slow and may add old committers and authors
772      that are no longer active maintainers to the output.
773  Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
774      other automated tools that expect only ["name"] <email address>
775      may not work because of additional output after <email address>.
776  Using "--rolestats" and "--git-blame" shows the #/total=% commits,
777      not the percentage of the entire file authored.  # of commits is
778      not a good measure of amount of code authored.  1 major commit may
779      contain a thousand lines, 5 trivial commits may modify a single line.
780  If git is not installed, but mercurial (hg) is installed and an .hg
781      repository exists, the following options apply to mercurial:
782          --git,
783          --git-min-signatures, --git-max-maintainers, --git-min-percent, and
784          --git-blame
785      Use --hg-since not --git-since to control date selection
786  File ".get_maintainer.conf", if it exists in the linux kernel source root
787      directory, can change whatever get_maintainer defaults are desired.
788      Entries in this file can be any command line argument.
789      This file is prepended to any additional command line arguments.
790      Multiple lines and # comments are allowed.
791EOT
792}
793
794sub top_of_kernel_tree {
795    my ($lk_path) = @_;
796
797    if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
798	$lk_path .= "/";
799    }
800    if (   (-f "${lk_path}COPYING")
801	&& (-f "${lk_path}CREDITS")
802	&& (-f "${lk_path}Kbuild")
803	&& (-f "${lk_path}MAINTAINERS")
804	&& (-f "${lk_path}Makefile")
805	&& (-f "${lk_path}README")
806	&& (-d "${lk_path}Documentation")
807	&& (-d "${lk_path}arch")
808	&& (-d "${lk_path}include")
809	&& (-d "${lk_path}drivers")
810	&& (-d "${lk_path}fs")
811	&& (-d "${lk_path}init")
812	&& (-d "${lk_path}ipc")
813	&& (-d "${lk_path}kernel")
814	&& (-d "${lk_path}lib")
815	&& (-d "${lk_path}scripts")) {
816	return 1;
817    }
818    return 0;
819}
820
821sub parse_email {
822    my ($formatted_email) = @_;
823
824    my $name = "";
825    my $address = "";
826
827    if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
828	$name = $1;
829	$address = $2;
830    } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
831	$address = $1;
832    } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
833	$address = $1;
834    }
835
836    $name =~ s/^\s+|\s+$//g;
837    $name =~ s/^\"|\"$//g;
838    $address =~ s/^\s+|\s+$//g;
839
840    if ($name =~ /[^\w \-]/i) {  	 ##has "must quote" chars
841	$name =~ s/(?<!\\)"/\\"/g;       ##escape quotes
842	$name = "\"$name\"";
843    }
844
845    return ($name, $address);
846}
847
848sub format_email {
849    my ($name, $address, $usename) = @_;
850
851    my $formatted_email;
852
853    $name =~ s/^\s+|\s+$//g;
854    $name =~ s/^\"|\"$//g;
855    $address =~ s/^\s+|\s+$//g;
856
857    if ($name =~ /[^\w \-]/i) {          ##has "must quote" chars
858	$name =~ s/(?<!\\)"/\\"/g;       ##escape quotes
859	$name = "\"$name\"";
860    }
861
862    if ($usename) {
863	if ("$name" eq "") {
864	    $formatted_email = "$address";
865	} else {
866	    $formatted_email = "$name <$address>";
867	}
868    } else {
869	$formatted_email = $address;
870    }
871
872    return $formatted_email;
873}
874
875sub find_first_section {
876    my $index = 0;
877
878    while ($index < @typevalue) {
879	my $tv = $typevalue[$index];
880	if (($tv =~ m/^(\C):\s*(.*)/)) {
881	    last;
882	}
883	$index++;
884    }
885
886    return $index;
887}
888
889sub find_starting_index {
890    my ($index) = @_;
891
892    while ($index > 0) {
893	my $tv = $typevalue[$index];
894	if (!($tv =~ m/^(\C):\s*(.*)/)) {
895	    last;
896	}
897	$index--;
898    }
899
900    return $index;
901}
902
903sub find_ending_index {
904    my ($index) = @_;
905
906    while ($index < @typevalue) {
907	my $tv = $typevalue[$index];
908	if (!($tv =~ m/^(\C):\s*(.*)/)) {
909	    last;
910	}
911	$index++;
912    }
913
914    return $index;
915}
916
917sub get_maintainer_role {
918    my ($index) = @_;
919
920    my $i;
921    my $start = find_starting_index($index);
922    my $end = find_ending_index($index);
923
924    my $role;
925    my $subsystem = $typevalue[$start];
926    if (length($subsystem) > 20) {
927	$subsystem = substr($subsystem, 0, 17);
928	$subsystem =~ s/\s*$//;
929	$subsystem = $subsystem . "...";
930    }
931
932    for ($i = $start + 1; $i < $end; $i++) {
933	my $tv = $typevalue[$i];
934	if ($tv =~ m/^(\C):\s*(.*)/) {
935	    my $ptype = $1;
936	    my $pvalue = $2;
937	    if ($ptype eq "S") {
938		$role = $pvalue;
939	    }
940	}
941    }
942
943    $role = lc($role);
944    if      ($role eq "supported") {
945	$role = "supporter";
946    } elsif ($role eq "maintained") {
947	$role = "maintainer";
948    } elsif ($role eq "odd fixes") {
949	$role = "odd fixer";
950    } elsif ($role eq "orphan") {
951	$role = "orphan minder";
952    } elsif ($role eq "obsolete") {
953	$role = "obsolete minder";
954    } elsif ($role eq "buried alive in reporters") {
955	$role = "chief penguin";
956    }
957
958    return $role . ":" . $subsystem;
959}
960
961sub get_list_role {
962    my ($index) = @_;
963
964    my $i;
965    my $start = find_starting_index($index);
966    my $end = find_ending_index($index);
967
968    my $subsystem = $typevalue[$start];
969    if (length($subsystem) > 20) {
970	$subsystem = substr($subsystem, 0, 17);
971	$subsystem =~ s/\s*$//;
972	$subsystem = $subsystem . "...";
973    }
974
975    if ($subsystem eq "THE REST") {
976	$subsystem = "";
977    }
978
979    return $subsystem;
980}
981
982sub add_categories {
983    my ($index) = @_;
984
985    my $i;
986    my $start = find_starting_index($index);
987    my $end = find_ending_index($index);
988
989    push(@subsystem, $typevalue[$start]);
990
991    for ($i = $start + 1; $i < $end; $i++) {
992	my $tv = $typevalue[$i];
993	if ($tv =~ m/^(\C):\s*(.*)/) {
994	    my $ptype = $1;
995	    my $pvalue = $2;
996	    if ($ptype eq "L") {
997		my $list_address = $pvalue;
998		my $list_additional = "";
999		my $list_role = get_list_role($i);
1000
1001		if ($list_role ne "") {
1002		    $list_role = ":" . $list_role;
1003		}
1004		if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1005		    $list_address = $1;
1006		    $list_additional = $2;
1007		}
1008		if ($list_additional =~ m/subscribers-only/) {
1009		    if ($email_subscriber_list) {
1010			if (!$hash_list_to{lc($list_address)}) {
1011			    $hash_list_to{lc($list_address)} = 1;
1012			    push(@list_to, [$list_address,
1013					    "subscriber list${list_role}"]);
1014			}
1015		    }
1016		} else {
1017		    if ($email_list) {
1018			if (!$hash_list_to{lc($list_address)}) {
1019			    $hash_list_to{lc($list_address)} = 1;
1020			    push(@list_to, [$list_address,
1021					    "open list${list_role}"]);
1022			}
1023		    }
1024		}
1025	    } elsif ($ptype eq "M") {
1026		my ($name, $address) = parse_email($pvalue);
1027		if ($name eq "") {
1028		    if ($i > 0) {
1029			my $tv = $typevalue[$i - 1];
1030			if ($tv =~ m/^(\C):\s*(.*)/) {
1031			    if ($1 eq "P") {
1032				$name = $2;
1033				$pvalue = format_email($name, $address, $email_usename);
1034			    }
1035			}
1036		    }
1037		}
1038		if ($email_maintainer) {
1039		    my $role = get_maintainer_role($i);
1040		    push_email_addresses($pvalue, $role);
1041		}
1042	    } elsif ($ptype eq "T") {
1043		push(@scm, $pvalue);
1044	    } elsif ($ptype eq "W") {
1045		push(@web, $pvalue);
1046	    } elsif ($ptype eq "S") {
1047		push(@status, $pvalue);
1048	    }
1049	}
1050    }
1051}
1052
1053sub email_inuse {
1054    my ($name, $address) = @_;
1055
1056    return 1 if (($name eq "") && ($address eq ""));
1057    return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1058    return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
1059
1060    return 0;
1061}
1062
1063sub push_email_address {
1064    my ($line, $role) = @_;
1065
1066    my ($name, $address) = parse_email($line);
1067
1068    if ($address eq "") {
1069	return 0;
1070    }
1071
1072    if (!$email_remove_duplicates) {
1073	push(@email_to, [format_email($name, $address, $email_usename), $role]);
1074    } elsif (!email_inuse($name, $address)) {
1075	push(@email_to, [format_email($name, $address, $email_usename), $role]);
1076	$email_hash_name{lc($name)}++ if ($name ne "");
1077	$email_hash_address{lc($address)}++;
1078    }
1079
1080    return 1;
1081}
1082
1083sub push_email_addresses {
1084    my ($address, $role) = @_;
1085
1086    my @address_list = ();
1087
1088    if (rfc822_valid($address)) {
1089	push_email_address($address, $role);
1090    } elsif (@address_list = rfc822_validlist($address)) {
1091	my $array_count = shift(@address_list);
1092	while (my $entry = shift(@address_list)) {
1093	    push_email_address($entry, $role);
1094	}
1095    } else {
1096	if (!push_email_address($address, $role)) {
1097	    warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1098	}
1099    }
1100}
1101
1102sub add_role {
1103    my ($line, $role) = @_;
1104
1105    my ($name, $address) = parse_email($line);
1106    my $email = format_email($name, $address, $email_usename);
1107
1108    foreach my $entry (@email_to) {
1109	if ($email_remove_duplicates) {
1110	    my ($entry_name, $entry_address) = parse_email($entry->[0]);
1111	    if (($name eq $entry_name || $address eq $entry_address)
1112		&& ($role eq "" || !($entry->[1] =~ m/$role/))
1113	    ) {
1114		if ($entry->[1] eq "") {
1115		    $entry->[1] = "$role";
1116		} else {
1117		    $entry->[1] = "$entry->[1],$role";
1118		}
1119	    }
1120	} else {
1121	    if ($email eq $entry->[0]
1122		&& ($role eq "" || !($entry->[1] =~ m/$role/))
1123	    ) {
1124		if ($entry->[1] eq "") {
1125		    $entry->[1] = "$role";
1126		} else {
1127		    $entry->[1] = "$entry->[1],$role";
1128		}
1129	    }
1130	}
1131    }
1132}
1133
1134sub which {
1135    my ($bin) = @_;
1136
1137    foreach my $path (split(/:/, $ENV{PATH})) {
1138	if (-e "$path/$bin") {
1139	    return "$path/$bin";
1140	}
1141    }
1142
1143    return "";
1144}
1145
1146sub which_conf {
1147    my ($conf) = @_;
1148
1149    foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1150	if (-e "$path/$conf") {
1151	    return "$path/$conf";
1152	}
1153    }
1154
1155    return "";
1156}
1157
1158sub mailmap_email {
1159    my ($line) = @_;
1160
1161    my ($name, $address) = parse_email($line);
1162    my $email = format_email($name, $address, 1);
1163    my $real_name = $name;
1164    my $real_address = $address;
1165
1166    if (exists $mailmap->{names}->{$email} ||
1167	exists $mailmap->{addresses}->{$email}) {
1168	if (exists $mailmap->{names}->{$email}) {
1169	    $real_name = $mailmap->{names}->{$email};
1170	}
1171	if (exists $mailmap->{addresses}->{$email}) {
1172	    $real_address = $mailmap->{addresses}->{$email};
1173	}
1174    } else {
1175	if (exists $mailmap->{names}->{$address}) {
1176	    $real_name = $mailmap->{names}->{$address};
1177	}
1178	if (exists $mailmap->{addresses}->{$address}) {
1179	    $real_address = $mailmap->{addresses}->{$address};
1180	}
1181    }
1182    return format_email($real_name, $real_address, 1);
1183}
1184
1185sub mailmap {
1186    my (@addresses) = @_;
1187
1188    my @mapped_emails = ();
1189    foreach my $line (@addresses) {
1190	push(@mapped_emails, mailmap_email($line));
1191    }
1192    merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1193    return @mapped_emails;
1194}
1195
1196sub merge_by_realname {
1197    my %address_map;
1198    my (@emails) = @_;
1199
1200    foreach my $email (@emails) {
1201	my ($name, $address) = parse_email($email);
1202	if (exists $address_map{$name}) {
1203	    $address = $address_map{$name};
1204	    $email = format_email($name, $address, 1);
1205	} else {
1206	    $address_map{$name} = $address;
1207	}
1208    }
1209}
1210
1211sub git_execute_cmd {
1212    my ($cmd) = @_;
1213    my @lines = ();
1214
1215    my $output = `$cmd`;
1216    $output =~ s/^\s*//gm;
1217    @lines = split("\n", $output);
1218
1219    return @lines;
1220}
1221
1222sub hg_execute_cmd {
1223    my ($cmd) = @_;
1224    my @lines = ();
1225
1226    my $output = `$cmd`;
1227    @lines = split("\n", $output);
1228
1229    return @lines;
1230}
1231
1232sub extract_formatted_signatures {
1233    my (@signature_lines) = @_;
1234
1235    my @type = @signature_lines;
1236
1237    s/\s*(.*):.*/$1/ for (@type);
1238
1239    # cut -f2- -d":"
1240    s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1241
1242## Reformat email addresses (with names) to avoid badly written signatures
1243
1244    foreach my $signer (@signature_lines) {
1245	$signer = deduplicate_email($signer);
1246    }
1247
1248    return (\@type, \@signature_lines);
1249}
1250
1251sub vcs_find_signers {
1252    my ($cmd) = @_;
1253    my $commits;
1254    my @lines = ();
1255    my @signatures = ();
1256
1257    @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1258
1259    my $pattern = $VCS_cmds{"commit_pattern"};
1260
1261    $commits = grep(/$pattern/, @lines);	# of commits
1262
1263    @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1264
1265    return (0, @signatures) if !@signatures;
1266
1267    save_commits_by_author(@lines) if ($interactive);
1268    save_commits_by_signer(@lines) if ($interactive);
1269
1270    if (!$email_git_penguin_chiefs) {
1271	@signatures = grep(!/${penguin_chiefs}/i, @signatures);
1272    }
1273
1274    my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1275
1276    return ($commits, @$signers_ref);
1277}
1278
1279sub vcs_find_author {
1280    my ($cmd) = @_;
1281    my @lines = ();
1282
1283    @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1284
1285    if (!$email_git_penguin_chiefs) {
1286	@lines = grep(!/${penguin_chiefs}/i, @lines);
1287    }
1288
1289    return @lines if !@lines;
1290
1291    my @authors = ();
1292    foreach my $line (@lines) {
1293	if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1294	    my $author = $1;
1295	    my ($name, $address) = parse_email($author);
1296	    $author = format_email($name, $address, 1);
1297	    push(@authors, $author);
1298	}
1299    }
1300
1301    save_commits_by_author(@lines) if ($interactive);
1302    save_commits_by_signer(@lines) if ($interactive);
1303
1304    return @authors;
1305}
1306
1307sub vcs_save_commits {
1308    my ($cmd) = @_;
1309    my @lines = ();
1310    my @commits = ();
1311
1312    @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1313
1314    foreach my $line (@lines) {
1315	if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1316	    push(@commits, $1);
1317	}
1318    }
1319
1320    return @commits;
1321}
1322
1323sub vcs_blame {
1324    my ($file) = @_;
1325    my $cmd;
1326    my @commits = ();
1327
1328    return @commits if (!(-f $file));
1329
1330    if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1331	my @all_commits = ();
1332
1333	$cmd = $VCS_cmds{"blame_file_cmd"};
1334	$cmd =~ s/(\$\w+)/$1/eeg;		#interpolate $cmd
1335	@all_commits = vcs_save_commits($cmd);
1336
1337	foreach my $file_range_diff (@range) {
1338	    next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1339	    my $diff_file = $1;
1340	    my $diff_start = $2;
1341	    my $diff_length = $3;
1342	    next if ("$file" ne "$diff_file");
1343	    for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1344		push(@commits, $all_commits[$i]);
1345	    }
1346	}
1347    } elsif (@range) {
1348	foreach my $file_range_diff (@range) {
1349	    next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1350	    my $diff_file = $1;
1351	    my $diff_start = $2;
1352	    my $diff_length = $3;
1353	    next if ("$file" ne "$diff_file");
1354	    $cmd = $VCS_cmds{"blame_range_cmd"};
1355	    $cmd =~ s/(\$\w+)/$1/eeg;		#interpolate $cmd
1356	    push(@commits, vcs_save_commits($cmd));
1357	}
1358    } else {
1359	$cmd = $VCS_cmds{"blame_file_cmd"};
1360	$cmd =~ s/(\$\w+)/$1/eeg;		#interpolate $cmd
1361	@commits = vcs_save_commits($cmd);
1362    }
1363
1364    foreach my $commit (@commits) {
1365	$commit =~ s/^\^//g;
1366    }
1367
1368    return @commits;
1369}
1370
1371my $printed_novcs = 0;
1372sub vcs_exists {
1373    %VCS_cmds = %VCS_cmds_git;
1374    return 1 if eval $VCS_cmds{"available"};
1375    %VCS_cmds = %VCS_cmds_hg;
1376    return 2 if eval $VCS_cmds{"available"};
1377    %VCS_cmds = ();
1378    if (!$printed_novcs) {
1379	warn("$P: No supported VCS found.  Add --nogit to options?\n");
1380	warn("Using a git repository produces better results.\n");
1381	warn("Try Linus Torvalds' latest git repository using:\n");
1382	warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
1383	$printed_novcs = 1;
1384    }
1385    return 0;
1386}
1387
1388sub vcs_is_git {
1389    vcs_exists();
1390    return $vcs_used == 1;
1391}
1392
1393sub vcs_is_hg {
1394    return $vcs_used == 2;
1395}
1396
1397sub interactive_get_maintainers {
1398    my ($list_ref) = @_;
1399    my @list = @$list_ref;
1400
1401    vcs_exists();
1402
1403    my %selected;
1404    my %authored;
1405    my %signed;
1406    my $count = 0;
1407    my $maintained = 0;
1408    foreach my $entry (@list) {
1409	$maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1410	$selected{$count} = 1;
1411	$authored{$count} = 0;
1412	$signed{$count} = 0;
1413	$count++;
1414    }
1415
1416    #menu loop
1417    my $done = 0;
1418    my $print_options = 0;
1419    my $redraw = 1;
1420    while (!$done) {
1421	$count = 0;
1422	if ($redraw) {
1423	    printf STDERR "\n%1s %2s %-65s",
1424			  "*", "#", "email/list and role:stats";
1425	    if ($email_git ||
1426		($email_git_fallback && !$maintained) ||
1427		$email_git_blame) {
1428		print STDERR "auth sign";
1429	    }
1430	    print STDERR "\n";
1431	    foreach my $entry (@list) {
1432		my $email = $entry->[0];
1433		my $role = $entry->[1];
1434		my $sel = "";
1435		$sel = "*" if ($selected{$count});
1436		my $commit_author = $commit_author_hash{$email};
1437		my $commit_signer = $commit_signer_hash{$email};
1438		my $authored = 0;
1439		my $signed = 0;
1440		$authored++ for (@{$commit_author});
1441		$signed++ for (@{$commit_signer});
1442		printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1443		printf STDERR "%4d %4d", $authored, $signed
1444		    if ($authored > 0 || $signed > 0);
1445		printf STDERR "\n     %s\n", $role;
1446		if ($authored{$count}) {
1447		    my $commit_author = $commit_author_hash{$email};
1448		    foreach my $ref (@{$commit_author}) {
1449			print STDERR "     Author: @{$ref}[1]\n";
1450		    }
1451		}
1452		if ($signed{$count}) {
1453		    my $commit_signer = $commit_signer_hash{$email};
1454		    foreach my $ref (@{$commit_signer}) {
1455			print STDERR "     @{$ref}[2]: @{$ref}[1]\n";
1456		    }
1457		}
1458
1459		$count++;
1460	    }
1461	}
1462	my $date_ref = \$email_git_since;
1463	$date_ref = \$email_hg_since if (vcs_is_hg());
1464	if ($print_options) {
1465	    $print_options = 0;
1466	    if (vcs_exists()) {
1467		print STDERR <<EOT
1468
1469Version Control options:
1470g  use git history      [$email_git]
1471gf use git-fallback     [$email_git_fallback]
1472b  use git blame        [$email_git_blame]
1473bs use blame signatures [$email_git_blame_signatures]
1474c# minimum commits      [$email_git_min_signatures]
1475%# min percent          [$email_git_min_percent]
1476d# history to use       [$$date_ref]
1477x# max maintainers      [$email_git_max_maintainers]
1478t  all signature types  [$email_git_all_signature_types]
1479m  use .mailmap         [$email_use_mailmap]
1480EOT
1481	    }
1482	    print STDERR <<EOT
1483
1484Additional options:
14850  toggle all
1486tm toggle maintainers
1487tg toggle git entries
1488tl toggle open list entries
1489ts toggle subscriber list entries
1490f  emails in file       [$file_emails]
1491k  keywords in file     [$keywords]
1492r  remove duplicates    [$email_remove_duplicates]
1493p# pattern match depth  [$pattern_depth]
1494EOT
1495	}
1496	print STDERR
1497"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1498
1499	my $input = <STDIN>;
1500	chomp($input);
1501
1502	$redraw = 1;
1503	my $rerun = 0;
1504	my @wish = split(/[, ]+/, $input);
1505	foreach my $nr (@wish) {
1506	    $nr = lc($nr);
1507	    my $sel = substr($nr, 0, 1);
1508	    my $str = substr($nr, 1);
1509	    my $val = 0;
1510	    $val = $1 if $str =~ /^(\d+)$/;
1511
1512	    if ($sel eq "y") {
1513		$interactive = 0;
1514		$done = 1;
1515		$output_rolestats = 0;
1516		$output_roles = 0;
1517		last;
1518	    } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1519		$selected{$nr - 1} = !$selected{$nr - 1};
1520	    } elsif ($sel eq "*" || $sel eq '^') {
1521		my $toggle = 0;
1522		$toggle = 1 if ($sel eq '*');
1523		for (my $i = 0; $i < $count; $i++) {
1524		    $selected{$i} = $toggle;
1525		}
1526	    } elsif ($sel eq "0") {
1527		for (my $i = 0; $i < $count; $i++) {
1528		    $selected{$i} = !$selected{$i};
1529		}
1530	    } elsif ($sel eq "t") {
1531		if (lc($str) eq "m") {
1532		    for (my $i = 0; $i < $count; $i++) {
1533			$selected{$i} = !$selected{$i}
1534			    if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1535		    }
1536		} elsif (lc($str) eq "g") {
1537		    for (my $i = 0; $i < $count; $i++) {
1538			$selected{$i} = !$selected{$i}
1539			    if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1540		    }
1541		} elsif (lc($str) eq "l") {
1542		    for (my $i = 0; $i < $count; $i++) {
1543			$selected{$i} = !$selected{$i}
1544			    if ($list[$i]->[1] =~ /^(open list)/i);
1545		    }
1546		} elsif (lc($str) eq "s") {
1547		    for (my $i = 0; $i < $count; $i++) {
1548			$selected{$i} = !$selected{$i}
1549			    if ($list[$i]->[1] =~ /^(subscriber list)/i);
1550		    }
1551		}
1552	    } elsif ($sel eq "a") {
1553		if ($val > 0 && $val <= $count) {
1554		    $authored{$val - 1} = !$authored{$val - 1};
1555		} elsif ($str eq '*' || $str eq '^') {
1556		    my $toggle = 0;
1557		    $toggle = 1 if ($str eq '*');
1558		    for (my $i = 0; $i < $count; $i++) {
1559			$authored{$i} = $toggle;
1560		    }
1561		}
1562	    } elsif ($sel eq "s") {
1563		if ($val > 0 && $val <= $count) {
1564		    $signed{$val - 1} = !$signed{$val - 1};
1565		} elsif ($str eq '*' || $str eq '^') {
1566		    my $toggle = 0;
1567		    $toggle = 1 if ($str eq '*');
1568		    for (my $i = 0; $i < $count; $i++) {
1569			$signed{$i} = $toggle;
1570		    }
1571		}
1572	    } elsif ($sel eq "o") {
1573		$print_options = 1;
1574		$redraw = 1;
1575	    } elsif ($sel eq "g") {
1576		if ($str eq "f") {
1577		    bool_invert(\$email_git_fallback);
1578		} else {
1579		    bool_invert(\$email_git);
1580		}
1581		$rerun = 1;
1582	    } elsif ($sel eq "b") {
1583		if ($str eq "s") {
1584		    bool_invert(\$email_git_blame_signatures);
1585		} else {
1586		    bool_invert(\$email_git_blame);
1587		}
1588		$rerun = 1;
1589	    } elsif ($sel eq "c") {
1590		if ($val > 0) {
1591		    $email_git_min_signatures = $val;
1592		    $rerun = 1;
1593		}
1594	    } elsif ($sel eq "x") {
1595		if ($val > 0) {
1596		    $email_git_max_maintainers = $val;
1597		    $rerun = 1;
1598		}
1599	    } elsif ($sel eq "%") {
1600		if ($str ne "" && $val >= 0) {
1601		    $email_git_min_percent = $val;
1602		    $rerun = 1;
1603		}
1604	    } elsif ($sel eq "d") {
1605		if (vcs_is_git()) {
1606		    $email_git_since = $str;
1607		} elsif (vcs_is_hg()) {
1608		    $email_hg_since = $str;
1609		}
1610		$rerun = 1;
1611	    } elsif ($sel eq "t") {
1612		bool_invert(\$email_git_all_signature_types);
1613		$rerun = 1;
1614	    } elsif ($sel eq "f") {
1615		bool_invert(\$file_emails);
1616		$rerun = 1;
1617	    } elsif ($sel eq "r") {
1618		bool_invert(\$email_remove_duplicates);
1619		$rerun = 1;
1620	    } elsif ($sel eq "m") {
1621		bool_invert(\$email_use_mailmap);
1622		read_mailmap();
1623		$rerun = 1;
1624	    } elsif ($sel eq "k") {
1625		bool_invert(\$keywords);
1626		$rerun = 1;
1627	    } elsif ($sel eq "p") {
1628		if ($str ne "" && $val >= 0) {
1629		    $pattern_depth = $val;
1630		    $rerun = 1;
1631		}
1632	    } elsif ($sel eq "h" || $sel eq "?") {
1633		print STDERR <<EOT
1634
1635Interactive mode allows you to select the various maintainers, submitters,
1636commit signers and mailing lists that could be CC'd on a patch.
1637
1638Any *'d entry is selected.
1639
1640If you have git or hg installed, you can choose to summarize the commit
1641history of files in the patch.  Also, each line of the current file can
1642be matched to its commit author and that commits signers with blame.
1643
1644Various knobs exist to control the length of time for active commit
1645tracking, the maximum number of commit authors and signers to add,
1646and such.
1647
1648Enter selections at the prompt until you are satisfied that the selected
1649maintainers are appropriate.  You may enter multiple selections separated
1650by either commas or spaces.
1651
1652EOT
1653	    } else {
1654		print STDERR "invalid option: '$nr'\n";
1655		$redraw = 0;
1656	    }
1657	}
1658	if ($rerun) {
1659	    print STDERR "git-blame can be very slow, please have patience..."
1660		if ($email_git_blame);
1661	    goto &get_maintainers;
1662	}
1663    }
1664
1665    #drop not selected entries
1666    $count = 0;
1667    my @new_emailto = ();
1668    foreach my $entry (@list) {
1669	if ($selected{$count}) {
1670	    push(@new_emailto, $list[$count]);
1671	}
1672	$count++;
1673    }
1674    return @new_emailto;
1675}
1676
1677sub bool_invert {
1678    my ($bool_ref) = @_;
1679
1680    if ($$bool_ref) {
1681	$$bool_ref = 0;
1682    } else {
1683	$$bool_ref = 1;
1684    }
1685}
1686
1687sub deduplicate_email {
1688    my ($email) = @_;
1689
1690    my $matched = 0;
1691    my ($name, $address) = parse_email($email);
1692    $email = format_email($name, $address, 1);
1693    $email = mailmap_email($email);
1694
1695    return $email if (!$email_remove_duplicates);
1696
1697    ($name, $address) = parse_email($email);
1698
1699    if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1700	$name = $deduplicate_name_hash{lc($name)}->[0];
1701	$address = $deduplicate_name_hash{lc($name)}->[1];
1702	$matched = 1;
1703    } elsif ($deduplicate_address_hash{lc($address)}) {
1704	$name = $deduplicate_address_hash{lc($address)}->[0];
1705	$address = $deduplicate_address_hash{lc($address)}->[1];
1706	$matched = 1;
1707    }
1708    if (!$matched) {
1709	$deduplicate_name_hash{lc($name)} = [ $name, $address ];
1710	$deduplicate_address_hash{lc($address)} = [ $name, $address ];
1711    }
1712    $email = format_email($name, $address, 1);
1713    $email = mailmap_email($email);
1714    return $email;
1715}
1716
1717sub save_commits_by_author {
1718    my (@lines) = @_;
1719
1720    my @authors = ();
1721    my @commits = ();
1722    my @subjects = ();
1723
1724    foreach my $line (@lines) {
1725	if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1726	    my $author = $1;
1727	    $author = deduplicate_email($author);
1728	    push(@authors, $author);
1729	}
1730	push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1731	push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1732    }
1733
1734    for (my $i = 0; $i < @authors; $i++) {
1735	my $exists = 0;
1736	foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1737	    if (@{$ref}[0] eq $commits[$i] &&
1738		@{$ref}[1] eq $subjects[$i]) {
1739		$exists = 1;
1740		last;
1741	    }
1742	}
1743	if (!$exists) {
1744	    push(@{$commit_author_hash{$authors[$i]}},
1745		 [ ($commits[$i], $subjects[$i]) ]);
1746	}
1747    }
1748}
1749
1750sub save_commits_by_signer {
1751    my (@lines) = @_;
1752
1753    my $commit = "";
1754    my $subject = "";
1755
1756    foreach my $line (@lines) {
1757	$commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1758	$subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1759	if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1760	    my @signatures = ($line);
1761	    my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1762	    my @types = @$types_ref;
1763	    my @signers = @$signers_ref;
1764
1765	    my $type = $types[0];
1766	    my $signer = $signers[0];
1767
1768	    $signer = deduplicate_email($signer);
1769
1770	    my $exists = 0;
1771	    foreach my $ref(@{$commit_signer_hash{$signer}}) {
1772		if (@{$ref}[0] eq $commit &&
1773		    @{$ref}[1] eq $subject &&
1774		    @{$ref}[2] eq $type) {
1775		    $exists = 1;
1776		    last;
1777		}
1778	    }
1779	    if (!$exists) {
1780		push(@{$commit_signer_hash{$signer}},
1781		     [ ($commit, $subject, $type) ]);
1782	    }
1783	}
1784    }
1785}
1786
1787sub vcs_assign {
1788    my ($role, $divisor, @lines) = @_;
1789
1790    my %hash;
1791    my $count = 0;
1792
1793    return if (@lines <= 0);
1794
1795    if ($divisor <= 0) {
1796	warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
1797	$divisor = 1;
1798    }
1799
1800    @lines = mailmap(@lines);
1801
1802    return if (@lines <= 0);
1803
1804    @lines = sort(@lines);
1805
1806    # uniq -c
1807    $hash{$_}++ for @lines;
1808
1809    # sort -rn
1810    foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
1811	my $sign_offs = $hash{$line};
1812	my $percent = $sign_offs * 100 / $divisor;
1813
1814	$percent = 100 if ($percent > 100);
1815	$count++;
1816	last if ($sign_offs < $email_git_min_signatures ||
1817		 $count > $email_git_max_maintainers ||
1818		 $percent < $email_git_min_percent);
1819	push_email_address($line, '');
1820	if ($output_rolestats) {
1821	    my $fmt_percent = sprintf("%.0f", $percent);
1822	    add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1823	} else {
1824	    add_role($line, $role);
1825	}
1826    }
1827}
1828
1829sub vcs_file_signoffs {
1830    my ($file) = @_;
1831
1832    my @signers = ();
1833    my $commits;
1834
1835    $vcs_used = vcs_exists();
1836    return if (!$vcs_used);
1837
1838    my $cmd = $VCS_cmds{"find_signers_cmd"};
1839    $cmd =~ s/(\$\w+)/$1/eeg;		# interpolate $cmd
1840
1841    ($commits, @signers) = vcs_find_signers($cmd);
1842
1843    foreach my $signer (@signers) {
1844	$signer = deduplicate_email($signer);
1845    }
1846
1847    vcs_assign("commit_signer", $commits, @signers);
1848}
1849
1850sub vcs_file_blame {
1851    my ($file) = @_;
1852
1853    my @signers = ();
1854    my @all_commits = ();
1855    my @commits = ();
1856    my $total_commits;
1857    my $total_lines;
1858
1859    $vcs_used = vcs_exists();
1860    return if (!$vcs_used);
1861
1862    @all_commits = vcs_blame($file);
1863    @commits = uniq(@all_commits);
1864    $total_commits = @commits;
1865    $total_lines = @all_commits;
1866
1867    if ($email_git_blame_signatures) {
1868	if (vcs_is_hg()) {
1869	    my $commit_count;
1870	    my @commit_signers = ();
1871	    my $commit = join(" -r ", @commits);
1872	    my $cmd;
1873
1874	    $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1875	    $cmd =~ s/(\$\w+)/$1/eeg;	#substitute variables in $cmd
1876
1877	    ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1878
1879	    push(@signers, @commit_signers);
1880	} else {
1881	    foreach my $commit (@commits) {
1882		my $commit_count;
1883		my @commit_signers = ();
1884		my $cmd;
1885
1886		$cmd = $VCS_cmds{"find_commit_signers_cmd"};
1887		$cmd =~ s/(\$\w+)/$1/eeg;	#substitute variables in $cmd
1888
1889		($commit_count, @commit_signers) = vcs_find_signers($cmd);
1890
1891		push(@signers, @commit_signers);
1892	    }
1893	}
1894    }
1895
1896    if ($from_filename) {
1897	if ($output_rolestats) {
1898	    my @blame_signers;
1899	    if (vcs_is_hg()) {{		# Double brace for last exit
1900		my $commit_count;
1901		my @commit_signers = ();
1902		@commits = uniq(@commits);
1903		@commits = sort(@commits);
1904		my $commit = join(" -r ", @commits);
1905		my $cmd;
1906
1907		$cmd = $VCS_cmds{"find_commit_author_cmd"};
1908		$cmd =~ s/(\$\w+)/$1/eeg;	#substitute variables in $cmd
1909
1910		my @lines = ();
1911
1912		@lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1913
1914		if (!$email_git_penguin_chiefs) {
1915		    @lines = grep(!/${penguin_chiefs}/i, @lines);
1916		}
1917
1918		last if !@lines;
1919
1920		my @authors = ();
1921		foreach my $line (@lines) {
1922		    if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1923			my $author = $1;
1924			$author = deduplicate_email($author);
1925			push(@authors, $author);
1926		    }
1927		}
1928
1929		save_commits_by_author(@lines) if ($interactive);
1930		save_commits_by_signer(@lines) if ($interactive);
1931
1932		push(@signers, @authors);
1933	    }}
1934	    else {
1935		foreach my $commit (@commits) {
1936		    my $i;
1937		    my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1938		    $cmd =~ s/(\$\w+)/$1/eeg;	#interpolate $cmd
1939		    my @author = vcs_find_author($cmd);
1940		    next if !@author;
1941
1942		    my $formatted_author = deduplicate_email($author[0]);
1943
1944		    my $count = grep(/$commit/, @all_commits);
1945		    for ($i = 0; $i < $count ; $i++) {
1946			push(@blame_signers, $formatted_author);
1947		    }
1948		}
1949	    }
1950	    if (@blame_signers) {
1951		vcs_assign("authored lines", $total_lines, @blame_signers);
1952	    }
1953	}
1954	foreach my $signer (@signers) {
1955	    $signer = deduplicate_email($signer);
1956	}
1957	vcs_assign("commits", $total_commits, @signers);
1958    } else {
1959	foreach my $signer (@signers) {
1960	    $signer = deduplicate_email($signer);
1961	}
1962	vcs_assign("modified commits", $total_commits, @signers);
1963    }
1964}
1965
1966sub uniq {
1967    my (@parms) = @_;
1968
1969    my %saw;
1970    @parms = grep(!$saw{$_}++, @parms);
1971    return @parms;
1972}
1973
1974sub sort_and_uniq {
1975    my (@parms) = @_;
1976
1977    my %saw;
1978    @parms = sort @parms;
1979    @parms = grep(!$saw{$_}++, @parms);
1980    return @parms;
1981}
1982
1983sub clean_file_emails {
1984    my (@file_emails) = @_;
1985    my @fmt_emails = ();
1986
1987    foreach my $email (@file_emails) {
1988	$email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
1989	my ($name, $address) = parse_email($email);
1990	if ($name eq '"[,\.]"') {
1991	    $name = "";
1992	}
1993
1994	my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
1995	if (@nw > 2) {
1996	    my $first = $nw[@nw - 3];
1997	    my $middle = $nw[@nw - 2];
1998	    my $last = $nw[@nw - 1];
1999
2000	    if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2001		 (length($first) == 2 && substr($first, -1) eq ".")) ||
2002		(length($middle) == 1 ||
2003		 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2004		$name = "$first $middle $last";
2005	    } else {
2006		$name = "$middle $last";
2007	    }
2008	}
2009
2010	if (substr($name, -1) =~ /[,\.]/) {
2011	    $name = substr($name, 0, length($name) - 1);
2012	} elsif (substr($name, -2) =~ /[,\.]"/) {
2013	    $name = substr($name, 0, length($name) - 2) . '"';
2014	}
2015
2016	if (substr($name, 0, 1) =~ /[,\.]/) {
2017	    $name = substr($name, 1, length($name) - 1);
2018	} elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2019	    $name = '"' . substr($name, 2, length($name) - 2);
2020	}
2021
2022	my $fmt_email = format_email($name, $address, $email_usename);
2023	push(@fmt_emails, $fmt_email);
2024    }
2025    return @fmt_emails;
2026}
2027
2028sub merge_email {
2029    my @lines;
2030    my %saw;
2031
2032    for (@_) {
2033	my ($address, $role) = @$_;
2034	if (!$saw{$address}) {
2035	    if ($output_roles) {
2036		push(@lines, "$address ($role)");
2037	    } else {
2038		push(@lines, $address);
2039	    }
2040	    $saw{$address} = 1;
2041	}
2042    }
2043
2044    return @lines;
2045}
2046
2047sub output {
2048    my (@parms) = @_;
2049
2050    if ($output_multiline) {
2051	foreach my $line (@parms) {
2052	    print("${line}\n");
2053	}
2054    } else {
2055	print(join($output_separator, @parms));
2056	print("\n");
2057    }
2058}
2059
2060my $rfc822re;
2061
2062sub make_rfc822re {
2063#   Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2064#   comment.  We must allow for rfc822_lwsp (or comments) after each of these.
2065#   This regexp will only work on addresses which have had comments stripped
2066#   and replaced with rfc822_lwsp.
2067
2068    my $specials = '()<>@,;:\\\\".\\[\\]';
2069    my $controls = '\\000-\\037\\177';
2070
2071    my $dtext = "[^\\[\\]\\r\\\\]";
2072    my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2073
2074    my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2075
2076#   Use zero-width assertion to spot the limit of an atom.  A simple
2077#   $rfc822_lwsp* causes the regexp engine to hang occasionally.
2078    my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2079    my $word = "(?:$atom|$quoted_string)";
2080    my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2081
2082    my $sub_domain = "(?:$atom|$domain_literal)";
2083    my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2084
2085    my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2086
2087    my $phrase = "$word*";
2088    my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2089    my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2090    my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2091
2092    my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2093    my $address = "(?:$mailbox|$group)";
2094
2095    return "$rfc822_lwsp*$address";
2096}
2097
2098sub rfc822_strip_comments {
2099    my $s = shift;
2100#   Recursively remove comments, and replace with a single space.  The simpler
2101#   regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2102#   chars in atoms, for example.
2103
2104    while ($s =~ s/^((?:[^"\\]|\\.)*
2105                    (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2106                    \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2107    return $s;
2108}
2109
2110#   valid: returns true if the parameter is an RFC822 valid address
2111#
2112sub rfc822_valid {
2113    my $s = rfc822_strip_comments(shift);
2114
2115    if (!$rfc822re) {
2116        $rfc822re = make_rfc822re();
2117    }
2118
2119    return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2120}
2121
2122#   validlist: In scalar context, returns true if the parameter is an RFC822
2123#              valid list of addresses.
2124#
2125#              In list context, returns an empty list on failure (an invalid
2126#              address was found); otherwise a list whose first element is the
2127#              number of addresses found and whose remaining elements are the
2128#              addresses.  This is needed to disambiguate failure (invalid)
2129#              from success with no addresses found, because an empty string is
2130#              a valid list.
2131
2132sub rfc822_validlist {
2133    my $s = rfc822_strip_comments(shift);
2134
2135    if (!$rfc822re) {
2136        $rfc822re = make_rfc822re();
2137    }
2138    # * null list items are valid according to the RFC
2139    # * the '1' business is to aid in distinguishing failure from no results
2140
2141    my @r;
2142    if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2143	$s =~ m/^$rfc822_char*$/) {
2144        while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
2145            push(@r, $1);
2146        }
2147        return wantarray ? (scalar(@r), @r) : 1;
2148    }
2149    return wantarray ? () : 0;
2150}
2151