qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

get_maintainer.pl (54652B)


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