Annotation of qemu/scripts/get_maintainer.pl, revision 1.1

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

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.