xref: /linux-6.15/scripts/get_maintainer.pl (revision 6ba31721)
1cb77f0d6SKamil Rytarowski#!/usr/bin/env perl
2882ea1d6SJoe Perches# SPDX-License-Identifier: GPL-2.0
3882ea1d6SJoe Perches#
4cb7301c7SJoe Perches# (c) 2007, Joe Perches <[email protected]>
5cb7301c7SJoe Perches#           created from checkpatch.pl
6cb7301c7SJoe Perches#
7cb7301c7SJoe Perches# Print selected MAINTAINERS information for
8cb7301c7SJoe Perches# the files modified in a patch or for a file
9cb7301c7SJoe Perches#
103bd7bf5fSRoel Kluin# usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
113bd7bf5fSRoel Kluin#        perl scripts/get_maintainer.pl [OPTIONS] -f <file>
12cb7301c7SJoe Perches
13cb77f0d6SKamil Rytarowskiuse warnings;
14cb7301c7SJoe Perchesuse strict;
15cb7301c7SJoe Perches
16cb7301c7SJoe Perchesmy $P = $0;
177e1863afSJoe Perchesmy $V = '0.26';
18cb7301c7SJoe Perches
19cb7301c7SJoe Perchesuse Getopt::Long qw(:config no_auto_abbrev);
20be17bddcSJoe Perchesuse Cwd;
216f7d98ecSJoe Perchesuse File::Find;
22e33c9fe8SJoe Perchesuse File::Spec::Functions;
239c334eb9SAlvin Šipragause open qw(:std :encoding(UTF-8));
24cb7301c7SJoe Perches
25be17bddcSJoe Perchesmy $cur_path = fastgetcwd() . '/';
26cb7301c7SJoe Perchesmy $lk_path = "./";
27cb7301c7SJoe Perchesmy $email = 1;
28cb7301c7SJoe Perchesmy $email_usename = 1;
29cb7301c7SJoe Perchesmy $email_maintainer = 1;
30c1c3f2c9SJoe Perchesmy $email_reviewer = 1;
312f5bd343SJoe Perchesmy $email_fixes = 1;
32cb7301c7SJoe Perchesmy $email_list = 1;
3349662503SJoe Perchesmy $email_moderated_list = 1;
34cb7301c7SJoe Perchesmy $email_subscriber_list = 0;
35cb7301c7SJoe Perchesmy $email_git_penguin_chiefs = 0;
36e3e9d114SJoe Perchesmy $email_git = 0;
370fa05599SFlorian Micklermy $email_git_all_signature_types = 0;
3860db31acSJoe Perchesmy $email_git_blame = 0;
39683c6f8fSJoe Perchesmy $email_git_blame_signatures = 1;
40e3e9d114SJoe Perchesmy $email_git_fallback = 1;
41cb7301c7SJoe Perchesmy $email_git_min_signatures = 1;
42cb7301c7SJoe Perchesmy $email_git_max_maintainers = 5;
43afa81ee1SJoe Perchesmy $email_git_min_percent = 5;
44cb7301c7SJoe Perchesmy $email_git_since = "1-year-ago";
4560db31acSJoe Perchesmy $email_hg_since = "-365";
46dace8e30SFlorian Micklermy $interactive = 0;
4711ecf53cSJoe Perchesmy $email_remove_duplicates = 1;
48b9e2331dSJoe Perchesmy $email_use_mailmap = 1;
49cb7301c7SJoe Perchesmy $output_multiline = 1;
50cb7301c7SJoe Perchesmy $output_separator = ", ";
513c7385b8SJoe Perchesmy $output_roles = 0;
527e1863afSJoe Perchesmy $output_rolestats = 1;
539ad18c85SVlastimil Babkamy $output_substatus = undef;
54364f68dcSJoe Perchesmy $output_section_maxlen = 50;
55cb7301c7SJoe Perchesmy $scm = 0;
5631bb82c9SAntonio Nino Diazmy $tree = 1;
57cb7301c7SJoe Perchesmy $web = 0;
58033964f1SJani Nikulamy $bug = 0;
59cb7301c7SJoe Perchesmy $subsystem = 0;
60cb7301c7SJoe Perchesmy $status = 0;
6103aed214SJoe Perchesmy $letters = "";
62dcf36a92SJoe Perchesmy $keywords = 1;
6371ca5ee1SJoe Perchesmy $keywords_in_file = 0;
644b76c9daSJoe Perchesmy $sections = 0;
650c78c013SJoe Perchesmy $email_file_emails = 0;
664a7fdb5fSJoe Perchesmy $from_filename = 0;
673fb55652SJoe Perchesmy $pattern_depth = 0;
68083bf9c5SJoe Perchesmy $self_test = undef;
69cb7301c7SJoe Perchesmy $version = 0;
70cb7301c7SJoe Perchesmy $help = 0;
716f7d98ecSJoe Perchesmy $find_maintainer_files = 0;
725f0baf95SJoe Perchesmy $maintainer_path;
73683c6f8fSJoe Perchesmy $vcs_used = 0;
74683c6f8fSJoe Perches
75cb7301c7SJoe Perchesmy $exit = 0;
76cb7301c7SJoe Perches
770c78c013SJoe Perchesmy @files = ();
780c78c013SJoe Perchesmy @fixes = ();			# If a patch description includes Fixes: lines
790c78c013SJoe Perchesmy @range = ();
800c78c013SJoe Perchesmy @keyword_tvi = ();
810c78c013SJoe Perchesmy @file_emails = ();
820c78c013SJoe Perches
83683c6f8fSJoe Perchesmy %commit_author_hash;
84683c6f8fSJoe Perchesmy %commit_signer_hash;
85dace8e30SFlorian Mickler
86cb7301c7SJoe Perchesmy @penguin_chief = ();
87cb7301c7SJoe Perchespush(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
88cb7301c7SJoe Perches#Andrew wants in on most everything - 2009/01/14
89cb7301c7SJoe Perches#push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
90cb7301c7SJoe Perches
91cb7301c7SJoe Perchesmy @penguin_chief_names = ();
92cb7301c7SJoe Perchesforeach my $chief (@penguin_chief) {
93cb7301c7SJoe Perches    if ($chief =~ m/^(.*):(.*)/) {
94cb7301c7SJoe Perches	my $chief_name = $1;
95cb7301c7SJoe Perches	my $chief_addr = $2;
96cb7301c7SJoe Perches	push(@penguin_chief_names, $chief_name);
97cb7301c7SJoe Perches    }
98cb7301c7SJoe Perches}
99cb7301c7SJoe Perchesmy $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
100cb7301c7SJoe Perches
101e4d26b02SJoe Perches# Signature types of people who are either
102e4d26b02SJoe Perches# 	a) responsible for the code in question, or
103e4d26b02SJoe Perches# 	b) familiar enough with it to give relevant feedback
104e4d26b02SJoe Perchesmy @signature_tags = ();
105e4d26b02SJoe Perchespush(@signature_tags, "Signed-off-by:");
106e4d26b02SJoe Perchespush(@signature_tags, "Reviewed-by:");
107e4d26b02SJoe Perchespush(@signature_tags, "Acked-by:");
108e4d26b02SJoe Perches
1097dea2681SJoe Perchesmy $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
1107dea2681SJoe Perches
1115f2441e9SJoe Perches# rfc822 email address - preloaded methods go here.
1121b5e1cf6SJoe Perchesmy $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
113df4cc036SJoe Perchesmy $rfc822_char = '[\\000-\\377]';
1141b5e1cf6SJoe Perches
11560db31acSJoe Perches# VCS command support: class-like functions and strings
11660db31acSJoe Perches
11760db31acSJoe Perchesmy %VCS_cmds;
11860db31acSJoe Perches
11960db31acSJoe Perchesmy %VCS_cmds_git = (
12060db31acSJoe Perches    "execute_cmd" => \&git_execute_cmd,
121ec83b616SRichard Genoud    "available" => '(which("git") ne "") && (-e ".git")',
122683c6f8fSJoe Perches    "find_signers_cmd" =>
123ed128feaSIan Campbell	"git log --no-color --follow --since=\$email_git_since " .
124c9ecefeaSJoe Perches	    '--numstat --no-merges ' .
125683c6f8fSJoe Perches	    '--format="GitCommit: %H%n' .
126683c6f8fSJoe Perches		      'GitAuthor: %an <%ae>%n' .
127683c6f8fSJoe Perches		      'GitDate: %aD%n' .
128683c6f8fSJoe Perches		      'GitSubject: %s%n' .
129683c6f8fSJoe Perches		      '%b%n"' .
130683c6f8fSJoe Perches	    " -- \$file",
131683c6f8fSJoe Perches    "find_commit_signers_cmd" =>
132683c6f8fSJoe Perches	"git log --no-color " .
133c9ecefeaSJoe Perches	    '--numstat ' .
134683c6f8fSJoe Perches	    '--format="GitCommit: %H%n' .
135683c6f8fSJoe Perches		      'GitAuthor: %an <%ae>%n' .
136683c6f8fSJoe Perches		      'GitDate: %aD%n' .
137683c6f8fSJoe Perches		      'GitSubject: %s%n' .
138683c6f8fSJoe Perches		      '%b%n"' .
139683c6f8fSJoe Perches	    " -1 \$commit",
140683c6f8fSJoe Perches    "find_commit_author_cmd" =>
141683c6f8fSJoe Perches	"git log --no-color " .
142c9ecefeaSJoe Perches	    '--numstat ' .
143683c6f8fSJoe Perches	    '--format="GitCommit: %H%n' .
144683c6f8fSJoe Perches		      'GitAuthor: %an <%ae>%n' .
145683c6f8fSJoe Perches		      'GitDate: %aD%n' .
146683c6f8fSJoe Perches		      'GitSubject: %s%n"' .
147683c6f8fSJoe Perches	    " -1 \$commit",
14860db31acSJoe Perches    "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
14960db31acSJoe Perches    "blame_file_cmd" => "git blame -l \$file",
150683c6f8fSJoe Perches    "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
151dace8e30SFlorian Mickler    "blame_commit_pattern" => "^([0-9a-f]+) ",
152683c6f8fSJoe Perches    "author_pattern" => "^GitAuthor: (.*)",
153683c6f8fSJoe Perches    "subject_pattern" => "^GitSubject: (.*)",
154c9ecefeaSJoe Perches    "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
1554cad35a7SJoe Perches    "file_exists_cmd" => "git ls-files \$file",
156e1f75904STom Saeger    "list_files_cmd" => "git ls-files \$file",
15760db31acSJoe Perches);
15860db31acSJoe Perches
15960db31acSJoe Perchesmy %VCS_cmds_hg = (
16060db31acSJoe Perches    "execute_cmd" => \&hg_execute_cmd,
16160db31acSJoe Perches    "available" => '(which("hg") ne "") && (-d ".hg")',
16260db31acSJoe Perches    "find_signers_cmd" =>
16360db31acSJoe Perches	"hg log --date=\$email_hg_since " .
164683c6f8fSJoe Perches	    "--template='HgCommit: {node}\\n" .
165683c6f8fSJoe Perches	                "HgAuthor: {author}\\n" .
166683c6f8fSJoe Perches			"HgSubject: {desc}\\n'" .
167683c6f8fSJoe Perches	    " -- \$file",
168683c6f8fSJoe Perches    "find_commit_signers_cmd" =>
169683c6f8fSJoe Perches	"hg log " .
170683c6f8fSJoe Perches	    "--template='HgSubject: {desc}\\n'" .
171683c6f8fSJoe Perches	    " -r \$commit",
172683c6f8fSJoe Perches    "find_commit_author_cmd" =>
173683c6f8fSJoe Perches	"hg log " .
174683c6f8fSJoe Perches	    "--template='HgCommit: {node}\\n" .
175683c6f8fSJoe Perches		        "HgAuthor: {author}\\n" .
176683c6f8fSJoe Perches			"HgSubject: {desc|firstline}\\n'" .
177683c6f8fSJoe Perches	    " -r \$commit",
17860db31acSJoe Perches    "blame_range_cmd" => "",		# not supported
179683c6f8fSJoe Perches    "blame_file_cmd" => "hg blame -n \$file",
180683c6f8fSJoe Perches    "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
181dace8e30SFlorian Mickler    "blame_commit_pattern" => "^([ 0-9a-f]+):",
182683c6f8fSJoe Perches    "author_pattern" => "^HgAuthor: (.*)",
183683c6f8fSJoe Perches    "subject_pattern" => "^HgSubject: (.*)",
184c9ecefeaSJoe Perches    "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$",
1854cad35a7SJoe Perches    "file_exists_cmd" => "hg files \$file",
186e1f75904STom Saeger    "list_files_cmd" => "hg manifest -R \$file",
18760db31acSJoe Perches);
18860db31acSJoe Perches
189bcde44edSJoe Perchesmy $conf = which_conf(".get_maintainer.conf");
190bcde44edSJoe Perchesif (-f $conf) {
191368669daSJoe Perches    my @conf_args;
192bcde44edSJoe Perches    open(my $conffile, '<', "$conf")
193bcde44edSJoe Perches	or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
194bcde44edSJoe Perches
195368669daSJoe Perches    while (<$conffile>) {
196368669daSJoe Perches	my $line = $_;
197368669daSJoe Perches
198368669daSJoe Perches	$line =~ s/\s*\n?$//g;
199368669daSJoe Perches	$line =~ s/^\s*//g;
200368669daSJoe Perches	$line =~ s/\s+/ /g;
201368669daSJoe Perches
202368669daSJoe Perches	next if ($line =~ m/^\s*#/);
203368669daSJoe Perches	next if ($line =~ m/^\s*$/);
204368669daSJoe Perches
205368669daSJoe Perches	my @words = split(" ", $line);
206368669daSJoe Perches	foreach my $word (@words) {
207368669daSJoe Perches	    last if ($word =~ m/^#/);
208368669daSJoe Perches	    push (@conf_args, $word);
209368669daSJoe Perches	}
210368669daSJoe Perches    }
211368669daSJoe Perches    close($conffile);
212368669daSJoe Perches    unshift(@ARGV, @conf_args) if @conf_args;
213368669daSJoe Perches}
214368669daSJoe Perches
215435de078SJoe Perchesmy @ignore_emails = ();
216435de078SJoe Perchesmy $ignore_file = which_conf(".get_maintainer.ignore");
217435de078SJoe Perchesif (-f $ignore_file) {
218435de078SJoe Perches    open(my $ignore, '<', "$ignore_file")
219435de078SJoe Perches	or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n";
220435de078SJoe Perches    while (<$ignore>) {
221435de078SJoe Perches	my $line = $_;
222435de078SJoe Perches
223435de078SJoe Perches	$line =~ s/\s*\n?$//;
224435de078SJoe Perches	$line =~ s/^\s*//;
225435de078SJoe Perches	$line =~ s/\s+$//;
226435de078SJoe Perches	$line =~ s/#.*$//;
227435de078SJoe Perches
228435de078SJoe Perches	next if ($line =~ m/^\s*$/);
229435de078SJoe Perches	if (rfc822_valid($line)) {
230435de078SJoe Perches	    push(@ignore_emails, $line);
231435de078SJoe Perches	}
232435de078SJoe Perches    }
233435de078SJoe Perches    close($ignore);
234435de078SJoe Perches}
235435de078SJoe Perches
236e1f75904STom Saegerif ($#ARGV > 0) {
237e1f75904STom Saeger    foreach (@ARGV) {
238083bf9c5SJoe Perches        if ($_ =~ /^-{1,2}self-test(?:=|$)/) {
239e1f75904STom Saeger            die "$P: using --self-test does not allow any other option or argument\n";
240e1f75904STom Saeger        }
241e1f75904STom Saeger    }
242e1f75904STom Saeger}
243e1f75904STom Saeger
244cb7301c7SJoe Perchesif (!GetOptions(
245cb7301c7SJoe Perches		'email!' => \$email,
246cb7301c7SJoe Perches		'git!' => \$email_git,
247e4d26b02SJoe Perches		'git-all-signature-types!' => \$email_git_all_signature_types,
24860db31acSJoe Perches		'git-blame!' => \$email_git_blame,
249683c6f8fSJoe Perches		'git-blame-signatures!' => \$email_git_blame_signatures,
250e3e9d114SJoe Perches		'git-fallback!' => \$email_git_fallback,
251cb7301c7SJoe Perches		'git-chief-penguins!' => \$email_git_penguin_chiefs,
252cb7301c7SJoe Perches		'git-min-signatures=i' => \$email_git_min_signatures,
253cb7301c7SJoe Perches		'git-max-maintainers=i' => \$email_git_max_maintainers,
254afa81ee1SJoe Perches		'git-min-percent=i' => \$email_git_min_percent,
255cb7301c7SJoe Perches		'git-since=s' => \$email_git_since,
25660db31acSJoe Perches		'hg-since=s' => \$email_hg_since,
257dace8e30SFlorian Mickler		'i|interactive!' => \$interactive,
25811ecf53cSJoe Perches		'remove-duplicates!' => \$email_remove_duplicates,
259b9e2331dSJoe Perches		'mailmap!' => \$email_use_mailmap,
260cb7301c7SJoe Perches		'm!' => \$email_maintainer,
261c1c3f2c9SJoe Perches		'r!' => \$email_reviewer,
262cb7301c7SJoe Perches		'n!' => \$email_usename,
263cb7301c7SJoe Perches		'l!' => \$email_list,
2642f5bd343SJoe Perches		'fixes!' => \$email_fixes,
26549662503SJoe Perches		'moderated!' => \$email_moderated_list,
266cb7301c7SJoe Perches		's!' => \$email_subscriber_list,
267cb7301c7SJoe Perches		'multiline!' => \$output_multiline,
2683c7385b8SJoe Perches		'roles!' => \$output_roles,
2693c7385b8SJoe Perches		'rolestats!' => \$output_rolestats,
270cb7301c7SJoe Perches		'separator=s' => \$output_separator,
271cb7301c7SJoe Perches		'subsystem!' => \$subsystem,
272cb7301c7SJoe Perches		'status!' => \$status,
2739ad18c85SVlastimil Babka		'substatus!' => \$output_substatus,
274cb7301c7SJoe Perches		'scm!' => \$scm,
27531bb82c9SAntonio Nino Diaz		'tree!' => \$tree,
276cb7301c7SJoe Perches		'web!' => \$web,
277033964f1SJani Nikula		'bug!' => \$bug,
27803aed214SJoe Perches		'letters=s' => \$letters,
2793fb55652SJoe Perches		'pattern-depth=i' => \$pattern_depth,
280dcf36a92SJoe Perches		'k|keywords!' => \$keywords,
28171ca5ee1SJoe Perches		'kf|keywords-in-file!' => \$keywords_in_file,
2824b76c9daSJoe Perches		'sections!' => \$sections,
2830c78c013SJoe Perches		'fe|file-emails!' => \$email_file_emails,
2844a7fdb5fSJoe Perches		'f|file' => \$from_filename,
2856f7d98ecSJoe Perches		'find-maintainer-files' => \$find_maintainer_files,
2865f0baf95SJoe Perches		'mpath|maintainer-path=s' => \$maintainer_path,
287083bf9c5SJoe Perches		'self-test:s' => \$self_test,
288cb7301c7SJoe Perches		'v|version' => \$version,
28964f77f31SJoe Perches		'h|help|usage' => \$help,
290cb7301c7SJoe Perches		)) {
2913c7385b8SJoe Perches    die "$P: invalid argument - use --help if necessary\n";
292cb7301c7SJoe Perches}
293cb7301c7SJoe Perches
294cb7301c7SJoe Perchesif ($help != 0) {
295cb7301c7SJoe Perches    usage();
296cb7301c7SJoe Perches    exit 0;
297cb7301c7SJoe Perches}
298cb7301c7SJoe Perches
299cb7301c7SJoe Perchesif ($version != 0) {
300cb7301c7SJoe Perches    print("${P} ${V}\n");
301cb7301c7SJoe Perches    exit 0;
302cb7301c7SJoe Perches}
303cb7301c7SJoe Perches
304083bf9c5SJoe Perchesif (defined $self_test) {
305e1f75904STom Saeger    read_all_maintainer_files();
306083bf9c5SJoe Perches    self_test();
307e1f75904STom Saeger    exit 0;
308e1f75904STom Saeger}
309e1f75904STom Saeger
31064f77f31SJoe Perchesif (-t STDIN && !@ARGV) {
31164f77f31SJoe Perches    # We're talking to a terminal, but have no command line arguments.
31264f77f31SJoe Perches    die "$P: missing patchfile or -f file - use --help if necessary\n";
313cb7301c7SJoe Perches}
314cb7301c7SJoe Perches
315683c6f8fSJoe Perches$output_multiline = 0 if ($output_separator ne ", ");
316683c6f8fSJoe Perches$output_rolestats = 1 if ($interactive);
317683c6f8fSJoe Perches$output_roles = 1 if ($output_rolestats);
3183c7385b8SJoe Perches
3199ad18c85SVlastimil Babkaif (!defined $output_substatus) {
3209ad18c85SVlastimil Babka    $output_substatus = $email && $output_roles && -t STDOUT;
3219ad18c85SVlastimil Babka}
3229ad18c85SVlastimil Babka
32303aed214SJoe Perchesif ($sections || $letters ne "") {
32403aed214SJoe Perches    $sections = 1;
3254b76c9daSJoe Perches    $email = 0;
3264b76c9daSJoe Perches    $email_list = 0;
3274b76c9daSJoe Perches    $scm = 0;
3284b76c9daSJoe Perches    $status = 0;
3294b76c9daSJoe Perches    $subsystem = 0;
3304b76c9daSJoe Perches    $web = 0;
331033964f1SJani Nikula    $bug = 0;
3324b76c9daSJoe Perches    $keywords = 0;
33371ca5ee1SJoe Perches    $keywords_in_file = 0;
3346ef1c52eSJoe Perches    $interactive = 0;
3354b76c9daSJoe Perches} else {
336033964f1SJani Nikula    my $selections = $email + $scm + $status + $subsystem + $web + $bug;
337cb7301c7SJoe Perches    if ($selections == 0) {
338033964f1SJani Nikula	die "$P:  Missing required option: email, scm, status, subsystem, web or bug\n";
339cb7301c7SJoe Perches    }
3404b76c9daSJoe Perches}
341cb7301c7SJoe Perches
342f5492666SJoe Perchesif ($email &&
343c1c3f2c9SJoe Perches    ($email_maintainer + $email_reviewer +
344c1c3f2c9SJoe Perches     $email_list + $email_subscriber_list +
345f5492666SJoe Perches     $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
346cb7301c7SJoe Perches    die "$P: Please select at least 1 email option\n";
347cb7301c7SJoe Perches}
348cb7301c7SJoe Perches
34931bb82c9SAntonio Nino Diazif ($tree && !top_of_kernel_tree($lk_path)) {
350cb7301c7SJoe Perches    die "$P: The current directory does not appear to be "
351cb7301c7SJoe Perches	. "a linux kernel source tree.\n";
352cb7301c7SJoe Perches}
353cb7301c7SJoe Perches
354cb7301c7SJoe Perches## Read MAINTAINERS for type/value pairs
355cb7301c7SJoe Perches
356cb7301c7SJoe Perchesmy @typevalue = ();
357dcf36a92SJoe Perchesmy %keyword_hash;
3586f7d98ecSJoe Perchesmy @mfiles = ();
359083bf9c5SJoe Perchesmy @self_test_info = ();
360dcf36a92SJoe Perches
3616f7d98ecSJoe Perchessub read_maintainer_file {
3626f7d98ecSJoe Perches    my ($file) = @_;
3636f7d98ecSJoe Perches
3646f7d98ecSJoe Perches    open (my $maint, '<', "$file")
3656f7d98ecSJoe Perches	or die "$P: Can't open MAINTAINERS file '$file': $!\n";
366e1f75904STom Saeger    my $i = 1;
36722dd5b0cSStephen Hemminger    while (<$maint>) {
368cb7301c7SJoe Perches	my $line = $_;
369083bf9c5SJoe Perches	chomp $line;
370cb7301c7SJoe Perches
371ce8155f7SJoe Perches	if ($line =~ m/^([A-Z]):\s*(.*)/) {
372cb7301c7SJoe Perches	    my $type = $1;
373cb7301c7SJoe Perches	    my $value = $2;
374cb7301c7SJoe Perches
375cb7301c7SJoe Perches	    ##Filename pattern matching
376cb7301c7SJoe Perches	    if ($type eq "F" || $type eq "X") {
377cb7301c7SJoe Perches		$value =~ s@\.@\\\.@g;       ##Convert . to \.
378cb7301c7SJoe Perches		$value =~ s/\*/\.\*/g;       ##Convert * to .*
379cb7301c7SJoe Perches		$value =~ s/\?/\./g;         ##Convert ? to .
380870020f9SJoe Perches		##if pattern is a directory and it lacks a trailing slash, add one
381870020f9SJoe Perches		if ((-d $value)) {
382870020f9SJoe Perches		    $value =~ s@([^/])$@$1/@;
383870020f9SJoe Perches		}
384dcf36a92SJoe Perches	    } elsif ($type eq "K") {
385dcf36a92SJoe Perches		$keyword_hash{@typevalue} = $value;
386cb7301c7SJoe Perches	    }
387cb7301c7SJoe Perches	    push(@typevalue, "$type:$value");
3886f7d98ecSJoe Perches	} elsif (!(/^\s*$/ || /^\s*\#/)) {
389cb7301c7SJoe Perches	    push(@typevalue, $line);
390cb7301c7SJoe Perches	}
391083bf9c5SJoe Perches	if (defined $self_test) {
392083bf9c5SJoe Perches	    push(@self_test_info, {file=>$file, linenr=>$i, line=>$line});
393083bf9c5SJoe Perches	}
394e1f75904STom Saeger	$i++;
395cb7301c7SJoe Perches    }
39622dd5b0cSStephen Hemminger    close($maint);
3976f7d98ecSJoe Perches}
398cb7301c7SJoe Perches
3996f7d98ecSJoe Perchessub find_is_maintainer_file {
4006f7d98ecSJoe Perches    my ($file) = $_;
4016f7d98ecSJoe Perches    return if ($file !~ m@/MAINTAINERS$@);
4026f7d98ecSJoe Perches    $file = $File::Find::name;
4036f7d98ecSJoe Perches    return if (! -f $file);
4046f7d98ecSJoe Perches    push(@mfiles, $file);
4056f7d98ecSJoe Perches}
4066f7d98ecSJoe Perches
4076f7d98ecSJoe Perchessub find_ignore_git {
4086f7d98ecSJoe Perches    return grep { $_ !~ /^\.git$/; } @_;
4096f7d98ecSJoe Perches}
4106f7d98ecSJoe Perches
411e1f75904STom Saegerread_all_maintainer_files();
412e1f75904STom Saeger
413e1f75904STom Saegersub read_all_maintainer_files {
4145f0baf95SJoe Perches    my $path = "${lk_path}MAINTAINERS";
4155f0baf95SJoe Perches    if (defined $maintainer_path) {
4165f0baf95SJoe Perches	$path = $maintainer_path;
4175f0baf95SJoe Perches	# Perl Cookbook tilde expansion if necessary
4185f0baf95SJoe Perches	$path =~ s@^~([^/]*)@ $1 ? (getpwnam($1))[7] : ( $ENV{HOME} || $ENV{LOGDIR} || (getpwuid($<))[7])@ex;
4195f0baf95SJoe Perches    }
4205f0baf95SJoe Perches
4215f0baf95SJoe Perches    if (-d $path) {
4225f0baf95SJoe Perches	$path .= '/' if ($path !~ m@/$@);
4230fbd75fdSJoe Perches	if ($find_maintainer_files) {
4240fbd75fdSJoe Perches	    find( { wanted => \&find_is_maintainer_file,
4250fbd75fdSJoe Perches		    preprocess => \&find_ignore_git,
4260fbd75fdSJoe Perches		    no_chdir => 1,
4270fbd75fdSJoe Perches		}, "$path");
4280fbd75fdSJoe Perches	} else {
4295f0baf95SJoe Perches	    opendir(DIR, "$path") or die $!;
4306f7d98ecSJoe Perches	    my @files = readdir(DIR);
4316f7d98ecSJoe Perches	    closedir(DIR);
4326f7d98ecSJoe Perches	    foreach my $file (@files) {
4335f0baf95SJoe Perches		push(@mfiles, "$path$file") if ($file !~ /^\./);
4346f7d98ecSJoe Perches	    }
4356f7d98ecSJoe Perches	}
4365f0baf95SJoe Perches    } elsif (-f "$path") {
4375f0baf95SJoe Perches	push(@mfiles, "$path");
4385f0baf95SJoe Perches    } else {
4395f0baf95SJoe Perches	die "$P: MAINTAINER file not found '$path'\n";
4405f0baf95SJoe Perches    }
4415f0baf95SJoe Perches    die "$P: No MAINTAINER files found in '$path'\n" if (scalar(@mfiles) == 0);
4426f7d98ecSJoe Perches    foreach my $file (@mfiles) {
4436f7d98ecSJoe Perches	read_maintainer_file("$file");
4446f7d98ecSJoe Perches    }
445e1f75904STom Saeger}
4468cbb3a77SJoe Perches
4470c78c013SJoe Perchessub maintainers_in_file {
4480c78c013SJoe Perches    my ($file) = @_;
4490c78c013SJoe Perches
4500c78c013SJoe Perches    return if ($file =~ m@\bMAINTAINERS$@);
4510c78c013SJoe Perches
4520c78c013SJoe Perches    if (-f $file && ($email_file_emails || $file =~ /\.yaml$/)) {
4530c78c013SJoe Perches	open(my $f, '<', $file)
4540c78c013SJoe Perches	    or die "$P: Can't open $file: $!\n";
4550c78c013SJoe Perches	my $text = do { local($/) ; <$f> };
4560c78c013SJoe Perches	close($f);
4570c78c013SJoe Perches
4589c334eb9SAlvin Šipraga	my @poss_addr = $text =~ m$[\p{L}\"\' \,\.\+-]*\s*[\,]*\s*[\(\<\{]{0,1}[A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+\.[A-Za-z0-9]+[\)\>\}]{0,1}$g;
4590c78c013SJoe Perches	push(@file_emails, clean_file_emails(@poss_addr));
4600c78c013SJoe Perches    }
4610c78c013SJoe Perches}
4620c78c013SJoe Perches
4637fa8ff2eSFlorian Mickler#
4647fa8ff2eSFlorian Mickler# Read mail address map
4657fa8ff2eSFlorian Mickler#
4667fa8ff2eSFlorian Mickler
467b9e2331dSJoe Perchesmy $mailmap;
468b9e2331dSJoe Perches
469b9e2331dSJoe Perchesread_mailmap();
4707fa8ff2eSFlorian Mickler
4717fa8ff2eSFlorian Micklersub read_mailmap {
472b9e2331dSJoe Perches    $mailmap = {
4737fa8ff2eSFlorian Mickler	names => {},
4747fa8ff2eSFlorian Mickler	addresses => {}
4757fa8ff2eSFlorian Mickler    };
4767fa8ff2eSFlorian Mickler
477b9e2331dSJoe Perches    return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
4787fa8ff2eSFlorian Mickler
4797fa8ff2eSFlorian Mickler    open(my $mailmap_file, '<', "${lk_path}.mailmap")
48022dd5b0cSStephen Hemminger	or warn "$P: Can't open .mailmap: $!\n";
4818cbb3a77SJoe Perches
4827fa8ff2eSFlorian Mickler    while (<$mailmap_file>) {
4837fa8ff2eSFlorian Mickler	s/#.*$//; #strip comments
4847fa8ff2eSFlorian Mickler	s/^\s+|\s+$//g; #trim
4858cbb3a77SJoe Perches
4867fa8ff2eSFlorian Mickler	next if (/^\s*$/); #skip empty lines
4877fa8ff2eSFlorian Mickler	#entries have one of the following formats:
4887fa8ff2eSFlorian Mickler	# name1 <mail1>
4897fa8ff2eSFlorian Mickler	# <mail1> <mail2>
4907fa8ff2eSFlorian Mickler	# name1 <mail1> <mail2>
4917fa8ff2eSFlorian Mickler	# name1 <mail1> name2 <mail2>
4927fa8ff2eSFlorian Mickler	# (see man git-shortlog)
4930334b382SJoe Perches
4940334b382SJoe Perches	if (/^([^<]+)<([^>]+)>$/) {
4957fa8ff2eSFlorian Mickler	    my $real_name = $1;
4967fa8ff2eSFlorian Mickler	    my $address = $2;
4978cbb3a77SJoe Perches
4987fa8ff2eSFlorian Mickler	    $real_name =~ s/\s+$//;
499b9e2331dSJoe Perches	    ($real_name, $address) = parse_email("$real_name <$address>");
5007fa8ff2eSFlorian Mickler	    $mailmap->{names}->{$address} = $real_name;
5018cbb3a77SJoe Perches
5020334b382SJoe Perches	} elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
5037fa8ff2eSFlorian Mickler	    my $real_address = $1;
5047fa8ff2eSFlorian Mickler	    my $wrong_address = $2;
5057fa8ff2eSFlorian Mickler
5067fa8ff2eSFlorian Mickler	    $mailmap->{addresses}->{$wrong_address} = $real_address;
5077fa8ff2eSFlorian Mickler
5080334b382SJoe Perches	} elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
5097fa8ff2eSFlorian Mickler	    my $real_name = $1;
5107fa8ff2eSFlorian Mickler	    my $real_address = $2;
5117fa8ff2eSFlorian Mickler	    my $wrong_address = $3;
5127fa8ff2eSFlorian Mickler
5137fa8ff2eSFlorian Mickler	    $real_name =~ s/\s+$//;
514b9e2331dSJoe Perches	    ($real_name, $real_address) =
515b9e2331dSJoe Perches		parse_email("$real_name <$real_address>");
5167fa8ff2eSFlorian Mickler	    $mailmap->{names}->{$wrong_address} = $real_name;
5177fa8ff2eSFlorian Mickler	    $mailmap->{addresses}->{$wrong_address} = $real_address;
5187fa8ff2eSFlorian Mickler
5190334b382SJoe Perches	} elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
5207fa8ff2eSFlorian Mickler	    my $real_name = $1;
5217fa8ff2eSFlorian Mickler	    my $real_address = $2;
5227fa8ff2eSFlorian Mickler	    my $wrong_name = $3;
5237fa8ff2eSFlorian Mickler	    my $wrong_address = $4;
5247fa8ff2eSFlorian Mickler
5257fa8ff2eSFlorian Mickler	    $real_name =~ s/\s+$//;
526b9e2331dSJoe Perches	    ($real_name, $real_address) =
527b9e2331dSJoe Perches		parse_email("$real_name <$real_address>");
5287fa8ff2eSFlorian Mickler
529b9e2331dSJoe Perches	    $wrong_name =~ s/\s+$//;
530b9e2331dSJoe Perches	    ($wrong_name, $wrong_address) =
531b9e2331dSJoe Perches		parse_email("$wrong_name <$wrong_address>");
532b9e2331dSJoe Perches
533b9e2331dSJoe Perches	    my $wrong_email = format_email($wrong_name, $wrong_address, 1);
534b9e2331dSJoe Perches	    $mailmap->{names}->{$wrong_email} = $real_name;
535b9e2331dSJoe Perches	    $mailmap->{addresses}->{$wrong_email} = $real_address;
5368cbb3a77SJoe Perches	}
5378cbb3a77SJoe Perches    }
5387fa8ff2eSFlorian Mickler    close($mailmap_file);
5398cbb3a77SJoe Perches}
5408cbb3a77SJoe Perches
5414a7fdb5fSJoe Perches## use the filenames on the command line or find the filenames in the patchfiles
542cb7301c7SJoe Perches
54364f77f31SJoe Perchesif (!@ARGV) {
54464f77f31SJoe Perches    push(@ARGV, "&STDIN");
54564f77f31SJoe Perches}
54664f77f31SJoe Perches
5474a7fdb5fSJoe Perchesforeach my $file (@ARGV) {
54864f77f31SJoe Perches    if ($file ne "&STDIN") {
549e33c9fe8SJoe Perches	$file = canonpath($file);
550870020f9SJoe Perches	##if $file is a directory and it lacks a trailing slash, add one
551870020f9SJoe Perches	if ((-d $file)) {
552870020f9SJoe Perches	    $file =~ s@([^/])$@$1/@;
553870020f9SJoe Perches	} elsif (!(-f $file)) {
5544a7fdb5fSJoe Perches	    die "$P: file '${file}' not found\n";
555cb7301c7SJoe Perches	}
55664f77f31SJoe Perches    }
557cdfe2d22SJoe Perches    if ($from_filename && (vcs_exists() && !vcs_file_exists($file))) {
558cdfe2d22SJoe Perches	warn "$P: file '$file' not found in version control $!\n";
559cdfe2d22SJoe Perches    }
560aec742e8SJoe Perches    if ($from_filename || ($file ne "&STDIN" && vcs_file_exists($file))) {
561be17bddcSJoe Perches	$file =~ s/^\Q${cur_path}\E//;	#strip any absolute path
562be17bddcSJoe Perches	$file =~ s/^\Q${lk_path}\E//;	#or the path to the lk tree
5634a7fdb5fSJoe Perches	push(@files, $file);
56471ca5ee1SJoe Perches	if ($file ne "MAINTAINERS" && -f $file && $keywords && $keywords_in_file) {
56522dd5b0cSStephen Hemminger	    open(my $f, '<', $file)
56622dd5b0cSStephen Hemminger		or die "$P: Can't open $file: $!\n";
56722dd5b0cSStephen Hemminger	    my $text = do { local($/) ; <$f> };
56822dd5b0cSStephen Hemminger	    close($f);
569dcf36a92SJoe Perches	    foreach my $line (keys %keyword_hash) {
570a8af2430SJoe Perches		if ($text =~ m/$keyword_hash{$line}/x) {
571dcf36a92SJoe Perches		    push(@keyword_tvi, $line);
572dcf36a92SJoe Perches		}
573dcf36a92SJoe Perches	    }
57403372dbbSJoe Perches	}
575cb7301c7SJoe Perches    } else {
5764a7fdb5fSJoe Perches	my $file_cnt = @files;
577f5492666SJoe Perches	my $lastfile;
57822dd5b0cSStephen Hemminger
5793a4df13dSWolfram Sang	open(my $patch, "< $file")
58022dd5b0cSStephen Hemminger	    or die "$P: Can't open $file: $!\n";
5817764dcb5SJoe Perches
5827764dcb5SJoe Perches	# We can check arbitrary information before the patch
5837764dcb5SJoe Perches	# like the commit message, mail headers, etc...
5847764dcb5SJoe Perches	# This allows us to match arbitrary keywords against any part
5857764dcb5SJoe Perches	# of a git format-patch generated file (subject tags, etc...)
5867764dcb5SJoe Perches
5877764dcb5SJoe Perches	my $patch_prefix = "";			#Parsing the intro
5887764dcb5SJoe Perches
58922dd5b0cSStephen Hemminger	while (<$patch>) {
590dcf36a92SJoe Perches	    my $patch_line = $_;
5910455c747SJoe Perches	    if (m/^ mode change [0-7]+ => [0-7]+ (\S+)\s*$/) {
5920455c747SJoe Perches		my $filename = $1;
5930455c747SJoe Perches		push(@files, $filename);
5940455c747SJoe Perches	    } elsif (m/^rename (?:from|to) (\S+)\s*$/) {
5950455c747SJoe Perches		my $filename = $1;
5960455c747SJoe Perches		push(@files, $filename);
5970455c747SJoe Perches	    } elsif (m/^diff --git a\/(\S+) b\/(\S+)\s*$/) {
5980455c747SJoe Perches		my $filename1 = $1;
5990455c747SJoe Perches		my $filename2 = $2;
6000455c747SJoe Perches		push(@files, $filename1);
6010455c747SJoe Perches		push(@files, $filename2);
6022f5bd343SJoe Perches	    } elsif (m/^Fixes:\s+([0-9a-fA-F]{6,40})/) {
6032f5bd343SJoe Perches		push(@fixes, $1) if ($email_fixes);
6040455c747SJoe Perches	    } elsif (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
6054a7fdb5fSJoe Perches		my $filename = $1;
6064a7fdb5fSJoe Perches		$filename =~ s@^[^/]*/@@;
6074a7fdb5fSJoe Perches		$filename =~ s@\n@@;
608f5492666SJoe Perches		$lastfile = $filename;
6094a7fdb5fSJoe Perches		push(@files, $filename);
6107764dcb5SJoe Perches		$patch_prefix = "^[+-].*";	#Now parsing the actual patch
611f5492666SJoe Perches	    } elsif (m/^\@\@ -(\d+),(\d+)/) {
612f5492666SJoe Perches		if ($email_git_blame) {
613f5492666SJoe Perches		    push(@range, "$lastfile:$1:$2");
614f5492666SJoe Perches		}
615dcf36a92SJoe Perches	    } elsif ($keywords) {
616dcf36a92SJoe Perches		foreach my $line (keys %keyword_hash) {
6177764dcb5SJoe Perches		    if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
618dcf36a92SJoe Perches			push(@keyword_tvi, $line);
619dcf36a92SJoe Perches		    }
620dcf36a92SJoe Perches		}
621cb7301c7SJoe Perches	    }
622cb7301c7SJoe Perches	}
62322dd5b0cSStephen Hemminger	close($patch);
62422dd5b0cSStephen Hemminger
6254a7fdb5fSJoe Perches	if ($file_cnt == @files) {
6267f29fd27SJoe Perches	    warn "$P: file '${file}' doesn't appear to be a patch.  "
627cb7301c7SJoe Perches		. "Add -f to options?\n";
628cb7301c7SJoe Perches	}
629cb7301c7SJoe Perches	@files = sort_and_uniq(@files);
630cb7301c7SJoe Perches    }
6314a7fdb5fSJoe Perches}
632cb7301c7SJoe Perches
63303372dbbSJoe Perches@file_emails = uniq(@file_emails);
6342f5bd343SJoe Perches@fixes = uniq(@fixes);
63503372dbbSJoe Perches
636683c6f8fSJoe Perchesmy %email_hash_name;
637683c6f8fSJoe Perchesmy %email_hash_address;
638cb7301c7SJoe Perchesmy @email_to = ();
639683c6f8fSJoe Perchesmy %hash_list_to;
640290603c1SJoe Perchesmy @list_to = ();
641cb7301c7SJoe Perchesmy @scm = ();
642cb7301c7SJoe Perchesmy @web = ();
643033964f1SJani Nikulamy @bug = ();
644cb7301c7SJoe Perchesmy @subsystem = ();
645cb7301c7SJoe Perchesmy @status = ();
6469ad18c85SVlastimil Babkamy @substatus = ();
647b9e2331dSJoe Perchesmy %deduplicate_name_hash = ();
648b9e2331dSJoe Perchesmy %deduplicate_address_hash = ();
649683c6f8fSJoe Perches
6506ef1c52eSJoe Perchesmy @maintainers = get_maintainers();
6516ef1c52eSJoe Perchesif (@maintainers) {
6526ef1c52eSJoe Perches    @maintainers = merge_email(@maintainers);
6536ef1c52eSJoe Perches    output(@maintainers);
6546ef1c52eSJoe Perches}
655683c6f8fSJoe Perches
656683c6f8fSJoe Perchesif ($scm) {
657683c6f8fSJoe Perches    @scm = uniq(@scm);
658683c6f8fSJoe Perches    output(@scm);
659683c6f8fSJoe Perches}
660683c6f8fSJoe Perches
6619ad18c85SVlastimil Babkaif ($output_substatus) {
6629ad18c85SVlastimil Babka    @substatus = uniq(@substatus);
6639ad18c85SVlastimil Babka    output(@substatus);
6649ad18c85SVlastimil Babka}
6659ad18c85SVlastimil Babka
666683c6f8fSJoe Perchesif ($status) {
667683c6f8fSJoe Perches    @status = uniq(@status);
668683c6f8fSJoe Perches    output(@status);
669683c6f8fSJoe Perches}
670683c6f8fSJoe Perches
671683c6f8fSJoe Perchesif ($subsystem) {
672683c6f8fSJoe Perches    @subsystem = uniq(@subsystem);
673683c6f8fSJoe Perches    output(@subsystem);
674683c6f8fSJoe Perches}
675683c6f8fSJoe Perches
676683c6f8fSJoe Perchesif ($web) {
677683c6f8fSJoe Perches    @web = uniq(@web);
678683c6f8fSJoe Perches    output(@web);
679683c6f8fSJoe Perches}
680683c6f8fSJoe Perches
681033964f1SJani Nikulaif ($bug) {
682033964f1SJani Nikula    @bug = uniq(@bug);
683033964f1SJani Nikula    output(@bug);
684033964f1SJani Nikula}
685033964f1SJani Nikula
686683c6f8fSJoe Perchesexit($exit);
687683c6f8fSJoe Perches
688083bf9c5SJoe Perchessub self_test {
689e1f75904STom Saeger    my @lsfiles = ();
690083bf9c5SJoe Perches    my @good_links = ();
691083bf9c5SJoe Perches    my @bad_links = ();
692083bf9c5SJoe Perches    my @section_headers = ();
693083bf9c5SJoe Perches    my $index = 0;
694e1f75904STom Saeger
695e1f75904STom Saeger    @lsfiles = vcs_list_files($lk_path);
696e1f75904STom Saeger
697083bf9c5SJoe Perches    for my $x (@self_test_info) {
698083bf9c5SJoe Perches	$index++;
699083bf9c5SJoe Perches
700083bf9c5SJoe Perches	## Section header duplication and missing section content
701083bf9c5SJoe Perches	if (($self_test eq "" || $self_test =~ /\bsections\b/) &&
702083bf9c5SJoe Perches	    $x->{line} =~ /^\S[^:]/ &&
703083bf9c5SJoe Perches	    defined $self_test_info[$index] &&
704083bf9c5SJoe Perches	    $self_test_info[$index]->{line} =~ /^([A-Z]):\s*\S/) {
705083bf9c5SJoe Perches	    my $has_S = 0;
706083bf9c5SJoe Perches	    my $has_F = 0;
707083bf9c5SJoe Perches	    my $has_ML = 0;
708083bf9c5SJoe Perches	    my $status = "";
709083bf9c5SJoe Perches	    if (grep(m@^\Q$x->{line}\E@, @section_headers)) {
710083bf9c5SJoe Perches		print("$x->{file}:$x->{linenr}: warning: duplicate section header\t$x->{line}\n");
711083bf9c5SJoe Perches	    } else {
712083bf9c5SJoe Perches		push(@section_headers, $x->{line});
713083bf9c5SJoe Perches	    }
714083bf9c5SJoe Perches	    my $nextline = $index;
715083bf9c5SJoe Perches	    while (defined $self_test_info[$nextline] &&
716083bf9c5SJoe Perches		   $self_test_info[$nextline]->{line} =~ /^([A-Z]):\s*(\S.*)/) {
717083bf9c5SJoe Perches		my $type = $1;
718083bf9c5SJoe Perches		my $value = $2;
719083bf9c5SJoe Perches		if ($type eq "S") {
720083bf9c5SJoe Perches		    $has_S = 1;
721083bf9c5SJoe Perches		    $status = $value;
722083bf9c5SJoe Perches		} elsif ($type eq "F" || $type eq "N") {
723083bf9c5SJoe Perches		    $has_F = 1;
724083bf9c5SJoe Perches		} elsif ($type eq "M" || $type eq "R" || $type eq "L") {
725083bf9c5SJoe Perches		    $has_ML = 1;
726083bf9c5SJoe Perches		}
727083bf9c5SJoe Perches		$nextline++;
728083bf9c5SJoe Perches	    }
729083bf9c5SJoe Perches	    if (!$has_ML && $status !~ /orphan|obsolete/i) {
730083bf9c5SJoe Perches		print("$x->{file}:$x->{linenr}: warning: section without email address\t$x->{line}\n");
731083bf9c5SJoe Perches	    }
732083bf9c5SJoe Perches	    if (!$has_S) {
733083bf9c5SJoe Perches		print("$x->{file}:$x->{linenr}: warning: section without status \t$x->{line}\n");
734083bf9c5SJoe Perches	    }
735083bf9c5SJoe Perches	    if (!$has_F) {
736083bf9c5SJoe Perches		print("$x->{file}:$x->{linenr}: warning: section without file pattern\t$x->{line}\n");
737083bf9c5SJoe Perches	    }
738083bf9c5SJoe Perches	}
739083bf9c5SJoe Perches
740083bf9c5SJoe Perches	next if ($x->{line} !~ /^([A-Z]):\s*(.*)/);
741083bf9c5SJoe Perches
742083bf9c5SJoe Perches	my $type = $1;
743083bf9c5SJoe Perches	my $value = $2;
744083bf9c5SJoe Perches
745083bf9c5SJoe Perches	## Filename pattern matching
746083bf9c5SJoe Perches	if (($type eq "F" || $type eq "X") &&
747083bf9c5SJoe Perches	    ($self_test eq "" || $self_test =~ /\bpatterns\b/)) {
748083bf9c5SJoe Perches	    $value =~ s@\.@\\\.@g;       ##Convert . to \.
749083bf9c5SJoe Perches	    $value =~ s/\*/\.\*/g;       ##Convert * to .*
750083bf9c5SJoe Perches	    $value =~ s/\?/\./g;         ##Convert ? to .
751083bf9c5SJoe Perches	    ##if pattern is a directory and it lacks a trailing slash, add one
752083bf9c5SJoe Perches	    if ((-d $value)) {
753083bf9c5SJoe Perches		$value =~ s@([^/])$@$1/@;
754083bf9c5SJoe Perches	    }
755083bf9c5SJoe Perches	    if (!grep(m@^$value@, @lsfiles)) {
756083bf9c5SJoe Perches		print("$x->{file}:$x->{linenr}: warning: no file matches\t$x->{line}\n");
757083bf9c5SJoe Perches	    }
758083bf9c5SJoe Perches
759083bf9c5SJoe Perches	## Link reachability
760083bf9c5SJoe Perches	} elsif (($type eq "W" || $type eq "Q" || $type eq "B") &&
761083bf9c5SJoe Perches		 $value =~ /^https?:/ &&
762083bf9c5SJoe Perches		 ($self_test eq "" || $self_test =~ /\blinks\b/)) {
763083bf9c5SJoe Perches	    next if (grep(m@^\Q$value\E$@, @good_links));
764083bf9c5SJoe Perches	    my $isbad = 0;
765083bf9c5SJoe Perches	    if (grep(m@^\Q$value\E$@, @bad_links)) {
766083bf9c5SJoe Perches	        $isbad = 1;
767083bf9c5SJoe Perches	    } else {
768083bf9c5SJoe Perches		my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $value`;
769083bf9c5SJoe Perches		if ($? == 0) {
770083bf9c5SJoe Perches		    push(@good_links, $value);
771083bf9c5SJoe Perches		} else {
772083bf9c5SJoe Perches		    push(@bad_links, $value);
773083bf9c5SJoe Perches		    $isbad = 1;
774083bf9c5SJoe Perches		}
775083bf9c5SJoe Perches	    }
776083bf9c5SJoe Perches	    if ($isbad) {
777083bf9c5SJoe Perches	        print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
778083bf9c5SJoe Perches	    }
779083bf9c5SJoe Perches
780083bf9c5SJoe Perches	## SCM reachability
781083bf9c5SJoe Perches	} elsif ($type eq "T" &&
782083bf9c5SJoe Perches		 ($self_test eq "" || $self_test =~ /\bscm\b/)) {
783083bf9c5SJoe Perches	    next if (grep(m@^\Q$value\E$@, @good_links));
784083bf9c5SJoe Perches	    my $isbad = 0;
785083bf9c5SJoe Perches	    if (grep(m@^\Q$value\E$@, @bad_links)) {
786083bf9c5SJoe Perches	        $isbad = 1;
787083bf9c5SJoe Perches            } elsif ($value !~ /^(?:git|quilt|hg)\s+\S/) {
788083bf9c5SJoe Perches		print("$x->{file}:$x->{linenr}: warning: malformed entry\t$x->{line}\n");
789083bf9c5SJoe Perches	    } elsif ($value =~ /^git\s+(\S+)(\s+([^\(]+\S+))?/) {
790083bf9c5SJoe Perches		my $url = $1;
791083bf9c5SJoe Perches		my $branch = "";
792083bf9c5SJoe Perches		$branch = $3 if $3;
793083bf9c5SJoe Perches		my $output = `git ls-remote --exit-code -h "$url" $branch > /dev/null 2>&1`;
794083bf9c5SJoe Perches		if ($? == 0) {
795083bf9c5SJoe Perches		    push(@good_links, $value);
796083bf9c5SJoe Perches		} else {
797083bf9c5SJoe Perches		    push(@bad_links, $value);
798083bf9c5SJoe Perches		    $isbad = 1;
799083bf9c5SJoe Perches		}
800083bf9c5SJoe Perches	    } elsif ($value =~ /^(?:quilt|hg)\s+(https?:\S+)/) {
801083bf9c5SJoe Perches		my $url = $1;
802083bf9c5SJoe Perches		my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $url`;
803083bf9c5SJoe Perches		if ($? == 0) {
804083bf9c5SJoe Perches		    push(@good_links, $value);
805083bf9c5SJoe Perches		} else {
806083bf9c5SJoe Perches		    push(@bad_links, $value);
807083bf9c5SJoe Perches		    $isbad = 1;
808083bf9c5SJoe Perches		}
809083bf9c5SJoe Perches	    }
810083bf9c5SJoe Perches	    if ($isbad) {
811083bf9c5SJoe Perches		print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
812083bf9c5SJoe Perches	    }
813e1f75904STom Saeger	}
814e1f75904STom Saeger    }
815e1f75904STom Saeger}
816e1f75904STom Saeger
817435de078SJoe Perchessub ignore_email_address {
818435de078SJoe Perches    my ($address) = @_;
819435de078SJoe Perches
820435de078SJoe Perches    foreach my $ignore (@ignore_emails) {
821435de078SJoe Perches	return 1 if ($ignore eq $address);
822435de078SJoe Perches    }
823435de078SJoe Perches
824435de078SJoe Perches    return 0;
825435de078SJoe Perches}
826435de078SJoe Perches
827ab6c937dSJoe Perchessub range_is_maintained {
828ab6c937dSJoe Perches    my ($start, $end) = @_;
829ab6c937dSJoe Perches
830ab6c937dSJoe Perches    for (my $i = $start; $i < $end; $i++) {
831ab6c937dSJoe Perches	my $line = $typevalue[$i];
832ce8155f7SJoe Perches	if ($line =~ m/^([A-Z]):\s*(.*)/) {
833ab6c937dSJoe Perches	    my $type = $1;
834ab6c937dSJoe Perches	    my $value = $2;
835ab6c937dSJoe Perches	    if ($type eq 'S') {
836ab6c937dSJoe Perches		if ($value =~ /(maintain|support)/i) {
837ab6c937dSJoe Perches		    return 1;
838ab6c937dSJoe Perches		}
839ab6c937dSJoe Perches	    }
840ab6c937dSJoe Perches	}
841ab6c937dSJoe Perches    }
842ab6c937dSJoe Perches    return 0;
843ab6c937dSJoe Perches}
844ab6c937dSJoe Perches
845ab6c937dSJoe Perchessub range_has_maintainer {
846ab6c937dSJoe Perches    my ($start, $end) = @_;
847ab6c937dSJoe Perches
848ab6c937dSJoe Perches    for (my $i = $start; $i < $end; $i++) {
849ab6c937dSJoe Perches	my $line = $typevalue[$i];
850ce8155f7SJoe Perches	if ($line =~ m/^([A-Z]):\s*(.*)/) {
851ab6c937dSJoe Perches	    my $type = $1;
852ab6c937dSJoe Perches	    my $value = $2;
853ab6c937dSJoe Perches	    if ($type eq 'M') {
854ab6c937dSJoe Perches		return 1;
855ab6c937dSJoe Perches	    }
856ab6c937dSJoe Perches	}
857ab6c937dSJoe Perches    }
858ab6c937dSJoe Perches    return 0;
859ab6c937dSJoe Perches}
860ab6c937dSJoe Perches
8616ef1c52eSJoe Perchessub get_maintainers {
862683c6f8fSJoe Perches    %email_hash_name = ();
863683c6f8fSJoe Perches    %email_hash_address = ();
864683c6f8fSJoe Perches    %commit_author_hash = ();
865683c6f8fSJoe Perches    %commit_signer_hash = ();
866683c6f8fSJoe Perches    @email_to = ();
867683c6f8fSJoe Perches    %hash_list_to = ();
868683c6f8fSJoe Perches    @list_to = ();
869683c6f8fSJoe Perches    @scm = ();
870683c6f8fSJoe Perches    @web = ();
871033964f1SJani Nikula    @bug = ();
872683c6f8fSJoe Perches    @subsystem = ();
873683c6f8fSJoe Perches    @status = ();
8749ad18c85SVlastimil Babka    @substatus = ();
875b9e2331dSJoe Perches    %deduplicate_name_hash = ();
876b9e2331dSJoe Perches    %deduplicate_address_hash = ();
877683c6f8fSJoe Perches    if ($email_git_all_signature_types) {
878683c6f8fSJoe Perches	$signature_pattern = "(.+?)[Bb][Yy]:";
879683c6f8fSJoe Perches    } else {
880683c6f8fSJoe Perches	$signature_pattern = "\(" . join("|", @signature_tags) . "\)";
881683c6f8fSJoe Perches    }
882cb7301c7SJoe Perches
883cb7301c7SJoe Perches    # Find responsible parties
884cb7301c7SJoe Perches
885b9e2331dSJoe Perches    my %exact_pattern_match_hash = ();
8866ef1c52eSJoe Perches
887cb7301c7SJoe Perches    foreach my $file (@files) {
888cb7301c7SJoe Perches
889272a8979SJoe Perches	my %hash;
890272a8979SJoe Perches	my $tvi = find_first_section();
891272a8979SJoe Perches	while ($tvi < @typevalue) {
892272a8979SJoe Perches	    my $start = find_starting_index($tvi);
893272a8979SJoe Perches	    my $end = find_ending_index($tvi);
894272a8979SJoe Perches	    my $exclude = 0;
895272a8979SJoe Perches	    my $i;
896272a8979SJoe Perches
897cb7301c7SJoe Perches	    #Do not match excluded file patterns
898cb7301c7SJoe Perches
899272a8979SJoe Perches	    for ($i = $start; $i < $end; $i++) {
900272a8979SJoe Perches		my $line = $typevalue[$i];
901ce8155f7SJoe Perches		if ($line =~ m/^([A-Z]):\s*(.*)/) {
902cb7301c7SJoe Perches		    my $type = $1;
903cb7301c7SJoe Perches		    my $value = $2;
904cb7301c7SJoe Perches		    if ($type eq 'X') {
905cb7301c7SJoe Perches			if (file_match_pattern($file, $value)) {
906cb7301c7SJoe Perches			    $exclude = 1;
9073c840c18SJoe Perches			    last;
908cb7301c7SJoe Perches			}
909cb7301c7SJoe Perches		    }
910cb7301c7SJoe Perches		}
911cb7301c7SJoe Perches	    }
912cb7301c7SJoe Perches
913cb7301c7SJoe Perches	    if (!$exclude) {
914272a8979SJoe Perches		for ($i = $start; $i < $end; $i++) {
915272a8979SJoe Perches		    my $line = $typevalue[$i];
916ce8155f7SJoe Perches		    if ($line =~ m/^([A-Z]):\s*(.*)/) {
917cb7301c7SJoe Perches			my $type = $1;
918cb7301c7SJoe Perches			my $value = $2;
919cb7301c7SJoe Perches			if ($type eq 'F') {
920cb7301c7SJoe Perches			    if (file_match_pattern($file, $value)) {
9213fb55652SJoe Perches				my $value_pd = ($value =~ tr@/@@);
9223fb55652SJoe Perches				my $file_pd = ($file  =~ tr@/@@);
9233fb55652SJoe Perches				$value_pd++ if (substr($value,-1,1) ne "/");
924e3e9d114SJoe Perches				$value_pd = -1 if ($value =~ /^\.\*/);
925ab6c937dSJoe Perches				if ($value_pd >= $file_pd &&
926ab6c937dSJoe Perches				    range_is_maintained($start, $end) &&
927ab6c937dSJoe Perches				    range_has_maintainer($start, $end)) {
9286ef1c52eSJoe Perches				    $exact_pattern_match_hash{$file} = 1;
9296ef1c52eSJoe Perches				}
9303fb55652SJoe Perches				if ($pattern_depth == 0 ||
9313fb55652SJoe Perches				    (($file_pd - $value_pd) < $pattern_depth)) {
9323fb55652SJoe Perches				    $hash{$tvi} = $value_pd;
9333fb55652SJoe Perches				}
934cb7301c7SJoe Perches			    }
935bbbe96edSStephen Warren			} elsif ($type eq 'N') {
936eb90d085SStephen Warren			    if ($file =~ m/$value/x) {
937eb90d085SStephen Warren				$hash{$tvi} = 0;
938eb90d085SStephen Warren			    }
939cb7301c7SJoe Perches			}
940cb7301c7SJoe Perches		    }
941cb7301c7SJoe Perches		}
942272a8979SJoe Perches	    }
9433c840c18SJoe Perches	    $tvi = $end + 1;
944272a8979SJoe Perches	}
945272a8979SJoe Perches
9461d606b4eSJoe Perches	foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
94771ca5ee1SJoe Perches	    add_categories($line, "");
9484b76c9daSJoe Perches	    if ($sections) {
9494b76c9daSJoe Perches		my $i;
9504b76c9daSJoe Perches		my $start = find_starting_index($line);
9514b76c9daSJoe Perches		my $end = find_ending_index($line);
9524b76c9daSJoe Perches		for ($i = $start; $i < $end; $i++) {
9534b76c9daSJoe Perches		    my $line = $typevalue[$i];
9544b76c9daSJoe Perches		    if ($line =~ /^[FX]:/) {		##Restore file patterns
9554b76c9daSJoe Perches			$line =~ s/([^\\])\.([^\*])/$1\?$2/g;
9564b76c9daSJoe Perches			$line =~ s/([^\\])\.$/$1\?/g;	##Convert . back to ?
9574b76c9daSJoe Perches			$line =~ s/\\\./\./g;       	##Convert \. to .
9584b76c9daSJoe Perches			$line =~ s/\.\*/\*/g;       	##Convert .* to *
9594b76c9daSJoe Perches		    }
96003aed214SJoe Perches		    my $count = $line =~ s/^([A-Z]):/$1:\t/g;
96103aed214SJoe Perches		    if ($letters eq "" || (!$count || $letters =~ /$1/i)) {
9624b76c9daSJoe Perches			print("$line\n");
9634b76c9daSJoe Perches		    }
96403aed214SJoe Perches		}
965f11e9a15SJoe Perches		print("\n");
9664b76c9daSJoe Perches	    }
9671d606b4eSJoe Perches	}
9680c78c013SJoe Perches
9690c78c013SJoe Perches	maintainers_in_file($file);
970cb7301c7SJoe Perches    }
971cb7301c7SJoe Perches
972dcf36a92SJoe Perches    if ($keywords) {
973dcf36a92SJoe Perches	@keyword_tvi = sort_and_uniq(@keyword_tvi);
974dcf36a92SJoe Perches	foreach my $line (@keyword_tvi) {
97571ca5ee1SJoe Perches	    add_categories($line, ":Keyword:$keyword_hash{$line}");
976dcf36a92SJoe Perches	}
977dcf36a92SJoe Perches    }
978dcf36a92SJoe Perches
979b9e2331dSJoe Perches    foreach my $email (@email_to, @list_to) {
980b9e2331dSJoe Perches	$email->[0] = deduplicate_email($email->[0]);
981b9e2331dSJoe Perches    }
9826ef1c52eSJoe Perches
9836ef1c52eSJoe Perches    foreach my $file (@files) {
9846ef1c52eSJoe Perches	if ($email &&
9856343f6b7SJoe Perches	    ($email_git ||
9866343f6b7SJoe Perches	     ($email_git_fallback &&
9876343f6b7SJoe Perches	      $file !~ /MAINTAINERS$/ &&
9886ef1c52eSJoe Perches	      !$exact_pattern_match_hash{$file}))) {
9896ef1c52eSJoe Perches	    vcs_file_signoffs($file);
9906ef1c52eSJoe Perches	}
9916ef1c52eSJoe Perches	if ($email && $email_git_blame) {
9926ef1c52eSJoe Perches	    vcs_file_blame($file);
9936ef1c52eSJoe Perches	}
9946ef1c52eSJoe Perches    }
9956ef1c52eSJoe Perches
996f5f5078dSJoe Perches    if ($email) {
997cb7301c7SJoe Perches	foreach my $chief (@penguin_chief) {
998cb7301c7SJoe Perches	    if ($chief =~ m/^(.*):(.*)/) {
999f5f5078dSJoe Perches		my $email_address;
10000e70e83dSJoe Perches
1001a8af2430SJoe Perches		$email_address = format_email($1, $2, $email_usename);
1002f5f5078dSJoe Perches		if ($email_git_penguin_chiefs) {
10033c7385b8SJoe Perches		    push(@email_to, [$email_address, 'chief penguin']);
1004f5f5078dSJoe Perches		} else {
10053c7385b8SJoe Perches		    @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
1006cb7301c7SJoe Perches		}
1007cb7301c7SJoe Perches	    }
1008cb7301c7SJoe Perches	}
100903372dbbSJoe Perches
101003372dbbSJoe Perches	foreach my $email (@file_emails) {
101111fb4896SRob Herring	    $email = mailmap_email($email);
101203372dbbSJoe Perches	    my ($name, $address) = parse_email($email);
101303372dbbSJoe Perches
101403372dbbSJoe Perches	    my $tmp_email = format_email($name, $address, $email_usename);
101503372dbbSJoe Perches	    push_email_address($tmp_email, '');
101603372dbbSJoe Perches	    add_role($tmp_email, 'in file');
101703372dbbSJoe Perches	}
1018cb7301c7SJoe Perches    }
1019cb7301c7SJoe Perches
10200ef82fceSDouglas Anderson    foreach my $fix (@fixes) {
10210ef82fceSDouglas Anderson	vcs_add_commit_signers($fix, "blamed_fixes");
10220ef82fceSDouglas Anderson    }
10230ef82fceSDouglas Anderson
1024290603c1SJoe Perches    my @to = ();
1025683c6f8fSJoe Perches    if ($email || $email_list) {
1026cb7301c7SJoe Perches	if ($email) {
1027290603c1SJoe Perches	    @to = (@to, @email_to);
1028cb7301c7SJoe Perches	}
1029290603c1SJoe Perches	if ($email_list) {
1030290603c1SJoe Perches	    @to = (@to, @list_to);
1031290603c1SJoe Perches	}
1032cb7301c7SJoe Perches    }
1033cb7301c7SJoe Perches
10346ef1c52eSJoe Perches    if ($interactive) {
1035b9e2331dSJoe Perches	@to = interactive_get_maintainers(\@to);
10366ef1c52eSJoe Perches    }
1037cb7301c7SJoe Perches
1038683c6f8fSJoe Perches    return @to;
1039cb7301c7SJoe Perches}
1040cb7301c7SJoe Perches
1041cb7301c7SJoe Perchessub file_match_pattern {
1042cb7301c7SJoe Perches    my ($file, $pattern) = @_;
1043cb7301c7SJoe Perches    if (substr($pattern, -1) eq "/") {
1044cb7301c7SJoe Perches	if ($file =~ m@^$pattern@) {
1045cb7301c7SJoe Perches	    return 1;
1046cb7301c7SJoe Perches	}
1047cb7301c7SJoe Perches    } else {
1048cb7301c7SJoe Perches	if ($file =~ m@^$pattern@) {
1049cb7301c7SJoe Perches	    my $s1 = ($file =~ tr@/@@);
1050cb7301c7SJoe Perches	    my $s2 = ($pattern =~ tr@/@@);
1051cb7301c7SJoe Perches	    if ($s1 == $s2) {
1052cb7301c7SJoe Perches		return 1;
1053cb7301c7SJoe Perches	    }
1054cb7301c7SJoe Perches	}
1055cb7301c7SJoe Perches    }
1056cb7301c7SJoe Perches    return 0;
1057cb7301c7SJoe Perches}
1058cb7301c7SJoe Perches
1059cb7301c7SJoe Perchessub usage {
1060cb7301c7SJoe Perches    print <<EOT;
1061cb7301c7SJoe Perchesusage: $P [options] patchfile
1062870020f9SJoe Perches       $P [options] -f file|directory
1063cb7301c7SJoe Perchesversion: $V
1064cb7301c7SJoe Perches
1065cb7301c7SJoe PerchesMAINTAINER field selection options:
1066cb7301c7SJoe Perches  --email => print email address(es) if any
1067cb7301c7SJoe Perches    --git => include recent git \*-by: signers
1068e4d26b02SJoe Perches    --git-all-signature-types => include signers regardless of signature type
1069683c6f8fSJoe Perches        or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
1070e3e9d114SJoe Perches    --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
1071cb7301c7SJoe Perches    --git-chief-penguins => include ${penguin_chiefs}
1072e4d26b02SJoe Perches    --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
1073e4d26b02SJoe Perches    --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
1074e4d26b02SJoe Perches    --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
1075f5492666SJoe Perches    --git-blame => use git blame to find modified commits for patch or file
10763cbcca8aSBrian Norris    --git-blame-signatures => when used with --git-blame, also include all commit signers
1077e4d26b02SJoe Perches    --git-since => git history to use (default: $email_git_since)
1078e4d26b02SJoe Perches    --hg-since => hg history to use (default: $email_hg_since)
1079dace8e30SFlorian Mickler    --interactive => display a menu (mostly useful if used with the --git option)
1080cb7301c7SJoe Perches    --m => include maintainer(s) if any
1081c1c3f2c9SJoe Perches    --r => include reviewer(s) if any
1082cb7301c7SJoe Perches    --n => include name 'Full Name <addr\@domain.tld>'
1083cb7301c7SJoe Perches    --l => include list(s) if any
108449662503SJoe Perches    --moderated => include moderated lists(s) if any (default: true)
108549662503SJoe Perches    --s => include subscriber only list(s) if any (default: false)
108611ecf53cSJoe Perches    --remove-duplicates => minimize duplicate email names/addresses
1087*6ba31721SVlastimil Babka    --roles => show roles (role:subsystem, git-signer, list, etc...)
10883c7385b8SJoe Perches    --rolestats => show roles and statistics (commits/total_commits, %)
10899ad18c85SVlastimil Babka    --substatus => show subsystem status if not Maintained (default: match --roles when output is tty)"
109003372dbbSJoe Perches    --file-emails => add email addresses found in -f file (default: 0 (off))
10912f5bd343SJoe Perches    --fixes => for patches, add signatures of commits with 'Fixes: <commit>' (default: 1 (on))
1092cb7301c7SJoe Perches  --scm => print SCM tree(s) if any
1093cb7301c7SJoe Perches  --status => print status if any
1094cb7301c7SJoe Perches  --subsystem => print subsystem name if any
1095cb7301c7SJoe Perches  --web => print website(s) if any
1096033964f1SJani Nikula  --bug => print bug reporting info if any
1097cb7301c7SJoe Perches
1098cb7301c7SJoe PerchesOutput type options:
1099cb7301c7SJoe Perches  --separator [, ] => separator for multiple entries on 1 line
110042498316SJoe Perches    using --separator also sets --nomultiline if --separator is not [, ]
1101cb7301c7SJoe Perches  --multiline => print 1 entry per line
1102cb7301c7SJoe Perches
1103cb7301c7SJoe PerchesOther options:
11043fb55652SJoe Perches  --pattern-depth => Number of pattern directory traversals (default: 0 (all))
1105b9e2331dSJoe Perches  --keywords => scan patch for keywords (default: $keywords)
110671ca5ee1SJoe Perches  --keywords-in-file => scan file for keywords (default: $keywords_in_file)
1107b9e2331dSJoe Perches  --sections => print all of the subsystem sections with pattern matches
110803aed214SJoe Perches  --letters => print all matching 'letter' types from all matching sections
1109b9e2331dSJoe Perches  --mailmap => use .mailmap file (default: $email_use_mailmap)
111031bb82c9SAntonio Nino Diaz  --no-tree => run without a kernel tree
1111e1f75904STom Saeger  --self-test => show potential issues with MAINTAINERS file content
1112f5f5078dSJoe Perches  --version => show version
1113cb7301c7SJoe Perches  --help => show this help information
1114cb7301c7SJoe Perches
11153fb55652SJoe PerchesDefault options:
111631bb82c9SAntonio Nino Diaz  [--email --tree --nogit --git-fallback --m --r --n --l --multiline
111771ca5ee1SJoe Perches   --pattern-depth=0 --remove-duplicates --rolestats --keywords]
11183fb55652SJoe Perches
1119870020f9SJoe PerchesNotes:
1120870020f9SJoe Perches  Using "-f directory" may give unexpected results:
1121870020f9SJoe Perches      Used with "--git", git signators for _all_ files in and below
1122870020f9SJoe Perches          directory are examined as git recurses directories.
1123870020f9SJoe Perches          Any specified X: (exclude) pattern matches are _not_ ignored.
1124870020f9SJoe Perches      Used with "--nogit", directory is used as a pattern match,
1125870020f9SJoe Perches          no individual file within the directory or subdirectory
1126870020f9SJoe Perches          is matched.
1127f5492666SJoe Perches      Used with "--git-blame", does not iterate all files in directory
1128f5492666SJoe Perches  Using "--git-blame" is slow and may add old committers and authors
1129f5492666SJoe Perches      that are no longer active maintainers to the output.
11303c7385b8SJoe Perches  Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
11313c7385b8SJoe Perches      other automated tools that expect only ["name"] <email address>
11323c7385b8SJoe Perches      may not work because of additional output after <email address>.
11333c7385b8SJoe Perches  Using "--rolestats" and "--git-blame" shows the #/total=% commits,
11343c7385b8SJoe Perches      not the percentage of the entire file authored.  # of commits is
11353c7385b8SJoe Perches      not a good measure of amount of code authored.  1 major commit may
11363c7385b8SJoe Perches      contain a thousand lines, 5 trivial commits may modify a single line.
113760db31acSJoe Perches  If git is not installed, but mercurial (hg) is installed and an .hg
113860db31acSJoe Perches      repository exists, the following options apply to mercurial:
113960db31acSJoe Perches          --git,
114060db31acSJoe Perches          --git-min-signatures, --git-max-maintainers, --git-min-percent, and
114160db31acSJoe Perches          --git-blame
114260db31acSJoe Perches      Use --hg-since not --git-since to control date selection
1143368669daSJoe Perches  File ".get_maintainer.conf", if it exists in the linux kernel source root
1144368669daSJoe Perches      directory, can change whatever get_maintainer defaults are desired.
1145368669daSJoe Perches      Entries in this file can be any command line argument.
1146368669daSJoe Perches      This file is prepended to any additional command line arguments.
1147368669daSJoe Perches      Multiple lines and # comments are allowed.
1148b1312bfeSBrian Norris  Most options have both positive and negative forms.
1149b1312bfeSBrian Norris      The negative forms for --<foo> are --no<foo> and --no-<foo>.
1150b1312bfeSBrian Norris
1151cb7301c7SJoe PerchesEOT
1152cb7301c7SJoe Perches}
1153cb7301c7SJoe Perches
1154cb7301c7SJoe Perchessub top_of_kernel_tree {
1155cb7301c7SJoe Perches    my ($lk_path) = @_;
1156cb7301c7SJoe Perches
1157cb7301c7SJoe Perches    if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
1158cb7301c7SJoe Perches	$lk_path .= "/";
1159cb7301c7SJoe Perches    }
1160cb7301c7SJoe Perches    if (   (-f "${lk_path}COPYING")
1161cb7301c7SJoe Perches	&& (-f "${lk_path}CREDITS")
1162cb7301c7SJoe Perches	&& (-f "${lk_path}Kbuild")
11636f7d98ecSJoe Perches	&& (-e "${lk_path}MAINTAINERS")
1164cb7301c7SJoe Perches	&& (-f "${lk_path}Makefile")
1165cb7301c7SJoe Perches	&& (-f "${lk_path}README")
1166cb7301c7SJoe Perches	&& (-d "${lk_path}Documentation")
1167cb7301c7SJoe Perches	&& (-d "${lk_path}arch")
1168cb7301c7SJoe Perches	&& (-d "${lk_path}include")
1169cb7301c7SJoe Perches	&& (-d "${lk_path}drivers")
1170cb7301c7SJoe Perches	&& (-d "${lk_path}fs")
1171cb7301c7SJoe Perches	&& (-d "${lk_path}init")
1172cb7301c7SJoe Perches	&& (-d "${lk_path}ipc")
1173cb7301c7SJoe Perches	&& (-d "${lk_path}kernel")
1174cb7301c7SJoe Perches	&& (-d "${lk_path}lib")
1175cb7301c7SJoe Perches	&& (-d "${lk_path}scripts")) {
1176cb7301c7SJoe Perches	return 1;
1177cb7301c7SJoe Perches    }
1178cb7301c7SJoe Perches    return 0;
1179cb7301c7SJoe Perches}
1180cb7301c7SJoe Perches
11819c334eb9SAlvin Šipragasub escape_name {
11829c334eb9SAlvin Šipraga    my ($name) = @_;
11839c334eb9SAlvin Šipraga
11849c334eb9SAlvin Šipraga    if ($name =~ /[^\w \-]/ai) {  	 ##has "must quote" chars
11859c334eb9SAlvin Šipraga	$name =~ s/(?<!\\)"/\\"/g;       ##escape quotes
11869c334eb9SAlvin Šipraga	$name = "\"$name\"";
11879c334eb9SAlvin Šipraga    }
11889c334eb9SAlvin Šipraga
11899c334eb9SAlvin Šipraga    return $name;
11909c334eb9SAlvin Šipraga}
11919c334eb9SAlvin Šipraga
11920e70e83dSJoe Perchessub parse_email {
11930e70e83dSJoe Perches    my ($formatted_email) = @_;
11940e70e83dSJoe Perches
11950e70e83dSJoe Perches    my $name = "";
11960e70e83dSJoe Perches    my $address = "";
11970e70e83dSJoe Perches
119811ecf53cSJoe Perches    if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
11990e70e83dSJoe Perches	$name = $1;
12000e70e83dSJoe Perches	$address = $2;
120111ecf53cSJoe Perches    } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
12020e70e83dSJoe Perches	$address = $1;
1203b781655aSJoe Perches    } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
12040e70e83dSJoe Perches	$address = $1;
12050e70e83dSJoe Perches    }
1206cb7301c7SJoe Perches
1207cb7301c7SJoe Perches    $name =~ s/^\s+|\s+$//g;
1208d789504aSJoe Perches    $name =~ s/^\"|\"$//g;
12099c334eb9SAlvin Šipraga    $name = escape_name($name);
12100e70e83dSJoe Perches    $address =~ s/^\s+|\s+$//g;
1211cb7301c7SJoe Perches
12120e70e83dSJoe Perches    return ($name, $address);
12130e70e83dSJoe Perches}
12140e70e83dSJoe Perches
12150e70e83dSJoe Perchessub format_email {
1216a8af2430SJoe Perches    my ($name, $address, $usename) = @_;
12170e70e83dSJoe Perches
12180e70e83dSJoe Perches    my $formatted_email;
12190e70e83dSJoe Perches
12200e70e83dSJoe Perches    $name =~ s/^\s+|\s+$//g;
12210e70e83dSJoe Perches    $name =~ s/^\"|\"$//g;
12229c334eb9SAlvin Šipraga    $name = escape_name($name);
12230e70e83dSJoe Perches    $address =~ s/^\s+|\s+$//g;
12240e70e83dSJoe Perches
1225a8af2430SJoe Perches    if ($usename) {
12260e70e83dSJoe Perches	if ("$name" eq "") {
12270e70e83dSJoe Perches	    $formatted_email = "$address";
12280e70e83dSJoe Perches	} else {
1229a8af2430SJoe Perches	    $formatted_email = "$name <$address>";
12300e70e83dSJoe Perches	}
12310e70e83dSJoe Perches    } else {
12320e70e83dSJoe Perches	$formatted_email = $address;
12330e70e83dSJoe Perches    }
12340e70e83dSJoe Perches
1235cb7301c7SJoe Perches    return $formatted_email;
1236cb7301c7SJoe Perches}
1237cb7301c7SJoe Perches
1238272a8979SJoe Perchessub find_first_section {
1239272a8979SJoe Perches    my $index = 0;
1240272a8979SJoe Perches
1241272a8979SJoe Perches    while ($index < @typevalue) {
1242272a8979SJoe Perches	my $tv = $typevalue[$index];
1243ce8155f7SJoe Perches	if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
1244272a8979SJoe Perches	    last;
1245272a8979SJoe Perches	}
1246272a8979SJoe Perches	$index++;
1247272a8979SJoe Perches    }
1248272a8979SJoe Perches
1249272a8979SJoe Perches    return $index;
1250272a8979SJoe Perches}
1251272a8979SJoe Perches
1252b781655aSJoe Perchessub find_starting_index {
1253b781655aSJoe Perches    my ($index) = @_;
1254b781655aSJoe Perches
1255b781655aSJoe Perches    while ($index > 0) {
1256b781655aSJoe Perches	my $tv = $typevalue[$index];
1257ce8155f7SJoe Perches	if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1258b781655aSJoe Perches	    last;
1259b781655aSJoe Perches	}
1260b781655aSJoe Perches	$index--;
1261b781655aSJoe Perches    }
1262b781655aSJoe Perches
1263b781655aSJoe Perches    return $index;
1264b781655aSJoe Perches}
1265b781655aSJoe Perches
1266b781655aSJoe Perchessub find_ending_index {
1267b781655aSJoe Perches    my ($index) = @_;
1268b781655aSJoe Perches
1269b781655aSJoe Perches    while ($index < @typevalue) {
1270b781655aSJoe Perches	my $tv = $typevalue[$index];
1271ce8155f7SJoe Perches	if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
1272b781655aSJoe Perches	    last;
1273b781655aSJoe Perches	}
1274b781655aSJoe Perches	$index++;
1275b781655aSJoe Perches    }
1276b781655aSJoe Perches
1277b781655aSJoe Perches    return $index;
1278b781655aSJoe Perches}
1279b781655aSJoe Perches
12802a7cb1dcSJoe Perchessub get_subsystem_name {
12812a7cb1dcSJoe Perches    my ($index) = @_;
12822a7cb1dcSJoe Perches
12832a7cb1dcSJoe Perches    my $start = find_starting_index($index);
12842a7cb1dcSJoe Perches
12852a7cb1dcSJoe Perches    my $subsystem = $typevalue[$start];
12862a7cb1dcSJoe Perches    if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
12872a7cb1dcSJoe Perches	$subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
12882a7cb1dcSJoe Perches	$subsystem =~ s/\s*$//;
12892a7cb1dcSJoe Perches	$subsystem = $subsystem . "...";
12902a7cb1dcSJoe Perches    }
12912a7cb1dcSJoe Perches    return $subsystem;
12922a7cb1dcSJoe Perches}
12932a7cb1dcSJoe Perches
12943c7385b8SJoe Perchessub get_maintainer_role {
12953c7385b8SJoe Perches    my ($index) = @_;
12963c7385b8SJoe Perches
12973c7385b8SJoe Perches    my $i;
12983c7385b8SJoe Perches    my $start = find_starting_index($index);
12993c7385b8SJoe Perches    my $end = find_ending_index($index);
13003c7385b8SJoe Perches
1301*6ba31721SVlastimil Babka    my $role = "maintainer";
13022a7cb1dcSJoe Perches    my $subsystem = get_subsystem_name($index);
1303*6ba31721SVlastimil Babka    my $status = "unknown";
13043c7385b8SJoe Perches
13053c7385b8SJoe Perches    for ($i = $start + 1; $i < $end; $i++) {
13063c7385b8SJoe Perches	my $tv = $typevalue[$i];
1307ce8155f7SJoe Perches	if ($tv =~ m/^([A-Z]):\s*(.*)/) {
13083c7385b8SJoe Perches	    my $ptype = $1;
13093c7385b8SJoe Perches	    my $pvalue = $2;
13103c7385b8SJoe Perches	    if ($ptype eq "S") {
1311*6ba31721SVlastimil Babka		$status = $pvalue;
13123c7385b8SJoe Perches	    }
13133c7385b8SJoe Perches	}
13143c7385b8SJoe Perches    }
13153c7385b8SJoe Perches
1316*6ba31721SVlastimil Babka    $status = lc($status);
1317*6ba31721SVlastimil Babka    if ($status eq "buried alive in reporters") {
13183c7385b8SJoe Perches	$role = "chief penguin";
13193c7385b8SJoe Perches    }
13203c7385b8SJoe Perches
13213c7385b8SJoe Perches    return $role . ":" . $subsystem;
13223c7385b8SJoe Perches}
13233c7385b8SJoe Perches
13243c7385b8SJoe Perchessub get_list_role {
13253c7385b8SJoe Perches    my ($index) = @_;
13263c7385b8SJoe Perches
13272a7cb1dcSJoe Perches    my $subsystem = get_subsystem_name($index);
13283c7385b8SJoe Perches
13293c7385b8SJoe Perches    if ($subsystem eq "THE REST") {
13303c7385b8SJoe Perches	$subsystem = "";
13313c7385b8SJoe Perches    }
13323c7385b8SJoe Perches
13333c7385b8SJoe Perches    return $subsystem;
13343c7385b8SJoe Perches}
13353c7385b8SJoe Perches
1336cb7301c7SJoe Perchessub add_categories {
133771ca5ee1SJoe Perches    my ($index, $suffix) = @_;
1338cb7301c7SJoe Perches
1339b781655aSJoe Perches    my $i;
1340b781655aSJoe Perches    my $start = find_starting_index($index);
1341b781655aSJoe Perches    my $end = find_ending_index($index);
1342b781655aSJoe Perches
13439ad18c85SVlastimil Babka    my $subsystem = $typevalue[$start];
13449ad18c85SVlastimil Babka    push(@subsystem, $subsystem);
13459ad18c85SVlastimil Babka    my $status = "Unknown";
1346b781655aSJoe Perches
1347b781655aSJoe Perches    for ($i = $start + 1; $i < $end; $i++) {
1348b781655aSJoe Perches	my $tv = $typevalue[$i];
1349ce8155f7SJoe Perches	if ($tv =~ m/^([A-Z]):\s*(.*)/) {
1350cb7301c7SJoe Perches	    my $ptype = $1;
1351cb7301c7SJoe Perches	    my $pvalue = $2;
1352cb7301c7SJoe Perches	    if ($ptype eq "L") {
1353290603c1SJoe Perches		my $list_address = $pvalue;
1354290603c1SJoe Perches		my $list_additional = "";
13553c7385b8SJoe Perches		my $list_role = get_list_role($i);
13563c7385b8SJoe Perches
13573c7385b8SJoe Perches		if ($list_role ne "") {
13583c7385b8SJoe Perches		    $list_role = ":" . $list_role;
13593c7385b8SJoe Perches		}
1360290603c1SJoe Perches		if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1361290603c1SJoe Perches		    $list_address = $1;
1362290603c1SJoe Perches		    $list_additional = $2;
1363290603c1SJoe Perches		}
1364bdf7c685SJoe Perches		if ($list_additional =~ m/subscribers-only/) {
1365cb7301c7SJoe Perches		    if ($email_subscriber_list) {
13666ef1c52eSJoe Perches			if (!$hash_list_to{lc($list_address)}) {
13676ef1c52eSJoe Perches			    $hash_list_to{lc($list_address)} = 1;
1368683c6f8fSJoe Perches			    push(@list_to, [$list_address,
136971ca5ee1SJoe Perches					    "subscriber list${list_role}" . $suffix]);
1370683c6f8fSJoe Perches			}
1371cb7301c7SJoe Perches		    }
1372cb7301c7SJoe Perches		} else {
1373cb7301c7SJoe Perches		    if ($email_list) {
13746ef1c52eSJoe Perches			if (!$hash_list_to{lc($list_address)}) {
1375728f5a94SRichard Weinberger			    if ($list_additional =~ m/moderated/) {
137649662503SJoe Perches				if ($email_moderated_list) {
137749662503SJoe Perches				    $hash_list_to{lc($list_address)} = 1;
1378728f5a94SRichard Weinberger				    push(@list_to, [$list_address,
137971ca5ee1SJoe Perches						    "moderated list${list_role}" . $suffix]);
138049662503SJoe Perches				}
1381728f5a94SRichard Weinberger			    } else {
138249662503SJoe Perches				$hash_list_to{lc($list_address)} = 1;
1383683c6f8fSJoe Perches				push(@list_to, [$list_address,
138471ca5ee1SJoe Perches						"open list${list_role}" . $suffix]);
1385683c6f8fSJoe Perches			    }
1386cb7301c7SJoe Perches			}
1387cb7301c7SJoe Perches		    }
1388728f5a94SRichard Weinberger		}
1389cb7301c7SJoe Perches	    } elsif ($ptype eq "M") {
13900e70e83dSJoe Perches		if ($email_maintainer) {
13913c7385b8SJoe Perches		    my $role = get_maintainer_role($i);
139271ca5ee1SJoe Perches		    push_email_addresses($pvalue, $role . $suffix);
1393cb7301c7SJoe Perches		}
1394c1c3f2c9SJoe Perches	    } elsif ($ptype eq "R") {
1395c1c3f2c9SJoe Perches		if ($email_reviewer) {
13969ad18c85SVlastimil Babka		    my $subs = get_subsystem_name($i);
13979ad18c85SVlastimil Babka		    push_email_addresses($pvalue, "reviewer:$subs" . $suffix);
1398c1c3f2c9SJoe Perches		}
1399cb7301c7SJoe Perches	    } elsif ($ptype eq "T") {
140071ca5ee1SJoe Perches		push(@scm, $pvalue . $suffix);
1401cb7301c7SJoe Perches	    } elsif ($ptype eq "W") {
140271ca5ee1SJoe Perches		push(@web, $pvalue . $suffix);
1403033964f1SJani Nikula	    } elsif ($ptype eq "B") {
1404033964f1SJani Nikula		push(@bug, $pvalue . $suffix);
1405cb7301c7SJoe Perches	    } elsif ($ptype eq "S") {
140671ca5ee1SJoe Perches		push(@status, $pvalue . $suffix);
14079ad18c85SVlastimil Babka		$status = $pvalue;
1408cb7301c7SJoe Perches	    }
1409cb7301c7SJoe Perches	}
1410cb7301c7SJoe Perches    }
14119ad18c85SVlastimil Babka
14129ad18c85SVlastimil Babka    if ($subsystem ne "THE REST" and $status ne "Maintained") {
14139ad18c85SVlastimil Babka	push(@substatus, $subsystem . " status: " . $status . $suffix)
14149ad18c85SVlastimil Babka    }
1415cb7301c7SJoe Perches}
1416cb7301c7SJoe Perches
141711ecf53cSJoe Perchessub email_inuse {
141811ecf53cSJoe Perches    my ($name, $address) = @_;
14190e70e83dSJoe Perches
142011ecf53cSJoe Perches    return 1 if (($name eq "") && ($address eq ""));
14216ef1c52eSJoe Perches    return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
14226ef1c52eSJoe Perches    return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
142311ecf53cSJoe Perches
14240e70e83dSJoe Perches    return 0;
14250e70e83dSJoe Perches}
14260e70e83dSJoe Perches
14271b5e1cf6SJoe Perchessub push_email_address {
14283c7385b8SJoe Perches    my ($line, $role) = @_;
14291b5e1cf6SJoe Perches
14300e70e83dSJoe Perches    my ($name, $address) = parse_email($line);
1431f5492666SJoe Perches
1432b781655aSJoe Perches    if ($address eq "") {
1433b781655aSJoe Perches	return 0;
1434b781655aSJoe Perches    }
1435b781655aSJoe Perches
143611ecf53cSJoe Perches    if (!$email_remove_duplicates) {
1437a8af2430SJoe Perches	push(@email_to, [format_email($name, $address, $email_usename), $role]);
143811ecf53cSJoe Perches    } elsif (!email_inuse($name, $address)) {
1439a8af2430SJoe Perches	push(@email_to, [format_email($name, $address, $email_usename), $role]);
1440fae99206SJoe Perches	$email_hash_name{lc($name)}++ if ($name ne "");
14416ef1c52eSJoe Perches	$email_hash_address{lc($address)}++;
14421b5e1cf6SJoe Perches    }
1443b781655aSJoe Perches
1444b781655aSJoe Perches    return 1;
14450a79c492SJoe Perches}
14461b5e1cf6SJoe Perches
14471b5e1cf6SJoe Perchessub push_email_addresses {
14483c7385b8SJoe Perches    my ($address, $role) = @_;
14491b5e1cf6SJoe Perches
14501b5e1cf6SJoe Perches    my @address_list = ();
14511b5e1cf6SJoe Perches
14525f2441e9SJoe Perches    if (rfc822_valid($address)) {
14533c7385b8SJoe Perches	push_email_address($address, $role);
14545f2441e9SJoe Perches    } elsif (@address_list = rfc822_validlist($address)) {
14551b5e1cf6SJoe Perches	my $array_count = shift(@address_list);
14561b5e1cf6SJoe Perches	while (my $entry = shift(@address_list)) {
14573c7385b8SJoe Perches	    push_email_address($entry, $role);
14581b5e1cf6SJoe Perches	}
14595f2441e9SJoe Perches    } else {
14603c7385b8SJoe Perches	if (!push_email_address($address, $role)) {
14615f2441e9SJoe Perches	    warn("Invalid MAINTAINERS address: '" . $address . "'\n");
14621b5e1cf6SJoe Perches	}
14631b5e1cf6SJoe Perches    }
1464b781655aSJoe Perches}
14651b5e1cf6SJoe Perches
14663c7385b8SJoe Perchessub add_role {
14673c7385b8SJoe Perches    my ($line, $role) = @_;
14683c7385b8SJoe Perches
14693c7385b8SJoe Perches    my ($name, $address) = parse_email($line);
1470a8af2430SJoe Perches    my $email = format_email($name, $address, $email_usename);
14713c7385b8SJoe Perches
14723c7385b8SJoe Perches    foreach my $entry (@email_to) {
14733c7385b8SJoe Perches	if ($email_remove_duplicates) {
14743c7385b8SJoe Perches	    my ($entry_name, $entry_address) = parse_email($entry->[0]);
147503372dbbSJoe Perches	    if (($name eq $entry_name || $address eq $entry_address)
147603372dbbSJoe Perches		&& ($role eq "" || !($entry->[1] =~ m/$role/))
147703372dbbSJoe Perches	    ) {
14783c7385b8SJoe Perches		if ($entry->[1] eq "") {
14793c7385b8SJoe Perches		    $entry->[1] = "$role";
14803c7385b8SJoe Perches		} else {
14813c7385b8SJoe Perches		    $entry->[1] = "$entry->[1],$role";
14823c7385b8SJoe Perches		}
14833c7385b8SJoe Perches	    }
14843c7385b8SJoe Perches	} else {
148503372dbbSJoe Perches	    if ($email eq $entry->[0]
148603372dbbSJoe Perches		&& ($role eq "" || !($entry->[1] =~ m/$role/))
148703372dbbSJoe Perches	    ) {
14883c7385b8SJoe Perches		if ($entry->[1] eq "") {
14893c7385b8SJoe Perches		    $entry->[1] = "$role";
14903c7385b8SJoe Perches		} else {
14913c7385b8SJoe Perches		    $entry->[1] = "$entry->[1],$role";
14923c7385b8SJoe Perches		}
14933c7385b8SJoe Perches	    }
14943c7385b8SJoe Perches	}
14953c7385b8SJoe Perches    }
14963c7385b8SJoe Perches}
14973c7385b8SJoe Perches
1498cb7301c7SJoe Perchessub which {
1499cb7301c7SJoe Perches    my ($bin) = @_;
1500cb7301c7SJoe Perches
1501f5f5078dSJoe Perches    foreach my $path (split(/:/, $ENV{PATH})) {
1502cb7301c7SJoe Perches	if (-e "$path/$bin") {
1503cb7301c7SJoe Perches	    return "$path/$bin";
1504cb7301c7SJoe Perches	}
1505cb7301c7SJoe Perches    }
1506cb7301c7SJoe Perches
1507cb7301c7SJoe Perches    return "";
1508cb7301c7SJoe Perches}
1509cb7301c7SJoe Perches
1510bcde44edSJoe Perchessub which_conf {
1511bcde44edSJoe Perches    my ($conf) = @_;
1512bcde44edSJoe Perches
1513bcde44edSJoe Perches    foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1514bcde44edSJoe Perches	if (-e "$path/$conf") {
1515bcde44edSJoe Perches	    return "$path/$conf";
1516bcde44edSJoe Perches	}
1517bcde44edSJoe Perches    }
1518bcde44edSJoe Perches
1519bcde44edSJoe Perches    return "";
1520bcde44edSJoe Perches}
1521bcde44edSJoe Perches
15227fa8ff2eSFlorian Micklersub mailmap_email {
1523b9e2331dSJoe Perches    my ($line) = @_;
15248cbb3a77SJoe Perches
15258cbb3a77SJoe Perches    my ($name, $address) = parse_email($line);
15267fa8ff2eSFlorian Mickler    my $email = format_email($name, $address, 1);
15277fa8ff2eSFlorian Mickler    my $real_name = $name;
15287fa8ff2eSFlorian Mickler    my $real_address = $address;
15297fa8ff2eSFlorian Mickler
153047abc722SJoe Perches    if (exists $mailmap->{names}->{$email} ||
153147abc722SJoe Perches	exists $mailmap->{addresses}->{$email}) {
15327fa8ff2eSFlorian Mickler	if (exists $mailmap->{names}->{$email}) {
15337fa8ff2eSFlorian Mickler	    $real_name = $mailmap->{names}->{$email};
15348cbb3a77SJoe Perches	}
15357fa8ff2eSFlorian Mickler	if (exists $mailmap->{addresses}->{$email}) {
15367fa8ff2eSFlorian Mickler	    $real_address = $mailmap->{addresses}->{$email};
15377fa8ff2eSFlorian Mickler	}
15387fa8ff2eSFlorian Mickler    } else {
15397fa8ff2eSFlorian Mickler	if (exists $mailmap->{names}->{$address}) {
15407fa8ff2eSFlorian Mickler	    $real_name = $mailmap->{names}->{$address};
15417fa8ff2eSFlorian Mickler	}
15427fa8ff2eSFlorian Mickler	if (exists $mailmap->{addresses}->{$address}) {
15437fa8ff2eSFlorian Mickler	    $real_address = $mailmap->{addresses}->{$address};
15448cbb3a77SJoe Perches	}
15458cbb3a77SJoe Perches    }
15467fa8ff2eSFlorian Mickler    return format_email($real_name, $real_address, 1);
15477fa8ff2eSFlorian Mickler}
15487fa8ff2eSFlorian Mickler
15497fa8ff2eSFlorian Micklersub mailmap {
15507fa8ff2eSFlorian Mickler    my (@addresses) = @_;
15517fa8ff2eSFlorian Mickler
1552b9e2331dSJoe Perches    my @mapped_emails = ();
15537fa8ff2eSFlorian Mickler    foreach my $line (@addresses) {
1554b9e2331dSJoe Perches	push(@mapped_emails, mailmap_email($line));
15557fa8ff2eSFlorian Mickler    }
1556b9e2331dSJoe Perches    merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1557b9e2331dSJoe Perches    return @mapped_emails;
15587fa8ff2eSFlorian Mickler}
15597fa8ff2eSFlorian Mickler
15607fa8ff2eSFlorian Micklersub merge_by_realname {
15617fa8ff2eSFlorian Mickler    my %address_map;
15627fa8ff2eSFlorian Mickler    my (@emails) = @_;
1563b9e2331dSJoe Perches
15647fa8ff2eSFlorian Mickler    foreach my $email (@emails) {
15657fa8ff2eSFlorian Mickler	my ($name, $address) = parse_email($email);
1566b9e2331dSJoe Perches	if (exists $address_map{$name}) {
15677fa8ff2eSFlorian Mickler	    $address = $address_map{$name};
15687fa8ff2eSFlorian Mickler	    $email = format_email($name, $address, 1);
1569b9e2331dSJoe Perches	} else {
1570b9e2331dSJoe Perches	    $address_map{$name} = $address;
15718cbb3a77SJoe Perches	}
15728cbb3a77SJoe Perches    }
15738cbb3a77SJoe Perches}
15748cbb3a77SJoe Perches
157560db31acSJoe Perchessub git_execute_cmd {
1576a8af2430SJoe Perches    my ($cmd) = @_;
157760db31acSJoe Perches    my @lines = ();
1578a8af2430SJoe Perches
157960db31acSJoe Perches    my $output = `$cmd`;
158060db31acSJoe Perches    $output =~ s/^\s*//gm;
158160db31acSJoe Perches    @lines = split("\n", $output);
158260db31acSJoe Perches
158360db31acSJoe Perches    return @lines;
158460db31acSJoe Perches}
158560db31acSJoe Perches
158660db31acSJoe Perchessub hg_execute_cmd {
158760db31acSJoe Perches    my ($cmd) = @_;
158860db31acSJoe Perches    my @lines = ();
158960db31acSJoe Perches
159060db31acSJoe Perches    my $output = `$cmd`;
159160db31acSJoe Perches    @lines = split("\n", $output);
159260db31acSJoe Perches
159360db31acSJoe Perches    return @lines;
159460db31acSJoe Perches}
159560db31acSJoe Perches
1596683c6f8fSJoe Perchessub extract_formatted_signatures {
1597683c6f8fSJoe Perches    my (@signature_lines) = @_;
1598683c6f8fSJoe Perches
1599683c6f8fSJoe Perches    my @type = @signature_lines;
1600683c6f8fSJoe Perches
1601683c6f8fSJoe Perches    s/\s*(.*):.*/$1/ for (@type);
1602683c6f8fSJoe Perches
1603683c6f8fSJoe Perches    # cut -f2- -d":"
1604683c6f8fSJoe Perches    s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1605683c6f8fSJoe Perches
1606683c6f8fSJoe Perches## Reformat email addresses (with names) to avoid badly written signatures
1607683c6f8fSJoe Perches
1608683c6f8fSJoe Perches    foreach my $signer (@signature_lines) {
1609b9e2331dSJoe Perches	$signer = deduplicate_email($signer);
1610683c6f8fSJoe Perches    }
1611683c6f8fSJoe Perches
1612683c6f8fSJoe Perches    return (\@type, \@signature_lines);
1613683c6f8fSJoe Perches}
1614683c6f8fSJoe Perches
161560db31acSJoe Perchessub vcs_find_signers {
1616c9ecefeaSJoe Perches    my ($cmd, $file) = @_;
1617a8af2430SJoe Perches    my $commits;
1618683c6f8fSJoe Perches    my @lines = ();
1619683c6f8fSJoe Perches    my @signatures = ();
1620c9ecefeaSJoe Perches    my @authors = ();
1621c9ecefeaSJoe Perches    my @stats = ();
1622a8af2430SJoe Perches
162360db31acSJoe Perches    @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1624cb7301c7SJoe Perches
162560db31acSJoe Perches    my $pattern = $VCS_cmds{"commit_pattern"};
1626c9ecefeaSJoe Perches    my $author_pattern = $VCS_cmds{"author_pattern"};
1627c9ecefeaSJoe Perches    my $stat_pattern = $VCS_cmds{"stat_pattern"};
1628c9ecefeaSJoe Perches
1629c9ecefeaSJoe Perches    $stat_pattern =~ s/(\$\w+)/$1/eeg;		#interpolate $stat_pattern
1630cb7301c7SJoe Perches
163160db31acSJoe Perches    $commits = grep(/$pattern/, @lines);	# of commits
1632afa81ee1SJoe Perches
1633c9ecefeaSJoe Perches    @authors = grep(/$author_pattern/, @lines);
1634683c6f8fSJoe Perches    @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1635c9ecefeaSJoe Perches    @stats = grep(/$stat_pattern/, @lines);
1636683c6f8fSJoe Perches
1637c9ecefeaSJoe Perches#    print("stats: <@stats>\n");
1638c9ecefeaSJoe Perches
1639c9ecefeaSJoe Perches    return (0, \@signatures, \@authors, \@stats) if !@signatures;
1640683c6f8fSJoe Perches
1641683c6f8fSJoe Perches    save_commits_by_author(@lines) if ($interactive);
1642683c6f8fSJoe Perches    save_commits_by_signer(@lines) if ($interactive);
1643683c6f8fSJoe Perches
16440e70e83dSJoe Perches    if (!$email_git_penguin_chiefs) {
1645683c6f8fSJoe Perches	@signatures = grep(!/${penguin_chiefs}/i, @signatures);
1646afa81ee1SJoe Perches    }
164763ab52dbSJoe Perches
1648c9ecefeaSJoe Perches    my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
1649683c6f8fSJoe Perches    my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
165063ab52dbSJoe Perches
1651c9ecefeaSJoe Perches    return ($commits, $signers_ref, $authors_ref, \@stats);
1652a8af2430SJoe Perches}
1653a8af2430SJoe Perches
165463ab52dbSJoe Perchessub vcs_find_author {
165563ab52dbSJoe Perches    my ($cmd) = @_;
165663ab52dbSJoe Perches    my @lines = ();
165763ab52dbSJoe Perches
165863ab52dbSJoe Perches    @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
165963ab52dbSJoe Perches
166063ab52dbSJoe Perches    if (!$email_git_penguin_chiefs) {
166163ab52dbSJoe Perches	@lines = grep(!/${penguin_chiefs}/i, @lines);
166263ab52dbSJoe Perches    }
166363ab52dbSJoe Perches
166463ab52dbSJoe Perches    return @lines if !@lines;
166563ab52dbSJoe Perches
1666683c6f8fSJoe Perches    my @authors = ();
166763ab52dbSJoe Perches    foreach my $line (@lines) {
1668683c6f8fSJoe Perches	if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1669683c6f8fSJoe Perches	    my $author = $1;
1670683c6f8fSJoe Perches	    my ($name, $address) = parse_email($author);
1671683c6f8fSJoe Perches	    $author = format_email($name, $address, 1);
1672683c6f8fSJoe Perches	    push(@authors, $author);
1673683c6f8fSJoe Perches	}
167463ab52dbSJoe Perches    }
167563ab52dbSJoe Perches
1676683c6f8fSJoe Perches    save_commits_by_author(@lines) if ($interactive);
1677683c6f8fSJoe Perches    save_commits_by_signer(@lines) if ($interactive);
1678683c6f8fSJoe Perches
1679683c6f8fSJoe Perches    return @authors;
168063ab52dbSJoe Perches}
168163ab52dbSJoe Perches
168260db31acSJoe Perchessub vcs_save_commits {
168360db31acSJoe Perches    my ($cmd) = @_;
168460db31acSJoe Perches    my @lines = ();
168560db31acSJoe Perches    my @commits = ();
168660db31acSJoe Perches
168760db31acSJoe Perches    @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
168860db31acSJoe Perches
168960db31acSJoe Perches    foreach my $line (@lines) {
169060db31acSJoe Perches	if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
169160db31acSJoe Perches	    push(@commits, $1);
169260db31acSJoe Perches	}
169360db31acSJoe Perches    }
169460db31acSJoe Perches
169560db31acSJoe Perches    return @commits;
169660db31acSJoe Perches}
169760db31acSJoe Perches
169860db31acSJoe Perchessub vcs_blame {
169960db31acSJoe Perches    my ($file) = @_;
170060db31acSJoe Perches    my $cmd;
170160db31acSJoe Perches    my @commits = ();
170260db31acSJoe Perches
170360db31acSJoe Perches    return @commits if (!(-f $file));
170460db31acSJoe Perches
170560db31acSJoe Perches    if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
170660db31acSJoe Perches	my @all_commits = ();
170760db31acSJoe Perches
170860db31acSJoe Perches	$cmd = $VCS_cmds{"blame_file_cmd"};
170960db31acSJoe Perches	$cmd =~ s/(\$\w+)/$1/eeg;		#interpolate $cmd
171060db31acSJoe Perches	@all_commits = vcs_save_commits($cmd);
171160db31acSJoe Perches
171260db31acSJoe Perches	foreach my $file_range_diff (@range) {
171360db31acSJoe Perches	    next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
171460db31acSJoe Perches	    my $diff_file = $1;
171560db31acSJoe Perches	    my $diff_start = $2;
171660db31acSJoe Perches	    my $diff_length = $3;
171760db31acSJoe Perches	    next if ("$file" ne "$diff_file");
171860db31acSJoe Perches	    for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
171960db31acSJoe Perches		push(@commits, $all_commits[$i]);
172060db31acSJoe Perches	    }
172160db31acSJoe Perches	}
172260db31acSJoe Perches    } elsif (@range) {
172360db31acSJoe Perches	foreach my $file_range_diff (@range) {
172460db31acSJoe Perches	    next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
172560db31acSJoe Perches	    my $diff_file = $1;
172660db31acSJoe Perches	    my $diff_start = $2;
172760db31acSJoe Perches	    my $diff_length = $3;
172860db31acSJoe Perches	    next if ("$file" ne "$diff_file");
172960db31acSJoe Perches	    $cmd = $VCS_cmds{"blame_range_cmd"};
173060db31acSJoe Perches	    $cmd =~ s/(\$\w+)/$1/eeg;		#interpolate $cmd
173160db31acSJoe Perches	    push(@commits, vcs_save_commits($cmd));
173260db31acSJoe Perches	}
173360db31acSJoe Perches    } else {
173460db31acSJoe Perches	$cmd = $VCS_cmds{"blame_file_cmd"};
173560db31acSJoe Perches	$cmd =~ s/(\$\w+)/$1/eeg;		#interpolate $cmd
173660db31acSJoe Perches	@commits = vcs_save_commits($cmd);
173760db31acSJoe Perches    }
173860db31acSJoe Perches
173963ab52dbSJoe Perches    foreach my $commit (@commits) {
174063ab52dbSJoe Perches	$commit =~ s/^\^//g;
174163ab52dbSJoe Perches    }
174263ab52dbSJoe Perches
174360db31acSJoe Perches    return @commits;
174460db31acSJoe Perches}
174560db31acSJoe Perches
174660db31acSJoe Perchesmy $printed_novcs = 0;
174760db31acSJoe Perchessub vcs_exists {
174860db31acSJoe Perches    %VCS_cmds = %VCS_cmds_git;
174960db31acSJoe Perches    return 1 if eval $VCS_cmds{"available"};
175060db31acSJoe Perches    %VCS_cmds = %VCS_cmds_hg;
1751683c6f8fSJoe Perches    return 2 if eval $VCS_cmds{"available"};
175260db31acSJoe Perches    %VCS_cmds = ();
175326d98e9fSRandy Dunlap    if (!$printed_novcs && $email_git) {
175460db31acSJoe Perches	warn("$P: No supported VCS found.  Add --nogit to options?\n");
175560db31acSJoe Perches	warn("Using a git repository produces better results.\n");
175660db31acSJoe Perches	warn("Try Linus Torvalds' latest git repository using:\n");
17573d1c2f72SRalf Thielow	warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
175860db31acSJoe Perches	$printed_novcs = 1;
175960db31acSJoe Perches    }
176060db31acSJoe Perches    return 0;
176160db31acSJoe Perches}
176260db31acSJoe Perches
1763683c6f8fSJoe Perchessub vcs_is_git {
1764b9e2331dSJoe Perches    vcs_exists();
1765683c6f8fSJoe Perches    return $vcs_used == 1;
1766683c6f8fSJoe Perches}
1767683c6f8fSJoe Perches
1768683c6f8fSJoe Perchessub vcs_is_hg {
1769683c6f8fSJoe Perches    return $vcs_used == 2;
1770683c6f8fSJoe Perches}
1771683c6f8fSJoe Perches
17722f5bd343SJoe Perchessub vcs_add_commit_signers {
17732f5bd343SJoe Perches    return if (!vcs_exists());
17742f5bd343SJoe Perches
17752f5bd343SJoe Perches    my ($commit, $desc) = @_;
17762f5bd343SJoe Perches    my $commit_count = 0;
17772f5bd343SJoe Perches    my $commit_authors_ref;
17782f5bd343SJoe Perches    my $commit_signers_ref;
17792f5bd343SJoe Perches    my $stats_ref;
17802f5bd343SJoe Perches    my @commit_authors = ();
17812f5bd343SJoe Perches    my @commit_signers = ();
17822f5bd343SJoe Perches    my $cmd;
17832f5bd343SJoe Perches
17842f5bd343SJoe Perches    $cmd = $VCS_cmds{"find_commit_signers_cmd"};
17852f5bd343SJoe Perches    $cmd =~ s/(\$\w+)/$1/eeg;	#substitute variables in $cmd
17862f5bd343SJoe Perches
17872f5bd343SJoe Perches    ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, "");
17882f5bd343SJoe Perches    @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
17892f5bd343SJoe Perches    @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
17902f5bd343SJoe Perches
17912f5bd343SJoe Perches    foreach my $signer (@commit_signers) {
17922f5bd343SJoe Perches	$signer = deduplicate_email($signer);
17932f5bd343SJoe Perches    }
17942f5bd343SJoe Perches
17952f5bd343SJoe Perches    vcs_assign($desc, 1, @commit_signers);
17962f5bd343SJoe Perches}
17972f5bd343SJoe Perches
17986ef1c52eSJoe Perchessub interactive_get_maintainers {
1799683c6f8fSJoe Perches    my ($list_ref) = @_;
1800dace8e30SFlorian Mickler    my @list = @$list_ref;
1801dace8e30SFlorian Mickler
1802683c6f8fSJoe Perches    vcs_exists();
1803dace8e30SFlorian Mickler
1804dace8e30SFlorian Mickler    my %selected;
1805683c6f8fSJoe Perches    my %authored;
1806683c6f8fSJoe Perches    my %signed;
1807dace8e30SFlorian Mickler    my $count = 0;
18086ef1c52eSJoe Perches    my $maintained = 0;
1809dace8e30SFlorian Mickler    foreach my $entry (@list) {
1810b9e2331dSJoe Perches	$maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1811b9e2331dSJoe Perches	$selected{$count} = 1;
1812683c6f8fSJoe Perches	$authored{$count} = 0;
1813683c6f8fSJoe Perches	$signed{$count} = 0;
1814dace8e30SFlorian Mickler	$count++;
1815dace8e30SFlorian Mickler    }
1816dace8e30SFlorian Mickler
1817dace8e30SFlorian Mickler    #menu loop
1818683c6f8fSJoe Perches    my $done = 0;
1819683c6f8fSJoe Perches    my $print_options = 0;
1820683c6f8fSJoe Perches    my $redraw = 1;
1821683c6f8fSJoe Perches    while (!$done) {
1822683c6f8fSJoe Perches	$count = 0;
1823683c6f8fSJoe Perches	if ($redraw) {
18246ef1c52eSJoe Perches	    printf STDERR "\n%1s %2s %-65s",
1825683c6f8fSJoe Perches			  "*", "#", "email/list and role:stats";
18266ef1c52eSJoe Perches	    if ($email_git ||
18276ef1c52eSJoe Perches		($email_git_fallback && !$maintained) ||
18286ef1c52eSJoe Perches		$email_git_blame) {
18296ef1c52eSJoe Perches		print STDERR "auth sign";
18306ef1c52eSJoe Perches	    }
18316ef1c52eSJoe Perches	    print STDERR "\n";
1832dace8e30SFlorian Mickler	    foreach my $entry (@list) {
1833dace8e30SFlorian Mickler		my $email = $entry->[0];
1834dace8e30SFlorian Mickler		my $role = $entry->[1];
1835683c6f8fSJoe Perches		my $sel = "";
1836683c6f8fSJoe Perches		$sel = "*" if ($selected{$count});
1837683c6f8fSJoe Perches		my $commit_author = $commit_author_hash{$email};
1838683c6f8fSJoe Perches		my $commit_signer = $commit_signer_hash{$email};
1839683c6f8fSJoe Perches		my $authored = 0;
1840683c6f8fSJoe Perches		my $signed = 0;
1841683c6f8fSJoe Perches		$authored++ for (@{$commit_author});
1842683c6f8fSJoe Perches		$signed++ for (@{$commit_signer});
1843683c6f8fSJoe Perches		printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1844683c6f8fSJoe Perches		printf STDERR "%4d %4d", $authored, $signed
1845683c6f8fSJoe Perches		    if ($authored > 0 || $signed > 0);
1846683c6f8fSJoe Perches		printf STDERR "\n     %s\n", $role;
1847683c6f8fSJoe Perches		if ($authored{$count}) {
1848683c6f8fSJoe Perches		    my $commit_author = $commit_author_hash{$email};
1849683c6f8fSJoe Perches		    foreach my $ref (@{$commit_author}) {
1850683c6f8fSJoe Perches			print STDERR "     Author: @{$ref}[1]\n";
1851dace8e30SFlorian Mickler		    }
1852dace8e30SFlorian Mickler		}
1853683c6f8fSJoe Perches		if ($signed{$count}) {
1854683c6f8fSJoe Perches		    my $commit_signer = $commit_signer_hash{$email};
1855683c6f8fSJoe Perches		    foreach my $ref (@{$commit_signer}) {
1856683c6f8fSJoe Perches			print STDERR "     @{$ref}[2]: @{$ref}[1]\n";
1857683c6f8fSJoe Perches		    }
1858683c6f8fSJoe Perches		}
1859683c6f8fSJoe Perches
1860dace8e30SFlorian Mickler		$count++;
1861dace8e30SFlorian Mickler	    }
1862683c6f8fSJoe Perches	}
1863683c6f8fSJoe Perches	my $date_ref = \$email_git_since;
1864683c6f8fSJoe Perches	$date_ref = \$email_hg_since if (vcs_is_hg());
1865683c6f8fSJoe Perches	if ($print_options) {
1866683c6f8fSJoe Perches	    $print_options = 0;
1867683c6f8fSJoe Perches	    if (vcs_exists()) {
1868b9e2331dSJoe Perches		print STDERR <<EOT
1869b9e2331dSJoe Perches
1870b9e2331dSJoe PerchesVersion Control options:
1871b9e2331dSJoe Perchesg  use git history      [$email_git]
1872b9e2331dSJoe Perchesgf use git-fallback     [$email_git_fallback]
1873b9e2331dSJoe Perchesb  use git blame        [$email_git_blame]
1874b9e2331dSJoe Perchesbs use blame signatures [$email_git_blame_signatures]
1875b9e2331dSJoe Perchesc# minimum commits      [$email_git_min_signatures]
1876b9e2331dSJoe Perches%# min percent          [$email_git_min_percent]
1877b9e2331dSJoe Perchesd# history to use       [$$date_ref]
1878b9e2331dSJoe Perchesx# max maintainers      [$email_git_max_maintainers]
1879b9e2331dSJoe Perchest  all signature types  [$email_git_all_signature_types]
1880b9e2331dSJoe Perchesm  use .mailmap         [$email_use_mailmap]
1881b9e2331dSJoe PerchesEOT
1882683c6f8fSJoe Perches	    }
1883b9e2331dSJoe Perches	    print STDERR <<EOT
1884b9e2331dSJoe Perches
1885b9e2331dSJoe PerchesAdditional options:
1886b9e2331dSJoe Perches0  toggle all
1887b9e2331dSJoe Perchestm toggle maintainers
1888b9e2331dSJoe Perchestg toggle git entries
1889b9e2331dSJoe Perchestl toggle open list entries
1890b9e2331dSJoe Perchests toggle subscriber list entries
18910c78c013SJoe Perchesf  emails in file       [$email_file_emails]
1892b9e2331dSJoe Perchesk  keywords in file     [$keywords]
1893b9e2331dSJoe Perchesr  remove duplicates    [$email_remove_duplicates]
1894b9e2331dSJoe Perchesp# pattern match depth  [$pattern_depth]
1895b9e2331dSJoe PerchesEOT
1896683c6f8fSJoe Perches	}
1897683c6f8fSJoe Perches	print STDERR
1898683c6f8fSJoe Perches"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1899683c6f8fSJoe Perches
1900683c6f8fSJoe Perches	my $input = <STDIN>;
1901dace8e30SFlorian Mickler	chomp($input);
1902dace8e30SFlorian Mickler
1903683c6f8fSJoe Perches	$redraw = 1;
1904683c6f8fSJoe Perches	my $rerun = 0;
1905dace8e30SFlorian Mickler	my @wish = split(/[, ]+/, $input);
1906dace8e30SFlorian Mickler	foreach my $nr (@wish) {
1907683c6f8fSJoe Perches	    $nr = lc($nr);
1908683c6f8fSJoe Perches	    my $sel = substr($nr, 0, 1);
1909683c6f8fSJoe Perches	    my $str = substr($nr, 1);
1910683c6f8fSJoe Perches	    my $val = 0;
1911683c6f8fSJoe Perches	    $val = $1 if $str =~ /^(\d+)$/;
1912683c6f8fSJoe Perches
1913683c6f8fSJoe Perches	    if ($sel eq "y") {
1914683c6f8fSJoe Perches		$interactive = 0;
1915683c6f8fSJoe Perches		$done = 1;
1916683c6f8fSJoe Perches		$output_rolestats = 0;
1917683c6f8fSJoe Perches		$output_roles = 0;
19189ad18c85SVlastimil Babka		$output_substatus = 0;
1919683c6f8fSJoe Perches		last;
1920683c6f8fSJoe Perches	    } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1921683c6f8fSJoe Perches		$selected{$nr - 1} = !$selected{$nr - 1};
1922683c6f8fSJoe Perches	    } elsif ($sel eq "*" || $sel eq '^') {
1923683c6f8fSJoe Perches		my $toggle = 0;
1924683c6f8fSJoe Perches		$toggle = 1 if ($sel eq '*');
1925683c6f8fSJoe Perches		for (my $i = 0; $i < $count; $i++) {
1926683c6f8fSJoe Perches		    $selected{$i} = $toggle;
1927dace8e30SFlorian Mickler		}
1928683c6f8fSJoe Perches	    } elsif ($sel eq "0") {
1929683c6f8fSJoe Perches		for (my $i = 0; $i < $count; $i++) {
1930683c6f8fSJoe Perches		    $selected{$i} = !$selected{$i};
1931683c6f8fSJoe Perches		}
1932b9e2331dSJoe Perches	    } elsif ($sel eq "t") {
1933b9e2331dSJoe Perches		if (lc($str) eq "m") {
1934b9e2331dSJoe Perches		    for (my $i = 0; $i < $count; $i++) {
1935b9e2331dSJoe Perches			$selected{$i} = !$selected{$i}
1936b9e2331dSJoe Perches			    if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1937b9e2331dSJoe Perches		    }
1938b9e2331dSJoe Perches		} elsif (lc($str) eq "g") {
1939b9e2331dSJoe Perches		    for (my $i = 0; $i < $count; $i++) {
1940b9e2331dSJoe Perches			$selected{$i} = !$selected{$i}
1941b9e2331dSJoe Perches			    if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1942b9e2331dSJoe Perches		    }
1943b9e2331dSJoe Perches		} elsif (lc($str) eq "l") {
1944b9e2331dSJoe Perches		    for (my $i = 0; $i < $count; $i++) {
1945b9e2331dSJoe Perches			$selected{$i} = !$selected{$i}
1946b9e2331dSJoe Perches			    if ($list[$i]->[1] =~ /^(open list)/i);
1947b9e2331dSJoe Perches		    }
1948b9e2331dSJoe Perches		} elsif (lc($str) eq "s") {
1949b9e2331dSJoe Perches		    for (my $i = 0; $i < $count; $i++) {
1950b9e2331dSJoe Perches			$selected{$i} = !$selected{$i}
1951b9e2331dSJoe Perches			    if ($list[$i]->[1] =~ /^(subscriber list)/i);
1952b9e2331dSJoe Perches		    }
1953b9e2331dSJoe Perches		}
1954683c6f8fSJoe Perches	    } elsif ($sel eq "a") {
1955683c6f8fSJoe Perches		if ($val > 0 && $val <= $count) {
1956683c6f8fSJoe Perches		    $authored{$val - 1} = !$authored{$val - 1};
1957683c6f8fSJoe Perches		} elsif ($str eq '*' || $str eq '^') {
1958683c6f8fSJoe Perches		    my $toggle = 0;
1959683c6f8fSJoe Perches		    $toggle = 1 if ($str eq '*');
1960683c6f8fSJoe Perches		    for (my $i = 0; $i < $count; $i++) {
1961683c6f8fSJoe Perches			$authored{$i} = $toggle;
1962683c6f8fSJoe Perches		    }
1963683c6f8fSJoe Perches		}
1964683c6f8fSJoe Perches	    } elsif ($sel eq "s") {
1965683c6f8fSJoe Perches		if ($val > 0 && $val <= $count) {
1966683c6f8fSJoe Perches		    $signed{$val - 1} = !$signed{$val - 1};
1967683c6f8fSJoe Perches		} elsif ($str eq '*' || $str eq '^') {
1968683c6f8fSJoe Perches		    my $toggle = 0;
1969683c6f8fSJoe Perches		    $toggle = 1 if ($str eq '*');
1970683c6f8fSJoe Perches		    for (my $i = 0; $i < $count; $i++) {
1971683c6f8fSJoe Perches			$signed{$i} = $toggle;
1972683c6f8fSJoe Perches		    }
1973683c6f8fSJoe Perches		}
1974683c6f8fSJoe Perches	    } elsif ($sel eq "o") {
1975683c6f8fSJoe Perches		$print_options = 1;
1976683c6f8fSJoe Perches		$redraw = 1;
1977683c6f8fSJoe Perches	    } elsif ($sel eq "g") {
1978683c6f8fSJoe Perches		if ($str eq "f") {
1979683c6f8fSJoe Perches		    bool_invert(\$email_git_fallback);
1980dace8e30SFlorian Mickler		} else {
1981683c6f8fSJoe Perches		    bool_invert(\$email_git);
1982683c6f8fSJoe Perches		}
1983683c6f8fSJoe Perches		$rerun = 1;
1984683c6f8fSJoe Perches	    } elsif ($sel eq "b") {
1985683c6f8fSJoe Perches		if ($str eq "s") {
1986683c6f8fSJoe Perches		    bool_invert(\$email_git_blame_signatures);
1987683c6f8fSJoe Perches		} else {
1988683c6f8fSJoe Perches		    bool_invert(\$email_git_blame);
1989683c6f8fSJoe Perches		}
1990683c6f8fSJoe Perches		$rerun = 1;
1991683c6f8fSJoe Perches	    } elsif ($sel eq "c") {
1992683c6f8fSJoe Perches		if ($val > 0) {
1993683c6f8fSJoe Perches		    $email_git_min_signatures = $val;
1994683c6f8fSJoe Perches		    $rerun = 1;
1995683c6f8fSJoe Perches		}
1996683c6f8fSJoe Perches	    } elsif ($sel eq "x") {
1997683c6f8fSJoe Perches		if ($val > 0) {
1998683c6f8fSJoe Perches		    $email_git_max_maintainers = $val;
1999683c6f8fSJoe Perches		    $rerun = 1;
2000683c6f8fSJoe Perches		}
2001683c6f8fSJoe Perches	    } elsif ($sel eq "%") {
2002683c6f8fSJoe Perches		if ($str ne "" && $val >= 0) {
2003683c6f8fSJoe Perches		    $email_git_min_percent = $val;
2004683c6f8fSJoe Perches		    $rerun = 1;
2005683c6f8fSJoe Perches		}
2006683c6f8fSJoe Perches	    } elsif ($sel eq "d") {
2007683c6f8fSJoe Perches		if (vcs_is_git()) {
2008683c6f8fSJoe Perches		    $email_git_since = $str;
2009683c6f8fSJoe Perches		} elsif (vcs_is_hg()) {
2010683c6f8fSJoe Perches		    $email_hg_since = $str;
2011683c6f8fSJoe Perches		}
2012683c6f8fSJoe Perches		$rerun = 1;
2013683c6f8fSJoe Perches	    } elsif ($sel eq "t") {
2014683c6f8fSJoe Perches		bool_invert(\$email_git_all_signature_types);
2015683c6f8fSJoe Perches		$rerun = 1;
2016683c6f8fSJoe Perches	    } elsif ($sel eq "f") {
20170c78c013SJoe Perches		bool_invert(\$email_file_emails);
2018683c6f8fSJoe Perches		$rerun = 1;
2019683c6f8fSJoe Perches	    } elsif ($sel eq "r") {
2020683c6f8fSJoe Perches		bool_invert(\$email_remove_duplicates);
2021683c6f8fSJoe Perches		$rerun = 1;
2022b9e2331dSJoe Perches	    } elsif ($sel eq "m") {
2023b9e2331dSJoe Perches		bool_invert(\$email_use_mailmap);
2024b9e2331dSJoe Perches		read_mailmap();
2025b9e2331dSJoe Perches		$rerun = 1;
2026683c6f8fSJoe Perches	    } elsif ($sel eq "k") {
2027683c6f8fSJoe Perches		bool_invert(\$keywords);
2028683c6f8fSJoe Perches		$rerun = 1;
2029683c6f8fSJoe Perches	    } elsif ($sel eq "p") {
2030683c6f8fSJoe Perches		if ($str ne "" && $val >= 0) {
2031683c6f8fSJoe Perches		    $pattern_depth = $val;
2032683c6f8fSJoe Perches		    $rerun = 1;
2033683c6f8fSJoe Perches		}
20346ef1c52eSJoe Perches	    } elsif ($sel eq "h" || $sel eq "?") {
20356ef1c52eSJoe Perches		print STDERR <<EOT
20366ef1c52eSJoe Perches
20376ef1c52eSJoe PerchesInteractive mode allows you to select the various maintainers, submitters,
20386ef1c52eSJoe Perchescommit signers and mailing lists that could be CC'd on a patch.
20396ef1c52eSJoe Perches
20406ef1c52eSJoe PerchesAny *'d entry is selected.
20416ef1c52eSJoe Perches
204247abc722SJoe PerchesIf you have git or hg installed, you can choose to summarize the commit
20436ef1c52eSJoe Percheshistory of files in the patch.  Also, each line of the current file can
20446ef1c52eSJoe Perchesbe matched to its commit author and that commits signers with blame.
20456ef1c52eSJoe Perches
20466ef1c52eSJoe PerchesVarious knobs exist to control the length of time for active commit
20476ef1c52eSJoe Perchestracking, the maximum number of commit authors and signers to add,
20486ef1c52eSJoe Perchesand such.
20496ef1c52eSJoe Perches
20506ef1c52eSJoe PerchesEnter selections at the prompt until you are satisfied that the selected
20516ef1c52eSJoe Perchesmaintainers are appropriate.  You may enter multiple selections separated
20526ef1c52eSJoe Perchesby either commas or spaces.
20536ef1c52eSJoe Perches
20546ef1c52eSJoe PerchesEOT
2055683c6f8fSJoe Perches	    } else {
2056683c6f8fSJoe Perches		print STDERR "invalid option: '$nr'\n";
2057683c6f8fSJoe Perches		$redraw = 0;
2058dace8e30SFlorian Mickler	    }
2059dace8e30SFlorian Mickler	}
2060683c6f8fSJoe Perches	if ($rerun) {
2061683c6f8fSJoe Perches	    print STDERR "git-blame can be very slow, please have patience..."
2062683c6f8fSJoe Perches		if ($email_git_blame);
20636ef1c52eSJoe Perches	    goto &get_maintainers;
2064683c6f8fSJoe Perches	}
2065683c6f8fSJoe Perches    }
2066dace8e30SFlorian Mickler
2067dace8e30SFlorian Mickler    #drop not selected entries
2068dace8e30SFlorian Mickler    $count = 0;
2069683c6f8fSJoe Perches    my @new_emailto = ();
2070dace8e30SFlorian Mickler    foreach my $entry (@list) {
2071dace8e30SFlorian Mickler	if ($selected{$count}) {
2072dace8e30SFlorian Mickler	    push(@new_emailto, $list[$count]);
2073dace8e30SFlorian Mickler	}
2074dace8e30SFlorian Mickler	$count++;
2075dace8e30SFlorian Mickler    }
2076683c6f8fSJoe Perches    return @new_emailto;
2077dace8e30SFlorian Mickler}
2078dace8e30SFlorian Mickler
2079683c6f8fSJoe Perchessub bool_invert {
2080683c6f8fSJoe Perches    my ($bool_ref) = @_;
2081683c6f8fSJoe Perches
2082683c6f8fSJoe Perches    if ($$bool_ref) {
2083683c6f8fSJoe Perches	$$bool_ref = 0;
2084683c6f8fSJoe Perches    } else {
2085683c6f8fSJoe Perches	$$bool_ref = 1;
2086683c6f8fSJoe Perches    }
2087dace8e30SFlorian Mickler}
2088dace8e30SFlorian Mickler
2089b9e2331dSJoe Perchessub deduplicate_email {
2090b9e2331dSJoe Perches    my ($email) = @_;
2091b9e2331dSJoe Perches
2092b9e2331dSJoe Perches    my $matched = 0;
2093b9e2331dSJoe Perches    my ($name, $address) = parse_email($email);
2094b9e2331dSJoe Perches    $email = format_email($name, $address, 1);
2095b9e2331dSJoe Perches    $email = mailmap_email($email);
2096b9e2331dSJoe Perches
2097b9e2331dSJoe Perches    return $email if (!$email_remove_duplicates);
2098b9e2331dSJoe Perches
2099b9e2331dSJoe Perches    ($name, $address) = parse_email($email);
2100b9e2331dSJoe Perches
2101fae99206SJoe Perches    if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
2102b9e2331dSJoe Perches	$name = $deduplicate_name_hash{lc($name)}->[0];
2103b9e2331dSJoe Perches	$address = $deduplicate_name_hash{lc($name)}->[1];
2104b9e2331dSJoe Perches	$matched = 1;
2105b9e2331dSJoe Perches    } elsif ($deduplicate_address_hash{lc($address)}) {
2106b9e2331dSJoe Perches	$name = $deduplicate_address_hash{lc($address)}->[0];
2107b9e2331dSJoe Perches	$address = $deduplicate_address_hash{lc($address)}->[1];
2108b9e2331dSJoe Perches	$matched = 1;
2109b9e2331dSJoe Perches    }
2110b9e2331dSJoe Perches    if (!$matched) {
2111b9e2331dSJoe Perches	$deduplicate_name_hash{lc($name)} = [ $name, $address ];
2112b9e2331dSJoe Perches	$deduplicate_address_hash{lc($address)} = [ $name, $address ];
2113b9e2331dSJoe Perches    }
2114b9e2331dSJoe Perches    $email = format_email($name, $address, 1);
2115b9e2331dSJoe Perches    $email = mailmap_email($email);
2116b9e2331dSJoe Perches    return $email;
2117b9e2331dSJoe Perches}
2118b9e2331dSJoe Perches
2119683c6f8fSJoe Perchessub save_commits_by_author {
2120683c6f8fSJoe Perches    my (@lines) = @_;
2121683c6f8fSJoe Perches
2122683c6f8fSJoe Perches    my @authors = ();
2123683c6f8fSJoe Perches    my @commits = ();
2124683c6f8fSJoe Perches    my @subjects = ();
2125683c6f8fSJoe Perches
2126683c6f8fSJoe Perches    foreach my $line (@lines) {
2127683c6f8fSJoe Perches	if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2128683c6f8fSJoe Perches	    my $author = $1;
2129b9e2331dSJoe Perches	    $author = deduplicate_email($author);
2130683c6f8fSJoe Perches	    push(@authors, $author);
2131dace8e30SFlorian Mickler	}
2132683c6f8fSJoe Perches	push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2133683c6f8fSJoe Perches	push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2134dace8e30SFlorian Mickler    }
2135dace8e30SFlorian Mickler
2136683c6f8fSJoe Perches    for (my $i = 0; $i < @authors; $i++) {
2137683c6f8fSJoe Perches	my $exists = 0;
2138683c6f8fSJoe Perches	foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
2139683c6f8fSJoe Perches	    if (@{$ref}[0] eq $commits[$i] &&
2140683c6f8fSJoe Perches		@{$ref}[1] eq $subjects[$i]) {
2141683c6f8fSJoe Perches		$exists = 1;
2142683c6f8fSJoe Perches		last;
2143683c6f8fSJoe Perches	    }
2144683c6f8fSJoe Perches	}
2145683c6f8fSJoe Perches	if (!$exists) {
2146683c6f8fSJoe Perches	    push(@{$commit_author_hash{$authors[$i]}},
2147683c6f8fSJoe Perches		 [ ($commits[$i], $subjects[$i]) ]);
2148683c6f8fSJoe Perches	}
2149683c6f8fSJoe Perches    }
2150683c6f8fSJoe Perches}
2151dace8e30SFlorian Mickler
2152683c6f8fSJoe Perchessub save_commits_by_signer {
2153683c6f8fSJoe Perches    my (@lines) = @_;
2154683c6f8fSJoe Perches
2155683c6f8fSJoe Perches    my $commit = "";
2156683c6f8fSJoe Perches    my $subject = "";
2157683c6f8fSJoe Perches
2158683c6f8fSJoe Perches    foreach my $line (@lines) {
2159683c6f8fSJoe Perches	$commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2160683c6f8fSJoe Perches	$subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2161683c6f8fSJoe Perches	if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
2162683c6f8fSJoe Perches	    my @signatures = ($line);
2163683c6f8fSJoe Perches	    my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
2164683c6f8fSJoe Perches	    my @types = @$types_ref;
2165683c6f8fSJoe Perches	    my @signers = @$signers_ref;
2166683c6f8fSJoe Perches
2167683c6f8fSJoe Perches	    my $type = $types[0];
2168683c6f8fSJoe Perches	    my $signer = $signers[0];
2169683c6f8fSJoe Perches
2170b9e2331dSJoe Perches	    $signer = deduplicate_email($signer);
21716ef1c52eSJoe Perches
2172683c6f8fSJoe Perches	    my $exists = 0;
2173683c6f8fSJoe Perches	    foreach my $ref(@{$commit_signer_hash{$signer}}) {
2174683c6f8fSJoe Perches		if (@{$ref}[0] eq $commit &&
2175683c6f8fSJoe Perches		    @{$ref}[1] eq $subject &&
2176683c6f8fSJoe Perches		    @{$ref}[2] eq $type) {
2177683c6f8fSJoe Perches		    $exists = 1;
2178683c6f8fSJoe Perches		    last;
2179683c6f8fSJoe Perches		}
2180683c6f8fSJoe Perches	    }
2181683c6f8fSJoe Perches	    if (!$exists) {
2182683c6f8fSJoe Perches		push(@{$commit_signer_hash{$signer}},
2183683c6f8fSJoe Perches		     [ ($commit, $subject, $type) ]);
2184683c6f8fSJoe Perches	    }
2185683c6f8fSJoe Perches	}
2186683c6f8fSJoe Perches    }
2187dace8e30SFlorian Mickler}
2188dace8e30SFlorian Mickler
218960db31acSJoe Perchessub vcs_assign {
2190a8af2430SJoe Perches    my ($role, $divisor, @lines) = @_;
2191a8af2430SJoe Perches
2192a8af2430SJoe Perches    my %hash;
2193a8af2430SJoe Perches    my $count = 0;
2194a8af2430SJoe Perches
2195a8af2430SJoe Perches    return if (@lines <= 0);
2196a8af2430SJoe Perches
2197a8af2430SJoe Perches    if ($divisor <= 0) {
219860db31acSJoe Perches	warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
2199a8af2430SJoe Perches	$divisor = 1;
22003c7385b8SJoe Perches    }
22018cbb3a77SJoe Perches
22020e70e83dSJoe Perches    @lines = mailmap(@lines);
22030e70e83dSJoe Perches
220463ab52dbSJoe Perches    return if (@lines <= 0);
220563ab52dbSJoe Perches
22060e70e83dSJoe Perches    @lines = sort(@lines);
2207afa81ee1SJoe Perches
220811ecf53cSJoe Perches    # uniq -c
220911ecf53cSJoe Perches    $hash{$_}++ for @lines;
221011ecf53cSJoe Perches
221111ecf53cSJoe Perches    # sort -rn
221211ecf53cSJoe Perches    foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
221311ecf53cSJoe Perches	my $sign_offs = $hash{$line};
2214a8af2430SJoe Perches	my $percent = $sign_offs * 100 / $divisor;
22153c7385b8SJoe Perches
2216a8af2430SJoe Perches	$percent = 100 if ($percent > 100);
2217435de078SJoe Perches	next if (ignore_email_address($line));
2218cb7301c7SJoe Perches	$count++;
221911ecf53cSJoe Perches	last if ($sign_offs < $email_git_min_signatures ||
2220afa81ee1SJoe Perches		 $count > $email_git_max_maintainers ||
2221a8af2430SJoe Perches		 $percent < $email_git_min_percent);
22223c7385b8SJoe Perches	push_email_address($line, '');
22233c7385b8SJoe Perches	if ($output_rolestats) {
2224a8af2430SJoe Perches	    my $fmt_percent = sprintf("%.0f", $percent);
2225a8af2430SJoe Perches	    add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
2226a8af2430SJoe Perches	} else {
22273c7385b8SJoe Perches	    add_role($line, $role);
2228cb7301c7SJoe Perches	}
2229cb7301c7SJoe Perches    }
2230a8af2430SJoe Perches}
2231a8af2430SJoe Perches
223260db31acSJoe Perchessub vcs_file_signoffs {
2233a8af2430SJoe Perches    my ($file) = @_;
2234a8af2430SJoe Perches
2235c9ecefeaSJoe Perches    my $authors_ref;
2236c9ecefeaSJoe Perches    my $signers_ref;
2237c9ecefeaSJoe Perches    my $stats_ref;
2238c9ecefeaSJoe Perches    my @authors = ();
2239a8af2430SJoe Perches    my @signers = ();
2240c9ecefeaSJoe Perches    my @stats = ();
224160db31acSJoe Perches    my $commits;
2242a8af2430SJoe Perches
2243683c6f8fSJoe Perches    $vcs_used = vcs_exists();
2244683c6f8fSJoe Perches    return if (!$vcs_used);
2245a8af2430SJoe Perches
224660db31acSJoe Perches    my $cmd = $VCS_cmds{"find_signers_cmd"};
224760db31acSJoe Perches    $cmd =~ s/(\$\w+)/$1/eeg;		# interpolate $cmd
224860db31acSJoe Perches
2249c9ecefeaSJoe Perches    ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2250c9ecefeaSJoe Perches
2251c9ecefeaSJoe Perches    @signers = @{$signers_ref} if defined $signers_ref;
2252c9ecefeaSJoe Perches    @authors = @{$authors_ref} if defined $authors_ref;
2253c9ecefeaSJoe Perches    @stats = @{$stats_ref} if defined $stats_ref;
2254c9ecefeaSJoe Perches
2255c9ecefeaSJoe Perches#    print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
2256b9e2331dSJoe Perches
2257b9e2331dSJoe Perches    foreach my $signer (@signers) {
2258b9e2331dSJoe Perches	$signer = deduplicate_email($signer);
2259b9e2331dSJoe Perches    }
2260b9e2331dSJoe Perches
226160db31acSJoe Perches    vcs_assign("commit_signer", $commits, @signers);
2262c9ecefeaSJoe Perches    vcs_assign("authored", $commits, @authors);
2263c9ecefeaSJoe Perches    if ($#authors == $#stats) {
2264c9ecefeaSJoe Perches	my $stat_pattern = $VCS_cmds{"stat_pattern"};
2265c9ecefeaSJoe Perches	$stat_pattern =~ s/(\$\w+)/$1/eeg;	#interpolate $stat_pattern
2266c9ecefeaSJoe Perches
2267c9ecefeaSJoe Perches	my $added = 0;
2268c9ecefeaSJoe Perches	my $deleted = 0;
2269c9ecefeaSJoe Perches	for (my $i = 0; $i <= $#stats; $i++) {
2270c9ecefeaSJoe Perches	    if ($stats[$i] =~ /$stat_pattern/) {
2271c9ecefeaSJoe Perches		$added += $1;
2272c9ecefeaSJoe Perches		$deleted += $2;
2273c9ecefeaSJoe Perches	    }
2274c9ecefeaSJoe Perches	}
2275c9ecefeaSJoe Perches	my @tmp_authors = uniq(@authors);
2276c9ecefeaSJoe Perches	foreach my $author (@tmp_authors) {
2277c9ecefeaSJoe Perches	    $author = deduplicate_email($author);
2278c9ecefeaSJoe Perches	}
2279c9ecefeaSJoe Perches	@tmp_authors = uniq(@tmp_authors);
2280c9ecefeaSJoe Perches	my @list_added = ();
2281c9ecefeaSJoe Perches	my @list_deleted = ();
2282c9ecefeaSJoe Perches	foreach my $author (@tmp_authors) {
2283c9ecefeaSJoe Perches	    my $auth_added = 0;
2284c9ecefeaSJoe Perches	    my $auth_deleted = 0;
2285c9ecefeaSJoe Perches	    for (my $i = 0; $i <= $#stats; $i++) {
2286c9ecefeaSJoe Perches		if ($author eq deduplicate_email($authors[$i]) &&
2287c9ecefeaSJoe Perches		    $stats[$i] =~ /$stat_pattern/) {
2288c9ecefeaSJoe Perches		    $auth_added += $1;
2289c9ecefeaSJoe Perches		    $auth_deleted += $2;
2290c9ecefeaSJoe Perches		}
2291c9ecefeaSJoe Perches	    }
2292c9ecefeaSJoe Perches	    for (my $i = 0; $i < $auth_added; $i++) {
2293c9ecefeaSJoe Perches		push(@list_added, $author);
2294c9ecefeaSJoe Perches	    }
2295c9ecefeaSJoe Perches	    for (my $i = 0; $i < $auth_deleted; $i++) {
2296c9ecefeaSJoe Perches		push(@list_deleted, $author);
2297c9ecefeaSJoe Perches	    }
2298c9ecefeaSJoe Perches	}
2299c9ecefeaSJoe Perches	vcs_assign("added_lines", $added, @list_added);
2300c9ecefeaSJoe Perches	vcs_assign("removed_lines", $deleted, @list_deleted);
2301c9ecefeaSJoe Perches    }
2302a8af2430SJoe Perches}
2303f5492666SJoe Perches
230460db31acSJoe Perchessub vcs_file_blame {
2305f5492666SJoe Perches    my ($file) = @_;
2306f5492666SJoe Perches
230760db31acSJoe Perches    my @signers = ();
230863ab52dbSJoe Perches    my @all_commits = ();
2309a8af2430SJoe Perches    my @commits = ();
2310a8af2430SJoe Perches    my $total_commits;
231163ab52dbSJoe Perches    my $total_lines;
2312f5492666SJoe Perches
2313683c6f8fSJoe Perches    $vcs_used = vcs_exists();
2314683c6f8fSJoe Perches    return if (!$vcs_used);
2315f5492666SJoe Perches
231663ab52dbSJoe Perches    @all_commits = vcs_blame($file);
231763ab52dbSJoe Perches    @commits = uniq(@all_commits);
2318a8af2430SJoe Perches    $total_commits = @commits;
231963ab52dbSJoe Perches    $total_lines = @all_commits;
2320a8af2430SJoe Perches
2321683c6f8fSJoe Perches    if ($email_git_blame_signatures) {
2322683c6f8fSJoe Perches	if (vcs_is_hg()) {
2323683c6f8fSJoe Perches	    my $commit_count;
2324c9ecefeaSJoe Perches	    my $commit_authors_ref;
2325c9ecefeaSJoe Perches	    my $commit_signers_ref;
2326c9ecefeaSJoe Perches	    my $stats_ref;
2327c9ecefeaSJoe Perches	    my @commit_authors = ();
2328683c6f8fSJoe Perches	    my @commit_signers = ();
2329683c6f8fSJoe Perches	    my $commit = join(" -r ", @commits);
2330683c6f8fSJoe Perches	    my $cmd;
2331683c6f8fSJoe Perches
2332683c6f8fSJoe Perches	    $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2333683c6f8fSJoe Perches	    $cmd =~ s/(\$\w+)/$1/eeg;	#substitute variables in $cmd
2334683c6f8fSJoe Perches
2335c9ecefeaSJoe Perches	    ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2336c9ecefeaSJoe Perches	    @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2337c9ecefeaSJoe Perches	    @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
2338683c6f8fSJoe Perches
2339683c6f8fSJoe Perches	    push(@signers, @commit_signers);
2340683c6f8fSJoe Perches	} else {
2341f5492666SJoe Perches	    foreach my $commit (@commits) {
2342a8af2430SJoe Perches		my $commit_count;
2343c9ecefeaSJoe Perches		my $commit_authors_ref;
2344c9ecefeaSJoe Perches		my $commit_signers_ref;
2345c9ecefeaSJoe Perches		my $stats_ref;
2346c9ecefeaSJoe Perches		my @commit_authors = ();
2347a8af2430SJoe Perches		my @commit_signers = ();
2348683c6f8fSJoe Perches		my $cmd;
2349f5492666SJoe Perches
2350683c6f8fSJoe Perches		$cmd = $VCS_cmds{"find_commit_signers_cmd"};
2351dace8e30SFlorian Mickler		$cmd =~ s/(\$\w+)/$1/eeg;	#substitute variables in $cmd
235260db31acSJoe Perches
2353c9ecefeaSJoe Perches		($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2354c9ecefeaSJoe Perches		@commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2355c9ecefeaSJoe Perches		@commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
235663ab52dbSJoe Perches
235760db31acSJoe Perches		push(@signers, @commit_signers);
23580e70e83dSJoe Perches	    }
2359683c6f8fSJoe Perches	}
2360683c6f8fSJoe Perches    }
23618cbb3a77SJoe Perches
23623c7385b8SJoe Perches    if ($from_filename) {
236363ab52dbSJoe Perches	if ($output_rolestats) {
236463ab52dbSJoe Perches	    my @blame_signers;
2365683c6f8fSJoe Perches	    if (vcs_is_hg()) {{		# Double brace for last exit
2366683c6f8fSJoe Perches		my $commit_count;
2367683c6f8fSJoe Perches		my @commit_signers = ();
2368683c6f8fSJoe Perches		@commits = uniq(@commits);
2369683c6f8fSJoe Perches		@commits = sort(@commits);
2370683c6f8fSJoe Perches		my $commit = join(" -r ", @commits);
2371683c6f8fSJoe Perches		my $cmd;
2372683c6f8fSJoe Perches
2373683c6f8fSJoe Perches		$cmd = $VCS_cmds{"find_commit_author_cmd"};
2374683c6f8fSJoe Perches		$cmd =~ s/(\$\w+)/$1/eeg;	#substitute variables in $cmd
2375683c6f8fSJoe Perches
2376683c6f8fSJoe Perches		my @lines = ();
2377683c6f8fSJoe Perches
2378683c6f8fSJoe Perches		@lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2379683c6f8fSJoe Perches
2380683c6f8fSJoe Perches		if (!$email_git_penguin_chiefs) {
2381683c6f8fSJoe Perches		    @lines = grep(!/${penguin_chiefs}/i, @lines);
2382683c6f8fSJoe Perches		}
2383683c6f8fSJoe Perches
2384683c6f8fSJoe Perches		last if !@lines;
2385683c6f8fSJoe Perches
2386683c6f8fSJoe Perches		my @authors = ();
2387683c6f8fSJoe Perches		foreach my $line (@lines) {
2388683c6f8fSJoe Perches		    if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2389683c6f8fSJoe Perches			my $author = $1;
2390b9e2331dSJoe Perches			$author = deduplicate_email($author);
2391b9e2331dSJoe Perches			push(@authors, $author);
2392683c6f8fSJoe Perches		    }
2393683c6f8fSJoe Perches		}
2394683c6f8fSJoe Perches
2395683c6f8fSJoe Perches		save_commits_by_author(@lines) if ($interactive);
2396683c6f8fSJoe Perches		save_commits_by_signer(@lines) if ($interactive);
2397683c6f8fSJoe Perches
2398683c6f8fSJoe Perches		push(@signers, @authors);
2399683c6f8fSJoe Perches	    }}
2400683c6f8fSJoe Perches	    else {
240163ab52dbSJoe Perches		foreach my $commit (@commits) {
240263ab52dbSJoe Perches		    my $i;
240363ab52dbSJoe Perches		    my $cmd = $VCS_cmds{"find_commit_author_cmd"};
240463ab52dbSJoe Perches		    $cmd =~ s/(\$\w+)/$1/eeg;	#interpolate $cmd
240563ab52dbSJoe Perches		    my @author = vcs_find_author($cmd);
240663ab52dbSJoe Perches		    next if !@author;
2407b9e2331dSJoe Perches
2408b9e2331dSJoe Perches		    my $formatted_author = deduplicate_email($author[0]);
2409b9e2331dSJoe Perches
241063ab52dbSJoe Perches		    my $count = grep(/$commit/, @all_commits);
241163ab52dbSJoe Perches		    for ($i = 0; $i < $count ; $i++) {
2412b9e2331dSJoe Perches			push(@blame_signers, $formatted_author);
241363ab52dbSJoe Perches		    }
241463ab52dbSJoe Perches		}
2415683c6f8fSJoe Perches	    }
241663ab52dbSJoe Perches	    if (@blame_signers) {
241763ab52dbSJoe Perches		vcs_assign("authored lines", $total_lines, @blame_signers);
241863ab52dbSJoe Perches	    }
241963ab52dbSJoe Perches	}
2420b9e2331dSJoe Perches	foreach my $signer (@signers) {
2421b9e2331dSJoe Perches	    $signer = deduplicate_email($signer);
2422b9e2331dSJoe Perches	}
242360db31acSJoe Perches	vcs_assign("commits", $total_commits, @signers);
24243c7385b8SJoe Perches    } else {
2425b9e2331dSJoe Perches	foreach my $signer (@signers) {
2426b9e2331dSJoe Perches	    $signer = deduplicate_email($signer);
2427b9e2331dSJoe Perches	}
242860db31acSJoe Perches	vcs_assign("modified commits", $total_commits, @signers);
2429cb7301c7SJoe Perches    }
2430cb7301c7SJoe Perches}
2431cb7301c7SJoe Perches
24324cad35a7SJoe Perchessub vcs_file_exists {
24334cad35a7SJoe Perches    my ($file) = @_;
24344cad35a7SJoe Perches
24354cad35a7SJoe Perches    my $exists;
24364cad35a7SJoe Perches
24374cad35a7SJoe Perches    my $vcs_used = vcs_exists();
24384cad35a7SJoe Perches    return 0 if (!$vcs_used);
24394cad35a7SJoe Perches
24404cad35a7SJoe Perches    my $cmd = $VCS_cmds{"file_exists_cmd"};
24414cad35a7SJoe Perches    $cmd =~ s/(\$\w+)/$1/eeg;		# interpolate $cmd
24428582fb59SJoe Perches    $cmd .= " 2>&1";
24434cad35a7SJoe Perches    $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
24444cad35a7SJoe Perches
24458582fb59SJoe Perches    return 0 if ($? != 0);
24468582fb59SJoe Perches
24474cad35a7SJoe Perches    return $exists;
24484cad35a7SJoe Perches}
24494cad35a7SJoe Perches
2450e1f75904STom Saegersub vcs_list_files {
2451e1f75904STom Saeger    my ($file) = @_;
2452e1f75904STom Saeger
2453e1f75904STom Saeger    my @lsfiles = ();
2454e1f75904STom Saeger
2455e1f75904STom Saeger    my $vcs_used = vcs_exists();
2456e1f75904STom Saeger    return 0 if (!$vcs_used);
2457e1f75904STom Saeger
2458e1f75904STom Saeger    my $cmd = $VCS_cmds{"list_files_cmd"};
2459e1f75904STom Saeger    $cmd =~ s/(\$\w+)/$1/eeg;   # interpolate $cmd
2460e1f75904STom Saeger    @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd);
2461e1f75904STom Saeger
2462e1f75904STom Saeger    return () if ($? != 0);
2463e1f75904STom Saeger
2464e1f75904STom Saeger    return @lsfiles;
2465e1f75904STom Saeger}
2466e1f75904STom Saeger
2467cb7301c7SJoe Perchessub uniq {
2468a8af2430SJoe Perches    my (@parms) = @_;
2469cb7301c7SJoe Perches
2470cb7301c7SJoe Perches    my %saw;
2471cb7301c7SJoe Perches    @parms = grep(!$saw{$_}++, @parms);
2472cb7301c7SJoe Perches    return @parms;
2473cb7301c7SJoe Perches}
2474cb7301c7SJoe Perches
2475cb7301c7SJoe Perchessub sort_and_uniq {
2476a8af2430SJoe Perches    my (@parms) = @_;
2477cb7301c7SJoe Perches
2478cb7301c7SJoe Perches    my %saw;
2479cb7301c7SJoe Perches    @parms = sort @parms;
2480cb7301c7SJoe Perches    @parms = grep(!$saw{$_}++, @parms);
2481cb7301c7SJoe Perches    return @parms;
2482cb7301c7SJoe Perches}
2483cb7301c7SJoe Perches
248403372dbbSJoe Perchessub clean_file_emails {
248503372dbbSJoe Perches    my (@file_emails) = @_;
248603372dbbSJoe Perches    my @fmt_emails = ();
248703372dbbSJoe Perches
248803372dbbSJoe Perches    foreach my $email (@file_emails) {
248903372dbbSJoe Perches	$email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
249003372dbbSJoe Perches	my ($name, $address) = parse_email($email);
249103372dbbSJoe Perches
24922639772aSAlvin Šipraga	# Strip quotes for easier processing, format_email will add them back
24932639772aSAlvin Šipraga	$name =~ s/^"(.*)"$/$1/;
24942639772aSAlvin Šipraga
24952639772aSAlvin Šipraga	# Split into name-like parts and remove stray punctuation particles
24969c334eb9SAlvin Šipraga	my @nw = split(/[^\p{L}\'\,\.\+-]/, $name);
24972639772aSAlvin Šipraga	@nw = grep(!/^[\'\,\.\+-]$/, @nw);
24982639772aSAlvin Šipraga
24992639772aSAlvin Šipraga	# Make a best effort to extract the name, and only the name, by taking
25002639772aSAlvin Šipraga	# only the last two names, or in the case of obvious initials, the last
25012639772aSAlvin Šipraga	# three names.
250203372dbbSJoe Perches	if (@nw > 2) {
250303372dbbSJoe Perches	    my $first = $nw[@nw - 3];
250403372dbbSJoe Perches	    my $middle = $nw[@nw - 2];
250503372dbbSJoe Perches	    my $last = $nw[@nw - 1];
250603372dbbSJoe Perches
25079c334eb9SAlvin Šipraga	    if (((length($first) == 1 && $first =~ m/\p{L}/) ||
250803372dbbSJoe Perches		 (length($first) == 2 && substr($first, -1) eq ".")) ||
250903372dbbSJoe Perches		(length($middle) == 1 ||
251003372dbbSJoe Perches		 (length($middle) == 2 && substr($middle, -1) eq "."))) {
251103372dbbSJoe Perches		$name = "$first $middle $last";
251203372dbbSJoe Perches	    } else {
251303372dbbSJoe Perches		$name = "$middle $last";
251403372dbbSJoe Perches	    }
25152639772aSAlvin Šipraga	} else {
25162639772aSAlvin Šipraga	    $name = "@nw";
251703372dbbSJoe Perches	}
251803372dbbSJoe Perches
251903372dbbSJoe Perches	if (substr($name, -1) =~ /[,\.]/) {
252003372dbbSJoe Perches	    $name = substr($name, 0, length($name) - 1);
252103372dbbSJoe Perches	}
252203372dbbSJoe Perches
252303372dbbSJoe Perches	if (substr($name, 0, 1) =~ /[,\.]/) {
252403372dbbSJoe Perches	    $name = substr($name, 1, length($name) - 1);
252503372dbbSJoe Perches	}
252603372dbbSJoe Perches
252703372dbbSJoe Perches	my $fmt_email = format_email($name, $address, $email_usename);
252803372dbbSJoe Perches	push(@fmt_emails, $fmt_email);
252903372dbbSJoe Perches    }
253003372dbbSJoe Perches    return @fmt_emails;
253103372dbbSJoe Perches}
253203372dbbSJoe Perches
25333c7385b8SJoe Perchessub merge_email {
25343c7385b8SJoe Perches    my @lines;
25353c7385b8SJoe Perches    my %saw;
25363c7385b8SJoe Perches
25373c7385b8SJoe Perches    for (@_) {
25383c7385b8SJoe Perches	my ($address, $role) = @$_;
25393c7385b8SJoe Perches	if (!$saw{$address}) {
25403c7385b8SJoe Perches	    if ($output_roles) {
254160db31acSJoe Perches		push(@lines, "$address ($role)");
25423c7385b8SJoe Perches	    } else {
254360db31acSJoe Perches		push(@lines, $address);
25443c7385b8SJoe Perches	    }
25453c7385b8SJoe Perches	    $saw{$address} = 1;
25463c7385b8SJoe Perches	}
25473c7385b8SJoe Perches    }
25483c7385b8SJoe Perches
25493c7385b8SJoe Perches    return @lines;
25503c7385b8SJoe Perches}
25513c7385b8SJoe Perches
2552cb7301c7SJoe Perchessub output {
2553a8af2430SJoe Perches    my (@parms) = @_;
2554cb7301c7SJoe Perches
2555cb7301c7SJoe Perches    if ($output_multiline) {
2556cb7301c7SJoe Perches	foreach my $line (@parms) {
2557cb7301c7SJoe Perches	    print("${line}\n");
2558cb7301c7SJoe Perches	}
2559cb7301c7SJoe Perches    } else {
2560cb7301c7SJoe Perches	print(join($output_separator, @parms));
2561cb7301c7SJoe Perches	print("\n");
2562cb7301c7SJoe Perches    }
2563cb7301c7SJoe Perches}
25641b5e1cf6SJoe Perches
25651b5e1cf6SJoe Perchesmy $rfc822re;
25661b5e1cf6SJoe Perches
25671b5e1cf6SJoe Perchessub make_rfc822re {
25681b5e1cf6SJoe Perches#   Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
25691b5e1cf6SJoe Perches#   comment.  We must allow for rfc822_lwsp (or comments) after each of these.
25701b5e1cf6SJoe Perches#   This regexp will only work on addresses which have had comments stripped
25711b5e1cf6SJoe Perches#   and replaced with rfc822_lwsp.
25721b5e1cf6SJoe Perches
25731b5e1cf6SJoe Perches    my $specials = '()<>@,;:\\\\".\\[\\]';
25741b5e1cf6SJoe Perches    my $controls = '\\000-\\037\\177';
25751b5e1cf6SJoe Perches
25761b5e1cf6SJoe Perches    my $dtext = "[^\\[\\]\\r\\\\]";
25771b5e1cf6SJoe Perches    my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
25781b5e1cf6SJoe Perches
25791b5e1cf6SJoe Perches    my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
25801b5e1cf6SJoe Perches
25811b5e1cf6SJoe Perches#   Use zero-width assertion to spot the limit of an atom.  A simple
25821b5e1cf6SJoe Perches#   $rfc822_lwsp* causes the regexp engine to hang occasionally.
25831b5e1cf6SJoe Perches    my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
25841b5e1cf6SJoe Perches    my $word = "(?:$atom|$quoted_string)";
25851b5e1cf6SJoe Perches    my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
25861b5e1cf6SJoe Perches
25871b5e1cf6SJoe Perches    my $sub_domain = "(?:$atom|$domain_literal)";
25881b5e1cf6SJoe Perches    my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
25891b5e1cf6SJoe Perches
25901b5e1cf6SJoe Perches    my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
25911b5e1cf6SJoe Perches
25921b5e1cf6SJoe Perches    my $phrase = "$word*";
25931b5e1cf6SJoe Perches    my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
25941b5e1cf6SJoe Perches    my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
25951b5e1cf6SJoe Perches    my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
25961b5e1cf6SJoe Perches
25971b5e1cf6SJoe Perches    my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
25981b5e1cf6SJoe Perches    my $address = "(?:$mailbox|$group)";
25991b5e1cf6SJoe Perches
26001b5e1cf6SJoe Perches    return "$rfc822_lwsp*$address";
26011b5e1cf6SJoe Perches}
26021b5e1cf6SJoe Perches
26031b5e1cf6SJoe Perchessub rfc822_strip_comments {
26041b5e1cf6SJoe Perches    my $s = shift;
26051b5e1cf6SJoe Perches#   Recursively remove comments, and replace with a single space.  The simpler
26061b5e1cf6SJoe Perches#   regexps in the Email Addressing FAQ are imperfect - they will miss escaped
26071b5e1cf6SJoe Perches#   chars in atoms, for example.
26081b5e1cf6SJoe Perches
26091b5e1cf6SJoe Perches    while ($s =~ s/^((?:[^"\\]|\\.)*
26101b5e1cf6SJoe Perches                    (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
26111b5e1cf6SJoe Perches                    \((?:[^()\\]|\\.)*\)/$1 /osx) {}
26121b5e1cf6SJoe Perches    return $s;
26131b5e1cf6SJoe Perches}
26141b5e1cf6SJoe Perches
26151b5e1cf6SJoe Perches#   valid: returns true if the parameter is an RFC822 valid address
26161b5e1cf6SJoe Perches#
261722dd5b0cSStephen Hemmingersub rfc822_valid {
26181b5e1cf6SJoe Perches    my $s = rfc822_strip_comments(shift);
26191b5e1cf6SJoe Perches
26201b5e1cf6SJoe Perches    if (!$rfc822re) {
26211b5e1cf6SJoe Perches        $rfc822re = make_rfc822re();
26221b5e1cf6SJoe Perches    }
26231b5e1cf6SJoe Perches
26241b5e1cf6SJoe Perches    return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
26251b5e1cf6SJoe Perches}
26261b5e1cf6SJoe Perches
26271b5e1cf6SJoe Perches#   validlist: In scalar context, returns true if the parameter is an RFC822
26281b5e1cf6SJoe Perches#              valid list of addresses.
26291b5e1cf6SJoe Perches#
26301b5e1cf6SJoe Perches#              In list context, returns an empty list on failure (an invalid
26311b5e1cf6SJoe Perches#              address was found); otherwise a list whose first element is the
26321b5e1cf6SJoe Perches#              number of addresses found and whose remaining elements are the
26331b5e1cf6SJoe Perches#              addresses.  This is needed to disambiguate failure (invalid)
26341b5e1cf6SJoe Perches#              from success with no addresses found, because an empty string is
26351b5e1cf6SJoe Perches#              a valid list.
26361b5e1cf6SJoe Perches
263722dd5b0cSStephen Hemmingersub rfc822_validlist {
26381b5e1cf6SJoe Perches    my $s = rfc822_strip_comments(shift);
26391b5e1cf6SJoe Perches
26401b5e1cf6SJoe Perches    if (!$rfc822re) {
26411b5e1cf6SJoe Perches        $rfc822re = make_rfc822re();
26421b5e1cf6SJoe Perches    }
26431b5e1cf6SJoe Perches    # * null list items are valid according to the RFC
26441b5e1cf6SJoe Perches    # * the '1' business is to aid in distinguishing failure from no results
26451b5e1cf6SJoe Perches
26461b5e1cf6SJoe Perches    my @r;
26471b5e1cf6SJoe Perches    if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
26481b5e1cf6SJoe Perches	$s =~ m/^$rfc822_char*$/) {
26491b5e1cf6SJoe Perches        while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
265060db31acSJoe Perches            push(@r, $1);
26511b5e1cf6SJoe Perches        }
26521b5e1cf6SJoe Perches        return wantarray ? (scalar(@r), @r) : 1;
26531b5e1cf6SJoe Perches    }
26541b5e1cf6SJoe Perches    return wantarray ? () : 0;
26551b5e1cf6SJoe Perches}
2656