From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([140.186.70.92]:53948) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QWXZA-0004Zd-2B for qemu-devel@nongnu.org; Tue, 14 Jun 2011 13:38:20 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1QWXZ5-0006g1-1g for qemu-devel@nongnu.org; Tue, 14 Jun 2011 13:38:15 -0400 Received: from mx1.redhat.com ([209.132.183.28]:2666) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QWXZ4-0006fr-Bp for qemu-devel@nongnu.org; Tue, 14 Jun 2011 13:38:11 -0400 Received: from int-mx10.intmail.prod.int.phx2.redhat.com (int-mx10.intmail.prod.int.phx2.redhat.com [10.5.11.23]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id p5EHc9t6032403 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK) for ; Tue, 14 Jun 2011 13:38:09 -0400 Received: from redhat.com (dhcp-1-35.tlv.redhat.com [10.35.1.35]) by int-mx10.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id p5EHc7lD019938 for ; Tue, 14 Jun 2011 13:38:08 -0400 Date: Tue, 14 Jun 2011 20:38:26 +0300 From: "Michael S. Tsirkin" Message-ID: <482b3679494573c75e7a30e28ad589148671491c.1308073076.git.mst@redhat.com> References: MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Subject: [Qemu-devel] [PATCH 1/2] get_maintainer.pl: copy utility from Linux List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Our MAINTAINERS file format matches Linux so get the utility to parse it from there. Updated as of linux 3.0-rc3 Signed-off-by: Michael S. Tsirkin --- scripts/get_maintainer.pl | 2159 +++++++++++++++++++++++++++++++++++++++= ++++++ 1 files changed, 2159 insertions(+), 0 deletions(-) create mode 100755 scripts/get_maintainer.pl diff --git a/scripts/get_maintainer.pl b/scripts/get_maintainer.pl new file mode 100755 index 0000000..d29a8d7 --- /dev/null +++ b/scripts/get_maintainer.pl @@ -0,0 +1,2159 @@ +#!/usr/bin/perl -w +# (c) 2007, Joe Perches +# created from checkpatch.pl +# +# Print selected MAINTAINERS information for +# the files modified in a patch or for a file +# +# usage: perl scripts/get_maintainer.pl [OPTIONS] +# perl scripts/get_maintainer.pl [OPTIONS] -f +# +# Licensed under the terms of the GNU GPL License version 2 + +use strict; + +my $P =3D $0; +my $V =3D '0.26'; + +use Getopt::Long qw(:config no_auto_abbrev); + +my $lk_path =3D "./"; +my $email =3D 1; +my $email_usename =3D 1; +my $email_maintainer =3D 1; +my $email_list =3D 1; +my $email_subscriber_list =3D 0; +my $email_git_penguin_chiefs =3D 0; +my $email_git =3D 0; +my $email_git_all_signature_types =3D 0; +my $email_git_blame =3D 0; +my $email_git_blame_signatures =3D 1; +my $email_git_fallback =3D 1; +my $email_git_min_signatures =3D 1; +my $email_git_max_maintainers =3D 5; +my $email_git_min_percent =3D 5; +my $email_git_since =3D "1-year-ago"; +my $email_hg_since =3D "-365"; +my $interactive =3D 0; +my $email_remove_duplicates =3D 1; +my $email_use_mailmap =3D 1; +my $output_multiline =3D 1; +my $output_separator =3D ", "; +my $output_roles =3D 0; +my $output_rolestats =3D 1; +my $scm =3D 0; +my $web =3D 0; +my $subsystem =3D 0; +my $status =3D 0; +my $keywords =3D 1; +my $sections =3D 0; +my $file_emails =3D 0; +my $from_filename =3D 0; +my $pattern_depth =3D 0; +my $version =3D 0; +my $help =3D 0; + +my $vcs_used =3D 0; + +my $exit =3D 0; + +my %commit_author_hash; +my %commit_signer_hash; + +my @penguin_chief =3D (); +push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org"); +#Andrew wants in on most everything - 2009/01/14 +#push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org"); + +my @penguin_chief_names =3D (); +foreach my $chief (@penguin_chief) { + if ($chief =3D~ m/^(.*):(.*)/) { + my $chief_name =3D $1; + my $chief_addr =3D $2; + push(@penguin_chief_names, $chief_name); + } +} +my $penguin_chiefs =3D "\(" . join("|", @penguin_chief_names) . "\)"; + +# Signature types of people who are either +# a) responsible for the code in question, or +# b) familiar enough with it to give relevant feedback +my @signature_tags =3D (); +push(@signature_tags, "Signed-off-by:"); +push(@signature_tags, "Reviewed-by:"); +push(@signature_tags, "Acked-by:"); + +# rfc822 email address - preloaded methods go here. +my $rfc822_lwsp =3D "(?:(?:\\r\\n)?[ \\t])"; +my $rfc822_char =3D '[\\000-\\377]'; + +# VCS command support: class-like functions and strings + +my %VCS_cmds; + +my %VCS_cmds_git =3D ( + "execute_cmd" =3D> \&git_execute_cmd, + "available" =3D> '(which("git") ne "") && (-d ".git")', + "find_signers_cmd" =3D> + "git log --no-color --since=3D\$email_git_since " . + '--format=3D"GitCommit: %H%n' . + 'GitAuthor: %an <%ae>%n' . + 'GitDate: %aD%n' . + 'GitSubject: %s%n' . + '%b%n"' . + " -- \$file", + "find_commit_signers_cmd" =3D> + "git log --no-color " . + '--format=3D"GitCommit: %H%n' . + 'GitAuthor: %an <%ae>%n' . + 'GitDate: %aD%n' . + 'GitSubject: %s%n' . + '%b%n"' . + " -1 \$commit", + "find_commit_author_cmd" =3D> + "git log --no-color " . + '--format=3D"GitCommit: %H%n' . + 'GitAuthor: %an <%ae>%n' . + 'GitDate: %aD%n' . + 'GitSubject: %s%n"' . + " -1 \$commit", + "blame_range_cmd" =3D> "git blame -l -L \$diff_start,+\$diff_length = \$file", + "blame_file_cmd" =3D> "git blame -l \$file", + "commit_pattern" =3D> "^GitCommit: ([0-9a-f]{40,40})", + "blame_commit_pattern" =3D> "^([0-9a-f]+) ", + "author_pattern" =3D> "^GitAuthor: (.*)", + "subject_pattern" =3D> "^GitSubject: (.*)", +); + +my %VCS_cmds_hg =3D ( + "execute_cmd" =3D> \&hg_execute_cmd, + "available" =3D> '(which("hg") ne "") && (-d ".hg")', + "find_signers_cmd" =3D> + "hg log --date=3D\$email_hg_since " . + "--template=3D'HgCommit: {node}\\n" . + "HgAuthor: {author}\\n" . + "HgSubject: {desc}\\n'" . + " -- \$file", + "find_commit_signers_cmd" =3D> + "hg log " . + "--template=3D'HgSubject: {desc}\\n'" . + " -r \$commit", + "find_commit_author_cmd" =3D> + "hg log " . + "--template=3D'HgCommit: {node}\\n" . + "HgAuthor: {author}\\n" . + "HgSubject: {desc|firstline}\\n'" . + " -r \$commit", + "blame_range_cmd" =3D> "", # not supported + "blame_file_cmd" =3D> "hg blame -n \$file", + "commit_pattern" =3D> "^HgCommit: ([0-9a-f]{40,40})", + "blame_commit_pattern" =3D> "^([ 0-9a-f]+):", + "author_pattern" =3D> "^HgAuthor: (.*)", + "subject_pattern" =3D> "^HgSubject: (.*)", +); + +my $conf =3D which_conf(".get_maintainer.conf"); +if (-f $conf) { + my @conf_args; + open(my $conffile, '<', "$conf") + or warn "$P: Can't find a readable .get_maintainer.conf file $!\n"; + + while (<$conffile>) { + my $line =3D $_; + + $line =3D~ s/\s*\n?$//g; + $line =3D~ s/^\s*//g; + $line =3D~ s/\s+/ /g; + + next if ($line =3D~ m/^\s*#/); + next if ($line =3D~ m/^\s*$/); + + my @words =3D split(" ", $line); + foreach my $word (@words) { + last if ($word =3D~ m/^#/); + push (@conf_args, $word); + } + } + close($conffile); + unshift(@ARGV, @conf_args) if @conf_args; +} + +if (!GetOptions( + 'email!' =3D> \$email, + 'git!' =3D> \$email_git, + 'git-all-signature-types!' =3D> \$email_git_all_signature_types, + 'git-blame!' =3D> \$email_git_blame, + 'git-blame-signatures!' =3D> \$email_git_blame_signatures, + 'git-fallback!' =3D> \$email_git_fallback, + 'git-chief-penguins!' =3D> \$email_git_penguin_chiefs, + 'git-min-signatures=3Di' =3D> \$email_git_min_signatures, + 'git-max-maintainers=3Di' =3D> \$email_git_max_maintainers, + 'git-min-percent=3Di' =3D> \$email_git_min_percent, + 'git-since=3Ds' =3D> \$email_git_since, + 'hg-since=3Ds' =3D> \$email_hg_since, + 'i|interactive!' =3D> \$interactive, + 'remove-duplicates!' =3D> \$email_remove_duplicates, + 'mailmap!' =3D> \$email_use_mailmap, + 'm!' =3D> \$email_maintainer, + 'n!' =3D> \$email_usename, + 'l!' =3D> \$email_list, + 's!' =3D> \$email_subscriber_list, + 'multiline!' =3D> \$output_multiline, + 'roles!' =3D> \$output_roles, + 'rolestats!' =3D> \$output_rolestats, + 'separator=3Ds' =3D> \$output_separator, + 'subsystem!' =3D> \$subsystem, + 'status!' =3D> \$status, + 'scm!' =3D> \$scm, + 'web!' =3D> \$web, + 'pattern-depth=3Di' =3D> \$pattern_depth, + 'k|keywords!' =3D> \$keywords, + 'sections!' =3D> \$sections, + 'fe|file-emails!' =3D> \$file_emails, + 'f|file' =3D> \$from_filename, + 'v|version' =3D> \$version, + 'h|help|usage' =3D> \$help, + )) { + die "$P: invalid argument - use --help if necessary\n"; +} + +if ($help !=3D 0) { + usage(); + exit 0; +} + +if ($version !=3D 0) { + print("${P} ${V}\n"); + exit 0; +} + +if (-t STDIN && !@ARGV) { + # We're talking to a terminal, but have no command line arguments. + die "$P: missing patchfile or -f file - use --help if necessary\n"; +} + +$output_multiline =3D 0 if ($output_separator ne ", "); +$output_rolestats =3D 1 if ($interactive); +$output_roles =3D 1 if ($output_rolestats); + +if ($sections) { + $email =3D 0; + $email_list =3D 0; + $scm =3D 0; + $status =3D 0; + $subsystem =3D 0; + $web =3D 0; + $keywords =3D 0; + $interactive =3D 0; +} else { + my $selections =3D $email + $scm + $status + $subsystem + $web; + if ($selections =3D=3D 0) { + die "$P: Missing required option: email, scm, status, subsystem or web= \n"; + } +} + +if ($email && + ($email_maintainer + $email_list + $email_subscriber_list + + $email_git + $email_git_penguin_chiefs + $email_git_blame) =3D=3D 0= ) { + die "$P: Please select at least 1 email option\n"; +} + +if (!top_of_kernel_tree($lk_path)) { + die "$P: The current directory does not appear to be " + . "a linux kernel source tree.\n"; +} + +## Read MAINTAINERS for type/value pairs + +my @typevalue =3D (); +my %keyword_hash; + +open (my $maint, '<', "${lk_path}MAINTAINERS") + or die "$P: Can't open MAINTAINERS: $!\n"; +while (<$maint>) { + my $line =3D $_; + + if ($line =3D~ m/^(\C):\s*(.*)/) { + my $type =3D $1; + my $value =3D $2; + + ##Filename pattern matching + if ($type eq "F" || $type eq "X") { + $value =3D~ s@\.@\\\.@g; ##Convert . to \. + $value =3D~ s/\*/\.\*/g; ##Convert * to .* + $value =3D~ s/\?/\./g; ##Convert ? to . + ##if pattern is a directory and it lacks a trailing slash, add one + if ((-d $value)) { + $value =3D~ s@([^/])$@$1/@; + } + } elsif ($type eq "K") { + $keyword_hash{@typevalue} =3D $value; + } + push(@typevalue, "$type:$value"); + } elsif (!/^(\s)*$/) { + $line =3D~ s/\n$//g; + push(@typevalue, $line); + } +} +close($maint); + + +# +# Read mail address map +# + +my $mailmap; + +read_mailmap(); + +sub read_mailmap { + $mailmap =3D { + names =3D> {}, + addresses =3D> {} + }; + + return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap")); + + open(my $mailmap_file, '<', "${lk_path}.mailmap") + or warn "$P: Can't open .mailmap: $!\n"; + + while (<$mailmap_file>) { + s/#.*$//; #strip comments + s/^\s+|\s+$//g; #trim + + next if (/^\s*$/); #skip empty lines + #entries have one of the following formats: + # name1 + # + # name1 + # name1 name2 + # (see man git-shortlog) + if (/^(.+)<(.+)>$/) { + my $real_name =3D $1; + my $address =3D $2; + + $real_name =3D~ s/\s+$//; + ($real_name, $address) =3D parse_email("$real_name <$address>"); + $mailmap->{names}->{$address} =3D $real_name; + + } elsif (/^<([^\s]+)>\s*<([^\s]+)>$/) { + my $real_address =3D $1; + my $wrong_address =3D $2; + + $mailmap->{addresses}->{$wrong_address} =3D $real_address; + + } elsif (/^(.+)<([^\s]+)>\s*<([^\s]+)>$/) { + my $real_name =3D $1; + my $real_address =3D $2; + my $wrong_address =3D $3; + + $real_name =3D~ s/\s+$//; + ($real_name, $real_address) =3D + parse_email("$real_name <$real_address>"); + $mailmap->{names}->{$wrong_address} =3D $real_name; + $mailmap->{addresses}->{$wrong_address} =3D $real_address; + + } elsif (/^(.+)<([^\s]+)>\s*([^\s].*)<([^\s]+)>$/) { + my $real_name =3D $1; + my $real_address =3D $2; + my $wrong_name =3D $3; + my $wrong_address =3D $4; + + $real_name =3D~ s/\s+$//; + ($real_name, $real_address) =3D + parse_email("$real_name <$real_address>"); + + $wrong_name =3D~ s/\s+$//; + ($wrong_name, $wrong_address) =3D + parse_email("$wrong_name <$wrong_address>"); + + my $wrong_email =3D format_email($wrong_name, $wrong_address, 1); + $mailmap->{names}->{$wrong_email} =3D $real_name; + $mailmap->{addresses}->{$wrong_email} =3D $real_address; + } + } + close($mailmap_file); +} + +## use the filenames on the command line or find the filenames in the pa= tchfiles + +my @files =3D (); +my @range =3D (); +my @keyword_tvi =3D (); +my @file_emails =3D (); + +if (!@ARGV) { + push(@ARGV, "&STDIN"); +} + +foreach my $file (@ARGV) { + if ($file ne "&STDIN") { + ##if $file is a directory and it lacks a trailing slash, add one + if ((-d $file)) { + $file =3D~ s@([^/])$@$1/@; + } elsif (!(-f $file)) { + die "$P: file '${file}' not found\n"; + } + } + if ($from_filename) { + push(@files, $file); + if ($file ne "MAINTAINERS" && -f $file && ($keywords || $file_emails)) = { + open(my $f, '<', $file) + or die "$P: Can't open $file: $!\n"; + my $text =3D do { local($/) ; <$f> }; + close($f); + if ($keywords) { + foreach my $line (keys %keyword_hash) { + if ($text =3D~ m/$keyword_hash{$line}/x) { + push(@keyword_tvi, $line); + } + } + } + if ($file_emails) { + my @poss_addr =3D $text =3D~ m$[A-Za-z=C0-=FF\"\' \,\.\+-]*\s*[\,]*\s*= [\(\<\{]{0,1}[A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+\.[A-Za-z0-9]+[\)\>\}]{0,= 1}$g; + push(@file_emails, clean_file_emails(@poss_addr)); + } + } + } else { + my $file_cnt =3D @files; + my $lastfile; + + open(my $patch, "< $file") + or die "$P: Can't open $file: $!\n"; + + # We can check arbitrary information before the patch + # like the commit message, mail headers, etc... + # This allows us to match arbitrary keywords against any part + # of a git format-patch generated file (subject tags, etc...) + + my $patch_prefix =3D ""; #Parsing the intro + + while (<$patch>) { + my $patch_line =3D $_; + if (m/^\+\+\+\s+(\S+)/) { + my $filename =3D $1; + $filename =3D~ s@^[^/]*/@@; + $filename =3D~ s@\n@@; + $lastfile =3D $filename; + push(@files, $filename); + $patch_prefix =3D "^[+-].*"; #Now parsing the actual patch + } elsif (m/^\@\@ -(\d+),(\d+)/) { + if ($email_git_blame) { + push(@range, "$lastfile:$1:$2"); + } + } elsif ($keywords) { + foreach my $line (keys %keyword_hash) { + if ($patch_line =3D~ m/${patch_prefix}$keyword_hash{$line}/x) { + push(@keyword_tvi, $line); + } + } + } + } + close($patch); + + if ($file_cnt =3D=3D @files) { + warn "$P: file '${file}' doesn't appear to be a patch. " + . "Add -f to options?\n"; + } + @files =3D sort_and_uniq(@files); + } +} + +@file_emails =3D uniq(@file_emails); + +my %email_hash_name; +my %email_hash_address; +my @email_to =3D (); +my %hash_list_to; +my @list_to =3D (); +my @scm =3D (); +my @web =3D (); +my @subsystem =3D (); +my @status =3D (); +my %deduplicate_name_hash =3D (); +my %deduplicate_address_hash =3D (); +my $signature_pattern; + +my @maintainers =3D get_maintainers(); + +if (@maintainers) { + @maintainers =3D merge_email(@maintainers); + output(@maintainers); +} + +if ($scm) { + @scm =3D uniq(@scm); + output(@scm); +} + +if ($status) { + @status =3D uniq(@status); + output(@status); +} + +if ($subsystem) { + @subsystem =3D uniq(@subsystem); + output(@subsystem); +} + +if ($web) { + @web =3D uniq(@web); + output(@web); +} + +exit($exit); + +sub range_is_maintained { + my ($start, $end) =3D @_; + + for (my $i =3D $start; $i < $end; $i++) { + my $line =3D $typevalue[$i]; + if ($line =3D~ m/^(\C):\s*(.*)/) { + my $type =3D $1; + my $value =3D $2; + if ($type eq 'S') { + if ($value =3D~ /(maintain|support)/i) { + return 1; + } + } + } + } + return 0; +} + +sub range_has_maintainer { + my ($start, $end) =3D @_; + + for (my $i =3D $start; $i < $end; $i++) { + my $line =3D $typevalue[$i]; + if ($line =3D~ m/^(\C):\s*(.*)/) { + my $type =3D $1; + my $value =3D $2; + if ($type eq 'M') { + return 1; + } + } + } + return 0; +} + +sub get_maintainers { + %email_hash_name =3D (); + %email_hash_address =3D (); + %commit_author_hash =3D (); + %commit_signer_hash =3D (); + @email_to =3D (); + %hash_list_to =3D (); + @list_to =3D (); + @scm =3D (); + @web =3D (); + @subsystem =3D (); + @status =3D (); + %deduplicate_name_hash =3D (); + %deduplicate_address_hash =3D (); + if ($email_git_all_signature_types) { + $signature_pattern =3D "(.+?)[Bb][Yy]:"; + } else { + $signature_pattern =3D "\(" . join("|", @signature_tags) . "\)"; + } + + # Find responsible parties + + my %exact_pattern_match_hash =3D (); + + foreach my $file (@files) { + + my %hash; + my $tvi =3D find_first_section(); + while ($tvi < @typevalue) { + my $start =3D find_starting_index($tvi); + my $end =3D find_ending_index($tvi); + my $exclude =3D 0; + my $i; + + #Do not match excluded file patterns + + for ($i =3D $start; $i < $end; $i++) { + my $line =3D $typevalue[$i]; + if ($line =3D~ m/^(\C):\s*(.*)/) { + my $type =3D $1; + my $value =3D $2; + if ($type eq 'X') { + if (file_match_pattern($file, $value)) { + $exclude =3D 1; + last; + } + } + } + } + + if (!$exclude) { + for ($i =3D $start; $i < $end; $i++) { + my $line =3D $typevalue[$i]; + if ($line =3D~ m/^(\C):\s*(.*)/) { + my $type =3D $1; + my $value =3D $2; + if ($type eq 'F') { + if (file_match_pattern($file, $value)) { + my $value_pd =3D ($value =3D~ tr@/@@); + my $file_pd =3D ($file =3D~ tr@/@@); + $value_pd++ if (substr($value,-1,1) ne "/"); + $value_pd =3D -1 if ($value =3D~ /^\.\*/); + if ($value_pd >=3D $file_pd && + range_is_maintained($start, $end) && + range_has_maintainer($start, $end)) { + $exact_pattern_match_hash{$file} =3D 1; + } + if ($pattern_depth =3D=3D 0 || + (($file_pd - $value_pd) < $pattern_depth)) { + $hash{$tvi} =3D $value_pd; + } + } + } + } + } + } + $tvi =3D $end + 1; + } + + foreach my $line (sort {$hash{$b} <=3D> $hash{$a}} keys %hash) { + add_categories($line); + if ($sections) { + my $i; + my $start =3D find_starting_index($line); + my $end =3D find_ending_index($line); + for ($i =3D $start; $i < $end; $i++) { + my $line =3D $typevalue[$i]; + if ($line =3D~ /^[FX]:/) { ##Restore file patterns + $line =3D~ s/([^\\])\.([^\*])/$1\?$2/g; + $line =3D~ s/([^\\])\.$/$1\?/g; ##Convert . back to ? + $line =3D~ s/\\\./\./g; ##Convert \. to . + $line =3D~ s/\.\*/\*/g; ##Convert .* to * + } + $line =3D~ s/^([A-Z]):/$1:\t/g; + print("$line\n"); + } + print("\n"); + } + } + } + + if ($keywords) { + @keyword_tvi =3D sort_and_uniq(@keyword_tvi); + foreach my $line (@keyword_tvi) { + add_categories($line); + } + } + + foreach my $email (@email_to, @list_to) { + $email->[0] =3D deduplicate_email($email->[0]); + } + + foreach my $file (@files) { + if ($email && + ($email_git || ($email_git_fallback && + !$exact_pattern_match_hash{$file}))) { + vcs_file_signoffs($file); + } + if ($email && $email_git_blame) { + vcs_file_blame($file); + } + } + + if ($email) { + foreach my $chief (@penguin_chief) { + if ($chief =3D~ m/^(.*):(.*)/) { + my $email_address; + + $email_address =3D format_email($1, $2, $email_usename); + if ($email_git_penguin_chiefs) { + push(@email_to, [$email_address, 'chief penguin']); + } else { + @email_to =3D grep($_->[0] !~ /${email_address}/, @email_to); + } + } + } + + foreach my $email (@file_emails) { + my ($name, $address) =3D parse_email($email); + + my $tmp_email =3D format_email($name, $address, $email_usename); + push_email_address($tmp_email, ''); + add_role($tmp_email, 'in file'); + } + } + + my @to =3D (); + if ($email || $email_list) { + if ($email) { + @to =3D (@to, @email_to); + } + if ($email_list) { + @to =3D (@to, @list_to); + } + } + + if ($interactive) { + @to =3D interactive_get_maintainers(\@to); + } + + return @to; +} + +sub file_match_pattern { + my ($file, $pattern) =3D @_; + if (substr($pattern, -1) eq "/") { + if ($file =3D~ m@^$pattern@) { + return 1; + } + } else { + if ($file =3D~ m@^$pattern@) { + my $s1 =3D ($file =3D~ tr@/@@); + my $s2 =3D ($pattern =3D~ tr@/@@); + if ($s1 =3D=3D $s2) { + return 1; + } + } + } + return 0; +} + +sub usage { + print < print email address(es) if any + --git =3D> include recent git \*-by: signers + --git-all-signature-types =3D> include signers regardless of signatu= re type + or use only ${signature_pattern} signers (default: $email_git_al= l_signature_types) + --git-fallback =3D> use git when no exact MAINTAINERS pattern (defau= lt: $email_git_fallback) + --git-chief-penguins =3D> include ${penguin_chiefs} + --git-min-signatures =3D> number of signatures required (default: $e= mail_git_min_signatures) + --git-max-maintainers =3D> maximum maintainers to add (default: $ema= il_git_max_maintainers) + --git-min-percent =3D> minimum percentage of commits required (defau= lt: $email_git_min_percent) + --git-blame =3D> use git blame to find modified commits for patch or= file + --git-since =3D> git history to use (default: $email_git_since) + --hg-since =3D> hg history to use (default: $email_hg_since) + --interactive =3D> display a menu (mostly useful if used with the --= git option) + --m =3D> include maintainer(s) if any + --n =3D> include name 'Full Name ' + --l =3D> include list(s) if any + --s =3D> include subscriber only list(s) if any + --remove-duplicates =3D> minimize duplicate email names/addresses + --roles =3D> show roles (status:subsystem, git-signer, list, etc...) + --rolestats =3D> show roles and statistics (commits/total_commits, %= ) + --file-emails =3D> add email addresses found in -f file (default: 0 = (off)) + --scm =3D> print SCM tree(s) if any + --status =3D> print status if any + --subsystem =3D> print subsystem name if any + --web =3D> print website(s) if any + +Output type options: + --separator [, ] =3D> separator for multiple entries on 1 line + using --separator also sets --nomultiline if --separator is not [, ] + --multiline =3D> print 1 entry per line + +Other options: + --pattern-depth =3D> Number of pattern directory traversals (default: = 0 (all)) + --keywords =3D> scan patch for keywords (default: $keywords) + --sections =3D> print all of the subsystem sections with pattern match= es + --mailmap =3D> use .mailmap file (default: $email_use_mailmap) + --version =3D> show version + --help =3D> show this help information + +Default options: + [--email --nogit --git-fallback --m --n --l --multiline -pattern-depth= =3D0 + --remove-duplicates --rolestats] + +Notes: + Using "-f directory" may give unexpected results: + Used with "--git", git signators for _all_ files in and below + directory are examined as git recurses directories. + Any specified X: (exclude) pattern matches are _not_ ignored. + Used with "--nogit", directory is used as a pattern match, + no individual file within the directory or subdirectory + is matched. + Used with "--git-blame", does not iterate all files in directory + Using "--git-blame" is slow and may add old committers and authors + that are no longer active maintainers to the output. + Using "--roles" or "--rolestats" with git send-email --cc-cmd or any + other automated tools that expect only ["name"] + may not work because of additional output after . + Using "--rolestats" and "--git-blame" shows the #/total=3D% commits, + not the percentage of the entire file authored. # of commits is + not a good measure of amount of code authored. 1 major commit may + contain a thousand lines, 5 trivial commits may modify a single li= ne. + If git is not installed, but mercurial (hg) is installed and an .hg + repository exists, the following options apply to mercurial: + --git, + --git-min-signatures, --git-max-maintainers, --git-min-percent= , and + --git-blame + Use --hg-since not --git-since to control date selection + File ".get_maintainer.conf", if it exists in the linux kernel source r= oot + directory, can change whatever get_maintainer defaults are desired. + Entries in this file can be any command line argument. + This file is prepended to any additional command line arguments. + Multiple lines and # comments are allowed. +EOT +} + +sub top_of_kernel_tree { + my ($lk_path) =3D @_; + + if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") = { + $lk_path .=3D "/"; + } + if ( (-f "${lk_path}COPYING") + && (-f "${lk_path}CREDITS") + && (-f "${lk_path}Kbuild") + && (-f "${lk_path}MAINTAINERS") + && (-f "${lk_path}Makefile") + && (-f "${lk_path}README") + && (-d "${lk_path}Documentation") + && (-d "${lk_path}arch") + && (-d "${lk_path}include") + && (-d "${lk_path}drivers") + && (-d "${lk_path}fs") + && (-d "${lk_path}init") + && (-d "${lk_path}ipc") + && (-d "${lk_path}kernel") + && (-d "${lk_path}lib") + && (-d "${lk_path}scripts")) { + return 1; + } + return 0; +} + +sub parse_email { + my ($formatted_email) =3D @_; + + my $name =3D ""; + my $address =3D ""; + + if ($formatted_email =3D~ /^([^<]+)<(.+\@.*)>.*$/) { + $name =3D $1; + $address =3D $2; + } elsif ($formatted_email =3D~ /^\s*<(.+\@\S*)>.*$/) { + $address =3D $1; + } elsif ($formatted_email =3D~ /^(.+\@\S*).*$/) { + $address =3D $1; + } + + $name =3D~ s/^\s+|\s+$//g; + $name =3D~ s/^\"|\"$//g; + $address =3D~ s/^\s+|\s+$//g; + + if ($name =3D~ /[^\w \-]/i) { ##has "must quote" chars + $name =3D~ s/(?"; + } + } else { + $formatted_email =3D $address; + } + + return $formatted_email; +} + +sub find_first_section { + my $index =3D 0; + + while ($index < @typevalue) { + my $tv =3D $typevalue[$index]; + if (($tv =3D~ m/^(\C):\s*(.*)/)) { + last; + } + $index++; + } + + return $index; +} + +sub find_starting_index { + my ($index) =3D @_; + + while ($index > 0) { + my $tv =3D $typevalue[$index]; + if (!($tv =3D~ m/^(\C):\s*(.*)/)) { + last; + } + $index--; + } + + return $index; +} + +sub find_ending_index { + my ($index) =3D @_; + + while ($index < @typevalue) { + my $tv =3D $typevalue[$index]; + if (!($tv =3D~ m/^(\C):\s*(.*)/)) { + last; + } + $index++; + } + + return $index; +} + +sub get_maintainer_role { + my ($index) =3D @_; + + my $i; + my $start =3D find_starting_index($index); + my $end =3D find_ending_index($index); + + my $role; + my $subsystem =3D $typevalue[$start]; + if (length($subsystem) > 20) { + $subsystem =3D substr($subsystem, 0, 17); + $subsystem =3D~ s/\s*$//; + $subsystem =3D $subsystem . "..."; + } + + for ($i =3D $start + 1; $i < $end; $i++) { + my $tv =3D $typevalue[$i]; + if ($tv =3D~ m/^(\C):\s*(.*)/) { + my $ptype =3D $1; + my $pvalue =3D $2; + if ($ptype eq "S") { + $role =3D $pvalue; + } + } + } + + $role =3D lc($role); + if ($role eq "supported") { + $role =3D "supporter"; + } elsif ($role eq "maintained") { + $role =3D "maintainer"; + } elsif ($role eq "odd fixes") { + $role =3D "odd fixer"; + } elsif ($role eq "orphan") { + $role =3D "orphan minder"; + } elsif ($role eq "obsolete") { + $role =3D "obsolete minder"; + } elsif ($role eq "buried alive in reporters") { + $role =3D "chief penguin"; + } + + return $role . ":" . $subsystem; +} + +sub get_list_role { + my ($index) =3D @_; + + my $i; + my $start =3D find_starting_index($index); + my $end =3D find_ending_index($index); + + my $subsystem =3D $typevalue[$start]; + if (length($subsystem) > 20) { + $subsystem =3D substr($subsystem, 0, 17); + $subsystem =3D~ s/\s*$//; + $subsystem =3D $subsystem . "..."; + } + + if ($subsystem eq "THE REST") { + $subsystem =3D ""; + } + + return $subsystem; +} + +sub add_categories { + my ($index) =3D @_; + + my $i; + my $start =3D find_starting_index($index); + my $end =3D find_ending_index($index); + + push(@subsystem, $typevalue[$start]); + + for ($i =3D $start + 1; $i < $end; $i++) { + my $tv =3D $typevalue[$i]; + if ($tv =3D~ m/^(\C):\s*(.*)/) { + my $ptype =3D $1; + my $pvalue =3D $2; + if ($ptype eq "L") { + my $list_address =3D $pvalue; + my $list_additional =3D ""; + my $list_role =3D get_list_role($i); + + if ($list_role ne "") { + $list_role =3D ":" . $list_role; + } + if ($list_address =3D~ m/([^\s]+)\s+(.*)$/) { + $list_address =3D $1; + $list_additional =3D $2; + } + if ($list_additional =3D~ m/subscribers-only/) { + if ($email_subscriber_list) { + if (!$hash_list_to{lc($list_address)}) { + $hash_list_to{lc($list_address)} =3D 1; + push(@list_to, [$list_address, + "subscriber list${list_role}"]); + } + } + } else { + if ($email_list) { + if (!$hash_list_to{lc($list_address)}) { + $hash_list_to{lc($list_address)} =3D 1; + push(@list_to, [$list_address, + "open list${list_role}"]); + } + } + } + } elsif ($ptype eq "M") { + my ($name, $address) =3D parse_email($pvalue); + if ($name eq "") { + if ($i > 0) { + my $tv =3D $typevalue[$i - 1]; + if ($tv =3D~ m/^(\C):\s*(.*)/) { + if ($1 eq "P") { + $name =3D $2; + $pvalue =3D format_email($name, $address, $email_usename); + } + } + } + } + if ($email_maintainer) { + my $role =3D get_maintainer_role($i); + push_email_addresses($pvalue, $role); + } + } elsif ($ptype eq "T") { + push(@scm, $pvalue); + } elsif ($ptype eq "W") { + push(@web, $pvalue); + } elsif ($ptype eq "S") { + push(@status, $pvalue); + } + } + } +} + +sub email_inuse { + my ($name, $address) =3D @_; + + return 1 if (($name eq "") && ($address eq "")); + return 1 if (($name ne "") && exists($email_hash_name{lc($name)})); + return 1 if (($address ne "") && exists($email_hash_address{lc($addr= ess)})); + + return 0; +} + +sub push_email_address { + my ($line, $role) =3D @_; + + my ($name, $address) =3D parse_email($line); + + if ($address eq "") { + return 0; + } + + if (!$email_remove_duplicates) { + push(@email_to, [format_email($name, $address, $email_usename), $role])= ; + } elsif (!email_inuse($name, $address)) { + push(@email_to, [format_email($name, $address, $email_usename), $role])= ; + $email_hash_name{lc($name)}++ if ($name ne ""); + $email_hash_address{lc($address)}++; + } + + return 1; +} + +sub push_email_addresses { + my ($address, $role) =3D @_; + + my @address_list =3D (); + + if (rfc822_valid($address)) { + push_email_address($address, $role); + } elsif (@address_list =3D rfc822_validlist($address)) { + my $array_count =3D shift(@address_list); + while (my $entry =3D shift(@address_list)) { + push_email_address($entry, $role); + } + } else { + if (!push_email_address($address, $role)) { + warn("Invalid MAINTAINERS address: '" . $address . "'\n"); + } + } +} + +sub add_role { + my ($line, $role) =3D @_; + + my ($name, $address) =3D parse_email($line); + my $email =3D format_email($name, $address, $email_usename); + + foreach my $entry (@email_to) { + if ($email_remove_duplicates) { + my ($entry_name, $entry_address) =3D parse_email($entry->[0]); + if (($name eq $entry_name || $address eq $entry_address) + && ($role eq "" || !($entry->[1] =3D~ m/$role/)) + ) { + if ($entry->[1] eq "") { + $entry->[1] =3D "$role"; + } else { + $entry->[1] =3D "$entry->[1],$role"; + } + } + } else { + if ($email eq $entry->[0] + && ($role eq "" || !($entry->[1] =3D~ m/$role/)) + ) { + if ($entry->[1] eq "") { + $entry->[1] =3D "$role"; + } else { + $entry->[1] =3D "$entry->[1],$role"; + } + } + } + } +} + +sub which { + my ($bin) =3D @_; + + foreach my $path (split(/:/, $ENV{PATH})) { + if (-e "$path/$bin") { + return "$path/$bin"; + } + } + + return ""; +} + +sub which_conf { + my ($conf) =3D @_; + + foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) { + if (-e "$path/$conf") { + return "$path/$conf"; + } + } + + return ""; +} + +sub mailmap_email { + my ($line) =3D @_; + + my ($name, $address) =3D parse_email($line); + my $email =3D format_email($name, $address, 1); + my $real_name =3D $name; + my $real_address =3D $address; + + if (exists $mailmap->{names}->{$email} || + exists $mailmap->{addresses}->{$email}) { + if (exists $mailmap->{names}->{$email}) { + $real_name =3D $mailmap->{names}->{$email}; + } + if (exists $mailmap->{addresses}->{$email}) { + $real_address =3D $mailmap->{addresses}->{$email}; + } + } else { + if (exists $mailmap->{names}->{$address}) { + $real_name =3D $mailmap->{names}->{$address}; + } + if (exists $mailmap->{addresses}->{$address}) { + $real_address =3D $mailmap->{addresses}->{$address}; + } + } + return format_email($real_name, $real_address, 1); +} + +sub mailmap { + my (@addresses) =3D @_; + + my @mapped_emails =3D (); + foreach my $line (@addresses) { + push(@mapped_emails, mailmap_email($line)); + } + merge_by_realname(@mapped_emails) if ($email_use_mailmap); + return @mapped_emails; +} + +sub merge_by_realname { + my %address_map; + my (@emails) =3D @_; + + foreach my $email (@emails) { + my ($name, $address) =3D parse_email($email); + if (exists $address_map{$name}) { + $address =3D $address_map{$name}; + $email =3D format_email($name, $address, 1); + } else { + $address_map{$name} =3D $address; + } + } +} + +sub git_execute_cmd { + my ($cmd) =3D @_; + my @lines =3D (); + + my $output =3D `$cmd`; + $output =3D~ s/^\s*//gm; + @lines =3D split("\n", $output); + + return @lines; +} + +sub hg_execute_cmd { + my ($cmd) =3D @_; + my @lines =3D (); + + my $output =3D `$cmd`; + @lines =3D split("\n", $output); + + return @lines; +} + +sub extract_formatted_signatures { + my (@signature_lines) =3D @_; + + my @type =3D @signature_lines; + + s/\s*(.*):.*/$1/ for (@type); + + # cut -f2- -d":" + s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines); + +## Reformat email addresses (with names) to avoid badly written signatur= es + + foreach my $signer (@signature_lines) { + $signer =3D deduplicate_email($signer); + } + + return (\@type, \@signature_lines); +} + +sub vcs_find_signers { + my ($cmd) =3D @_; + my $commits; + my @lines =3D (); + my @signatures =3D (); + + @lines =3D &{$VCS_cmds{"execute_cmd"}}($cmd); + + my $pattern =3D $VCS_cmds{"commit_pattern"}; + + $commits =3D grep(/$pattern/, @lines); # of commits + + @signatures =3D grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines); + + return (0, @signatures) if !@signatures; + + save_commits_by_author(@lines) if ($interactive); + save_commits_by_signer(@lines) if ($interactive); + + if (!$email_git_penguin_chiefs) { + @signatures =3D grep(!/${penguin_chiefs}/i, @signatures); + } + + my ($types_ref, $signers_ref) =3D extract_formatted_signatures(@sign= atures); + + return ($commits, @$signers_ref); +} + +sub vcs_find_author { + my ($cmd) =3D @_; + my @lines =3D (); + + @lines =3D &{$VCS_cmds{"execute_cmd"}}($cmd); + + if (!$email_git_penguin_chiefs) { + @lines =3D grep(!/${penguin_chiefs}/i, @lines); + } + + return @lines if !@lines; + + my @authors =3D (); + foreach my $line (@lines) { + if ($line =3D~ m/$VCS_cmds{"author_pattern"}/) { + my $author =3D $1; + my ($name, $address) =3D parse_email($author); + $author =3D format_email($name, $address, 1); + push(@authors, $author); + } + } + + save_commits_by_author(@lines) if ($interactive); + save_commits_by_signer(@lines) if ($interactive); + + return @authors; +} + +sub vcs_save_commits { + my ($cmd) =3D @_; + my @lines =3D (); + my @commits =3D (); + + @lines =3D &{$VCS_cmds{"execute_cmd"}}($cmd); + + foreach my $line (@lines) { + if ($line =3D~ m/$VCS_cmds{"blame_commit_pattern"}/) { + push(@commits, $1); + } + } + + return @commits; +} + +sub vcs_blame { + my ($file) =3D @_; + my $cmd; + my @commits =3D (); + + return @commits if (!(-f $file)); + + if (@range && $VCS_cmds{"blame_range_cmd"} eq "") { + my @all_commits =3D (); + + $cmd =3D $VCS_cmds{"blame_file_cmd"}; + $cmd =3D~ s/(\$\w+)/$1/eeg; #interpolate $cmd + @all_commits =3D vcs_save_commits($cmd); + + foreach my $file_range_diff (@range) { + next if (!($file_range_diff =3D~ m/(.+):(.+):(.+)/)); + my $diff_file =3D $1; + my $diff_start =3D $2; + my $diff_length =3D $3; + next if ("$file" ne "$diff_file"); + for (my $i =3D $diff_start; $i < $diff_start + $diff_length; $i++) = { + push(@commits, $all_commits[$i]); + } + } + } elsif (@range) { + foreach my $file_range_diff (@range) { + next if (!($file_range_diff =3D~ m/(.+):(.+):(.+)/)); + my $diff_file =3D $1; + my $diff_start =3D $2; + my $diff_length =3D $3; + next if ("$file" ne "$diff_file"); + $cmd =3D $VCS_cmds{"blame_range_cmd"}; + $cmd =3D~ s/(\$\w+)/$1/eeg; #interpolate $cmd + push(@commits, vcs_save_commits($cmd)); + } + } else { + $cmd =3D $VCS_cmds{"blame_file_cmd"}; + $cmd =3D~ s/(\$\w+)/$1/eeg; #interpolate $cmd + @commits =3D vcs_save_commits($cmd); + } + + foreach my $commit (@commits) { + $commit =3D~ s/^\^//g; + } + + return @commits; +} + +my $printed_novcs =3D 0; +sub vcs_exists { + %VCS_cmds =3D %VCS_cmds_git; + return 1 if eval $VCS_cmds{"available"}; + %VCS_cmds =3D %VCS_cmds_hg; + return 2 if eval $VCS_cmds{"available"}; + %VCS_cmds =3D (); + if (!$printed_novcs) { + warn("$P: No supported VCS found. Add --nogit to options?\n"); + warn("Using a git repository produces better results.\n"); + warn("Try Linus Torvalds' latest git repository using:\n"); + warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/= linux-2.6.git\n"); + $printed_novcs =3D 1; + } + return 0; +} + +sub vcs_is_git { + vcs_exists(); + return $vcs_used =3D=3D 1; +} + +sub vcs_is_hg { + return $vcs_used =3D=3D 2; +} + +sub interactive_get_maintainers { + my ($list_ref) =3D @_; + my @list =3D @$list_ref; + + vcs_exists(); + + my %selected; + my %authored; + my %signed; + my $count =3D 0; + my $maintained =3D 0; + foreach my $entry (@list) { + $maintained =3D 1 if ($entry->[1] =3D~ /^(maintainer|supporter)/i); + $selected{$count} =3D 1; + $authored{$count} =3D 0; + $signed{$count} =3D 0; + $count++; + } + + #menu loop + my $done =3D 0; + my $print_options =3D 0; + my $redraw =3D 1; + while (!$done) { + $count =3D 0; + if ($redraw) { + printf STDERR "\n%1s %2s %-65s", + "*", "#", "email/list and role:stats"; + if ($email_git || + ($email_git_fallback && !$maintained) || + $email_git_blame) { + print STDERR "auth sign"; + } + print STDERR "\n"; + foreach my $entry (@list) { + my $email =3D $entry->[0]; + my $role =3D $entry->[1]; + my $sel =3D ""; + $sel =3D "*" if ($selected{$count}); + my $commit_author =3D $commit_author_hash{$email}; + my $commit_signer =3D $commit_signer_hash{$email}; + my $authored =3D 0; + my $signed =3D 0; + $authored++ for (@{$commit_author}); + $signed++ for (@{$commit_signer}); + printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email; + printf STDERR "%4d %4d", $authored, $signed + if ($authored > 0 || $signed > 0); + printf STDERR "\n %s\n", $role; + if ($authored{$count}) { + my $commit_author =3D $commit_author_hash{$email}; + foreach my $ref (@{$commit_author}) { + print STDERR " Author: @{$ref}[1]\n"; + } + } + if ($signed{$count}) { + my $commit_signer =3D $commit_signer_hash{$email}; + foreach my $ref (@{$commit_signer}) { + print STDERR " @{$ref}[2]: @{$ref}[1]\n"; + } + } + + $count++; + } + } + my $date_ref =3D \$email_git_since; + $date_ref =3D \$email_hg_since if (vcs_is_hg()); + if ($print_options) { + $print_options =3D 0; + if (vcs_exists()) { + print STDERR <; + chomp($input); + + $redraw =3D 1; + my $rerun =3D 0; + my @wish =3D split(/[, ]+/, $input); + foreach my $nr (@wish) { + $nr =3D lc($nr); + my $sel =3D substr($nr, 0, 1); + my $str =3D substr($nr, 1); + my $val =3D 0; + $val =3D $1 if $str =3D~ /^(\d+)$/; + + if ($sel eq "y") { + $interactive =3D 0; + $done =3D 1; + $output_rolestats =3D 0; + $output_roles =3D 0; + last; + } elsif ($nr =3D~ /^\d+$/ && $nr > 0 && $nr <=3D $count) { + $selected{$nr - 1} =3D !$selected{$nr - 1}; + } elsif ($sel eq "*" || $sel eq '^') { + my $toggle =3D 0; + $toggle =3D 1 if ($sel eq '*'); + for (my $i =3D 0; $i < $count; $i++) { + $selected{$i} =3D $toggle; + } + } elsif ($sel eq "0") { + for (my $i =3D 0; $i < $count; $i++) { + $selected{$i} =3D !$selected{$i}; + } + } elsif ($sel eq "t") { + if (lc($str) eq "m") { + for (my $i =3D 0; $i < $count; $i++) { + $selected{$i} =3D !$selected{$i} + if ($list[$i]->[1] =3D~ /^(maintainer|supporter)/i); + } + } elsif (lc($str) eq "g") { + for (my $i =3D 0; $i < $count; $i++) { + $selected{$i} =3D !$selected{$i} + if ($list[$i]->[1] =3D~ /^(author|commit|signer)/i); + } + } elsif (lc($str) eq "l") { + for (my $i =3D 0; $i < $count; $i++) { + $selected{$i} =3D !$selected{$i} + if ($list[$i]->[1] =3D~ /^(open list)/i); + } + } elsif (lc($str) eq "s") { + for (my $i =3D 0; $i < $count; $i++) { + $selected{$i} =3D !$selected{$i} + if ($list[$i]->[1] =3D~ /^(subscriber list)/i); + } + } + } elsif ($sel eq "a") { + if ($val > 0 && $val <=3D $count) { + $authored{$val - 1} =3D !$authored{$val - 1}; + } elsif ($str eq '*' || $str eq '^') { + my $toggle =3D 0; + $toggle =3D 1 if ($str eq '*'); + for (my $i =3D 0; $i < $count; $i++) { + $authored{$i} =3D $toggle; + } + } + } elsif ($sel eq "s") { + if ($val > 0 && $val <=3D $count) { + $signed{$val - 1} =3D !$signed{$val - 1}; + } elsif ($str eq '*' || $str eq '^') { + my $toggle =3D 0; + $toggle =3D 1 if ($str eq '*'); + for (my $i =3D 0; $i < $count; $i++) { + $signed{$i} =3D $toggle; + } + } + } elsif ($sel eq "o") { + $print_options =3D 1; + $redraw =3D 1; + } elsif ($sel eq "g") { + if ($str eq "f") { + bool_invert(\$email_git_fallback); + } else { + bool_invert(\$email_git); + } + $rerun =3D 1; + } elsif ($sel eq "b") { + if ($str eq "s") { + bool_invert(\$email_git_blame_signatures); + } else { + bool_invert(\$email_git_blame); + } + $rerun =3D 1; + } elsif ($sel eq "c") { + if ($val > 0) { + $email_git_min_signatures =3D $val; + $rerun =3D 1; + } + } elsif ($sel eq "x") { + if ($val > 0) { + $email_git_max_maintainers =3D $val; + $rerun =3D 1; + } + } elsif ($sel eq "%") { + if ($str ne "" && $val >=3D 0) { + $email_git_min_percent =3D $val; + $rerun =3D 1; + } + } elsif ($sel eq "d") { + if (vcs_is_git()) { + $email_git_since =3D $str; + } elsif (vcs_is_hg()) { + $email_hg_since =3D $str; + } + $rerun =3D 1; + } elsif ($sel eq "t") { + bool_invert(\$email_git_all_signature_types); + $rerun =3D 1; + } elsif ($sel eq "f") { + bool_invert(\$file_emails); + $rerun =3D 1; + } elsif ($sel eq "r") { + bool_invert(\$email_remove_duplicates); + $rerun =3D 1; + } elsif ($sel eq "m") { + bool_invert(\$email_use_mailmap); + read_mailmap(); + $rerun =3D 1; + } elsif ($sel eq "k") { + bool_invert(\$keywords); + $rerun =3D 1; + } elsif ($sel eq "p") { + if ($str ne "" && $val >=3D 0) { + $pattern_depth =3D $val; + $rerun =3D 1; + } + } elsif ($sel eq "h" || $sel eq "?") { + print STDERR <[0]; + $address =3D $deduplicate_name_hash{lc($name)}->[1]; + $matched =3D 1; + } elsif ($deduplicate_address_hash{lc($address)}) { + $name =3D $deduplicate_address_hash{lc($address)}->[0]; + $address =3D $deduplicate_address_hash{lc($address)}->[1]; + $matched =3D 1; + } + if (!$matched) { + $deduplicate_name_hash{lc($name)} =3D [ $name, $address ]; + $deduplicate_address_hash{lc($address)} =3D [ $name, $address ]; + } + $email =3D format_email($name, $address, 1); + $email =3D mailmap_email($email); + return $email; +} + +sub save_commits_by_author { + my (@lines) =3D @_; + + my @authors =3D (); + my @commits =3D (); + my @subjects =3D (); + + foreach my $line (@lines) { + if ($line =3D~ m/$VCS_cmds{"author_pattern"}/) { + my $author =3D $1; + $author =3D deduplicate_email($author); + push(@authors, $author); + } + push(@commits, $1) if ($line =3D~ m/$VCS_cmds{"commit_pattern"}/); + push(@subjects, $1) if ($line =3D~ m/$VCS_cmds{"subject_pattern"}/); + } + + for (my $i =3D 0; $i < @authors; $i++) { + my $exists =3D 0; + foreach my $ref(@{$commit_author_hash{$authors[$i]}}) { + if (@{$ref}[0] eq $commits[$i] && + @{$ref}[1] eq $subjects[$i]) { + $exists =3D 1; + last; + } + } + if (!$exists) { + push(@{$commit_author_hash{$authors[$i]}}, + [ ($commits[$i], $subjects[$i]) ]); + } + } +} + +sub save_commits_by_signer { + my (@lines) =3D @_; + + my $commit =3D ""; + my $subject =3D ""; + + foreach my $line (@lines) { + $commit =3D $1 if ($line =3D~ m/$VCS_cmds{"commit_pattern"}/); + $subject =3D $1 if ($line =3D~ m/$VCS_cmds{"subject_pattern"}/); + if ($line =3D~ /^[ \t]*${signature_pattern}.*\@.*$/) { + my @signatures =3D ($line); + my ($types_ref, $signers_ref) =3D extract_formatted_signatures(@sig= natures); + my @types =3D @$types_ref; + my @signers =3D @$signers_ref; + + my $type =3D $types[0]; + my $signer =3D $signers[0]; + + $signer =3D deduplicate_email($signer); + + my $exists =3D 0; + foreach my $ref(@{$commit_signer_hash{$signer}}) { + if (@{$ref}[0] eq $commit && + @{$ref}[1] eq $subject && + @{$ref}[2] eq $type) { + $exists =3D 1; + last; + } + } + if (!$exists) { + push(@{$commit_signer_hash{$signer}}, + [ ($commit, $subject, $type) ]); + } + } + } +} + +sub vcs_assign { + my ($role, $divisor, @lines) =3D @_; + + my %hash; + my $count =3D 0; + + return if (@lines <=3D 0); + + if ($divisor <=3D 0) { + warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n"); + $divisor =3D 1; + } + + @lines =3D mailmap(@lines); + + return if (@lines <=3D 0); + + @lines =3D sort(@lines); + + # uniq -c + $hash{$_}++ for @lines; + + # sort -rn + foreach my $line (sort {$hash{$b} <=3D> $hash{$a}} keys %hash) { + my $sign_offs =3D $hash{$line}; + my $percent =3D $sign_offs * 100 / $divisor; + + $percent =3D 100 if ($percent > 100); + $count++; + last if ($sign_offs < $email_git_min_signatures || + $count > $email_git_max_maintainers || + $percent < $email_git_min_percent); + push_email_address($line, ''); + if ($output_rolestats) { + my $fmt_percent =3D sprintf("%.0f", $percent); + add_role($line, "$role:$sign_offs/$divisor=3D$fmt_percent%"); + } else { + add_role($line, $role); + } + } +} + +sub vcs_file_signoffs { + my ($file) =3D @_; + + my @signers =3D (); + my $commits; + + $vcs_used =3D vcs_exists(); + return if (!$vcs_used); + + my $cmd =3D $VCS_cmds{"find_signers_cmd"}; + $cmd =3D~ s/(\$\w+)/$1/eeg; # interpolate $cmd + + ($commits, @signers) =3D vcs_find_signers($cmd); + + foreach my $signer (@signers) { + $signer =3D deduplicate_email($signer); + } + + vcs_assign("commit_signer", $commits, @signers); +} + +sub vcs_file_blame { + my ($file) =3D @_; + + my @signers =3D (); + my @all_commits =3D (); + my @commits =3D (); + my $total_commits; + my $total_lines; + + $vcs_used =3D vcs_exists(); + return if (!$vcs_used); + + @all_commits =3D vcs_blame($file); + @commits =3D uniq(@all_commits); + $total_commits =3D @commits; + $total_lines =3D @all_commits; + + if ($email_git_blame_signatures) { + if (vcs_is_hg()) { + my $commit_count; + my @commit_signers =3D (); + my $commit =3D join(" -r ", @commits); + my $cmd; + + $cmd =3D $VCS_cmds{"find_commit_signers_cmd"}; + $cmd =3D~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd + + ($commit_count, @commit_signers) =3D vcs_find_signers($cmd); + + push(@signers, @commit_signers); + } else { + foreach my $commit (@commits) { + my $commit_count; + my @commit_signers =3D (); + my $cmd; + + $cmd =3D $VCS_cmds{"find_commit_signers_cmd"}; + $cmd =3D~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd + + ($commit_count, @commit_signers) =3D vcs_find_signers($cmd); + + push(@signers, @commit_signers); + } + } + } + + if ($from_filename) { + if ($output_rolestats) { + my @blame_signers; + if (vcs_is_hg()) {{ # Double brace for last exit + my $commit_count; + my @commit_signers =3D (); + @commits =3D uniq(@commits); + @commits =3D sort(@commits); + my $commit =3D join(" -r ", @commits); + my $cmd; + + $cmd =3D $VCS_cmds{"find_commit_author_cmd"}; + $cmd =3D~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd + + my @lines =3D (); + + @lines =3D &{$VCS_cmds{"execute_cmd"}}($cmd); + + if (!$email_git_penguin_chiefs) { + @lines =3D grep(!/${penguin_chiefs}/i, @lines); + } + + last if !@lines; + + my @authors =3D (); + foreach my $line (@lines) { + if ($line =3D~ m/$VCS_cmds{"author_pattern"}/) { + my $author =3D $1; + $author =3D deduplicate_email($author); + push(@authors, $author); + } + } + + save_commits_by_author(@lines) if ($interactive); + save_commits_by_signer(@lines) if ($interactive); + + push(@signers, @authors); + }} + else { + foreach my $commit (@commits) { + my $i; + my $cmd =3D $VCS_cmds{"find_commit_author_cmd"}; + $cmd =3D~ s/(\$\w+)/$1/eeg; #interpolate $cmd + my @author =3D vcs_find_author($cmd); + next if !@author; + + my $formatted_author =3D deduplicate_email($author[0]); + + my $count =3D grep(/$commit/, @all_commits); + for ($i =3D 0; $i < $count ; $i++) { + push(@blame_signers, $formatted_author); + } + } + } + if (@blame_signers) { + vcs_assign("authored lines", $total_lines, @blame_signers); + } + } + foreach my $signer (@signers) { + $signer =3D deduplicate_email($signer); + } + vcs_assign("commits", $total_commits, @signers); + } else { + foreach my $signer (@signers) { + $signer =3D deduplicate_email($signer); + } + vcs_assign("modified commits", $total_commits, @signers); + } +} + +sub uniq { + my (@parms) =3D @_; + + my %saw; + @parms =3D grep(!$saw{$_}++, @parms); + return @parms; +} + +sub sort_and_uniq { + my (@parms) =3D @_; + + my %saw; + @parms =3D sort @parms; + @parms =3D grep(!$saw{$_}++, @parms); + return @parms; +} + +sub clean_file_emails { + my (@file_emails) =3D @_; + my @fmt_emails =3D (); + + foreach my $email (@file_emails) { + $email =3D~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}= ]{0,1}/\<$1\>/g; + my ($name, $address) =3D parse_email($email); + if ($name eq '"[,\.]"') { + $name =3D ""; + } + + my @nw =3D split(/[^A-Za-z=C0-=FF\'\,\.\+-]/, $name); + if (@nw > 2) { + my $first =3D $nw[@nw - 3]; + my $middle =3D $nw[@nw - 2]; + my $last =3D $nw[@nw - 1]; + + if (((length($first) =3D=3D 1 && $first =3D~ m/[A-Za-z]/) || + (length($first) =3D=3D 2 && substr($first, -1) eq ".")) || + (length($middle) =3D=3D 1 || + (length($middle) =3D=3D 2 && substr($middle, -1) eq "."))) { + $name =3D "$first $middle $last"; + } else { + $name =3D "$middle $last"; + } + } + + if (substr($name, -1) =3D~ /[,\.]/) { + $name =3D substr($name, 0, length($name) - 1); + } elsif (substr($name, -2) =3D~ /[,\.]"/) { + $name =3D substr($name, 0, length($name) - 2) . '"'; + } + + if (substr($name, 0, 1) =3D~ /[,\.]/) { + $name =3D substr($name, 1, length($name) - 1); + } elsif (substr($name, 0, 2) =3D~ /"[,\.]/) { + $name =3D '"' . substr($name, 2, length($name) - 2); + } + + my $fmt_email =3D format_email($name, $address, $email_usename); + push(@fmt_emails, $fmt_email); + } + return @fmt_emails; +} + +sub merge_email { + my @lines; + my %saw; + + for (@_) { + my ($address, $role) =3D @$_; + if (!$saw{$address}) { + if ($output_roles) { + push(@lines, "$address ($role)"); + } else { + push(@lines, $address); + } + $saw{$address} =3D 1; + } + } + + return @lines; +} + +sub output { + my (@parms) =3D @_; + + if ($output_multiline) { + foreach my $line (@parms) { + print("${line}\n"); + } + } else { + print(join($output_separator, @parms)); + print("\n"); + } +} + +my $rfc822re; + +sub make_rfc822re { +# Basic lexical tokens are specials, domain_literal, quoted_string, at= om, and +# comment. We must allow for rfc822_lwsp (or comments) after each of = these. +# This regexp will only work on addresses which have had comments stri= pped +# and replaced with rfc822_lwsp. + + my $specials =3D '()<>@,;:\\\\".\\[\\]'; + my $controls =3D '\\000-\\037\\177'; + + my $dtext =3D "[^\\[\\]\\r\\\\]"; + my $domain_literal =3D "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*"; + + my $quoted_string =3D "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rf= c822_lwsp*"; + +# Use zero-width assertion to spot the limit of an atom. A simple +# $rfc822_lwsp* causes the regexp engine to hang occasionally. + my $atom =3D "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=3D[\\[\= "$specials]))"; + my $word =3D "(?:$atom|$quoted_string)"; + my $localpart =3D "$word(?:\\.$rfc822_lwsp*$word)*"; + + my $sub_domain =3D "(?:$atom|$domain_literal)"; + my $domain =3D "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*"; + + my $addr_spec =3D "$localpart\@$rfc822_lwsp*$domain"; + + my $phrase =3D "$word*"; + my $route =3D "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp= *)"; + my $route_addr =3D "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp= *"; + my $mailbox =3D "(?:$addr_spec|$phrase$route_addr)"; + + my $group =3D "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;= \\s*"; + my $address =3D "(?:$mailbox|$group)"; + + return "$rfc822_lwsp*$address"; +} + +sub rfc822_strip_comments { + my $s =3D shift; +# Recursively remove comments, and replace with a single space. The s= impler +# regexps in the Email Addressing FAQ are imperfect - they will miss e= scaped +# chars in atoms, for example. + + while ($s =3D~ s/^((?:[^"\\]|\\.)* + (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*) + \((?:[^()\\]|\\.)*\)/$1 /osx) {} + return $s; +} + +# valid: returns true if the parameter is an RFC822 valid address +# +sub rfc822_valid { + my $s =3D rfc822_strip_comments(shift); + + if (!$rfc822re) { + $rfc822re =3D make_rfc822re(); + } + + return $s =3D~ m/^$rfc822re$/so && $s =3D~ m/^$rfc822_char*$/; +} + +# validlist: In scalar context, returns true if the parameter is an RF= C822 +# valid list of addresses. +# +# In list context, returns an empty list on failure (an inv= alid +# address was found); otherwise a list whose first element = is the +# number of addresses found and whose remaining elements ar= e the +# addresses. This is needed to disambiguate failure (inval= id) +# from success with no addresses found, because an empty st= ring is +# a valid list. + +sub rfc822_validlist { + my $s =3D rfc822_strip_comments(shift); + + if (!$rfc822re) { + $rfc822re =3D make_rfc822re(); + } + # * null list items are valid according to the RFC + # * the '1' business is to aid in distinguishing failure from no res= ults + + my @r; + if ($s =3D~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so && + $s =3D~ m/^$rfc822_char*$/) { + while ($s =3D~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) { + push(@r, $1); + } + return wantarray ? (scalar(@r), @r) : 1; + } + return wantarray ? () : 0; +} --=20 1.7.5.53.gc233e