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