diff options
author | Joe Perches <joe@perches.com> | 2009-12-14 21:00:46 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-12-15 11:53:28 -0500 |
commit | 3c7385b81f721f0e7648d5134afb2088b28f8c69 (patch) | |
tree | d1bb0060604f39b9fc87c465b2614b22dfbd5005 | |
parent | 5ada918b82399eef3afd6a71e3637697d6bd719f (diff) |
scripts/get_maintainer.pl: add --roles and --rolestats
--roles shows the role of each email address, i.e. why it was selected.
--rolestats selects --roles and adds git log/blame signers #'s and %
Multiple roles are possible (supporter, maintainer, git-signer...)
--roles or --rolestats is meant to help identify appropriate maintainers
to notify and should not be used with "git send-email --cc-cmd"
Example output:
Existing:
$ ./scripts/get_maintainer.pl -f arch/x86/kernel/acpi/boot.c
Corentin Chary <corentincj@iksaif.net>
Karol Kozimor <sziwan@users.sourceforge.net>
Len Brown <len.brown@intel.com>
Pavel Machek <pavel@ucw.cz>
Rafael J. Wysocki <rjw@sisk.pl>
Thomas Gleixner <tglx@linutronix.de>
Ingo Molnar <mingo@redhat.com>
H. Peter Anvin <hpa@zytor.com>
x86@kernel.org
Yinghai Lu <yhlu.kernel@gmail.com>
Jeremy Fitzhardinge <jeremy.fitzhardinge@citrix.com>
acpi4asus-user@lists.sourceforge.net
linux-pm@lists.linux-foundation.org
linux-kernel@vger.kernel.org
With --roles
$ ./scripts/get_maintainer.pl --roles -f arch/x86/kernel/acpi/boot.c
Corentin Chary <corentincj@iksaif.net> (maintainer:ASUS ACPI EXTRAS...)
Karol Kozimor <sziwan@users.sourceforge.net> (maintainer:ASUS ACPI EXTRAS...)
Len Brown <len.brown@intel.com> (supporter:SUSPEND TO RAM,git-signer)
Pavel Machek <pavel@ucw.cz> (supporter:SUSPEND TO RAM)
Rafael J. Wysocki <rjw@sisk.pl> (supporter:SUSPEND TO RAM)
Thomas Gleixner <tglx@linutronix.de> (maintainer:X86 ARCHITECTURE...)
Ingo Molnar <mingo@redhat.com> (maintainer:X86 ARCHITECTURE...,git-signer)
H. Peter Anvin <hpa@zytor.com> (maintainer:X86 ARCHITECTURE...)
x86@kernel.org (maintainer:X86 ARCHITECTURE...)
Yinghai Lu <yhlu.kernel@gmail.com> (git-signer)
Jeremy Fitzhardinge <jeremy.fitzhardinge@citrix.com> (git-signer)
acpi4asus-user@lists.sourceforge.net (open list:ASUS ACPI EXTRAS...)
linux-pm@lists.linux-foundation.org (open list:SUSPEND TO RAM)
linux-kernel@vger.kernel.org (open list)
With --rolestats
$ ./scripts/get_maintainer.pl --rolestats -f arch/x86/kernel/acpi/boot.c
Corentin Chary <corentincj@iksaif.net> (maintainer:ASUS ACPI EXTRAS...)
Karol Kozimor <sziwan@users.sourceforge.net> (maintainer:ASUS ACPI EXTRAS...)
Len Brown <len.brown@intel.com> (supporter:SUSPEND TO RAM,git-signer:16/79=20%)
Pavel Machek <pavel@ucw.cz> (supporter:SUSPEND TO RAM)
Rafael J. Wysocki <rjw@sisk.pl> (supporter:SUSPEND TO RAM)
Thomas Gleixner <tglx@linutronix.de> (maintainer:X86 ARCHITECTURE...)
Ingo Molnar <mingo@redhat.com> (maintainer:X86 ARCHITECTURE...,git-signer:29/79=37%)
H. Peter Anvin <hpa@zytor.com> (maintainer:X86 ARCHITECTURE...)
x86@kernel.org (maintainer:X86 ARCHITECTURE...)
Yinghai Lu <yhlu.kernel@gmail.com> (git-signer:12/79=15%)
Jeremy Fitzhardinge <jeremy.fitzhardinge@citrix.com> (git-signer:6/79=8%)
acpi4asus-user@lists.sourceforge.net (open list:ASUS ACPI EXTRAS...)
linux-pm@lists.linux-foundation.org (open list:SUSPEND TO RAM)
linux-kernel@vger.kernel.org (open list)
With --rolestats and --git-blame
$ ./scripts/get_maintainer.pl --rolestats --git-blame -f arch/x86/kernel/acpi/boot.c
Corentin Chary <corentincj@iksaif.net> (maintainer:ASUS ACPI EXTRAS...)
Karol Kozimor <sziwan@users.sourceforge.net> (maintainer:ASUS ACPI EXTRAS...)
Len Brown <len.brown@intel.com> (supporter:SUSPEND TO RAM,git-signer:16/79=20%,commits:22/154=14%)
Pavel Machek <pavel@ucw.cz> (supporter:SUSPEND TO RAM)
Rafael J. Wysocki <rjw@sisk.pl> (supporter:SUSPEND TO RAM)
Thomas Gleixner <tglx@linutronix.de> (maintainer:X86 ARCHITECTURE...)
Ingo Molnar <mingo@redhat.com> (maintainer:X86 ARCHITECTURE...,git-signer:29/79=37%,commits:36/154=23%)
H. Peter Anvin <hpa@zytor.com> (maintainer:X86 ARCHITECTURE...)
x86@kernel.org (maintainer:X86 ARCHITECTURE...)
Yinghai Lu <yhlu.kernel@gmail.com> (git-signer:12/79=15%,commits:9/154=6%)
Jeremy Fitzhardinge <jeremy.fitzhardinge@citrix.com> (git-signer:6/79=8%)
Andi Kleen <ak@suse.de> (commits:11/154=7%)
Andrew Morton <akpm@osdl.org> (commits:10/154=6%)
acpi4asus-user@lists.sourceforge.net (open list:ASUS ACPI EXTRAS...)
linux-pm@lists.linux-foundation.org (open list:SUSPEND TO RAM)
linux-kernel@vger.kernel.org (open list)
Other changes:
Format git-signers email addresses a bit to reduce bad signatures
Command line bad arguments emitted a verbose usage(), just show --help
Version number bumped to .22
Ben Hutchings had the idea and created a good deal of this implementation.
Signed-off-by: Joe Perches <joe@perches.com>
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
Cc: Greg KH <greg@kroah.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rwxr-xr-x | scripts/get_maintainer.pl | 194 |
1 files changed, 176 insertions, 18 deletions
diff --git a/scripts/get_maintainer.pl b/scripts/get_maintainer.pl index 81a67a458e78..4e11c271e613 100755 --- a/scripts/get_maintainer.pl +++ b/scripts/get_maintainer.pl | |||
@@ -13,7 +13,7 @@ | |||
13 | use strict; | 13 | use strict; |
14 | 14 | ||
15 | my $P = $0; | 15 | my $P = $0; |
16 | my $V = '0.21'; | 16 | my $V = '0.22'; |
17 | 17 | ||
18 | use Getopt::Long qw(:config no_auto_abbrev); | 18 | use Getopt::Long qw(:config no_auto_abbrev); |
19 | 19 | ||
@@ -33,6 +33,8 @@ my $email_git_blame = 0; | |||
33 | my $email_remove_duplicates = 1; | 33 | my $email_remove_duplicates = 1; |
34 | my $output_multiline = 1; | 34 | my $output_multiline = 1; |
35 | my $output_separator = ", "; | 35 | my $output_separator = ", "; |
36 | my $output_roles = 0; | ||
37 | my $output_rolestats = 0; | ||
36 | my $scm = 0; | 38 | my $scm = 0; |
37 | my $web = 0; | 39 | my $web = 0; |
38 | my $subsystem = 0; | 40 | my $subsystem = 0; |
@@ -79,6 +81,8 @@ if (!GetOptions( | |||
79 | 'l!' => \$email_list, | 81 | 'l!' => \$email_list, |
80 | 's!' => \$email_subscriber_list, | 82 | 's!' => \$email_subscriber_list, |
81 | 'multiline!' => \$output_multiline, | 83 | 'multiline!' => \$output_multiline, |
84 | 'roles!' => \$output_roles, | ||
85 | 'rolestats!' => \$output_rolestats, | ||
82 | 'separator=s' => \$output_separator, | 86 | 'separator=s' => \$output_separator, |
83 | 'subsystem!' => \$subsystem, | 87 | 'subsystem!' => \$subsystem, |
84 | 'status!' => \$status, | 88 | 'status!' => \$status, |
@@ -90,8 +94,7 @@ if (!GetOptions( | |||
90 | 'v|version' => \$version, | 94 | 'v|version' => \$version, |
91 | 'h|help' => \$help, | 95 | 'h|help' => \$help, |
92 | )) { | 96 | )) { |
93 | usage(); | 97 | die "$P: invalid argument - use --help if necessary\n"; |
94 | die "$P: invalid argument\n"; | ||
95 | } | 98 | } |
96 | 99 | ||
97 | if ($help != 0) { | 100 | if ($help != 0) { |
@@ -113,6 +116,10 @@ if ($output_separator ne ", ") { | |||
113 | $output_multiline = 0; | 116 | $output_multiline = 0; |
114 | } | 117 | } |
115 | 118 | ||
119 | if ($output_rolestats) { | ||
120 | $output_roles = 1; | ||
121 | } | ||
122 | |||
116 | my $selections = $email + $scm + $status + $subsystem + $web; | 123 | my $selections = $email + $scm + $status + $subsystem + $web; |
117 | if ($selections == 0) { | 124 | if ($selections == 0) { |
118 | usage(); | 125 | usage(); |
@@ -326,9 +333,9 @@ if ($email) { | |||
326 | 333 | ||
327 | $email_address = format_email($1, $2); | 334 | $email_address = format_email($1, $2); |
328 | if ($email_git_penguin_chiefs) { | 335 | if ($email_git_penguin_chiefs) { |
329 | push(@email_to, $email_address); | 336 | push(@email_to, [$email_address, 'chief penguin']); |
330 | } else { | 337 | } else { |
331 | @email_to = grep(!/${email_address}/, @email_to); | 338 | @email_to = grep($_->[0] !~ /${email_address}/, @email_to); |
332 | } | 339 | } |
333 | } | 340 | } |
334 | } | 341 | } |
@@ -342,7 +349,7 @@ if ($email || $email_list) { | |||
342 | if ($email_list) { | 349 | if ($email_list) { |
343 | @to = (@to, @list_to); | 350 | @to = (@to, @list_to); |
344 | } | 351 | } |
345 | output(uniq(@to)); | 352 | output(merge_email(@to)); |
346 | } | 353 | } |
347 | 354 | ||
348 | if ($scm) { | 355 | if ($scm) { |
@@ -405,6 +412,8 @@ MAINTAINER field selection options: | |||
405 | --l => include list(s) if any | 412 | --l => include list(s) if any |
406 | --s => include subscriber only list(s) if any | 413 | --s => include subscriber only list(s) if any |
407 | --remove-duplicates => minimize duplicate email names/addresses | 414 | --remove-duplicates => minimize duplicate email names/addresses |
415 | --roles => show roles (status:subsystem, git-signer, list, etc...) | ||
416 | --rolestats => show roles and statistics (commits/total_commits, %) | ||
408 | --scm => print SCM tree(s) if any | 417 | --scm => print SCM tree(s) if any |
409 | --status => print status if any | 418 | --status => print status if any |
410 | --subsystem => print subsystem name if any | 419 | --subsystem => print subsystem name if any |
@@ -435,6 +444,13 @@ Notes: | |||
435 | Used with "--git-blame", does not iterate all files in directory | 444 | Used with "--git-blame", does not iterate all files in directory |
436 | Using "--git-blame" is slow and may add old committers and authors | 445 | Using "--git-blame" is slow and may add old committers and authors |
437 | that are no longer active maintainers to the output. | 446 | that are no longer active maintainers to the output. |
447 | Using "--roles" or "--rolestats" with git send-email --cc-cmd or any | ||
448 | other automated tools that expect only ["name"] <email address> | ||
449 | may not work because of additional output after <email address>. | ||
450 | Using "--rolestats" and "--git-blame" shows the #/total=% commits, | ||
451 | not the percentage of the entire file authored. # of commits is | ||
452 | not a good measure of amount of code authored. 1 major commit may | ||
453 | contain a thousand lines, 5 trivial commits may modify a single line. | ||
438 | EOT | 454 | EOT |
439 | } | 455 | } |
440 | 456 | ||
@@ -547,6 +563,71 @@ sub find_ending_index { | |||
547 | return $index; | 563 | return $index; |
548 | } | 564 | } |
549 | 565 | ||
566 | sub get_maintainer_role { | ||
567 | my ($index) = @_; | ||
568 | |||
569 | my $i; | ||
570 | my $start = find_starting_index($index); | ||
571 | my $end = find_ending_index($index); | ||
572 | |||
573 | my $role; | ||
574 | my $subsystem = $typevalue[$start]; | ||
575 | if (length($subsystem) > 20) { | ||
576 | $subsystem = substr($subsystem, 0, 17); | ||
577 | $subsystem =~ s/\s*$//; | ||
578 | $subsystem = $subsystem . "..."; | ||
579 | } | ||
580 | |||
581 | for ($i = $start + 1; $i < $end; $i++) { | ||
582 | my $tv = $typevalue[$i]; | ||
583 | if ($tv =~ m/^(\C):\s*(.*)/) { | ||
584 | my $ptype = $1; | ||
585 | my $pvalue = $2; | ||
586 | if ($ptype eq "S") { | ||
587 | $role = $pvalue; | ||
588 | } | ||
589 | } | ||
590 | } | ||
591 | |||
592 | $role = lc($role); | ||
593 | if ($role eq "supported") { | ||
594 | $role = "supporter"; | ||
595 | } elsif ($role eq "maintained") { | ||
596 | $role = "maintainer"; | ||
597 | } elsif ($role eq "odd fixes") { | ||
598 | $role = "odd fixer"; | ||
599 | } elsif ($role eq "orphan") { | ||
600 | $role = "orphan minder"; | ||
601 | } elsif ($role eq "obsolete") { | ||
602 | $role = "obsolete minder"; | ||
603 | } elsif ($role eq "buried alive in reporters") { | ||
604 | $role = "chief penguin"; | ||
605 | } | ||
606 | |||
607 | return $role . ":" . $subsystem; | ||
608 | } | ||
609 | |||
610 | sub get_list_role { | ||
611 | my ($index) = @_; | ||
612 | |||
613 | my $i; | ||
614 | my $start = find_starting_index($index); | ||
615 | my $end = find_ending_index($index); | ||
616 | |||
617 | my $subsystem = $typevalue[$start]; | ||
618 | if (length($subsystem) > 20) { | ||
619 | $subsystem = substr($subsystem, 0, 17); | ||
620 | $subsystem =~ s/\s*$//; | ||
621 | $subsystem = $subsystem . "..."; | ||
622 | } | ||
623 | |||
624 | if ($subsystem eq "THE REST") { | ||
625 | $subsystem = ""; | ||
626 | } | ||
627 | |||
628 | return $subsystem; | ||
629 | } | ||
630 | |||
550 | sub add_categories { | 631 | sub add_categories { |
551 | my ($index) = @_; | 632 | my ($index) = @_; |
552 | 633 | ||
@@ -564,17 +645,22 @@ sub add_categories { | |||
564 | if ($ptype eq "L") { | 645 | if ($ptype eq "L") { |
565 | my $list_address = $pvalue; | 646 | my $list_address = $pvalue; |
566 | my $list_additional = ""; | 647 | my $list_additional = ""; |
648 | my $list_role = get_list_role($i); | ||
649 | |||
650 | if ($list_role ne "") { | ||
651 | $list_role = ":" . $list_role; | ||
652 | } | ||
567 | if ($list_address =~ m/([^\s]+)\s+(.*)$/) { | 653 | if ($list_address =~ m/([^\s]+)\s+(.*)$/) { |
568 | $list_address = $1; | 654 | $list_address = $1; |
569 | $list_additional = $2; | 655 | $list_additional = $2; |
570 | } | 656 | } |
571 | if ($list_additional =~ m/subscribers-only/) { | 657 | if ($list_additional =~ m/subscribers-only/) { |
572 | if ($email_subscriber_list) { | 658 | if ($email_subscriber_list) { |
573 | push(@list_to, $list_address); | 659 | push(@list_to, [$list_address, "subscriber list${list_role}"]); |
574 | } | 660 | } |
575 | } else { | 661 | } else { |
576 | if ($email_list) { | 662 | if ($email_list) { |
577 | push(@list_to, $list_address); | 663 | push(@list_to, [$list_address, "open list${list_role}"]); |
578 | } | 664 | } |
579 | } | 665 | } |
580 | } elsif ($ptype eq "M") { | 666 | } elsif ($ptype eq "M") { |
@@ -591,7 +677,8 @@ sub add_categories { | |||
591 | } | 677 | } |
592 | } | 678 | } |
593 | if ($email_maintainer) { | 679 | if ($email_maintainer) { |
594 | push_email_addresses($pvalue); | 680 | my $role = get_maintainer_role($i); |
681 | push_email_addresses($pvalue, $role); | ||
595 | } | 682 | } |
596 | } elsif ($ptype eq "T") { | 683 | } elsif ($ptype eq "T") { |
597 | push(@scm, $pvalue); | 684 | push(@scm, $pvalue); |
@@ -618,7 +705,7 @@ sub email_inuse { | |||
618 | } | 705 | } |
619 | 706 | ||
620 | sub push_email_address { | 707 | sub push_email_address { |
621 | my ($line) = @_; | 708 | my ($line, $role) = @_; |
622 | 709 | ||
623 | my ($name, $address) = parse_email($line); | 710 | my ($name, $address) = parse_email($line); |
624 | 711 | ||
@@ -627,9 +714,9 @@ sub push_email_address { | |||
627 | } | 714 | } |
628 | 715 | ||
629 | if (!$email_remove_duplicates) { | 716 | if (!$email_remove_duplicates) { |
630 | push(@email_to, format_email($name, $address)); | 717 | push(@email_to, [format_email($name, $address), $role]); |
631 | } elsif (!email_inuse($name, $address)) { | 718 | } elsif (!email_inuse($name, $address)) { |
632 | push(@email_to, format_email($name, $address)); | 719 | push(@email_to, [format_email($name, $address), $role]); |
633 | $email_hash_name{$name}++; | 720 | $email_hash_name{$name}++; |
634 | $email_hash_address{$address}++; | 721 | $email_hash_address{$address}++; |
635 | } | 722 | } |
@@ -638,24 +725,52 @@ sub push_email_address { | |||
638 | } | 725 | } |
639 | 726 | ||
640 | sub push_email_addresses { | 727 | sub push_email_addresses { |
641 | my ($address) = @_; | 728 | my ($address, $role) = @_; |
642 | 729 | ||
643 | my @address_list = (); | 730 | my @address_list = (); |
644 | 731 | ||
645 | if (rfc822_valid($address)) { | 732 | if (rfc822_valid($address)) { |
646 | push_email_address($address); | 733 | push_email_address($address, $role); |
647 | } elsif (@address_list = rfc822_validlist($address)) { | 734 | } elsif (@address_list = rfc822_validlist($address)) { |
648 | my $array_count = shift(@address_list); | 735 | my $array_count = shift(@address_list); |
649 | while (my $entry = shift(@address_list)) { | 736 | while (my $entry = shift(@address_list)) { |
650 | push_email_address($entry); | 737 | push_email_address($entry, $role); |
651 | } | 738 | } |
652 | } else { | 739 | } else { |
653 | if (!push_email_address($address)) { | 740 | if (!push_email_address($address, $role)) { |
654 | warn("Invalid MAINTAINERS address: '" . $address . "'\n"); | 741 | warn("Invalid MAINTAINERS address: '" . $address . "'\n"); |
655 | } | 742 | } |
656 | } | 743 | } |
657 | } | 744 | } |
658 | 745 | ||
746 | sub add_role { | ||
747 | my ($line, $role) = @_; | ||
748 | |||
749 | my ($name, $address) = parse_email($line); | ||
750 | my $email = format_email($name, $address); | ||
751 | |||
752 | foreach my $entry (@email_to) { | ||
753 | if ($email_remove_duplicates) { | ||
754 | my ($entry_name, $entry_address) = parse_email($entry->[0]); | ||
755 | if ($name eq $entry_name || $address eq $entry_address) { | ||
756 | if ($entry->[1] eq "") { | ||
757 | $entry->[1] = "$role"; | ||
758 | } else { | ||
759 | $entry->[1] = "$entry->[1],$role"; | ||
760 | } | ||
761 | } | ||
762 | } else { | ||
763 | if ($email eq $entry->[0]) { | ||
764 | if ($entry->[1] eq "") { | ||
765 | $entry->[1] = "$role"; | ||
766 | } else { | ||
767 | $entry->[1] = "$entry->[1],$role"; | ||
768 | } | ||
769 | } | ||
770 | } | ||
771 | } | ||
772 | } | ||
773 | |||
659 | sub which { | 774 | sub which { |
660 | my ($bin) = @_; | 775 | my ($bin) = @_; |
661 | 776 | ||
@@ -730,6 +845,10 @@ sub recent_git_signoffs { | |||
730 | s/.*:\s*(.+)\s*/$1/ for (@lines); | 845 | s/.*:\s*(.+)\s*/$1/ for (@lines); |
731 | 846 | ||
732 | $total_sign_offs = @lines; | 847 | $total_sign_offs = @lines; |
848 | foreach my $line (@lines) { | ||
849 | my ($name, $address) = parse_email($line); | ||
850 | $line = format_email($name, $address); | ||
851 | } | ||
733 | 852 | ||
734 | if ($email_remove_duplicates) { | 853 | if ($email_remove_duplicates) { |
735 | @lines = mailmap(@lines); | 854 | @lines = mailmap(@lines); |
@@ -743,11 +862,19 @@ sub recent_git_signoffs { | |||
743 | # sort -rn | 862 | # sort -rn |
744 | foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { | 863 | foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { |
745 | my $sign_offs = $hash{$line}; | 864 | my $sign_offs = $hash{$line}; |
865 | my $role; | ||
866 | |||
746 | $count++; | 867 | $count++; |
747 | last if ($sign_offs < $email_git_min_signatures || | 868 | last if ($sign_offs < $email_git_min_signatures || |
748 | $count > $email_git_max_maintainers || | 869 | $count > $email_git_max_maintainers || |
749 | $sign_offs * 100 / $total_sign_offs < $email_git_min_percent); | 870 | $sign_offs * 100 / $total_sign_offs < $email_git_min_percent); |
750 | push_email_address($line); | 871 | push_email_address($line, ''); |
872 | $role = "git-signer"; | ||
873 | if ($output_rolestats) { | ||
874 | my $percent = sprintf("%.0f", $sign_offs * 100 / $total_sign_offs); | ||
875 | $role = "$role:$sign_offs/$total_sign_offs=$percent%"; | ||
876 | } | ||
877 | add_role($line, $role); | ||
751 | } | 878 | } |
752 | } | 879 | } |
753 | 880 | ||
@@ -824,11 +951,23 @@ sub git_assign_blame { | |||
824 | $count = 0; | 951 | $count = 0; |
825 | foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { | 952 | foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { |
826 | my $sign_offs = $hash{$line}; | 953 | my $sign_offs = $hash{$line}; |
954 | my $role; | ||
955 | |||
827 | $count++; | 956 | $count++; |
828 | last if ($sign_offs < $email_git_min_signatures || | 957 | last if ($sign_offs < $email_git_min_signatures || |
829 | $count > $email_git_max_maintainers || | 958 | $count > $email_git_max_maintainers || |
830 | $sign_offs * 100 / $total_sign_offs < $email_git_min_percent); | 959 | $sign_offs * 100 / $total_sign_offs < $email_git_min_percent); |
831 | push_email_address($line); | 960 | push_email_address($line, ''); |
961 | if ($from_filename) { | ||
962 | $role = "commits"; | ||
963 | } else { | ||
964 | $role = "modified commits"; | ||
965 | } | ||
966 | if ($output_rolestats) { | ||
967 | my $percent = sprintf("%.0f", $sign_offs * 100 / $total_sign_offs); | ||
968 | $role = "$role:$sign_offs/$total_sign_offs=$percent%"; | ||
969 | } | ||
970 | add_role($line, $role); | ||
832 | } | 971 | } |
833 | } | 972 | } |
834 | 973 | ||
@@ -849,6 +988,25 @@ sub sort_and_uniq { | |||
849 | return @parms; | 988 | return @parms; |
850 | } | 989 | } |
851 | 990 | ||
991 | sub merge_email { | ||
992 | my @lines; | ||
993 | my %saw; | ||
994 | |||
995 | for (@_) { | ||
996 | my ($address, $role) = @$_; | ||
997 | if (!$saw{$address}) { | ||
998 | if ($output_roles) { | ||
999 | push @lines, "$address ($role)"; | ||
1000 | } else { | ||
1001 | push @lines, $address; | ||
1002 | } | ||
1003 | $saw{$address} = 1; | ||
1004 | } | ||
1005 | } | ||
1006 | |||
1007 | return @lines; | ||
1008 | } | ||
1009 | |||
852 | sub output { | 1010 | sub output { |
853 | my @parms = @_; | 1011 | my @parms = @_; |
854 | 1012 | ||