aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoe Perches <joe@perches.com>2010-10-26 17:22:55 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2010-10-26 19:52:17 -0400
commit683c6f8fcbcb6de8d07545ba70aff49e50d8bcf2 (patch)
tree3c6d4a57f2db6ae5d2b8c51c8b0d65669cd23eb4
parentdace8e300d6820c2842de750d12b498a743bcfe5 (diff)
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 Signed-off-by: Joe Perches <joe@perches.com> Cc: Florian Mickler <florian@mickler.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rwxr-xr-xscripts/get_maintainer.pl871
1 files changed, 596 insertions, 275 deletions
diff --git a/scripts/get_maintainer.pl b/scripts/get_maintainer.pl
index 1ae8c50f1908..f51176039ff5 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.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;
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;
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 = ();
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;
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 supported 146 "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(
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) {
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)) {
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 pairs 263## Read MAINTAINERS for type/value pairs
237 264
238my @typevalue = (); 265my @typevalue = ();
@@ -371,168 +398,193 @@ foreach my $file (@ARGV) {
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 parties 412my @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 patterns 425 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 patterns 524 $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:
561 --email => print email address(es) if any 613 --email => print email address(es) if any
562 --git => include recent git \*-by: signers 614 --git => include recent git \*-by: signers
563 --git-all-signature-types => include signers regardless of signature type 615 --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 {
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 {
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 {
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 {
1048 1126
1049 $commits = grep(/$pattern/, @lines); # of commits 1127 $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 signatures 1136 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 {
1080 1154
1081 return @lines if !@lines; 1155 return @lines if !@lines;
1082 1156
1083## Reformat email addresses (with names) to avoid badly written signatures 1157 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 {
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 {
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 default 1273 #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 loop 1282 #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 numbers 1377 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 selected 1409 $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 entries 1468 #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 variables 1531 $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 {
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 $cmd 1613 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
@@ -1360,37 +1625,93 @@ sub vcs_file_blame {
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 $cmd 1644 $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 $cmd 1671 @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) {