aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/get_maintainer.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/get_maintainer.pl')
-rwxr-xr-xscripts/get_maintainer.pl261
1 files changed, 168 insertions, 93 deletions
diff --git a/scripts/get_maintainer.pl b/scripts/get_maintainer.pl
index fe91a984247b..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.22'; 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,13 +23,14 @@ 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 = ", ";
@@ -66,15 +67,44 @@ my $penguin_chiefs = "\(" . join("|",@penguin_chief_names) . "\)";
66my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])"; 67my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
67my $rfc822_char = '[\\000-\\377]'; 68my $rfc822_char = '[\\000-\\377]';
68 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
69if (!GetOptions( 98if (!GetOptions(
70 'email!' => \$email, 99 'email!' => \$email,
71 'git!' => \$email_git, 100 'git!' => \$email_git,
101 'git-blame!' => \$email_git_blame,
72 'git-chief-penguins!' => \$email_git_penguin_chiefs, 102 'git-chief-penguins!' => \$email_git_penguin_chiefs,
73 'git-min-signatures=i' => \$email_git_min_signatures, 103 'git-min-signatures=i' => \$email_git_min_signatures,
74 'git-max-maintainers=i' => \$email_git_max_maintainers, 104 'git-max-maintainers=i' => \$email_git_max_maintainers,
75 'git-min-percent=i' => \$email_git_min_percent, 105 'git-min-percent=i' => \$email_git_min_percent,
76 'git-since=s' => \$email_git_since, 106 'git-since=s' => \$email_git_since,
77 'git-blame!' => \$email_git_blame, 107 'hg-since=s' => \$email_hg_since,
78 'remove-duplicates!' => \$email_remove_duplicates, 108 'remove-duplicates!' => \$email_remove_duplicates,
79 'm!' => \$email_maintainer, 109 'm!' => \$email_maintainer,
80 'n!' => \$email_usename, 110 'n!' => \$email_usename,
@@ -309,11 +339,11 @@ foreach my $file (@files) {
309 } 339 }
310 340
311 if ($email && $email_git) { 341 if ($email && $email_git) {
312 git_file_signoffs($file); 342 vcs_file_signoffs($file);
313 } 343 }
314 344
315 if ($email && $email_git_blame) { 345 if ($email && $email_git_blame) {
316 git_assign_blame($file); 346 vcs_file_blame($file);
317 } 347 }
318} 348}
319 349
@@ -403,8 +433,9 @@ MAINTAINER field selection options:
403 --git-min-signatures => number of signatures required (default: 1) 433 --git-min-signatures => number of signatures required (default: 1)
404 --git-max-maintainers => maximum maintainers to add (default: 5) 434 --git-max-maintainers => maximum maintainers to add (default: 5)
405 --git-min-percent => minimum percentage of commits required (default: 5) 435 --git-min-percent => minimum percentage of commits required (default: 5)
406 --git-since => git history to use (default: 1-year-ago)
407 --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)
408 --m => include maintainer(s) if any 439 --m => include maintainer(s) if any
409 --n => include name 'Full Name <addr\@domain.tld>' 440 --n => include name 'Full Name <addr\@domain.tld>'
410 --l => include list(s) if any 441 --l => include list(s) if any
@@ -437,8 +468,8 @@ Notes:
437 directory are examined as git recurses directories. 468 directory are examined as git recurses directories.
438 Any specified X: (exclude) pattern matches are _not_ ignored. 469 Any specified X: (exclude) pattern matches are _not_ ignored.
439 Used with "--nogit", directory is used as a pattern match, 470 Used with "--nogit", directory is used as a pattern match,
440 no individual file within the directory or subdirectory 471 no individual file within the directory or subdirectory
441 is matched. 472 is matched.
442 Used with "--git-blame", does not iterate all files in directory 473 Used with "--git-blame", does not iterate all files in directory
443 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
444 that are no longer active maintainers to the output. 475 that are no longer active maintainers to the output.
@@ -449,6 +480,12 @@ Notes:
449 not the percentage of the entire file authored. # of commits is 480 not the percentage of the entire file authored. # of commits is
450 not a good measure of amount of code authored. 1 major commit may 481 not a good measure of amount of code authored. 1 major commit may
451 contain a thousand lines, 5 trivial commits may modify a single line. 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
452EOT 489EOT
453} 490}
454 491
@@ -807,44 +844,37 @@ sub mailmap {
807 return @lines; 844 return @lines;
808} 845}
809 846
810my $printed_nogit = 0; 847sub git_execute_cmd {
811my $printed_nogitdir = 0; 848 my ($cmd) = @_;
812sub has_git { 849 my @lines = ();
813 if (which("git") eq "") {
814 if (!$printed_nogit) {
815 warn("$P: git not found. Add --nogit to options?\n");
816 $printed_nogit = 1;
817 }
818 return 0;
819 }
820 if (!(-d ".git")) {
821 if (!$printed_nogitdir) {
822 warn(".git directory not found. "
823 . "Using a git repository produces better results.\n");
824 warn("Try Linus Torvalds' latest git repository using:\n");
825 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
826 $printed_nogitdir = 1;
827 }
828 return 0;
829 }
830 850
831 return 1; 851 my $output = `$cmd`;
852 $output =~ s/^\s*//gm;
853 @lines = split("\n", $output);
854
855 return @lines;
832} 856}
833 857
834sub git_find_signers { 858sub hg_execute_cmd {
835 my ($cmd) = @_; 859 my ($cmd) = @_;
860 my @lines = ();
861
862 my $output = `$cmd`;
863 @lines = split("\n", $output);
836 864
837 my $output; 865 return @lines;
866}
867
868sub vcs_find_signers {
869 my ($cmd) = @_;
838 my @lines = (); 870 my @lines = ();
839 my $commits; 871 my $commits;
840 872
841 return (0, @lines) if (!has_git()); 873 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
842 874
843 $output = `${cmd}`; 875 my $pattern = $VCS_cmds{"commit_pattern"};
844 $output =~ s/^\s*//gm;
845 876
846 @lines = split("\n", $output); 877 $commits = grep(/$pattern/, @lines); # of commits
847 $commits = grep(/^commit [0-9a-f]{40,40}/, @lines); # of commits
848 878
849 @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines); 879 @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines);
850 if (!$email_git_penguin_chiefs) { 880 if (!$email_git_penguin_chiefs) {
@@ -863,17 +893,93 @@ sub git_find_signers {
863 return ($commits, @lines); 893 return ($commits, @lines);
864} 894}
865 895
866sub git_assign_signers { 896sub vcs_save_commits {
897 my ($cmd) = @_;
898 my @lines = ();
899 my @commits = ();
900
901 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
902
903 foreach my $line (@lines) {
904 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
905 push(@commits, $1);
906 }
907 }
908
909 return @commits;
910}
911
912sub vcs_blame {
913 my ($file) = @_;
914 my $cmd;
915 my @commits = ();
916
917 return @commits if (!(-f $file));
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);
925
926 foreach my $file_range_diff (@range) {
927 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
928 my $diff_file = $1;
929 my $diff_start = $2;
930 my $diff_length = $3;
931 next if ("$file" ne "$diff_file");
932 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
933 push(@commits, $all_commits[$i]);
934 }
935 }
936 } elsif (@range) {
937 foreach my $file_range_diff (@range) {
938 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
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));
946 }
947 } else {
948 $cmd = $VCS_cmds{"blame_file_cmd"};
949 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
950 @commits = vcs_save_commits($cmd);
951 }
952
953 return @commits;
954}
955
956my $printed_novcs = 0;
957sub vcs_exists {
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}
972
973sub vcs_assign {
867 my ($role, $divisor, @lines) = @_; 974 my ($role, $divisor, @lines) = @_;
868 975
869 my %hash; 976 my %hash;
870 my $count = 0; 977 my $count = 0;
871 978
872 return if (!has_git());
873 return if (@lines <= 0); 979 return if (@lines <= 0);
874 980
875 if ($divisor <= 0) { 981 if ($divisor <= 0) {
876 warn("Bad divisor in git_assign_signers: $divisor\n"); 982 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
877 $divisor = 1; 983 $divisor = 1;
878 } 984 }
879 985
@@ -906,62 +1012,31 @@ sub git_assign_signers {
906 } 1012 }
907} 1013}
908 1014
909sub git_file_signoffs { 1015sub vcs_file_signoffs {
910 my ($file) = @_; 1016 my ($file) = @_;
911 1017
912 my @signers = (); 1018 my @signers = ();
913 my $total_signers; 1019 my $commits;
914
915 return if (!has_git());
916
917 ($total_signers, @signers) =
918 git_find_signers("git log --since=$email_git_since -- $file");
919 git_assign_signers("git_signer", $total_signers, @signers);
920}
921
922sub save_commits {
923 my ($cmd, @commits) = @_;
924 my $output;
925 my @lines = ();
926 1020
927 return (@lines) if (!has_git()); 1021 return if (!vcs_exists());
928 1022
929 $output = `${cmd}`; 1023 my $cmd = $VCS_cmds{"find_signers_cmd"};
1024 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
930 1025
931 @lines = split("\n", $output); 1026 ($commits, @signers) = vcs_find_signers($cmd);
932 foreach my $line (@lines) { 1027 vcs_assign("commit_signer", $commits, @signers);
933 if ($line =~ m/^(\w+) /) {
934 push (@commits, $1);
935 }
936 }
937 return @commits;
938} 1028}
939 1029
940sub git_assign_blame { 1030sub vcs_file_blame {
941 my ($file) = @_; 1031 my ($file) = @_;
942 1032
943 my $cmd;
944 my @commits = ();
945 my @signers = (); 1033 my @signers = ();
1034 my @commits = ();
946 my $total_commits; 1035 my $total_commits;
947 1036
948 if (@range) { 1037 return if (!vcs_exists());
949 foreach my $file_range_diff (@range) {
950 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
951 my $diff_file = $1;
952 my $diff_start = $2;
953 my $diff_length = $3;
954 next if (!("$file" eq "$diff_file"));
955 $cmd = "git blame -l -L $diff_start,+$diff_length $file";
956 @commits = save_commits($cmd, @commits);
957 }
958 } else {
959 if (-f $file) {
960 $cmd = "git blame -l $file";
961 @commits = save_commits($cmd, @commits);
962 }
963 }
964 1038
1039 @commits = vcs_blame($file);
965 @commits = uniq(@commits); 1040 @commits = uniq(@commits);
966 $total_commits = @commits; 1041 $total_commits = @commits;
967 1042
@@ -969,15 +1044,17 @@ sub git_assign_blame {
969 my $commit_count; 1044 my $commit_count;
970 my @commit_signers = (); 1045 my @commit_signers = ();
971 1046
972 ($commit_count, @commit_signers) = 1047 my $cmd = $VCS_cmds{"find_commit_signers_cmd"};
973 git_find_signers("git log -1 $commit"); 1048 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
974 @signers = (@signers, @commit_signers); 1049
1050 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1051 push(@signers, @commit_signers);
975 } 1052 }
976 1053
977 if ($from_filename) { 1054 if ($from_filename) {
978 git_assign_signers("commits", $total_commits, @signers); 1055 vcs_assign("commits", $total_commits, @signers);
979 } else { 1056 } else {
980 git_assign_signers("modified commits", $total_commits, @signers); 1057 vcs_assign("modified commits", $total_commits, @signers);
981 } 1058 }
982} 1059}
983 1060
@@ -1006,9 +1083,9 @@ sub merge_email {
1006 my ($address, $role) = @$_; 1083 my ($address, $role) = @$_;
1007 if (!$saw{$address}) { 1084 if (!$saw{$address}) {
1008 if ($output_roles) { 1085 if ($output_roles) {
1009 push @lines, "$address ($role)"; 1086 push(@lines, "$address ($role)");
1010 } else { 1087 } else {
1011 push @lines, $address; 1088 push(@lines, $address);
1012 } 1089 }
1013 $saw{$address} = 1; 1090 $saw{$address} = 1;
1014 } 1091 }
@@ -1115,11 +1192,9 @@ sub rfc822_validlist ($) {
1115 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so && 1192 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
1116 $s =~ m/^$rfc822_char*$/) { 1193 $s =~ m/^$rfc822_char*$/) {
1117 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) { 1194 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
1118 push @r, $1; 1195 push(@r, $1);
1119 } 1196 }
1120 return wantarray ? (scalar(@r), @r) : 1; 1197 return wantarray ? (scalar(@r), @r) : 1;
1121 } 1198 }
1122 else { 1199 return wantarray ? () : 0;
1123 return wantarray ? () : 0;
1124 }
1125} 1200}