aboutsummaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/get_maintainer.pl499
1 files changed, 371 insertions, 128 deletions
diff --git a/scripts/get_maintainer.pl b/scripts/get_maintainer.pl
index 81a67a458e78..445e8845f0a4 100755
--- a/scripts/get_maintainer.pl
+++ b/scripts/get_maintainer.pl
@@ -13,7 +13,7 @@
13use strict; 13use strict;
14 14
15my $P = $0; 15my $P = $0;
16my $V = '0.21'; 16my $V = '0.23';
17 17
18use Getopt::Long qw(:config no_auto_abbrev); 18use Getopt::Long qw(:config no_auto_abbrev);
19 19
@@ -23,16 +23,19 @@ my $email_usename = 1;
23my $email_maintainer = 1; 23my $email_maintainer = 1;
24my $email_list = 1; 24my $email_list = 1;
25my $email_subscriber_list = 0; 25my $email_subscriber_list = 0;
26my $email_git = 1;
27my $email_git_penguin_chiefs = 0; 26my $email_git_penguin_chiefs = 0;
27my $email_git = 1;
28my $email_git_blame = 0;
28my $email_git_min_signatures = 1; 29my $email_git_min_signatures = 1;
29my $email_git_max_maintainers = 5; 30my $email_git_max_maintainers = 5;
30my $email_git_min_percent = 5; 31my $email_git_min_percent = 5;
31my $email_git_since = "1-year-ago"; 32my $email_git_since = "1-year-ago";
32my $email_git_blame = 0; 33my $email_hg_since = "-365";
33my $email_remove_duplicates = 1; 34my $email_remove_duplicates = 1;
34my $output_multiline = 1; 35my $output_multiline = 1;
35my $output_separator = ", "; 36my $output_separator = ", ";
37my $output_roles = 0;
38my $output_rolestats = 0;
36my $scm = 0; 39my $scm = 0;
37my $web = 0; 40my $web = 0;
38my $subsystem = 0; 41my $subsystem = 0;
@@ -64,21 +67,52 @@ my $penguin_chiefs = "\(" . join("|",@penguin_chief_names) . "\)";
64my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])"; 67my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
65my $rfc822_char = '[\\000-\\377]'; 68my $rfc822_char = '[\\000-\\377]';
66 69
70# VCS command support: class-like functions and strings
71
72my %VCS_cmds;
73
74my %VCS_cmds_git = (
75 "execute_cmd" => \&git_execute_cmd,
76 "available" => '(which("git") ne "") && (-d ".git")',
77 "find_signers_cmd" => "git log --since=\$email_git_since -- \$file",
78 "find_commit_signers_cmd" => "git log -1 \$commit",
79 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
80 "blame_file_cmd" => "git blame -l \$file",
81 "commit_pattern" => "^commit [0-9a-f]{40,40}",
82 "blame_commit_pattern" => "^([0-9a-f]+) "
83);
84
85my %VCS_cmds_hg = (
86 "execute_cmd" => \&hg_execute_cmd,
87 "available" => '(which("hg") ne "") && (-d ".hg")',
88 "find_signers_cmd" =>
89 "hg log --date=\$email_hg_since" .
90 " --template='commit {node}\\n{desc}\\n' -- \$file",
91 "find_commit_signers_cmd" => "hg log --template='{desc}\\n' -r \$commit",
92 "blame_range_cmd" => "", # not supported
93 "blame_file_cmd" => "hg blame -c \$file",
94 "commit_pattern" => "^commit [0-9a-f]{40,40}",
95 "blame_commit_pattern" => "^([0-9a-f]+):"
96);
97
67if (!GetOptions( 98if (!GetOptions(
68 'email!' => \$email, 99 'email!' => \$email,
69 'git!' => \$email_git, 100 'git!' => \$email_git,
101 'git-blame!' => \$email_git_blame,
70 'git-chief-penguins!' => \$email_git_penguin_chiefs, 102 'git-chief-penguins!' => \$email_git_penguin_chiefs,
71 'git-min-signatures=i' => \$email_git_min_signatures, 103 'git-min-signatures=i' => \$email_git_min_signatures,
72 'git-max-maintainers=i' => \$email_git_max_maintainers, 104 'git-max-maintainers=i' => \$email_git_max_maintainers,
73 'git-min-percent=i' => \$email_git_min_percent, 105 'git-min-percent=i' => \$email_git_min_percent,
74 'git-since=s' => \$email_git_since, 106 'git-since=s' => \$email_git_since,
75 'git-blame!' => \$email_git_blame, 107 'hg-since=s' => \$email_hg_since,
76 'remove-duplicates!' => \$email_remove_duplicates, 108 'remove-duplicates!' => \$email_remove_duplicates,
77 'm!' => \$email_maintainer, 109 'm!' => \$email_maintainer,
78 'n!' => \$email_usename, 110 'n!' => \$email_usename,
79 'l!' => \$email_list, 111 'l!' => \$email_list,
80 's!' => \$email_subscriber_list, 112 's!' => \$email_subscriber_list,
81 'multiline!' => \$output_multiline, 113 'multiline!' => \$output_multiline,
114 'roles!' => \$output_roles,
115 'rolestats!' => \$output_rolestats,
82 'separator=s' => \$output_separator, 116 'separator=s' => \$output_separator,
83 'subsystem!' => \$subsystem, 117 'subsystem!' => \$subsystem,
84 'status!' => \$status, 118 'status!' => \$status,
@@ -90,8 +124,7 @@ if (!GetOptions(
90 'v|version' => \$version, 124 'v|version' => \$version,
91 'h|help' => \$help, 125 'h|help' => \$help,
92 )) { 126 )) {
93 usage(); 127 die "$P: invalid argument - use --help if necessary\n";
94 die "$P: invalid argument\n";
95} 128}
96 129
97if ($help != 0) { 130if ($help != 0) {
@@ -113,6 +146,10 @@ if ($output_separator ne ", ") {
113 $output_multiline = 0; 146 $output_multiline = 0;
114} 147}
115 148
149if ($output_rolestats) {
150 $output_roles = 1;
151}
152
116my $selections = $email + $scm + $status + $subsystem + $web; 153my $selections = $email + $scm + $status + $subsystem + $web;
117if ($selections == 0) { 154if ($selections == 0) {
118 usage(); 155 usage();
@@ -175,7 +212,7 @@ if ($email_remove_duplicates) {
175 next if ($line =~ m/^\s*$/); 212 next if ($line =~ m/^\s*$/);
176 213
177 my ($name, $address) = parse_email($line); 214 my ($name, $address) = parse_email($line);
178 $line = format_email($name, $address); 215 $line = format_email($name, $address, $email_usename);
179 216
180 next if ($line =~ m/^\s*$/); 217 next if ($line =~ m/^\s*$/);
181 218
@@ -207,12 +244,10 @@ foreach my $file (@ARGV) {
207 push(@files, $file); 244 push(@files, $file);
208 if (-f $file && $keywords) { 245 if (-f $file && $keywords) {
209 open(FILE, "<$file") or die "$P: Can't open ${file}\n"; 246 open(FILE, "<$file") or die "$P: Can't open ${file}\n";
210 while (<FILE>) { 247 my $text = do { local($/) ; <FILE> };
211 my $patch_line = $_; 248 foreach my $line (keys %keyword_hash) {
212 foreach my $line (keys %keyword_hash) { 249 if ($text =~ m/$keyword_hash{$line}/x) {
213 if ($patch_line =~ m/^.*$keyword_hash{$line}/x) { 250 push(@keyword_tvi, $line);
214 push(@keyword_tvi, $line);
215 }
216 } 251 }
217 } 252 }
218 close(FILE); 253 close(FILE);
@@ -304,11 +339,11 @@ foreach my $file (@files) {
304 } 339 }
305 340
306 if ($email && $email_git) { 341 if ($email && $email_git) {
307 recent_git_signoffs($file); 342 vcs_file_signoffs($file);
308 } 343 }
309 344
310 if ($email && $email_git_blame) { 345 if ($email && $email_git_blame) {
311 git_assign_blame($file); 346 vcs_file_blame($file);
312 } 347 }
313} 348}
314 349
@@ -324,11 +359,11 @@ if ($email) {
324 if ($chief =~ m/^(.*):(.*)/) { 359 if ($chief =~ m/^(.*):(.*)/) {
325 my $email_address; 360 my $email_address;
326 361
327 $email_address = format_email($1, $2); 362 $email_address = format_email($1, $2, $email_usename);
328 if ($email_git_penguin_chiefs) { 363 if ($email_git_penguin_chiefs) {
329 push(@email_to, $email_address); 364 push(@email_to, [$email_address, 'chief penguin']);
330 } else { 365 } else {
331 @email_to = grep(!/${email_address}/, @email_to); 366 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
332 } 367 }
333 } 368 }
334 } 369 }
@@ -342,7 +377,7 @@ if ($email || $email_list) {
342 if ($email_list) { 377 if ($email_list) {
343 @to = (@to, @list_to); 378 @to = (@to, @list_to);
344 } 379 }
345 output(uniq(@to)); 380 output(merge_email(@to));
346} 381}
347 382
348if ($scm) { 383if ($scm) {
@@ -398,13 +433,16 @@ MAINTAINER field selection options:
398 --git-min-signatures => number of signatures required (default: 1) 433 --git-min-signatures => number of signatures required (default: 1)
399 --git-max-maintainers => maximum maintainers to add (default: 5) 434 --git-max-maintainers => maximum maintainers to add (default: 5)
400 --git-min-percent => minimum percentage of commits required (default: 5) 435 --git-min-percent => minimum percentage of commits required (default: 5)
401 --git-since => git history to use (default: 1-year-ago)
402 --git-blame => use git blame to find modified commits for patch or file 436 --git-blame => use git blame to find modified commits for patch or file
437 --git-since => git history to use (default: 1-year-ago)
438 --hg-since => hg history to use (default: -365)
403 --m => include maintainer(s) if any 439 --m => include maintainer(s) if any
404 --n => include name 'Full Name <addr\@domain.tld>' 440 --n => include name 'Full Name <addr\@domain.tld>'
405 --l => include list(s) if any 441 --l => include list(s) if any
406 --s => include subscriber only list(s) if any 442 --s => include subscriber only list(s) if any
407 --remove-duplicates => minimize duplicate email names/addresses 443 --remove-duplicates => minimize duplicate email names/addresses
444 --roles => show roles (status:subsystem, git-signer, list, etc...)
445 --rolestats => show roles and statistics (commits/total_commits, %)
408 --scm => print SCM tree(s) if any 446 --scm => print SCM tree(s) if any
409 --status => print status if any 447 --status => print status if any
410 --subsystem => print subsystem name if any 448 --subsystem => print subsystem name if any
@@ -430,11 +468,24 @@ Notes:
430 directory are examined as git recurses directories. 468 directory are examined as git recurses directories.
431 Any specified X: (exclude) pattern matches are _not_ ignored. 469 Any specified X: (exclude) pattern matches are _not_ ignored.
432 Used with "--nogit", directory is used as a pattern match, 470 Used with "--nogit", directory is used as a pattern match,
433 no individual file within the directory or subdirectory 471 no individual file within the directory or subdirectory
434 is matched. 472 is matched.
435 Used with "--git-blame", does not iterate all files in directory 473 Used with "--git-blame", does not iterate all files in directory
436 Using "--git-blame" is slow and may add old committers and authors 474 Using "--git-blame" is slow and may add old committers and authors
437 that are no longer active maintainers to the output. 475 that are no longer active maintainers to the output.
476 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
477 other automated tools that expect only ["name"] <email address>
478 may not work because of additional output after <email address>.
479 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
480 not the percentage of the entire file authored. # of commits is
481 not a good measure of amount of code authored. 1 major commit may
482 contain a thousand lines, 5 trivial commits may modify a single line.
483 If git is not installed, but mercurial (hg) is installed and an .hg
484 repository exists, the following options apply to mercurial:
485 --git,
486 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
487 --git-blame
488 Use --hg-since not --git-since to control date selection
438EOT 489EOT
439} 490}
440 491
@@ -493,7 +544,7 @@ sub parse_email {
493} 544}
494 545
495sub format_email { 546sub format_email {
496 my ($name, $address) = @_; 547 my ($name, $address, $usename) = @_;
497 548
498 my $formatted_email; 549 my $formatted_email;
499 550
@@ -506,11 +557,11 @@ sub format_email {
506 $name = "\"$name\""; 557 $name = "\"$name\"";
507 } 558 }
508 559
509 if ($email_usename) { 560 if ($usename) {
510 if ("$name" eq "") { 561 if ("$name" eq "") {
511 $formatted_email = "$address"; 562 $formatted_email = "$address";
512 } else { 563 } else {
513 $formatted_email = "$name <${address}>"; 564 $formatted_email = "$name <$address>";
514 } 565 }
515 } else { 566 } else {
516 $formatted_email = $address; 567 $formatted_email = $address;
@@ -547,6 +598,71 @@ sub find_ending_index {
547 return $index; 598 return $index;
548} 599}
549 600
601sub get_maintainer_role {
602 my ($index) = @_;
603
604 my $i;
605 my $start = find_starting_index($index);
606 my $end = find_ending_index($index);
607
608 my $role;
609 my $subsystem = $typevalue[$start];
610 if (length($subsystem) > 20) {
611 $subsystem = substr($subsystem, 0, 17);
612 $subsystem =~ s/\s*$//;
613 $subsystem = $subsystem . "...";
614 }
615
616 for ($i = $start + 1; $i < $end; $i++) {
617 my $tv = $typevalue[$i];
618 if ($tv =~ m/^(\C):\s*(.*)/) {
619 my $ptype = $1;
620 my $pvalue = $2;
621 if ($ptype eq "S") {
622 $role = $pvalue;
623 }
624 }
625 }
626
627 $role = lc($role);
628 if ($role eq "supported") {
629 $role = "supporter";
630 } elsif ($role eq "maintained") {
631 $role = "maintainer";
632 } elsif ($role eq "odd fixes") {
633 $role = "odd fixer";
634 } elsif ($role eq "orphan") {
635 $role = "orphan minder";
636 } elsif ($role eq "obsolete") {
637 $role = "obsolete minder";
638 } elsif ($role eq "buried alive in reporters") {
639 $role = "chief penguin";
640 }
641
642 return $role . ":" . $subsystem;
643}
644
645sub get_list_role {
646 my ($index) = @_;
647
648 my $i;
649 my $start = find_starting_index($index);
650 my $end = find_ending_index($index);
651
652 my $subsystem = $typevalue[$start];
653 if (length($subsystem) > 20) {
654 $subsystem = substr($subsystem, 0, 17);
655 $subsystem =~ s/\s*$//;
656 $subsystem = $subsystem . "...";
657 }
658
659 if ($subsystem eq "THE REST") {
660 $subsystem = "";
661 }
662
663 return $subsystem;
664}
665
550sub add_categories { 666sub add_categories {
551 my ($index) = @_; 667 my ($index) = @_;
552 668
@@ -564,17 +680,22 @@ sub add_categories {
564 if ($ptype eq "L") { 680 if ($ptype eq "L") {
565 my $list_address = $pvalue; 681 my $list_address = $pvalue;
566 my $list_additional = ""; 682 my $list_additional = "";
683 my $list_role = get_list_role($i);
684
685 if ($list_role ne "") {
686 $list_role = ":" . $list_role;
687 }
567 if ($list_address =~ m/([^\s]+)\s+(.*)$/) { 688 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
568 $list_address = $1; 689 $list_address = $1;
569 $list_additional = $2; 690 $list_additional = $2;
570 } 691 }
571 if ($list_additional =~ m/subscribers-only/) { 692 if ($list_additional =~ m/subscribers-only/) {
572 if ($email_subscriber_list) { 693 if ($email_subscriber_list) {
573 push(@list_to, $list_address); 694 push(@list_to, [$list_address, "subscriber list${list_role}"]);
574 } 695 }
575 } else { 696 } else {
576 if ($email_list) { 697 if ($email_list) {
577 push(@list_to, $list_address); 698 push(@list_to, [$list_address, "open list${list_role}"]);
578 } 699 }
579 } 700 }
580 } elsif ($ptype eq "M") { 701 } elsif ($ptype eq "M") {
@@ -585,13 +706,14 @@ sub add_categories {
585 if ($tv =~ m/^(\C):\s*(.*)/) { 706 if ($tv =~ m/^(\C):\s*(.*)/) {
586 if ($1 eq "P") { 707 if ($1 eq "P") {
587 $name = $2; 708 $name = $2;
588 $pvalue = format_email($name, $address); 709 $pvalue = format_email($name, $address, $email_usename);
589 } 710 }
590 } 711 }
591 } 712 }
592 } 713 }
593 if ($email_maintainer) { 714 if ($email_maintainer) {
594 push_email_addresses($pvalue); 715 my $role = get_maintainer_role($i);
716 push_email_addresses($pvalue, $role);
595 } 717 }
596 } elsif ($ptype eq "T") { 718 } elsif ($ptype eq "T") {
597 push(@scm, $pvalue); 719 push(@scm, $pvalue);
@@ -618,7 +740,7 @@ sub email_inuse {
618} 740}
619 741
620sub push_email_address { 742sub push_email_address {
621 my ($line) = @_; 743 my ($line, $role) = @_;
622 744
623 my ($name, $address) = parse_email($line); 745 my ($name, $address) = parse_email($line);
624 746
@@ -627,9 +749,9 @@ sub push_email_address {
627 } 749 }
628 750
629 if (!$email_remove_duplicates) { 751 if (!$email_remove_duplicates) {
630 push(@email_to, format_email($name, $address)); 752 push(@email_to, [format_email($name, $address, $email_usename), $role]);
631 } elsif (!email_inuse($name, $address)) { 753 } elsif (!email_inuse($name, $address)) {
632 push(@email_to, format_email($name, $address)); 754 push(@email_to, [format_email($name, $address, $email_usename), $role]);
633 $email_hash_name{$name}++; 755 $email_hash_name{$name}++;
634 $email_hash_address{$address}++; 756 $email_hash_address{$address}++;
635 } 757 }
@@ -638,24 +760,52 @@ sub push_email_address {
638} 760}
639 761
640sub push_email_addresses { 762sub push_email_addresses {
641 my ($address) = @_; 763 my ($address, $role) = @_;
642 764
643 my @address_list = (); 765 my @address_list = ();
644 766
645 if (rfc822_valid($address)) { 767 if (rfc822_valid($address)) {
646 push_email_address($address); 768 push_email_address($address, $role);
647 } elsif (@address_list = rfc822_validlist($address)) { 769 } elsif (@address_list = rfc822_validlist($address)) {
648 my $array_count = shift(@address_list); 770 my $array_count = shift(@address_list);
649 while (my $entry = shift(@address_list)) { 771 while (my $entry = shift(@address_list)) {
650 push_email_address($entry); 772 push_email_address($entry, $role);
651 } 773 }
652 } else { 774 } else {
653 if (!push_email_address($address)) { 775 if (!push_email_address($address, $role)) {
654 warn("Invalid MAINTAINERS address: '" . $address . "'\n"); 776 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
655 } 777 }
656 } 778 }
657} 779}
658 780
781sub add_role {
782 my ($line, $role) = @_;
783
784 my ($name, $address) = parse_email($line);
785 my $email = format_email($name, $address, $email_usename);
786
787 foreach my $entry (@email_to) {
788 if ($email_remove_duplicates) {
789 my ($entry_name, $entry_address) = parse_email($entry->[0]);
790 if ($name eq $entry_name || $address eq $entry_address) {
791 if ($entry->[1] eq "") {
792 $entry->[1] = "$role";
793 } else {
794 $entry->[1] = "$entry->[1],$role";
795 }
796 }
797 } else {
798 if ($email eq $entry->[0]) {
799 if ($entry->[1] eq "") {
800 $entry->[1] = "$role";
801 } else {
802 $entry->[1] = "$entry->[1],$role";
803 }
804 }
805 }
806 }
807}
808
659sub which { 809sub which {
660 my ($bin) = @_; 810 my ($bin) = @_;
661 811
@@ -669,7 +819,7 @@ sub which {
669} 819}
670 820
671sub mailmap { 821sub mailmap {
672 my @lines = @_; 822 my (@lines) = @_;
673 my %hash; 823 my %hash;
674 824
675 foreach my $line (@lines) { 825 foreach my $line (@lines) {
@@ -678,14 +828,14 @@ sub mailmap {
678 $hash{$name} = $address; 828 $hash{$name} = $address;
679 } elsif ($address ne $hash{$name}) { 829 } elsif ($address ne $hash{$name}) {
680 $address = $hash{$name}; 830 $address = $hash{$name};
681 $line = format_email($name, $address); 831 $line = format_email($name, $address, $email_usename);
682 } 832 }
683 if (exists($mailmap{$name})) { 833 if (exists($mailmap{$name})) {
684 my $obj = $mailmap{$name}; 834 my $obj = $mailmap{$name};
685 foreach my $map_address (@$obj) { 835 foreach my $map_address (@$obj) {
686 if (($map_address eq $address) && 836 if (($map_address eq $address) &&
687 ($map_address ne $hash{$name})) { 837 ($map_address ne $hash{$name})) {
688 $line = format_email($name, $hash{$name}); 838 $line = format_email($name, $hash{$name}, $email_usename);
689 } 839 }
690 } 840 }
691 } 841 }
@@ -694,34 +844,38 @@ sub mailmap {
694 return @lines; 844 return @lines;
695} 845}
696 846
697sub recent_git_signoffs { 847sub git_execute_cmd {
698 my ($file) = @_; 848 my ($cmd) = @_;
699
700 my $sign_offs = "";
701 my $cmd = "";
702 my $output = "";
703 my $count = 0;
704 my @lines = (); 849 my @lines = ();
705 my %hash;
706 my $total_sign_offs;
707 850
708 if (which("git") eq "") { 851 my $output = `$cmd`;
709 warn("$P: git not found. Add --nogit to options?\n"); 852 $output =~ s/^\s*//gm;
710 return; 853 @lines = split("\n", $output);
711 }
712 if (!(-d ".git")) {
713 warn("$P: .git directory not found. Use a git repository for better results.\n");
714 warn("$P: perhaps 'git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git'\n");
715 return;
716 }
717 854
718 $cmd = "git log --since=${email_git_since} -- ${file}"; 855 return @lines;
856}
719 857
720 $output = `${cmd}`; 858sub hg_execute_cmd {
721 $output =~ s/^\s*//gm; 859 my ($cmd) = @_;
860 my @lines = ();
722 861
862 my $output = `$cmd`;
723 @lines = split("\n", $output); 863 @lines = split("\n", $output);
724 864
865 return @lines;
866}
867
868sub vcs_find_signers {
869 my ($cmd) = @_;
870 my @lines = ();
871 my $commits;
872
873 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
874
875 my $pattern = $VCS_cmds{"commit_pattern"};
876
877 $commits = grep(/$pattern/, @lines); # of commits
878
725 @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines); 879 @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines);
726 if (!$email_git_penguin_chiefs) { 880 if (!$email_git_penguin_chiefs) {
727 @lines = grep(!/${penguin_chiefs}/i, @lines); 881 @lines = grep(!/${penguin_chiefs}/i, @lines);
@@ -729,111 +883,183 @@ sub recent_git_signoffs {
729 # cut -f2- -d":" 883 # cut -f2- -d":"
730 s/.*:\s*(.+)\s*/$1/ for (@lines); 884 s/.*:\s*(.+)\s*/$1/ for (@lines);
731 885
732 $total_sign_offs = @lines; 886## Reformat email addresses (with names) to avoid badly written signatures
733 887
734 if ($email_remove_duplicates) { 888 foreach my $line (@lines) {
735 @lines = mailmap(@lines); 889 my ($name, $address) = parse_email($line);
890 $line = format_email($name, $address, 1);
736 } 891 }
737 892
738 @lines = sort(@lines); 893 return ($commits, @lines);
739
740 # uniq -c
741 $hash{$_}++ for @lines;
742
743 # sort -rn
744 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
745 my $sign_offs = $hash{$line};
746 $count++;
747 last if ($sign_offs < $email_git_min_signatures ||
748 $count > $email_git_max_maintainers ||
749 $sign_offs * 100 / $total_sign_offs < $email_git_min_percent);
750 push_email_address($line);
751 }
752} 894}
753 895
754sub save_commits { 896sub vcs_save_commits {
755 my ($cmd, @commits) = @_; 897 my ($cmd) = @_;
756 my $output;
757 my @lines = (); 898 my @lines = ();
899 my @commits = ();
758 900
759 $output = `${cmd}`; 901 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
760 902
761 @lines = split("\n", $output);
762 foreach my $line (@lines) { 903 foreach my $line (@lines) {
763 if ($line =~ m/^(\w+) /) { 904 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
764 push (@commits, $1); 905 push(@commits, $1);
765 } 906 }
766 } 907 }
908
767 return @commits; 909 return @commits;
768} 910}
769 911
770sub git_assign_blame { 912sub vcs_blame {
771 my ($file) = @_; 913 my ($file) = @_;
772
773 my @lines = ();
774 my @commits = ();
775 my $cmd; 914 my $cmd;
776 my $output; 915 my @commits = ();
777 my %hash; 916
778 my $total_sign_offs; 917 return @commits if (!(-f $file));
779 my $count; 918
919 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
920 my @all_commits = ();
921
922 $cmd = $VCS_cmds{"blame_file_cmd"};
923 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
924 @all_commits = vcs_save_commits($cmd);
780 925
781 if (@range) {
782 foreach my $file_range_diff (@range) { 926 foreach my $file_range_diff (@range) {
783 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/)); 927 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
784 my $diff_file = $1; 928 my $diff_file = $1;
785 my $diff_start = $2; 929 my $diff_start = $2;
786 my $diff_length = $3; 930 my $diff_length = $3;
787 next if (!("$file" eq "$diff_file")); 931 next if ("$file" ne "$diff_file");
788 $cmd = "git blame -l -L $diff_start,+$diff_length $file"; 932 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
789 @commits = save_commits($cmd, @commits); 933 push(@commits, $all_commits[$i]);
934 }
790 } 935 }
791 } else { 936 } elsif (@range) {
792 if (-f $file) { 937 foreach my $file_range_diff (@range) {
793 $cmd = "git blame -l $file"; 938 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
794 @commits = save_commits($cmd, @commits); 939 my $diff_file = $1;
940 my $diff_start = $2;
941 my $diff_length = $3;
942 next if ("$file" ne "$diff_file");
943 $cmd = $VCS_cmds{"blame_range_cmd"};
944 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
945 push(@commits, vcs_save_commits($cmd));
795 } 946 }
947 } else {
948 $cmd = $VCS_cmds{"blame_file_cmd"};
949 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
950 @commits = vcs_save_commits($cmd);
796 } 951 }
797 952
798 $total_sign_offs = 0; 953 return @commits;
799 @commits = uniq(@commits); 954}
800 foreach my $commit (@commits) {
801 $cmd = "git log -1 ${commit}";
802 955
803 $output = `${cmd}`; 956my $printed_novcs = 0;
804 $output =~ s/^\s*//gm; 957sub vcs_exists {
805 @lines = split("\n", $output); 958 %VCS_cmds = %VCS_cmds_git;
959 return 1 if eval $VCS_cmds{"available"};
960 %VCS_cmds = %VCS_cmds_hg;
961 return 1 if eval $VCS_cmds{"available"};
962 %VCS_cmds = ();
963 if (!$printed_novcs) {
964 warn("$P: No supported VCS found. Add --nogit to options?\n");
965 warn("Using a git repository produces better results.\n");
966 warn("Try Linus Torvalds' latest git repository using:\n");
967 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
968 $printed_novcs = 1;
969 }
970 return 0;
971}
806 972
807 @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines); 973sub vcs_assign {
808 if (!$email_git_penguin_chiefs) { 974 my ($role, $divisor, @lines) = @_;
809 @lines = grep(!/${penguin_chiefs}/i, @lines);
810 }
811 975
812 # cut -f2- -d":" 976 my %hash;
813 s/.*:\s*(.+)\s*/$1/ for (@lines); 977 my $count = 0;
814 978
815 $total_sign_offs += @lines; 979 return if (@lines <= 0);
816 980
817 if ($email_remove_duplicates) { 981 if ($divisor <= 0) {
818 @lines = mailmap(@lines); 982 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
819 } 983 $divisor = 1;
984 }
820 985
821 $hash{$_}++ for @lines; 986 if ($email_remove_duplicates) {
987 @lines = mailmap(@lines);
822 } 988 }
823 989
824 $count = 0; 990 @lines = sort(@lines);
991
992 # uniq -c
993 $hash{$_}++ for @lines;
994
995 # sort -rn
825 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) { 996 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
826 my $sign_offs = $hash{$line}; 997 my $sign_offs = $hash{$line};
998 my $percent = $sign_offs * 100 / $divisor;
999
1000 $percent = 100 if ($percent > 100);
827 $count++; 1001 $count++;
828 last if ($sign_offs < $email_git_min_signatures || 1002 last if ($sign_offs < $email_git_min_signatures ||
829 $count > $email_git_max_maintainers || 1003 $count > $email_git_max_maintainers ||
830 $sign_offs * 100 / $total_sign_offs < $email_git_min_percent); 1004 $percent < $email_git_min_percent);
831 push_email_address($line); 1005 push_email_address($line, '');
1006 if ($output_rolestats) {
1007 my $fmt_percent = sprintf("%.0f", $percent);
1008 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1009 } else {
1010 add_role($line, $role);
1011 }
1012 }
1013}
1014
1015sub vcs_file_signoffs {
1016 my ($file) = @_;
1017
1018 my @signers = ();
1019 my $commits;
1020
1021 return if (!vcs_exists());
1022
1023 my $cmd = $VCS_cmds{"find_signers_cmd"};
1024 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1025
1026 ($commits, @signers) = vcs_find_signers($cmd);
1027 vcs_assign("commit_signer", $commits, @signers);
1028}
1029
1030sub vcs_file_blame {
1031 my ($file) = @_;
1032
1033 my @signers = ();
1034 my @commits = ();
1035 my $total_commits;
1036
1037 return if (!vcs_exists());
1038
1039 @commits = vcs_blame($file);
1040 @commits = uniq(@commits);
1041 $total_commits = @commits;
1042
1043 foreach my $commit (@commits) {
1044 my $commit_count;
1045 my @commit_signers = ();
1046
1047 my $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1048 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1049
1050 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1051 push(@signers, @commit_signers);
1052 }
1053
1054 if ($from_filename) {
1055 vcs_assign("commits", $total_commits, @signers);
1056 } else {
1057 vcs_assign("modified commits", $total_commits, @signers);
832 } 1058 }
833} 1059}
834 1060
835sub uniq { 1061sub uniq {
836 my @parms = @_; 1062 my (@parms) = @_;
837 1063
838 my %saw; 1064 my %saw;
839 @parms = grep(!$saw{$_}++, @parms); 1065 @parms = grep(!$saw{$_}++, @parms);
@@ -841,7 +1067,7 @@ sub uniq {
841} 1067}
842 1068
843sub sort_and_uniq { 1069sub sort_and_uniq {
844 my @parms = @_; 1070 my (@parms) = @_;
845 1071
846 my %saw; 1072 my %saw;
847 @parms = sort @parms; 1073 @parms = sort @parms;
@@ -849,8 +1075,27 @@ sub sort_and_uniq {
849 return @parms; 1075 return @parms;
850} 1076}
851 1077
1078sub merge_email {
1079 my @lines;
1080 my %saw;
1081
1082 for (@_) {
1083 my ($address, $role) = @$_;
1084 if (!$saw{$address}) {
1085 if ($output_roles) {
1086 push(@lines, "$address ($role)");
1087 } else {
1088 push(@lines, $address);
1089 }
1090 $saw{$address} = 1;
1091 }
1092 }
1093
1094 return @lines;
1095}
1096
852sub output { 1097sub output {
853 my @parms = @_; 1098 my (@parms) = @_;
854 1099
855 if ($output_multiline) { 1100 if ($output_multiline) {
856 foreach my $line (@parms) { 1101 foreach my $line (@parms) {
@@ -947,11 +1192,9 @@ sub rfc822_validlist ($) {
947 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so && 1192 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
948 $s =~ m/^$rfc822_char*$/) { 1193 $s =~ m/^$rfc822_char*$/) {
949 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) { 1194 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
950 push @r, $1; 1195 push(@r, $1);
951 } 1196 }
952 return wantarray ? (scalar(@r), @r) : 1; 1197 return wantarray ? (scalar(@r), @r) : 1;
953 } 1198 }
954 else { 1199 return wantarray ? () : 0;
955 return wantarray ? () : 0;
956 }
957} 1200}