Skip to content
Snippets Groups Projects
get_maintainer.pl 54.1 KiB
Newer Older
  • Learn to ignore specific revisions
  •     my $start = find_starting_index($index);
        my $end = find_ending_index($index);
    
        push(@subsystem, $typevalue[$start]);
    
        for ($i = $start + 1; $i < $end; $i++) {
    	my $tv = $typevalue[$i];
    
    	    my $ptype = $1;
    	    my $pvalue = $2;
    	    if ($ptype eq "L") {
    
    		my $list_address = $pvalue;
    		my $list_additional = "";
    
    		my $list_role = get_list_role($i);
    
    		if ($list_role ne "") {
    		    $list_role = ":" . $list_role;
    		}
    
    		if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
    		    $list_address = $1;
    		    $list_additional = $2;
    		}
    
    		if ($list_additional =~ m/subscribers-only/) {
    
    		    if ($email_subscriber_list) {
    
    			if (!$hash_list_to{lc($list_address)}) {
    			    $hash_list_to{lc($list_address)} = 1;
    
    			    push(@list_to, [$list_address,
    					    "subscriber list${list_role}"]);
    			}
    
    		    }
    		} else {
    		    if ($email_list) {
    
    			if (!$hash_list_to{lc($list_address)}) {
    			    $hash_list_to{lc($list_address)} = 1;
    
    			    if ($list_additional =~ m/moderated/) {
    				push(@list_to, [$list_address,
    						"moderated list${list_role}"]);
    			    } else {
    				push(@list_to, [$list_address,
    						"open list${list_role}"]);
    			    }
    
    		    }
    		}
    	    } elsif ($ptype eq "M") {
    
    		my ($name, $address) = parse_email($pvalue);
    		if ($name eq "") {
    
    		    if ($i > 0) {
    			my $tv = $typevalue[$i - 1];
    
    			if ($tv =~ m/^(\C):\s*(.*)/) {
    			    if ($1 eq "P") {
    				$name = $2;
    
    				$pvalue = format_email($name, $address, $email_usename);
    
    		    my $role = get_maintainer_role($i);
    		    push_email_addresses($pvalue, $role);
    
    		}
    	    } elsif ($ptype eq "T") {
    		push(@scm, $pvalue);
    	    } elsif ($ptype eq "W") {
    		push(@web, $pvalue);
    	    } elsif ($ptype eq "S") {
    		push(@status, $pvalue);
    	    }
    	}
        }
    }
    
    
    sub email_inuse {
        my ($name, $address) = @_;
    
        return 1 if (($name eq "") && ($address eq ""));
    
        return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
        return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
    
        my ($line, $role) = @_;
    
        my ($name, $address) = parse_email($line);
    
        if (!$email_remove_duplicates) {
    
    	push(@email_to, [format_email($name, $address, $email_usename), $role]);
    
        } elsif (!email_inuse($name, $address)) {
    
    	push(@email_to, [format_email($name, $address, $email_usename), $role]);
    
    	$email_hash_name{lc($name)}++ if ($name ne "");
    
    	$email_hash_address{lc($address)}++;
    
        my ($address, $role) = @_;
    
    	push_email_address($address, $role);
    
        } elsif (@address_list = rfc822_validlist($address)) {
    
    	my $array_count = shift(@address_list);
    	while (my $entry = shift(@address_list)) {
    
    	    push_email_address($entry, $role);
    
    	if (!push_email_address($address, $role)) {
    
    	    warn("Invalid MAINTAINERS address: '" . $address . "'\n");
    	}
    
    sub add_role {
        my ($line, $role) = @_;
    
        my ($name, $address) = parse_email($line);
    
        my $email = format_email($name, $address, $email_usename);
    
    
        foreach my $entry (@email_to) {
    	if ($email_remove_duplicates) {
    	    my ($entry_name, $entry_address) = parse_email($entry->[0]);
    
    	    if (($name eq $entry_name || $address eq $entry_address)
    		&& ($role eq "" || !($entry->[1] =~ m/$role/))
    	    ) {
    
    		if ($entry->[1] eq "") {
    		    $entry->[1] = "$role";
    		} else {
    		    $entry->[1] = "$entry->[1],$role";
    		}
    	    }
    	} else {
    
    	    if ($email eq $entry->[0]
    		&& ($role eq "" || !($entry->[1] =~ m/$role/))
    	    ) {
    
    		if ($entry->[1] eq "") {
    		    $entry->[1] = "$role";
    		} else {
    		    $entry->[1] = "$entry->[1],$role";
    		}
    	    }
    	}
        }
    }
    
    
    sub which {
        my ($bin) = @_;
    
    
        foreach my $path (split(/:/, $ENV{PATH})) {
    
    	if (-e "$path/$bin") {
    	    return "$path/$bin";
    	}
        }
    
        return "";
    }
    
    
    sub which_conf {
        my ($conf) = @_;
    
        foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
    	if (-e "$path/$conf") {
    	    return "$path/$conf";
    	}
        }
    
        return "";
    }
    
    
        my ($name, $address) = parse_email($line);
        my $email = format_email($name, $address, 1);
        my $real_name = $name;
        my $real_address = $address;
    
        if (exists $mailmap->{names}->{$email} ||
    	exists $mailmap->{addresses}->{$email}) {
    	if (exists $mailmap->{names}->{$email}) {
    	    $real_name = $mailmap->{names}->{$email};
    	}
    	if (exists $mailmap->{addresses}->{$email}) {
    	    $real_address = $mailmap->{addresses}->{$email};
    	}
        } else {
    	if (exists $mailmap->{names}->{$address}) {
    	    $real_name = $mailmap->{names}->{$address};
    	}
    	if (exists $mailmap->{addresses}->{$address}) {
    	    $real_address = $mailmap->{addresses}->{$address};
    
        }
        return format_email($real_name, $real_address, 1);
    
        foreach my $line (@addresses) {
    
    	push(@mapped_emails, mailmap_email($line));
    
        merge_by_realname(@mapped_emails) if ($email_use_mailmap);
        return @mapped_emails;
    
        my %address_map;
        my (@emails) = @_;
    
        foreach my $email (@emails) {
    	my ($name, $address) = parse_email($email);
    
    	    $address = $address_map{$name};
    
    	    $email = format_email($name, $address, 1);
    	} else {
    	    $address_map{$name} = $address;
    
    sub git_execute_cmd {
        my ($cmd) = @_;
        my @lines = ();
    
        my $output = `$cmd`;
        $output =~ s/^\s*//gm;
        @lines = split("\n", $output);
    
        return @lines;
    
        my @lines = ();
    
        my $output = `$cmd`;
        @lines = split("\n", $output);
    
    sub extract_formatted_signatures {
        my (@signature_lines) = @_;
    
        my @type = @signature_lines;
    
        s/\s*(.*):.*/$1/ for (@type);
    
        # cut -f2- -d":"
        s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
    
    ## Reformat email addresses (with names) to avoid badly written signatures
    
        foreach my $signer (@signature_lines) {
    
    sub vcs_find_signers {
        my ($cmd) = @_;
    
        my @lines = ();
        my @signatures = ();
    
        @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
    
        my $pattern = $VCS_cmds{"commit_pattern"};
    
        $commits = grep(/$pattern/, @lines);	# of commits
    
        @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
    
        return (0, @signatures) if !@signatures;
    
        save_commits_by_author(@lines) if ($interactive);
        save_commits_by_signer(@lines) if ($interactive);
    
        if (!$email_git_penguin_chiefs) {
    	@signatures = grep(!/${penguin_chiefs}/i, @signatures);
    
        my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
    
        return ($commits, @$signers_ref);
    
    sub vcs_find_author {
        my ($cmd) = @_;
        my @lines = ();
    
        @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
    
        if (!$email_git_penguin_chiefs) {
    	@lines = grep(!/${penguin_chiefs}/i, @lines);
        }
    
        return @lines if !@lines;
    
    
    	if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
    	    my $author = $1;
    	    my ($name, $address) = parse_email($author);
    	    $author = format_email($name, $address, 1);
    	    push(@authors, $author);
    	}
    
        save_commits_by_author(@lines) if ($interactive);
        save_commits_by_signer(@lines) if ($interactive);
    
        return @authors;
    
    sub vcs_save_commits {
        my ($cmd) = @_;
        my @lines = ();
        my @commits = ();
    
        @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
    
        foreach my $line (@lines) {
    	if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
    	    push(@commits, $1);
    	}
        }
    
        return @commits;
    }
    
    sub vcs_blame {
        my ($file) = @_;
        my $cmd;
        my @commits = ();
    
        return @commits if (!(-f $file));
    
        if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
    	my @all_commits = ();
    
    	$cmd = $VCS_cmds{"blame_file_cmd"};
    	$cmd =~ s/(\$\w+)/$1/eeg;		#interpolate $cmd
    	@all_commits = vcs_save_commits($cmd);
    
    	foreach my $file_range_diff (@range) {
    	    next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
    	    my $diff_file = $1;
    	    my $diff_start = $2;
    	    my $diff_length = $3;
    	    next if ("$file" ne "$diff_file");
    	    for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
    		push(@commits, $all_commits[$i]);
    	    }
    	}
        } elsif (@range) {
    	foreach my $file_range_diff (@range) {
    	    next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
    	    my $diff_file = $1;
    	    my $diff_start = $2;
    	    my $diff_length = $3;
    	    next if ("$file" ne "$diff_file");
    	    $cmd = $VCS_cmds{"blame_range_cmd"};
    	    $cmd =~ s/(\$\w+)/$1/eeg;		#interpolate $cmd
    	    push(@commits, vcs_save_commits($cmd));
    	}
        } else {
    	$cmd = $VCS_cmds{"blame_file_cmd"};
    	$cmd =~ s/(\$\w+)/$1/eeg;		#interpolate $cmd
    	@commits = vcs_save_commits($cmd);
        }
    
    
        return @commits;
    }
    
    my $printed_novcs = 0;
    sub vcs_exists {
        %VCS_cmds = %VCS_cmds_git;
        return 1 if eval $VCS_cmds{"available"};
        %VCS_cmds = %VCS_cmds_hg;
    
        return 2 if eval $VCS_cmds{"available"};
    
        %VCS_cmds = ();
        if (!$printed_novcs) {
    	warn("$P: No supported VCS found.  Add --nogit to options?\n");
    	warn("Using a git repository produces better results.\n");
    	warn("Try Linus Torvalds' latest git repository using:\n");
    
    	warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
    
        return $vcs_used == 1;
    }
    
    sub vcs_is_hg {
        return $vcs_used == 2;
    }
    
    
    sub interactive_get_maintainers {
    
        my ($list_ref) = @_;
    
        my %authored;
        my %signed;
    
        my $maintained = 0;
        foreach my $entry (@list) {
    
    	$maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
    	$selected{$count} = 1;
    
    	$authored{$count} = 0;
    	$signed{$count} = 0;
    	$count++;
    
        my $done = 0;
        my $print_options = 0;
        my $redraw = 1;
        while (!$done) {
    	$count = 0;
    	if ($redraw) {
    
    	    printf STDERR "\n%1s %2s %-65s",
    			  "*", "#", "email/list and role:stats";
    	    if ($email_git ||
    		($email_git_fallback && !$maintained) ||
    		$email_git_blame) {
    		print STDERR "auth sign";
    	    }
    	    print STDERR "\n";
    
    	    foreach my $entry (@list) {
    		my $email = $entry->[0];
    		my $role = $entry->[1];
    		my $sel = "";
    		$sel = "*" if ($selected{$count});
    		my $commit_author = $commit_author_hash{$email};
    		my $commit_signer = $commit_signer_hash{$email};
    		my $authored = 0;
    		my $signed = 0;
    		$authored++ for (@{$commit_author});
    		$signed++ for (@{$commit_signer});
    		printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
    		printf STDERR "%4d %4d", $authored, $signed
    		    if ($authored > 0 || $signed > 0);
    		printf STDERR "\n     %s\n", $role;
    		if ($authored{$count}) {
    		    my $commit_author = $commit_author_hash{$email};
    		    foreach my $ref (@{$commit_author}) {
    			print STDERR "     Author: @{$ref}[1]\n";
    
    		if ($signed{$count}) {
    		    my $commit_signer = $commit_signer_hash{$email};
    		    foreach my $ref (@{$commit_signer}) {
    			print STDERR "     @{$ref}[2]: @{$ref}[1]\n";
    		    }
    		}
    
    		$count++;
    	    }
    	}
    	my $date_ref = \$email_git_since;
    	$date_ref = \$email_hg_since if (vcs_is_hg());
    	if ($print_options) {
    	    $print_options = 0;
    	    if (vcs_exists()) {
    
    		print STDERR <<EOT
    
    Version Control options:
    g  use git history      [$email_git]
    gf use git-fallback     [$email_git_fallback]
    b  use git blame        [$email_git_blame]
    bs use blame signatures [$email_git_blame_signatures]
    c# minimum commits      [$email_git_min_signatures]
    %# min percent          [$email_git_min_percent]
    d# history to use       [$$date_ref]
    x# max maintainers      [$email_git_max_maintainers]
    t  all signature types  [$email_git_all_signature_types]
    m  use .mailmap         [$email_use_mailmap]
    EOT
    
    	    print STDERR <<EOT
    
    Additional options:
    0  toggle all
    tm toggle maintainers
    tg toggle git entries
    tl toggle open list entries
    ts toggle subscriber list entries
    f  emails in file       [$file_emails]
    k  keywords in file     [$keywords]
    r  remove duplicates    [$email_remove_duplicates]
    p# pattern match depth  [$pattern_depth]
    EOT
    
    	print STDERR
    "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
    
    	my $input = <STDIN>;
    
    	$redraw = 1;
    	my $rerun = 0;
    	my @wish = split(/[, ]+/, $input);
    	foreach my $nr (@wish) {
    	    $nr = lc($nr);
    	    my $sel = substr($nr, 0, 1);
    	    my $str = substr($nr, 1);
    	    my $val = 0;
    	    $val = $1 if $str =~ /^(\d+)$/;
    
    	    if ($sel eq "y") {
    		$interactive = 0;
    		$done = 1;
    		$output_rolestats = 0;
    		$output_roles = 0;
    		last;
    	    } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
    		$selected{$nr - 1} = !$selected{$nr - 1};
    	    } elsif ($sel eq "*" || $sel eq '^') {
    		my $toggle = 0;
    		$toggle = 1 if ($sel eq '*');
    		for (my $i = 0; $i < $count; $i++) {
    		    $selected{$i} = $toggle;
    
    	    } elsif ($sel eq "0") {
    		for (my $i = 0; $i < $count; $i++) {
    		    $selected{$i} = !$selected{$i};
    		}
    
    	    } elsif ($sel eq "t") {
    		if (lc($str) eq "m") {
    		    for (my $i = 0; $i < $count; $i++) {
    			$selected{$i} = !$selected{$i}
    			    if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
    		    }
    		} elsif (lc($str) eq "g") {
    		    for (my $i = 0; $i < $count; $i++) {
    			$selected{$i} = !$selected{$i}
    			    if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
    		    }
    		} elsif (lc($str) eq "l") {
    		    for (my $i = 0; $i < $count; $i++) {
    			$selected{$i} = !$selected{$i}
    			    if ($list[$i]->[1] =~ /^(open list)/i);
    		    }
    		} elsif (lc($str) eq "s") {
    		    for (my $i = 0; $i < $count; $i++) {
    			$selected{$i} = !$selected{$i}
    			    if ($list[$i]->[1] =~ /^(subscriber list)/i);
    		    }
    		}
    
    	    } elsif ($sel eq "a") {
    		if ($val > 0 && $val <= $count) {
    		    $authored{$val - 1} = !$authored{$val - 1};
    		} elsif ($str eq '*' || $str eq '^') {
    		    my $toggle = 0;
    		    $toggle = 1 if ($str eq '*');
    		    for (my $i = 0; $i < $count; $i++) {
    			$authored{$i} = $toggle;
    		    }
    		}
    	    } elsif ($sel eq "s") {
    		if ($val > 0 && $val <= $count) {
    		    $signed{$val - 1} = !$signed{$val - 1};
    		} elsif ($str eq '*' || $str eq '^') {
    		    my $toggle = 0;
    		    $toggle = 1 if ($str eq '*');
    		    for (my $i = 0; $i < $count; $i++) {
    			$signed{$i} = $toggle;
    		    }
    		}
    	    } elsif ($sel eq "o") {
    		$print_options = 1;
    		$redraw = 1;
    	    } elsif ($sel eq "g") {
    		if ($str eq "f") {
    		    bool_invert(\$email_git_fallback);
    
    		    bool_invert(\$email_git);
    		}
    		$rerun = 1;
    	    } elsif ($sel eq "b") {
    		if ($str eq "s") {
    		    bool_invert(\$email_git_blame_signatures);
    		} else {
    		    bool_invert(\$email_git_blame);
    		}
    		$rerun = 1;
    	    } elsif ($sel eq "c") {
    		if ($val > 0) {
    		    $email_git_min_signatures = $val;
    		    $rerun = 1;
    		}
    	    } elsif ($sel eq "x") {
    		if ($val > 0) {
    		    $email_git_max_maintainers = $val;
    		    $rerun = 1;
    		}
    	    } elsif ($sel eq "%") {
    		if ($str ne "" && $val >= 0) {
    		    $email_git_min_percent = $val;
    		    $rerun = 1;
    
    	    } elsif ($sel eq "d") {
    		if (vcs_is_git()) {
    		    $email_git_since = $str;
    		} elsif (vcs_is_hg()) {
    		    $email_hg_since = $str;
    		}
    		$rerun = 1;
    	    } elsif ($sel eq "t") {
    		bool_invert(\$email_git_all_signature_types);
    		$rerun = 1;
    	    } elsif ($sel eq "f") {
    		bool_invert(\$file_emails);
    		$rerun = 1;
    	    } elsif ($sel eq "r") {
    		bool_invert(\$email_remove_duplicates);
    		$rerun = 1;
    
    	    } elsif ($sel eq "m") {
    		bool_invert(\$email_use_mailmap);
    		read_mailmap();
    		$rerun = 1;
    
    	    } elsif ($sel eq "k") {
    		bool_invert(\$keywords);
    		$rerun = 1;
    	    } elsif ($sel eq "p") {
    		if ($str ne "" && $val >= 0) {
    		    $pattern_depth = $val;
    		    $rerun = 1;
    		}
    
    	    } elsif ($sel eq "h" || $sel eq "?") {
    		print STDERR <<EOT
    
    Interactive mode allows you to select the various maintainers, submitters,
    commit signers and mailing lists that could be CC'd on a patch.
    
    Any *'d entry is selected.
    
    
    If you have git or hg installed, you can choose to summarize the commit
    
    history of files in the patch.  Also, each line of the current file can
    be matched to its commit author and that commits signers with blame.
    
    Various knobs exist to control the length of time for active commit
    tracking, the maximum number of commit authors and signers to add,
    and such.
    
    Enter selections at the prompt until you are satisfied that the selected
    maintainers are appropriate.  You may enter multiple selections separated
    by either commas or spaces.
    
    EOT
    
    	    } else {
    		print STDERR "invalid option: '$nr'\n";
    		$redraw = 0;
    	    }
    	}
    	if ($rerun) {
    	    print STDERR "git-blame can be very slow, please have patience..."
    		if ($email_git_blame);
    
    
        #drop not selected entries
        $count = 0;
    
        my @new_emailto = ();
        foreach my $entry (@list) {
    	if ($selected{$count}) {
    	    push(@new_emailto, $list[$count]);
    
        return @new_emailto;
    
    sub bool_invert {
        my ($bool_ref) = @_;
    
        if ($$bool_ref) {
    	$$bool_ref = 0;
        } else {
    	$$bool_ref = 1;
        }
    
    sub deduplicate_email {
        my ($email) = @_;
    
        my $matched = 0;
        my ($name, $address) = parse_email($email);
        $email = format_email($name, $address, 1);
        $email = mailmap_email($email);
    
        return $email if (!$email_remove_duplicates);
    
        ($name, $address) = parse_email($email);
    
    
        if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
    
    	$name = $deduplicate_name_hash{lc($name)}->[0];
    	$address = $deduplicate_name_hash{lc($name)}->[1];
    	$matched = 1;
        } elsif ($deduplicate_address_hash{lc($address)}) {
    	$name = $deduplicate_address_hash{lc($address)}->[0];
    	$address = $deduplicate_address_hash{lc($address)}->[1];
    	$matched = 1;
        }
        if (!$matched) {
    	$deduplicate_name_hash{lc($name)} = [ $name, $address ];
    	$deduplicate_address_hash{lc($address)} = [ $name, $address ];
        }
        $email = format_email($name, $address, 1);
        $email = mailmap_email($email);
        return $email;
    }
    
    
    sub save_commits_by_author {
        my (@lines) = @_;
    
        my @authors = ();
        my @commits = ();
        my @subjects = ();
    
        foreach my $line (@lines) {
    	if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
    	    my $author = $1;
    
    	    push(@authors, $author);
    	}
    	push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
    	push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
        }
    
        for (my $i = 0; $i < @authors; $i++) {
    	my $exists = 0;
    	foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
    	    if (@{$ref}[0] eq $commits[$i] &&
    		@{$ref}[1] eq $subjects[$i]) {
    		$exists = 1;
    		last;
    	    }
    	}
    	if (!$exists) {
    	    push(@{$commit_author_hash{$authors[$i]}},
    		 [ ($commits[$i], $subjects[$i]) ]);
    	}
    
    sub save_commits_by_signer {
        my (@lines) = @_;
    
        my $commit = "";
        my $subject = "";
    
        foreach my $line (@lines) {
    	$commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
    	$subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
    	if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
    	    my @signatures = ($line);
    	    my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
    	    my @types = @$types_ref;
    	    my @signers = @$signers_ref;
    
    	    my $type = $types[0];
    	    my $signer = $signers[0];
    
    
    	    my $exists = 0;
    	    foreach my $ref(@{$commit_signer_hash{$signer}}) {
    		if (@{$ref}[0] eq $commit &&
    		    @{$ref}[1] eq $subject &&
    		    @{$ref}[2] eq $type) {
    		    $exists = 1;
    		    last;
    		}
    	    }
    	    if (!$exists) {
    		push(@{$commit_signer_hash{$signer}},
    		     [ ($commit, $subject, $type) ]);
    	    }
    	}
        }
    
        my ($role, $divisor, @lines) = @_;
    
        my %hash;
        my $count = 0;
    
        return if (@lines <= 0);
    
        if ($divisor <= 0) {
    
    	warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
    
        @lines = mailmap(@lines);
    
        # sort -rn
        foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
    
    	my $sign_offs = $hash{$line};
    
    	my $percent = $sign_offs * 100 / $divisor;
    
    	$percent = 100 if ($percent > 100);
    
    	$count++;
    	last if ($sign_offs < $email_git_min_signatures ||
    		 $count > $email_git_max_maintainers ||
    
    		 $percent < $email_git_min_percent);
    
    	push_email_address($line, '');
    	if ($output_rolestats) {
    
    	    my $fmt_percent = sprintf("%.0f", $percent);
    	    add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
    	} else {
    	    add_role($line, $role);
    
        $vcs_used = vcs_exists();
        return if (!$vcs_used);
    
        my $cmd = $VCS_cmds{"find_signers_cmd"};
        $cmd =~ s/(\$\w+)/$1/eeg;		# interpolate $cmd
    
        ($commits, @signers) = vcs_find_signers($cmd);
    
    
        foreach my $signer (@signers) {
    	$signer = deduplicate_email($signer);
        }
    
    
        vcs_assign("commit_signer", $commits, @signers);
    
        $vcs_used = vcs_exists();
        return if (!$vcs_used);
    
        @all_commits = vcs_blame($file);
        @commits = uniq(@all_commits);
    
        if ($email_git_blame_signatures) {
    	if (vcs_is_hg()) {
    	    my $commit_count;
    	    my @commit_signers = ();
    	    my $commit = join(" -r ", @commits);
    	    my $cmd;
    
    	    $cmd = $VCS_cmds{"find_commit_signers_cmd"};
    	    $cmd =~ s/(\$\w+)/$1/eeg;	#substitute variables in $cmd
    
    	    ($commit_count, @commit_signers) = vcs_find_signers($cmd);
    
    	    push(@signers, @commit_signers);
    	} else {
    	    foreach my $commit (@commits) {
    		my $commit_count;
    		my @commit_signers = ();
    		my $cmd;
    
    		$cmd = $VCS_cmds{"find_commit_signers_cmd"};
    		$cmd =~ s/(\$\w+)/$1/eeg;	#substitute variables in $cmd
    
    		($commit_count, @commit_signers) = vcs_find_signers($cmd);
    
    		push(@signers, @commit_signers);
    	    }
    	}
    
    	    if (vcs_is_hg()) {{		# Double brace for last exit
    		my $commit_count;
    		my @commit_signers = ();
    		@commits = uniq(@commits);
    		@commits = sort(@commits);
    		my $commit = join(" -r ", @commits);
    		my $cmd;
    
    		$cmd = $VCS_cmds{"find_commit_author_cmd"};
    		$cmd =~ s/(\$\w+)/$1/eeg;	#substitute variables in $cmd
    
    		my @lines = ();
    
    		@lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
    
    		if (!$email_git_penguin_chiefs) {
    		    @lines = grep(!/${penguin_chiefs}/i, @lines);
    		}
    
    		last if !@lines;
    
    		my @authors = ();
    		foreach my $line (@lines) {
    		    if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
    			my $author = $1;
    
    			$author = deduplicate_email($author);
    			push(@authors, $author);
    
    		    }
    		}
    
    		save_commits_by_author(@lines) if ($interactive);
    		save_commits_by_signer(@lines) if ($interactive);
    
    		push(@signers, @authors);
    	    }}
    	    else {
    		foreach my $commit (@commits) {
    		    my $i;
    		    my $cmd = $VCS_cmds{"find_commit_author_cmd"};
    		    $cmd =~ s/(\$\w+)/$1/eeg;	#interpolate $cmd
    		    my @author = vcs_find_author($cmd);
    		    next if !@author;
    
    
    		    my $formatted_author = deduplicate_email($author[0]);
    
    
    		    my $count = grep(/$commit/, @all_commits);
    		    for ($i = 0; $i < $count ; $i++) {
    
    			push(@blame_signers, $formatted_author);
    
    		}
    	    }
    	    if (@blame_signers) {
    		vcs_assign("authored lines", $total_lines, @blame_signers);
    	    }
    	}
    
    	foreach my $signer (@signers) {
    	    $signer = deduplicate_email($signer);
    	}
    
    	vcs_assign("commits", $total_commits, @signers);
    
    	foreach my $signer (@signers) {
    	    $signer = deduplicate_email($signer);
    	}
    
    	vcs_assign("modified commits", $total_commits, @signers);
    
    
        my %saw;
        @parms = grep(!$saw{$_}++, @parms);
        return @parms;
    }
    
    sub sort_and_uniq {
    
    
        my %saw;
        @parms = sort @parms;
        @parms = grep(!$saw{$_}++, @parms);
        return @parms;