History

scripts/get_maintainer.pl: improve --interactive UI

o Added searching by git-blame as well as git-history o Added different selection toggles o Added ability to list commits by author or by sign-off-type o Use custom git and hg formats to make searching for subject/author a bit easier. o Move inlined section matching and searching git/hg history to new get_maintainer subroutine o Added subroutines save_commits_by_author and save_commits_by_signer o Removed subroutines vcs_get_shortlog and vcs_email_shortlog o Rename camelcase signaturePattern to signature_pattern Update to 0.26 beta3

Joe Perches committed 16 years ago

Commit 683c6f81 parent dace8e31 file · +596 −275Browse files

Showing the changes to scripts (1 of 1 file). Show all

Unified · Split

scripts/get_maintainer.pl+596−275
@@ -13,7 +13,7 @@ · 12 unmodified lines
13use strict;13use strict;
14 14
15my $P = $0;15my $P = $0;
16my $V = '0.25';16my $V = '0.26-beta3';
17 17
18use Getopt::Long qw(:config no_auto_abbrev);18use Getopt::Long qw(:config no_auto_abbrev);
19 19
@@ -27,6 +27,7 @@ my $email_git_penguin_chiefs = 0; · 7 unmodified lines
27my $email_git = 0;27my $email_git = 0;
28my $email_git_all_signature_types = 0;28my $email_git_all_signature_types = 0;
29my $email_git_blame = 0;29my $email_git_blame = 0;
30my $email_git_blame_signatures = 1;
30my $email_git_fallback = 1;31my $email_git_fallback = 1;
31my $email_git_min_signatures = 1;32my $email_git_min_signatures = 1;
32my $email_git_max_maintainers = 5;33my $email_git_max_maintainers = 5;
@@ -51,9 +52,12 @@ my $pattern_depth = 0; · 18 unmodified lines
51my $version = 0;52my $version = 0;
52my $help = 0;53my $help = 0;
53 54
55my $vcs_used = 0;
56
54my $exit = 0;57my $exit = 0;
55 58
56my %shortlog_buffer;59my %commit_author_hash;
60my %commit_signer_hash;
57 61
58my @penguin_chief = ();62my @penguin_chief = ();
59push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");63push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
@@ -77,7 +81,6 @@ my @signature_tags = (); · 17 unmodified lines
77push(@signature_tags, "Signed-off-by:");81push(@signature_tags, "Signed-off-by:");
78push(@signature_tags, "Reviewed-by:");82push(@signature_tags, "Reviewed-by:");
79push(@signature_tags, "Acked-by:");83push(@signature_tags, "Acked-by:");
80my $signaturePattern = "\(" . join("|", @signature_tags) . "\)";
81 84
82# rfc822 email address - preloaded methods go here.85# rfc822 email address - preloaded methods go here.
83my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";86my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
@@ -90,29 +93,62 @@ my %VCS_cmds; · 6 unmodified lines
90my %VCS_cmds_git = (93my %VCS_cmds_git = (
91 "execute_cmd" => \&git_execute_cmd,94 "execute_cmd" => \&git_execute_cmd,
92 "available" => '(which("git") ne "") && (-d ".git")',95 "available" => '(which("git") ne "") && (-d ".git")',
93 "find_signers_cmd" => "git log --no-color --since=\$email_git_since -- \$file",96 "find_signers_cmd" =>
94 "find_commit_signers_cmd" => "git log --no-color -1 \$commit",97 "git log --no-color --since=\$email_git_since " .
95 "find_commit_author_cmd" => "git log -1 --format=\"%an <%ae>\" \$commit",98 '--format="GitCommit: %H%n' .
99 'GitAuthor: %an <%ae>%n' .
100 'GitDate: %aD%n' .
101 'GitSubject: %s%n' .
102 '%b%n"' .
103 " -- \$file",
104 "find_commit_signers_cmd" =>
105 "git log --no-color " .
106 '--format="GitCommit: %H%n' .
107 'GitAuthor: %an <%ae>%n' .
108 'GitDate: %aD%n' .
109 'GitSubject: %s%n' .
110 '%b%n"' .
111 " -1 \$commit",
112 "find_commit_author_cmd" =>
113 "git log --no-color " .
114 '--format="GitCommit: %H%n' .
115 'GitAuthor: %an <%ae>%n' .
116 'GitDate: %aD%n' .
117 'GitSubject: %s%n"' .
118 " -1 \$commit",
96 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",119 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
97 "blame_file_cmd" => "git blame -l \$file",120 "blame_file_cmd" => "git blame -l \$file",
98 "commit_pattern" => "^commit [0-9a-f]{40,40}",121 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
99 "blame_commit_pattern" => "^([0-9a-f]+) ",122 "blame_commit_pattern" => "^([0-9a-f]+) ",
100 "shortlog_cmd" => "git log --no-color --oneline --since=\$email_git_since --author=\"\$email\" -- \$file"123 "author_pattern" => "^GitAuthor: (.*)",
124 "subject_pattern" => "^GitSubject: (.*)",
101);125);
102 126
103my %VCS_cmds_hg = (127my %VCS_cmds_hg = (
104 "execute_cmd" => \&hg_execute_cmd,128 "execute_cmd" => \&hg_execute_cmd,
105 "available" => '(which("hg") ne "") && (-d ".hg")',129 "available" => '(which("hg") ne "") && (-d ".hg")',
106 "find_signers_cmd" =>130 "find_signers_cmd" =>
107 "hg log --date=\$email_hg_since" .131 "hg log --date=\$email_hg_since " .
108 " --template='commit {node}\\n{desc}\\n' -- \$file",132 "--template='HgCommit: {node}\\n" .
109 "find_commit_signers_cmd" => "hg log --template='{desc}\\n' -r \$commit",133 "HgAuthor: {author}\\n" .
110 "find_commit_author_cmd" => "hg log -l 1 --template='{author}\\n' -r \$commit",134 "HgSubject: {desc}\\n'" .
135 " -- \$file",
136 "find_commit_signers_cmd" =>
137 "hg log " .
138 "--template='HgSubject: {desc}\\n'" .
139 " -r \$commit",
140 "find_commit_author_cmd" =>
141 "hg log " .
142 "--template='HgCommit: {node}\\n" .
143 "HgAuthor: {author}\\n" .
144 "HgSubject: {desc|firstline}\\n'" .
145 " -r \$commit",
111 "blame_range_cmd" => "", # not supported146 "blame_range_cmd" => "", # not supported
112 "blame_file_cmd" => "hg blame -c \$file",147 "blame_file_cmd" => "hg blame -n \$file",
113 "commit_pattern" => "^commit [0-9a-f]{40,40}",148 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
114 "blame_commit_pattern" => "^([0-9a-f]+):",149 "blame_commit_pattern" => "^([ 0-9a-f]+):",
115 "shortlog_cmd" => "ht log --date=\$email_hg_since"150 "author_pattern" => "^HgAuthor: (.*)",
151 "subject_pattern" => "^HgSubject: (.*)",
116);152);
117 153
118my $conf = which_conf(".get_maintainer.conf");154my $conf = which_conf(".get_maintainer.conf");
@@ -146,6 +182,7 @@ if (!GetOptions( · 27 unmodified lines
146 'git!' => \$email_git,182 'git!' => \$email_git,
147 'git-all-signature-types!' => \$email_git_all_signature_types,183 'git-all-signature-types!' => \$email_git_all_signature_types,
148 'git-blame!' => \$email_git_blame,184 'git-blame!' => \$email_git_blame,
185 'git-blame-signatures!' => \$email_git_blame_signatures,
149 'git-fallback!' => \$email_git_fallback,186 'git-fallback!' => \$email_git_fallback,
150 'git-chief-penguins!' => \$email_git_penguin_chiefs,187 'git-chief-penguins!' => \$email_git_penguin_chiefs,
151 'git-min-signatures=i' => \$email_git_min_signatures,188 'git-min-signatures=i' => \$email_git_min_signatures,
@@ -193,13 +230,9 @@ if (-t STDIN && !@ARGV) { · 41 unmodified lines
193 die "$P: missing patchfile or -f file - use --help if necessary\n";230 die "$P: missing patchfile or -f file - use --help if necessary\n";
194}231}
195 232
196if ($output_separator ne ", ") {233$output_multiline = 0 if ($output_separator ne ", ");
197 $output_multiline = 0;234$output_rolestats = 1 if ($interactive);
198}235$output_roles = 1 if ($output_rolestats);
199
200if ($output_rolestats) {
201 $output_roles = 1;
202}
203 236
204if ($sections) {237if ($sections) {
205 $email = 0;238 $email = 0;
@@ -227,12 +260,6 @@ if (!top_of_kernel_tree($lk_path)) { · 21 unmodified lines
227 . "a linux kernel source tree.\n";260 . "a linux kernel source tree.\n";
228}261}
229 262
230if ($email_git_all_signature_types) {
231 $signaturePattern = "(.+?)[Bb][Yy]:";
232}
233
234
235
236## Read MAINTAINERS for type/value pairs263## Read MAINTAINERS for type/value pairs
237 264
238my @typevalue = ();265my @typevalue = ();
@@ -371,168 +398,193 @@ foreach my $file (@ARGV) { · 132 unmodified lines
371 398
372@file_emails = uniq(@file_emails);399@file_emails = uniq(@file_emails);
373 400
401my %email_hash_name;
402my %email_hash_address;
374my @email_to = ();403my @email_to = ();
404my %hash_list_to;
375my @list_to = ();405my @list_to = ();
376my @scm = ();406my @scm = ();
377my @web = ();407my @web = ();
378my @subsystem = ();408my @subsystem = ();
379my @status = ();409my @status = ();
410my $signature_pattern;
380 411
381# Find responsible parties412my @to = get_maintainer();
382 413
383foreach my $file (@files) {414@to = merge_email(@to);
384 415
385 my %hash;416output(@to) if (@to);
386 my $exact_pattern_match = 0;417
387 my $tvi = find_first_section();418if ($scm) {
388 while ($tvi < @typevalue) {419 @scm = uniq(@scm);
389 my $start = find_starting_index($tvi);420 output(@scm);
390 my $end = find_ending_index($tvi);421}
391 my $exclude = 0;422
392 my $i;423if ($status) {
393 424 @status = uniq(@status);
394 #Do not match excluded file patterns425 output(@status);
395 426}
396 for ($i = $start; $i < $end; $i++) {427
397 my $line = $typevalue[$i];428if ($subsystem) {
398 if ($line =~ m/^(\C):\s*(.*)/) {429 @subsystem = uniq(@subsystem);
399 my $type = $1;430 output(@subsystem);
400 my $value = $2;431}
401 if ($type eq 'X') {432
402 if (file_match_pattern($file, $value)) {433if ($web) {
403 $exclude = 1;434 @web = uniq(@web);
404 last;435 output(@web);
405 }436}
406 }437
407 }438exit($exit);
408 }439
440sub get_maintainer {
441 %email_hash_name = ();
442 %email_hash_address = ();
443 %commit_author_hash = ();
444 %commit_signer_hash = ();
445 @email_to = ();
446 %hash_list_to = ();
447 @list_to = ();
448 @scm = ();
449 @web = ();
450 @subsystem = ();
451 @status = ();
452
453 if ($email_git_all_signature_types) {
454 $signature_pattern = "(.+?)[Bb][Yy]:";
455 } else {
456 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
457 }
458
459 # Find responsible parties
460
461 foreach my $file (@files) {
462
463 my %hash;
464 my $exact_pattern_match = 0;
465 my $tvi = find_first_section();
466 while ($tvi < @typevalue) {
467 my $start = find_starting_index($tvi);
468 my $end = find_ending_index($tvi);
469 my $exclude = 0;
470 my $i;
471
472 #Do not match excluded file patterns
409 473
410 if (!$exclude) {
411 for ($i = $start; $i < $end; $i++) {474 for ($i = $start; $i < $end; $i++) {
412 my $line = $typevalue[$i];475 my $line = $typevalue[$i];
413 if ($line =~ m/^(\C):\s*(.*)/) {476 if ($line =~ m/^(\C):\s*(.*)/) {
414 my $type = $1;477 my $type = $1;
415 my $value = $2;478 my $value = $2;
416 if ($type eq 'F') {479 if ($type eq 'X') {
417 if (file_match_pattern($file, $value)) {480 if (file_match_pattern($file, $value)) {
418 my $value_pd = ($value =~ tr@/@@);481 $exclude = 1;
419 my $file_pd = ($file =~ tr@/@@);482 last;
420 $value_pd++ if (substr($value,-1,1) ne "/");483 }
421 $value_pd = -1 if ($value =~ /^\.\*/);484 }
422 $exact_pattern_match = 1 if ($value_pd >= $file_pd);485 }
423 if ($pattern_depth == 0 ||486 }
424 (($file_pd - $value_pd) < $pattern_depth)) {487
425 $hash{$tvi} = $value_pd;488 if (!$exclude) {
489 for ($i = $start; $i < $end; $i++) {
490 my $line = $typevalue[$i];
491 if ($line =~ m/^(\C):\s*(.*)/) {
492 my $type = $1;
493 my $value = $2;
494 if ($type eq 'F') {
495 if (file_match_pattern($file, $value)) {
496 my $value_pd = ($value =~ tr@/@@);
497 my $file_pd = ($file =~ tr@/@@);
498 $value_pd++ if (substr($value,-1,1) ne "/");
499 $value_pd = -1 if ($value =~ /^\.\*/);
500 $exact_pattern_match = 1 if ($value_pd >= $file_pd);
501 if ($pattern_depth == 0 ||
502 (($file_pd - $value_pd) < $pattern_depth)) {
503 $hash{$tvi} = $value_pd;
504 }
426 }505 }
427 }506 }
428 }507 }
429 }508 }
430 }509 }
510 $tvi = $end + 1;
431 }511 }
432 512
433 $tvi = $end + 1;513 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
434 }514 add_categories($line);
435 515 if ($sections) {
436 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {516 my $i;
437 add_categories($line);517 my $start = find_starting_index($line);
438 if ($sections) {518 my $end = find_ending_index($line);
439 my $i;519 for ($i = $start; $i < $end; $i++) {
440 my $start = find_starting_index($line);520 my $line = $typevalue[$i];
441 my $end = find_ending_index($line);521 if ($line =~ /^[FX]:/) { ##Restore file patterns
442 for ($i = $start; $i < $end; $i++) {522 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
443 my $line = $typevalue[$i];523 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
444 if ($line =~ /^[FX]:/) { ##Restore file patterns524 $line =~ s/\\\./\./g; ##Convert \. to .
445 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;525 $line =~ s/\.\*/\*/g; ##Convert .* to *
446 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?526 }
447 $line =~ s/\\\./\./g; ##Convert \. to .527 $line =~ s/^([A-Z]):/$1:\t/g;
448 $line =~ s/\.\*/\*/g; ##Convert .* to *528 print("$line\n");
449 }529 }
450 $line =~ s/^([A-Z]):/$1:\t/g;530 print("\n");
451 print("$line\n");
452 }531 }
453 print("\n");
454 }532 }
455 }
456
457 if ($email &&
458 ($email_git || ($email_git_fallback && !$exact_pattern_match))) {
459 vcs_file_signoffs($file);
460 }
461 if ($email && $email_git_blame) {
462 vcs_file_blame($file);
463 }
464 if ($email && $interactive){
465 vcs_file_shortlogs($file);
466 533
534 if ($email && ($email_git ||
535 ($email_git_fallback && !$exact_pattern_match))) {
536 vcs_file_signoffs($file);
537 }
538 if ($email && $email_git_blame) {
539 vcs_file_blame($file);
540 }
467 }541 }
468}
469 542
470if ($keywords) {543 if ($keywords) {
471 @keyword_tvi = sort_and_uniq(@keyword_tvi);544 @keyword_tvi = sort_and_uniq(@keyword_tvi);
472 foreach my $line (@keyword_tvi) {545 foreach my $line (@keyword_tvi) {
473 add_categories($line);546 add_categories($line);
547 }
474 }548 }
475}
476 549
477if ($email) {550 if ($email) {
478 foreach my $chief (@penguin_chief) {551 foreach my $chief (@penguin_chief) {
479 if ($chief =~ m/^(.*):(.*)/) {552 if ($chief =~ m/^(.*):(.*)/) {
480 my $email_address;553 my $email_address;
481 554
482 $email_address = format_email($1, $2, $email_usename);555 $email_address = format_email($1, $2, $email_usename);
483 if ($email_git_penguin_chiefs) {556 if ($email_git_penguin_chiefs) {
484 push(@email_to, [$email_address, 'chief penguin']);557 push(@email_to, [$email_address, 'chief penguin']);
485 } else {558 } else {
486 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);559 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
560 }
487 }561 }
488 }562 }
489 }
490 563
491 foreach my $email (@file_emails) {564 foreach my $email (@file_emails) {
492 my ($name, $address) = parse_email($email);565 my ($name, $address) = parse_email($email);
493 566
494 my $tmp_email = format_email($name, $address, $email_usename);567 my $tmp_email = format_email($name, $address, $email_usename);
495 push_email_address($tmp_email, '');568 push_email_address($tmp_email, '');
496 add_role($tmp_email, 'in file');569 add_role($tmp_email, 'in file');
570 }
497 }571 }
498}
499
500 572
501if ($email || $email_list) {
502 my @to = ();573 my @to = ();
503 if ($email) {574 if ($email || $email_list) {
504 if ($interactive) {575 if ($email) {
505 @email_to = @{vcs_interactive_menu(\@email_to)};576 @to = (@to, @email_to);
577 }
578 if ($email_list) {
579 @to = (@to, @list_to);
506 }580 }
507 @to = (@to, @email_to);
508 }
509 if ($email_list) {
510 @to = (@to, @list_to);
511 }581 }
512 output(merge_email(@to));
513}
514 582
515if ($scm) {583 @to = interactive_get_maintainer(\@to) if ($interactive);
516 @scm = uniq(@scm);
517 output(@scm);
518}
519if ($status) {
520 @status = uniq(@status);
521 output(@status);
522}
523 584
524if ($subsystem) {585 return @to;
525 @subsystem = uniq(@subsystem);
526 output(@subsystem);
527}586}
528 587
529if ($web) {
530 @web = uniq(@web);
531 output(@web);
532}
533
534exit($exit);
535
536sub file_match_pattern {588sub file_match_pattern {
537 my ($file, $pattern) = @_;589 my ($file, $pattern) = @_;
538 if (substr($pattern, -1) eq "/") {590 if (substr($pattern, -1) eq "/") {
@@ -561,7 +613,7 @@ MAINTAINER field selection options: · 22 unmodified lines
561 --email => print email address(es) if any613 --email => print email address(es) if any
562 --git => include recent git \*-by: signers614 --git => include recent git \*-by: signers
563 --git-all-signature-types => include signers regardless of signature type615 --git-all-signature-types => include signers regardless of signature type
564 or use only ${signaturePattern} signers (default: $email_git_all_signature_types)616 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
565 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)617 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
566 --git-chief-penguins => include ${penguin_chiefs}618 --git-chief-penguins => include ${penguin_chiefs}
567 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)619 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
@@ -847,11 +899,19 @@ sub add_categories { · 279 unmodified lines
847 }899 }
848 if ($list_additional =~ m/subscribers-only/) {900 if ($list_additional =~ m/subscribers-only/) {
849 if ($email_subscriber_list) {901 if ($email_subscriber_list) {
850 push(@list_to, [$list_address, "subscriber list${list_role}"]);902 if (!$hash_list_to{$list_address}) {
903 $hash_list_to{$list_address} = 1;
904 push(@list_to, [$list_address,
905 "subscriber list${list_role}"]);
906 }
851 }907 }
852 } else {908 } else {
853 if ($email_list) {909 if ($email_list) {
854 push(@list_to, [$list_address, "open list${list_role}"]);910 if (!$hash_list_to{$list_address}) {
911 $hash_list_to{$list_address} = 1;
912 push(@list_to, [$list_address,
913 "open list${list_role}"]);
914 }
855 }915 }
856 }916 }
857 } elsif ($ptype eq "M") {917 } elsif ($ptype eq "M") {
@@ -882,9 +942,6 @@ sub add_categories { · 24 unmodified lines
882 }942 }
883}943}
884 944
885my %email_hash_name;
886my %email_hash_address;
887
888sub email_inuse {945sub email_inuse {
889 my ($name, $address) = @_;946 my ($name, $address) = @_;
890 947
@@ -1037,10 +1094,31 @@ sub hg_execute_cmd { · 146 unmodified lines
1037 return @lines;1094 return @lines;
1038}1095}
1039 1096
1097sub extract_formatted_signatures {
1098 my (@signature_lines) = @_;
1099
1100 my @type = @signature_lines;
1101
1102 s/\s*(.*):.*/$1/ for (@type);
1103
1104 # cut -f2- -d":"
1105 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1106
1107## Reformat email addresses (with names) to avoid badly written signatures
1108
1109 foreach my $signer (@signature_lines) {
1110 my ($name, $address) = parse_email($signer);
1111 $signer = format_email($name, $address, 1);
1112 }
1113
1114 return (\@type, \@signature_lines);
1115}
1116
1040sub vcs_find_signers {1117sub vcs_find_signers {
1041 my ($cmd) = @_;1118 my ($cmd) = @_;
1042 my @lines = ();
1043 my $commits;1119 my $commits;
1120 my @lines = ();
1121 my @signatures = ();
1044 1122
1045 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);1123 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1046 1124
@@ -1048,24 +1126,20 @@ sub vcs_find_signers { · 1 unmodified line
1048 1126
1049 $commits = grep(/$pattern/, @lines); # of commits1127 $commits = grep(/$pattern/, @lines); # of commits
1050 1128
1051 @lines = grep(/^[ \t]*${signaturePattern}.*\@.*$/, @lines);1129 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1052 if (!$email_git_penguin_chiefs) {
1053 @lines = grep(!/${penguin_chiefs}/i, @lines);
1054 }
1055 1130
1056 return (0, @lines) if !@lines;1131 return (0, @signatures) if !@signatures;
1057 1132
1058 # cut -f2- -d":"1133 save_commits_by_author(@lines) if ($interactive);
1059 s/.*:\s*(.+)\s*/$1/ for (@lines);1134 save_commits_by_signer(@lines) if ($interactive);
1060 1135
1061## Reformat email addresses (with names) to avoid badly written signatures1136 if (!$email_git_penguin_chiefs) {
1062 1137 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1063 foreach my $line (@lines) {
1064 my ($name, $address) = parse_email($line);
1065 $line = format_email($name, $address, 1);
1066 }1138 }
1067 1139
1068 return ($commits, @lines);1140 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1141
1142 return ($commits, @$signers_ref);
1069}1143}
1070 1144
1071sub vcs_find_author {1145sub vcs_find_author {
@@ -1080,14 +1154,20 @@ sub vcs_find_author { · 8 unmodified lines
1080 1154
1081 return @lines if !@lines;1155 return @lines if !@lines;
1082 1156
1083## Reformat email addresses (with names) to avoid badly written signatures1157 my @authors = ();
1084
1085 foreach my $line (@lines) {1158 foreach my $line (@lines) {
1086 my ($name, $address) = parse_email($line);1159 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1087 $line = format_email($name, $address, 1);1160 my $author = $1;
1161 my ($name, $address) = parse_email($author);
1162 $author = format_email($name, $address, 1);
1163 push(@authors, $author);
1164 }
1088 }1165 }
1089 1166
1090 return @lines;1167 save_commits_by_author(@lines) if ($interactive);
1168 save_commits_by_signer(@lines) if ($interactive);
1169
1170 return @authors;
1091}1171}
1092 1172
1093sub vcs_save_commits {1173sub vcs_save_commits {
@@ -1159,7 +1239,7 @@ sub vcs_exists { · 65 unmodified lines
1159 %VCS_cmds = %VCS_cmds_git;1239 %VCS_cmds = %VCS_cmds_git;
1160 return 1 if eval $VCS_cmds{"available"};1240 return 1 if eval $VCS_cmds{"available"};
1161 %VCS_cmds = %VCS_cmds_hg;1241 %VCS_cmds = %VCS_cmds_hg;
1162 return 1 if eval $VCS_cmds{"available"};1242 return 2 if eval $VCS_cmds{"available"};
1163 %VCS_cmds = ();1243 %VCS_cmds = ();
1164 if (!$printed_novcs) {1244 if (!$printed_novcs) {
1165 warn("$P: No supported VCS found. Add --nogit to options?\n");1245 warn("$P: No supported VCS found. Add --nogit to options?\n");
@@ -1171,125 +1251,309 @@ sub vcs_exists { · 5 unmodified lines
1171 return 0;1251 return 0;
1172}1252}
1173 1253
1174sub vcs_interactive_menu {1254sub vcs_is_git {
1175 my $list_ref = shift;1255 return $vcs_used == 1;
1256}
1257
1258sub vcs_is_hg {
1259 return $vcs_used == 2;
1260}
1261
1262sub interactive_get_maintainer {
1263 my ($list_ref) = @_;
1176 my @list = @$list_ref;1264 my @list = @$list_ref;
1177 1265
1178 return if (!vcs_exists());1266 vcs_exists();
1179 1267
1180 my %selected;1268 my %selected;
1181 my %shortlog;1269 my %authored;
1182 my $input;1270 my %signed;
1183 my $count = 0;1271 my $count = 0;
1184 1272
1185 #select maintainers by default1273 #select maintainers by default
1186 foreach my $entry (@list){1274 foreach my $entry (@list){
1187 my $role = $entry->[1];1275 my $role = $entry->[1];
1188 $selected{$count} = ($role =~ /maintainer:|supporter:/);1276 $selected{$count} = ($role =~ /^(maintainer|supporter|open list)/);
1189 $count++;1277 $authored{$count} = 0;
1278 $signed{$count} = 0;
1279 $count++;
1190 }1280 }
1191 1281
1192 #menu loop1282 #menu loop
1193 do {1283 my $done = 0;
1194 my $count = 0;1284 my $print_options = 0;
1195 foreach my $entry (@list){1285 my $redraw = 1;
1196 my $email = $entry->[0];1286 while (!$done) {
1197 my $role = $entry->[1];1287 $count = 0;
1198 if ($selected{$count}){1288 if ($redraw) {
1199 print STDERR "* ";1289 printf STDERR "\n%1s %2s %-65sauth sign\n",
1200 } else {1290 "*", "#", "email/list and role:stats";
1201 print STDERR " ";1291 foreach my $entry (@list) {
1202 }1292 my $email = $entry->[0];
1203 print STDERR "$count: $email,\t\t $role";1293 my $role = $entry->[1];
1204 print STDERR "\n";1294 my $sel = "";
1205 if ($shortlog{$count}){1295 $sel = "*" if ($selected{$count});
1206 my $entries_ref = vcs_get_shortlog($email);1296 my $commit_author = $commit_author_hash{$email};
1207 foreach my $entry_ref (@{$entries_ref}){1297 my $commit_signer = $commit_signer_hash{$email};
1208 my $filename = @{$entry_ref}[0];1298 my $authored = 0;
1209 my @shortlog = @{@{$entry_ref}[1]};1299 my $signed = 0;
1210 print STDERR "\tshortlog for $filename (authored commits: " . @shortlog . ").\n";1300 $authored++ for (@{$commit_author});
1211 foreach my $commit (@shortlog){1301 $signed++ for (@{$commit_signer});
1212 print STDERR "\t $commit\n";1302 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1303 printf STDERR "%4d %4d", $authored, $signed
1304 if ($authored > 0 || $signed > 0);
1305 printf STDERR "\n %s\n", $role;
1306 if ($authored{$count}) {
1307 my $commit_author = $commit_author_hash{$email};
1308 foreach my $ref (@{$commit_author}) {
1309 print STDERR " Author: @{$ref}[1]\n";
1213 }1310 }
1214 print STDERR "\n";
1215 }1311 }
1312 if ($signed{$count}) {
1313 my $commit_signer = $commit_signer_hash{$email};
1314 foreach my $ref (@{$commit_signer}) {
1315 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1316 }
1317 }
1318
1319 $count++;
1216 }1320 }
1217 $count++;
1218 }1321 }
1219 print STDERR "\n";1322 my $date_ref = \$email_git_since;
1220 print STDERR "Choose whom to cc by entering a commaseperated list of numbers and hitting enter.\n";1323 $date_ref = \$email_hg_since if (vcs_is_hg());
1221 print STDERR "To show a short list of commits, precede the number by a '?',\n";1324 if ($print_options) {
1222 print STDERR "A blank line indicates that you are satisfied with your choice.\n";1325 $print_options = 0;
1223 $input = <STDIN>;1326 if (vcs_exists()) {
1327 print STDERR
1328"\nVersion Control options:\n" .
1329"g use git history [$email_git]\n" .
1330"gf use git-fallback [$email_git_fallback]\n" .
1331"b use git blame [$email_git_blame]\n" .
1332"bs use blame signatures [$email_git_blame_signatures]\n" .
1333"c# minimum commits [$email_git_min_signatures]\n" .
1334"%# min percent [$email_git_min_percent]\n" .
1335"d# history to use [$$date_ref]\n" .
1336"x# max maintainers [$email_git_max_maintainers]\n" .
1337"t all signature types [$email_git_all_signature_types]\n";
1338 }
1339 print STDERR "\nAdditional options:\n" .
1340"0 toggle all\n" .
1341"f emails in file [$file_emails]\n" .
1342"k keywords in file [$keywords]\n" .
1343"r remove duplicates [$email_remove_duplicates]\n" .
1344"p# pattern match depth [$pattern_depth]\n";
1345 }
1346 print STDERR
1347"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1348
1349 my $input = <STDIN>;
1224 chomp($input);1350 chomp($input);
1225 1351
1226 my @wish = split(/[, ]+/,$input);1352 $redraw = 1;
1227 foreach my $nr (@wish){1353 my $rerun = 0;
1228 my $logtoggle = 0;1354 my @wish = split(/[, ]+/, $input);
1229 if ($nr =~ /\?/){1355 foreach my $nr (@wish) {
1230 $nr =~ s/\?//;1356 $nr = lc($nr);
1231 $logtoggle = 1;1357 my $sel = substr($nr, 0, 1);
1358 my $str = substr($nr, 1);
1359 my $val = 0;
1360 $val = $1 if $str =~ /^(\d+)$/;
1361
1362 if ($sel eq "y") {
1363 $interactive = 0;
1364 $done = 1;
1365 $output_rolestats = 0;
1366 $output_roles = 0;
1367 last;
1368 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1369 $selected{$nr - 1} = !$selected{$nr - 1};
1370 } elsif ($sel eq "*" || $sel eq '^') {
1371 my $toggle = 0;
1372 $toggle = 1 if ($sel eq '*');
1373 for (my $i = 0; $i < $count; $i++) {
1374 $selected{$i} = $toggle;
1232 }1375 }
1233 1376 } elsif ($sel eq "0") {
1234 #skip out of bounds numbers1377 for (my $i = 0; $i < $count; $i++) {
1235 next unless ($nr <= $count && $nr >= 0);1378 $selected{$i} = !$selected{$i};
1236 1379 }
1237 if ($logtoggle){1380 } elsif ($sel eq "a") {
1238 $shortlog{$nr} = !$shortlog{$nr};1381 if ($val > 0 && $val <= $count) {
1382 $authored{$val - 1} = !$authored{$val - 1};
1383 } elsif ($str eq '*' || $str eq '^') {
1384 my $toggle = 0;
1385 $toggle = 1 if ($str eq '*');
1386 for (my $i = 0; $i < $count; $i++) {
1387 $authored{$i} = $toggle;
1388 }
1389 }
1390 } elsif ($sel eq "s") {
1391 if ($val > 0 && $val <= $count) {
1392 $signed{$val - 1} = !$signed{$val - 1};
1393 } elsif ($str eq '*' || $str eq '^') {
1394 my $toggle = 0;
1395 $toggle = 1 if ($str eq '*');
1396 for (my $i = 0; $i < $count; $i++) {
1397 $signed{$i} = $toggle;
1398 }
1399 }
1400 } elsif ($sel eq "o") {
1401 $print_options = 1;
1402 $redraw = 1;
1403 } elsif ($sel eq "g") {
1404 if ($str eq "f") {
1405 bool_invert(\$email_git_fallback);
1239 } else {1406 } else {
1240 $selected{$nr} = !$selected{$nr};1407 bool_invert(\$email_git);
1241 1408 }
1242 #switch shortlog on if an entry get's selected1409 $rerun = 1;
1243 if ($selected{$nr}){1410 } elsif ($sel eq "b") {
1244 $shortlog{$nr}=1;1411 if ($str eq "s") {
1245 }1412 bool_invert(\$email_git_blame_signatures);
1413 } else {
1414 bool_invert(\$email_git_blame);
1415 }
1416 $rerun = 1;
1417 } elsif ($sel eq "c") {
1418 if ($val > 0) {
1419 $email_git_min_signatures = $val;
1420 $rerun = 1;
1421 }
1422 } elsif ($sel eq "x") {
1423 if ($val > 0) {
1424 $email_git_max_maintainers = $val;
1425 $rerun = 1;
1426 }
1427 } elsif ($sel eq "%") {
1428 if ($str ne "" && $val >= 0) {
1429 $email_git_min_percent = $val;
1430 $rerun = 1;
1246 }1431 }
1247 };1432 } elsif ($sel eq "d") {
1248 } while(length($input) > 0);1433 if (vcs_is_git()) {
1434 $email_git_since = $str;
1435 } elsif (vcs_is_hg()) {
1436 $email_hg_since = $str;
1437 }
1438 $rerun = 1;
1439 } elsif ($sel eq "t") {
1440 bool_invert(\$email_git_all_signature_types);
1441 $rerun = 1;
1442 } elsif ($sel eq "f") {
1443 bool_invert(\$file_emails);
1444 $rerun = 1;
1445 } elsif ($sel eq "r") {
1446 bool_invert(\$email_remove_duplicates);
1447 $rerun = 1;
1448 } elsif ($sel eq "k") {
1449 bool_invert(\$keywords);
1450 $rerun = 1;
1451 } elsif ($sel eq "p") {
1452 if ($str ne "" && $val >= 0) {
1453 $pattern_depth = $val;
1454 $rerun = 1;
1455 }
1456 } else {
1457 print STDERR "invalid option: '$nr'\n";
1458 $redraw = 0;
1459 }
1460 }
1461 if ($rerun) {
1462 print STDERR "git-blame can be very slow, please have patience..."
1463 if ($email_git_blame);
1464 goto &get_maintainer;
1465 }
1466 }
1249 1467
1250 #drop not selected entries1468 #drop not selected entries
1251 $count = 0;1469 $count = 0;
1252 my @new_emailto;1470 my @new_emailto = ();
1253 foreach my $entry (@list){1471 foreach my $entry (@list) {
1254 if ($selected{$count}){1472 if ($selected{$count}) {
1255 push(@new_emailto,$list[$count]);1473 push(@new_emailto, $list[$count]);
1256 print STDERR "$count: ";
1257 print STDERR $email_to[$count]->[0];
1258 print STDERR ",\t\t ";
1259 print STDERR $email_to[$count]->[1];
1260 print STDERR "\n";
1261 }1474 }
1262 $count++;1475 $count++;
1263 }1476 }
1264 return \@new_emailto;1477 return @new_emailto;
1265}1478}
1266 1479
1267sub vcs_get_shortlog {1480sub bool_invert {
1268 my $arg = shift;1481 my ($bool_ref) = @_;
1269 my ($name, $address) = parse_email($arg);1482
1270 return $shortlog_buffer{$address};1483 if ($$bool_ref) {
1484 $$bool_ref = 0;
1485 } else {
1486 $$bool_ref = 1;
1487 }
1271}1488}
1272 1489
1273sub vcs_file_shortlogs {1490sub save_commits_by_author {
1274 my ($file) = @_;1491 my (@lines) = @_;
1275 print STDERR "shortlog processing $file:";1492
1276 foreach my $entry (@email_to){1493 my @authors = ();
1277 my ($name, $address) = parse_email($entry->[0]);1494 my @commits = ();
1278 print STDERR ".";1495 my @subjects = ();
1279 my $commits_ref = vcs_email_shortlog($address, $file);1496
1280 push(@{$shortlog_buffer{$address}}, [ $file, $commits_ref ]);1497 foreach my $line (@lines) {
1498 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1499 my $author = $1;
1500 my ($name, $address) = parse_email($author);
1501 $author = format_email($name, $address, 1);
1502 push(@authors, $author);
1503 }
1504 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1505 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1506 }
1507
1508 for (my $i = 0; $i < @authors; $i++) {
1509 my $exists = 0;
1510 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1511 if (@{$ref}[0] eq $commits[$i] &&
1512 @{$ref}[1] eq $subjects[$i]) {
1513 $exists = 1;
1514 last;
1515 }
1516 }
1517 if (!$exists) {
1518 push(@{$commit_author_hash{$authors[$i]}},
1519 [ ($commits[$i], $subjects[$i]) ]);
1520 }
1281 }1521 }
1282 print STDERR "\n";
1283}1522}
1284 1523
1285sub vcs_email_shortlog {1524sub save_commits_by_signer {
1286 my $email = shift;1525 my (@lines) = @_;
1287 my ($file) = @_;1526
1527 my $commit = "";
1528 my $subject = "";
1288 1529
1289 my $cmd = $VCS_cmds{"shortlog_cmd"};1530 foreach my $line (@lines) {
1290 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables1531 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1291 my @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);1532 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1292 return \@lines;1533 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1534 my @signatures = ($line);
1535 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1536 my @types = @$types_ref;
1537 my @signers = @$signers_ref;
1538
1539 my $type = $types[0];
1540 my $signer = $signers[0];
1541
1542 my $exists = 0;
1543 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1544 if (@{$ref}[0] eq $commit &&
1545 @{$ref}[1] eq $subject &&
1546 @{$ref}[2] eq $type) {
1547 $exists = 1;
1548 last;
1549 }
1550 }
1551 if (!$exists) {
1552 push(@{$commit_signer_hash{$signer}},
1553 [ ($commit, $subject, $type) ]);
1554 }
1555 }
1556 }
1293}1557}
1294 1558
1295sub vcs_assign {1559sub vcs_assign {
@@ -1342,7 +1606,8 @@ sub vcs_file_signoffs { · 46 unmodified lines
1342 my @signers = ();1606 my @signers = ();
1343 my $commits;1607 my $commits;
1344 1608
1345 return if (!vcs_exists());1609 $vcs_used = vcs_exists();
1610 return if (!$vcs_used);
1346 1611
1347 my $cmd = $VCS_cmds{"find_signers_cmd"};1612 my $cmd = $VCS_cmds{"find_signers_cmd"};
1348 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd1613 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
@@ -1360,37 +1625,93 @@ sub vcs_file_blame { · 11 unmodified lines
1360 my $total_commits;1625 my $total_commits;
1361 my $total_lines;1626 my $total_lines;
1362 1627
1363 return if (!vcs_exists());1628 $vcs_used = vcs_exists();
1629 return if (!$vcs_used);
1364 1630
1365 @all_commits = vcs_blame($file);1631 @all_commits = vcs_blame($file);
1366 @commits = uniq(@all_commits);1632 @commits = uniq(@all_commits);
1367 $total_commits = @commits;1633 $total_commits = @commits;
1368 $total_lines = @all_commits;1634 $total_lines = @all_commits;
1369 1635
1370 foreach my $commit (@commits) {1636 if ($email_git_blame_signatures) {
1371 my $commit_count;1637 if (vcs_is_hg()) {
1372 my @commit_signers = ();1638 my $commit_count;
1639 my @commit_signers = ();
1640 my $commit = join(" -r ", @commits);
1641 my $cmd;
1373 1642
1374 my $cmd = $VCS_cmds{"find_commit_signers_cmd"};1643 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1375 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd1644 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1376 1645
1377 ($commit_count, @commit_signers) = vcs_find_signers($cmd);1646 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1378 1647
1379 push(@signers, @commit_signers);1648 push(@signers, @commit_signers);
1649 } else {
1650 foreach my $commit (@commits) {
1651 my $commit_count;
1652 my @commit_signers = ();
1653 my $cmd;
1654
1655 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1656 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1657
1658 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1659
1660 push(@signers, @commit_signers);
1661 }
1662 }
1380 }1663 }
1381 1664
1382 if ($from_filename) {1665 if ($from_filename) {
1383 if ($output_rolestats) {1666 if ($output_rolestats) {
1384 my @blame_signers;1667 my @blame_signers;
1385 foreach my $commit (@commits) {1668 if (vcs_is_hg()) {{ # Double brace for last exit
1386 my $i;1669 my $commit_count;
1387 my $cmd = $VCS_cmds{"find_commit_author_cmd"};1670 my @commit_signers = ();
1388 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd1671 @commits = uniq(@commits);
1389 my @author = vcs_find_author($cmd);1672 @commits = sort(@commits);
1390 next if !@author;1673 my $commit = join(" -r ", @commits);
1391 my $count = grep(/$commit/, @all_commits);1674 my $cmd;
1392 for ($i = 0; $i < $count ; $i++) {1675
1393 push(@blame_signers, $author[0]);1676 $cmd = $VCS_cmds{"find_commit_author_cmd"};
1677 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1678
1679 my @lines = ();
1680
1681 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1682
1683 if (!$email_git_penguin_chiefs) {
1684 @lines = grep(!/${penguin_chiefs}/i, @lines);
1685 }
1686
1687 last if !@lines;
1688
1689 my @authors = ();
1690 foreach my $line (@lines) {
1691 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1692 my $author = $1;
1693 my ($name, $address) = parse_email($author);
1694 $author = format_email($name, $address, 1);
1695 push(@authors, $1);
1696 }
1697 }
1698
1699 save_commits_by_author(@lines) if ($interactive);
1700 save_commits_by_signer(@lines) if ($interactive);
1701
1702 push(@signers, @authors);
1703 }}
1704 else {
1705 foreach my $commit (@commits) {
1706 my $i;
1707 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1708 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1709 my @author = vcs_find_author($cmd);
1710 next if !@author;
1711 my $count = grep(/$commit/, @all_commits);
1712 for ($i = 0; $i < $count ; $i++) {
1713 push(@blame_signers, $author[0]);
1714 }
1394 }1715 }
1395 }1716 }
1396 if (@blame_signers) {1717 if (@blame_signers) {