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