diff options
-rwxr-xr-x | scripts/get_maintainer.pl | 261 |
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 @@ | |||
13 | use strict; | 13 | use strict; |
14 | 14 | ||
15 | my $P = $0; | 15 | my $P = $0; |
16 | my $V = '0.22'; | 16 | my $V = '0.23'; |
17 | 17 | ||
18 | use Getopt::Long qw(:config no_auto_abbrev); | 18 | use Getopt::Long qw(:config no_auto_abbrev); |
19 | 19 | ||
@@ -23,13 +23,14 @@ my $email_usename = 1; | |||
23 | my $email_maintainer = 1; | 23 | my $email_maintainer = 1; |
24 | my $email_list = 1; | 24 | my $email_list = 1; |
25 | my $email_subscriber_list = 0; | 25 | my $email_subscriber_list = 0; |
26 | my $email_git = 1; | ||
27 | my $email_git_penguin_chiefs = 0; | 26 | my $email_git_penguin_chiefs = 0; |
27 | my $email_git = 1; | ||
28 | my $email_git_blame = 0; | ||
28 | my $email_git_min_signatures = 1; | 29 | my $email_git_min_signatures = 1; |
29 | my $email_git_max_maintainers = 5; | 30 | my $email_git_max_maintainers = 5; |
30 | my $email_git_min_percent = 5; | 31 | my $email_git_min_percent = 5; |
31 | my $email_git_since = "1-year-ago"; | 32 | my $email_git_since = "1-year-ago"; |
32 | my $email_git_blame = 0; | 33 | my $email_hg_since = "-365"; |
33 | my $email_remove_duplicates = 1; | 34 | my $email_remove_duplicates = 1; |
34 | my $output_multiline = 1; | 35 | my $output_multiline = 1; |
35 | my $output_separator = ", "; | 36 | my $output_separator = ", "; |
@@ -66,15 +67,44 @@ my $penguin_chiefs = "\(" . join("|",@penguin_chief_names) . "\)"; | |||
66 | my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])"; | 67 | my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])"; |
67 | my $rfc822_char = '[\\000-\\377]'; | 68 | my $rfc822_char = '[\\000-\\377]'; |
68 | 69 | ||
70 | # VCS command support: class-like functions and strings | ||
71 | |||
72 | my %VCS_cmds; | ||
73 | |||
74 | my %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 | |||
85 | my %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 | |||
69 | if (!GetOptions( | 98 | if (!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 | ||
452 | EOT | 489 | EOT |
453 | } | 490 | } |
454 | 491 | ||
@@ -807,44 +844,37 @@ sub mailmap { | |||
807 | return @lines; | 844 | return @lines; |
808 | } | 845 | } |
809 | 846 | ||
810 | my $printed_nogit = 0; | 847 | sub git_execute_cmd { |
811 | my $printed_nogitdir = 0; | 848 | my ($cmd) = @_; |
812 | sub 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 | ||
834 | sub git_find_signers { | 858 | sub 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 | |||
868 | sub 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 | ||
866 | sub git_assign_signers { | 896 | sub 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 | |||
912 | sub 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 | |||
956 | my $printed_novcs = 0; | ||
957 | sub 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 | |||
973 | sub 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 | ||
909 | sub git_file_signoffs { | 1015 | sub 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 | |||
922 | sub 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 | ||
940 | sub git_assign_blame { | 1030 | sub 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 | } |