]> bbs.cooldavid.org Git - net-next-2.6.git/blobdiff - scripts/get_maintainer.pl
ipv6: AF_INET6 link address family
[net-next-2.6.git] / scripts / get_maintainer.pl
index f51176039ff51c46bec671b8da128c8a387b4561..d21ec3a89603b0fa8155671587deaebaa52b6163 100755 (executable)
@@ -13,7 +13,7 @@
 use strict;
 
 my $P = $0;
-my $V = '0.26-beta3';
+my $V = '0.26-beta6';
 
 use Getopt::Long qw(:config no_auto_abbrev);
 
@@ -36,6 +36,7 @@ my $email_git_since = "1-year-ago";
 my $email_hg_since = "-365";
 my $interactive = 0;
 my $email_remove_duplicates = 1;
+my $email_use_mailmap = 1;
 my $output_multiline = 1;
 my $output_separator = ", ";
 my $output_roles = 0;
@@ -192,6 +193,7 @@ if (!GetOptions(
                'hg-since=s' => \$email_hg_since,
                'i|interactive!' => \$interactive,
                'remove-duplicates!' => \$email_remove_duplicates,
+               'mailmap!' => \$email_use_mailmap,
                'm!' => \$email_maintainer,
                'n!' => \$email_usename,
                'l!' => \$email_list,
@@ -242,6 +244,7 @@ if ($sections) {
     $subsystem = 0;
     $web = 0;
     $keywords = 0;
+    $interactive = 0;
 } else {
     my $selections = $email + $scm + $status + $subsystem + $web;
     if ($selections == 0) {
@@ -294,31 +297,82 @@ while (<$maint>) {
 }
 close($maint);
 
-my %mailmap;
 
-if ($email_remove_duplicates) {
-    open(my $mailmap, '<', "${lk_path}.mailmap")
-       or warn "$P: Can't open .mailmap: $!\n";
-    while (<$mailmap>) {
-       my $line = $_;
+#
+# Read mail address map
+#
 
-       next if ($line =~ m/^\s*#/);
-       next if ($line =~ m/^\s*$/);
+my $mailmap;
 
-       my ($name, $address) = parse_email($line);
-       $line = format_email($name, $address, $email_usename);
+read_mailmap();
 
-       next if ($line =~ m/^\s*$/);
+sub read_mailmap {
+    $mailmap = {
+       names => {},
+       addresses => {}
+    };
 
-       if (exists($mailmap{$name})) {
-           my $obj = $mailmap{$name};
-           push(@$obj, $address);
-       } else {
-           my @arr = ($address);
-           $mailmap{$name} = \@arr;
+    return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
+
+    open(my $mailmap_file, '<', "${lk_path}.mailmap")
+       or warn "$P: Can't open .mailmap: $!\n";
+
+    while (<$mailmap_file>) {
+       s/#.*$//; #strip comments
+       s/^\s+|\s+$//g; #trim
+
+       next if (/^\s*$/); #skip empty lines
+       #entries have one of the following formats:
+       # name1 <mail1>
+       # <mail1> <mail2>
+       # name1 <mail1> <mail2>
+       # name1 <mail1> name2 <mail2>
+       # (see man git-shortlog)
+       if (/^(.+)<(.+)>$/) {
+           my $real_name = $1;
+           my $address = $2;
+
+           $real_name =~ s/\s+$//;
+           ($real_name, $address) = parse_email("$real_name <$address>");
+           $mailmap->{names}->{$address} = $real_name;
+
+       } elsif (/^<([^\s]+)>\s*<([^\s]+)>$/) {
+           my $real_address = $1;
+           my $wrong_address = $2;
+
+           $mailmap->{addresses}->{$wrong_address} = $real_address;
+
+       } elsif (/^(.+)<([^\s]+)>\s*<([^\s]+)>$/) {
+           my $real_name = $1;
+           my $real_address = $2;
+           my $wrong_address = $3;
+
+           $real_name =~ s/\s+$//;
+           ($real_name, $real_address) =
+               parse_email("$real_name <$real_address>");
+           $mailmap->{names}->{$wrong_address} = $real_name;
+           $mailmap->{addresses}->{$wrong_address} = $real_address;
+
+       } elsif (/^(.+)<([^\s]+)>\s*([^\s].*)<([^\s]+)>$/) {
+           my $real_name = $1;
+           my $real_address = $2;
+           my $wrong_name = $3;
+           my $wrong_address = $4;
+
+           $real_name =~ s/\s+$//;
+           ($real_name, $real_address) =
+               parse_email("$real_name <$real_address>");
+
+           $wrong_name =~ s/\s+$//;
+           ($wrong_name, $wrong_address) =
+               parse_email("$wrong_name <$wrong_address>");
+
+           my $wrong_email = format_email($wrong_name, $wrong_address, 1);
+           $mailmap->{names}->{$wrong_email} = $real_name;
+           $mailmap->{addresses}->{$wrong_email} = $real_address;
        }
     }
-    close($mailmap);
+    close($mailmap_file);
 }
 
 ## use the filenames on the command line or find the filenames in the patchfiles
@@ -407,13 +461,16 @@ my @scm = ();
 my @web = ();
 my @subsystem = ();
 my @status = ();
+my %deduplicate_name_hash = ();
+my %deduplicate_address_hash = ();
 my $signature_pattern;
 
-my @to = get_maintainer();
+my @maintainers = get_maintainers();
 
-@to = merge_email(@to);
-
-output(@to) if (@to);
+if (@maintainers) {
+    @maintainers = merge_email(@maintainers);
+    output(@maintainers);
+}
 
 if ($scm) {
     @scm = uniq(@scm);
@@ -437,7 +494,7 @@ if ($web) {
 
 exit($exit);
 
-sub get_maintainer {
+sub get_maintainers {
     %email_hash_name = ();
     %email_hash_address = ();
     %commit_author_hash = ();
@@ -449,7 +506,8 @@ sub get_maintainer {
     @web = ();
     @subsystem = ();
     @status = ();
-
+    %deduplicate_name_hash = ();
+    %deduplicate_address_hash = ();
     if ($email_git_all_signature_types) {
        $signature_pattern = "(.+?)[Bb][Yy]:";
     } else {
@@ -458,10 +516,11 @@ sub get_maintainer {
 
     # Find responsible parties
 
+    my %exact_pattern_match_hash = ();
+
     foreach my $file (@files) {
 
        my %hash;
-       my $exact_pattern_match = 0;
        my $tvi = find_first_section();
        while ($tvi < @typevalue) {
            my $start = find_starting_index($tvi);
@@ -497,7 +556,9 @@ sub get_maintainer {
                                my $file_pd = ($file  =~ tr@/@@);
                                $value_pd++ if (substr($value,-1,1) ne "/");
                                $value_pd = -1 if ($value =~ /^\.\*/);
-                               $exact_pattern_match = 1 if ($value_pd >= $file_pd);
+                               if ($value_pd >= $file_pd) {
+                                   $exact_pattern_match_hash{$file} = 1;
+                               }
                                if ($pattern_depth == 0 ||
                                    (($file_pd - $value_pd) < $pattern_depth)) {
                                    $hash{$tvi} = $value_pd;
@@ -530,14 +591,6 @@ sub get_maintainer {
                print("\n");
            }
        }
-
-       if ($email && ($email_git ||
-                      ($email_git_fallback && !$exact_pattern_match))) {
-           vcs_file_signoffs($file);
-       }
-       if ($email && $email_git_blame) {
-           vcs_file_blame($file);
-       }
     }
 
     if ($keywords) {
@@ -547,6 +600,21 @@ sub get_maintainer {
        }
     }
 
+    foreach my $email (@email_to, @list_to) {
+       $email->[0] = deduplicate_email($email->[0]);
+    }
+
+    foreach my $file (@files) {
+       if ($email &&
+           ($email_git || ($email_git_fallback &&
+                           !$exact_pattern_match_hash{$file}))) {
+           vcs_file_signoffs($file);
+       }
+       if ($email && $email_git_blame) {
+           vcs_file_blame($file);
+       }
+    }
+
     if ($email) {
        foreach my $chief (@penguin_chief) {
            if ($chief =~ m/^(.*):(.*)/) {
@@ -580,7 +648,9 @@ sub get_maintainer {
        }
     }
 
-    @to = interactive_get_maintainer(\@to) if ($interactive);
+    if ($interactive) {
+       @to = interactive_get_maintainers(\@to);
+    }
 
     return @to;
 }
@@ -643,8 +713,9 @@ Output type options:
 
 Other options:
   --pattern-depth => Number of pattern directory traversals (default: 0 (all))
-  --keywords => scan patch for keywords (default: 1 (on))
-  --sections => print the entire subsystem sections with pattern matches
+  --keywords => scan patch for keywords (default: $keywords)
+  --sections => print all of the subsystem sections with pattern matches
+  --mailmap => use .mailmap file (default: $email_use_mailmap)
   --version => show version
   --help => show this help information
 
@@ -684,30 +755,30 @@ EOT
 }
 
 sub top_of_kernel_tree {
-       my ($lk_path) = @_;
+    my ($lk_path) = @_;
 
-       if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
-           $lk_path .= "/";
-       }
-       if (   (-f "${lk_path}COPYING")
-           && (-f "${lk_path}CREDITS")
-           && (-f "${lk_path}Kbuild")
-           && (-f "${lk_path}MAINTAINERS")
-           && (-f "${lk_path}Makefile")
-           && (-f "${lk_path}README")
-           && (-d "${lk_path}Documentation")
-           && (-d "${lk_path}arch")
-           && (-d "${lk_path}include")
-           && (-d "${lk_path}drivers")
-           && (-d "${lk_path}fs")
-           && (-d "${lk_path}init")
-           && (-d "${lk_path}ipc")
-           && (-d "${lk_path}kernel")
-           && (-d "${lk_path}lib")
-           && (-d "${lk_path}scripts")) {
-               return 1;
-       }
-       return 0;
+    if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
+       $lk_path .= "/";
+    }
+    if (   (-f "${lk_path}COPYING")
+       && (-f "${lk_path}CREDITS")
+       && (-f "${lk_path}Kbuild")
+       && (-f "${lk_path}MAINTAINERS")
+       && (-f "${lk_path}Makefile")
+       && (-f "${lk_path}README")
+       && (-d "${lk_path}Documentation")
+       && (-d "${lk_path}arch")
+       && (-d "${lk_path}include")
+       && (-d "${lk_path}drivers")
+       && (-d "${lk_path}fs")
+       && (-d "${lk_path}init")
+       && (-d "${lk_path}ipc")
+       && (-d "${lk_path}kernel")
+       && (-d "${lk_path}lib")
+       && (-d "${lk_path}scripts")) {
+       return 1;
+    }
+    return 0;
 }
 
 sub parse_email {
@@ -899,16 +970,16 @@ sub add_categories {
                }
                if ($list_additional =~ m/subscribers-only/) {
                    if ($email_subscriber_list) {
-                       if (!$hash_list_to{$list_address}) {
-                           $hash_list_to{$list_address} = 1;
+                       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{$list_address}) {
-                           $hash_list_to{$list_address} = 1;
+                       if (!$hash_list_to{lc($list_address)}) {
+                           $hash_list_to{lc($list_address)} = 1;
                            push(@list_to, [$list_address,
                                            "open list${list_role}"]);
                        }
@@ -946,8 +1017,8 @@ sub email_inuse {
     my ($name, $address) = @_;
 
     return 1 if (($name eq "") && ($address eq ""));
-    return 1 if (($name ne "") && exists($email_hash_name{$name}));
-    return 1 if (($address ne "") && exists($email_hash_address{$address}));
+    return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
+    return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
 
     return 0;
 }
@@ -965,8 +1036,8 @@ sub push_email_address {
        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{$name}++;
-       $email_hash_address{$address}++;
+       $email_hash_name{lc($name)}++ if ($name ne "");
+       $email_hash_address{lc($address)}++;
     }
 
     return 1;
@@ -1047,30 +1118,57 @@ sub which_conf {
     return "";
 }
 
-sub mailmap {
-    my (@lines) = @_;
-    my %hash;
+sub mailmap_email {
+    my ($line) = @_;
 
-    foreach my $line (@lines) {
-       my ($name, $address) = parse_email($line);
-       if (!exists($hash{$name})) {
-           $hash{$name} = $address;
-       } elsif ($address ne $hash{$name}) {
-           $address = $hash{$name};
-           $line = format_email($name, $address, $email_usename);
+    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{$name})) {
-           my $obj = $mailmap{$name};
-           foreach my $map_address (@$obj) {
-               if (($map_address eq $address) &&
-                   ($map_address ne $hash{$name})) {
-                   $line = format_email($name, $hash{$name}, $email_usename);
-               }
-           }
+       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);
+}
 
-    return @lines;
+sub mailmap {
+    my (@addresses) = @_;
+
+    my @mapped_emails = ();
+    foreach my $line (@addresses) {
+       push(@mapped_emails, mailmap_email($line));
+    }
+    merge_by_realname(@mapped_emails) if ($email_use_mailmap);
+    return @mapped_emails;
+}
+
+sub merge_by_realname {
+    my %address_map;
+    my (@emails) = @_;
+
+    foreach my $email (@emails) {
+       my ($name, $address) = parse_email($email);
+       if (exists $address_map{$name}) {
+           $address = $address_map{$name};
+           $email = format_email($name, $address, 1);
+       } else {
+           $address_map{$name} = $address;
+       }
+    }
 }
 
 sub git_execute_cmd {
@@ -1107,8 +1205,7 @@ sub extract_formatted_signatures {
 ## Reformat email addresses (with names) to avoid badly written signatures
 
     foreach my $signer (@signature_lines) {
-       my ($name, $address) = parse_email($signer);
-       $signer = format_email($name, $address, 1);
+       $signer = deduplicate_email($signer);
     }
 
     return (\@type, \@signature_lines);
@@ -1252,6 +1349,7 @@ sub vcs_exists {
 }
 
 sub vcs_is_git {
+    vcs_exists();
     return $vcs_used == 1;
 }
 
@@ -1259,7 +1357,7 @@ sub vcs_is_hg {
     return $vcs_used == 2;
 }
 
-sub interactive_get_maintainer {
+sub interactive_get_maintainers {
     my ($list_ref) = @_;
     my @list = @$list_ref;
 
@@ -1269,11 +1367,10 @@ sub interactive_get_maintainer {
     my %authored;
     my %signed;
     my $count = 0;
-
-    #select maintainers by default
-    foreach my $entry (@list){
-       my $role = $entry->[1];
-       $selected{$count} = ($role =~ /^(maintainer|supporter|open list)/);
+    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++;
@@ -1286,8 +1383,14 @@ sub interactive_get_maintainer {
     while (!$done) {
        $count = 0;
        if ($redraw) {
-           printf STDERR "\n%1s %2s %-65sauth sign\n",
-               "*", "#", "email/list and role:stats";
+           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];
@@ -1324,24 +1427,34 @@ sub interactive_get_maintainer {
        if ($print_options) {
            $print_options = 0;
            if (vcs_exists()) {
-               print STDERR
-"\nVersion Control options:\n" .
-"g  use git history      [$email_git]\n" .
-"gf use git-fallback     [$email_git_fallback]\n" .
-"b  use git blame        [$email_git_blame]\n" .
-"bs use blame signatures [$email_git_blame_signatures]\n" .
-"c# minimum commits      [$email_git_min_signatures]\n" .
-"%# min percent          [$email_git_min_percent]\n" .
-"d# history to use       [$$date_ref]\n" .
-"x# max maintainers      [$email_git_max_maintainers]\n" .
-"t  all signature types  [$email_git_all_signature_types]\n";
+               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 "\nAdditional options:\n" .
-"0  toggle all\n" .
-"f  emails in file       [$file_emails]\n" .
-"k  keywords in file     [$keywords]\n" .
-"r  remove duplicates    [$email_remove_duplicates]\n" .
-"p# pattern match depth  [$pattern_depth]\n";
+           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): ";
@@ -1377,6 +1490,28 @@ sub interactive_get_maintainer {
                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};
@@ -1445,6 +1580,10 @@ sub interactive_get_maintainer {
            } 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;
@@ -1453,6 +1592,27 @@ sub interactive_get_maintainer {
                    $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;
@@ -1461,7 +1621,7 @@ sub interactive_get_maintainer {
        if ($rerun) {
            print STDERR "git-blame can be very slow, please have patience..."
                if ($email_git_blame);
-           goto &get_maintainer;
+           goto &get_maintainers;
        }
     }
 
@@ -1487,6 +1647,36 @@ sub bool_invert {
     }
 }
 
+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) = @_;
 
@@ -1497,8 +1687,7 @@ sub save_commits_by_author {
     foreach my $line (@lines) {
        if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
            my $author = $1;
-           my ($name, $address) = parse_email($author);
-           $author = format_email($name, $address, 1);
+           $author = deduplicate_email($author);
            push(@authors, $author);
        }
        push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
@@ -1539,6 +1728,8 @@ sub save_commits_by_signer {
            my $type = $types[0];
            my $signer = $signers[0];
 
+           $signer = deduplicate_email($signer);
+
            my $exists = 0;
            foreach my $ref(@{$commit_signer_hash{$signer}}) {
                if (@{$ref}[0] eq $commit &&
@@ -1569,9 +1760,7 @@ sub vcs_assign {
        $divisor = 1;
     }
 
-    if ($email_remove_duplicates) {
-       @lines = mailmap(@lines);
-    }
+    @lines = mailmap(@lines);
 
     return if (@lines <= 0);
 
@@ -1613,6 +1802,11 @@ sub vcs_file_signoffs {
     $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);
 }
 
@@ -1690,9 +1884,8 @@ sub vcs_file_blame {
                foreach my $line (@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, $1);
+                       $author = deduplicate_email($author);
+                       push(@authors, $author);
                    }
                }
 
@@ -1708,9 +1901,12 @@ sub vcs_file_blame {
                    $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, $author[0]);
+                       push(@blame_signers, $formatted_author);
                    }
                }
            }
@@ -1718,8 +1914,14 @@ sub vcs_file_blame {
                vcs_assign("authored lines", $total_lines, @blame_signers);
            }
        }
+       foreach my $signer (@signers) {
+           $signer = deduplicate_email($signer);
+       }
        vcs_assign("commits", $total_commits, @signers);
     } else {
+       foreach my $signer (@signers) {
+           $signer = deduplicate_email($signer);
+       }
        vcs_assign("modified commits", $total_commits, @signers);
     }
 }