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
1313 use strict;
1414
1515 my $P = $0;
16−my $V = '0.25';
16+my $V = '0.26-beta3';
1717
1818 use Getopt::Long qw(:config no_auto_abbrev);
1919
@@ -27,6 +27,7 @@ my $email_git_penguin_chiefs = 0; · 7 unmodified lines
2727 my $email_git = 0;
2828 my $email_git_all_signature_types = 0;
2929 my $email_git_blame = 0;
30+my $email_git_blame_signatures = 1;
3031 my $email_git_fallback = 1;
3132 my $email_git_min_signatures = 1;
3233 my $email_git_max_maintainers = 5;
@@ -51,9 +52,12 @@ my $pattern_depth = 0; · 18 unmodified lines
5152 my $version = 0;
5253 my $help = 0;
5354
55+my $vcs_used = 0;
56+
5457 my $exit = 0;
5558
56−my %shortlog_buffer;
59+my %commit_author_hash;
60+my %commit_signer_hash;
5761
5862 my @penguin_chief = ();
5963 push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
@@ -77,7 +81,6 @@ my @signature_tags = (); · 17 unmodified lines
7781 push(@signature_tags, "Signed-off-by:");
7882 push(@signature_tags, "Reviewed-by:");
7983 push(@signature_tags, "Acked-by:");
80−my $signaturePattern = "\(" . join("|", @signature_tags) . "\)";
8184
8285 # rfc822 email address - preloaded methods go here.
8386 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
@@ -90,29 +93,62 @@ my %VCS_cmds; · 6 unmodified lines
9093 my %VCS_cmds_git = (
9194 "execute_cmd" => \&git_execute_cmd,
9295 "available" => '(which("git") ne "") && (-d ".git")',
93− "find_signers_cmd" => "git log --no-color --since=\$email_git_since -- \$file",
94− "find_commit_signers_cmd" => "git log --no-color -1 \$commit",
95− "find_commit_author_cmd" => "git log -1 --format=\"%an <%ae>\" \$commit",
96+ "find_signers_cmd" =>
97+ "git log --no-color --since=\$email_git_since " .
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",
96119 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
97120 "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})",
99122 "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: (.*)",
101125 );
102126
103127 my %VCS_cmds_hg = (
104128 "execute_cmd" => \&hg_execute_cmd,
105129 "available" => '(which("hg") ne "") && (-d ".hg")',
106130 "find_signers_cmd" =>
107− "hg log --date=\$email_hg_since" .
108− " --template='commit {node}\\n{desc}\\n' -- \$file",
109− "find_commit_signers_cmd" => "hg log --template='{desc}\\n' -r \$commit",
110− "find_commit_author_cmd" => "hg log -l 1 --template='{author}\\n' -r \$commit",
131+ "hg log --date=\$email_hg_since " .
132+ "--template='HgCommit: {node}\\n" .
133+ "HgAuthor: {author}\\n" .
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",
111146 "blame_range_cmd" => "", # not supported
112− "blame_file_cmd" => "hg blame -c \$file",
113− "commit_pattern" => "^commit [0-9a-f]{40,40}",
114− "blame_commit_pattern" => "^([0-9a-f]+):",
115− "shortlog_cmd" => "ht log --date=\$email_hg_since"
147+ "blame_file_cmd" => "hg blame -n \$file",
148+ "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
149+ "blame_commit_pattern" => "^([ 0-9a-f]+):",
150+ "author_pattern" => "^HgAuthor: (.*)",
151+ "subject_pattern" => "^HgSubject: (.*)",
116152 );
117153
118154 my $conf = which_conf(".get_maintainer.conf");
@@ -146,6 +182,7 @@ if (!GetOptions( · 27 unmodified lines
146182 'git!' => \$email_git,
147183 'git-all-signature-types!' => \$email_git_all_signature_types,
148184 'git-blame!' => \$email_git_blame,
185+ 'git-blame-signatures!' => \$email_git_blame_signatures,
149186 'git-fallback!' => \$email_git_fallback,
150187 'git-chief-penguins!' => \$email_git_penguin_chiefs,
151188 'git-min-signatures=i' => \$email_git_min_signatures,
@@ -193,13 +230,9 @@ if (-t STDIN && !@ARGV) { · 41 unmodified lines
193230 die "$P: missing patchfile or -f file - use --help if necessary\n";
194231 }
195232
196−if ($output_separator ne ", ") {
197− $output_multiline = 0;
198−}
199−
200−if ($output_rolestats) {
201− $output_roles = 1;
202−}
233+$output_multiline = 0 if ($output_separator ne ", ");
234+$output_rolestats = 1 if ($interactive);
235+$output_roles = 1 if ($output_rolestats);
203236
204237 if ($sections) {
205238 $email = 0;
@@ -227,12 +260,6 @@ if (!top_of_kernel_tree($lk_path)) { · 21 unmodified lines
227260 . "a linux kernel source tree.\n";
228261 }
229262
230−if ($email_git_all_signature_types) {
231− $signaturePattern = "(.+?)[Bb][Yy]:";
232−}
233−
234−
235−
236263 ## Read MAINTAINERS for type/value pairs
237264
238265 my @typevalue = ();
@@ -371,168 +398,193 @@ foreach my $file (@ARGV) { · 132 unmodified lines
371398
372399 @file_emails = uniq(@file_emails);
373400
401+my %email_hash_name;
402+my %email_hash_address;
374403 my @email_to = ();
404+my %hash_list_to;
375405 my @list_to = ();
376406 my @scm = ();
377407 my @web = ();
378408 my @subsystem = ();
379409 my @status = ();
410+my $signature_pattern;
380411
381−# Find responsible parties
412+my @to = get_maintainer();
382413
383−foreach my $file (@files) {
414+@to = merge_email(@to);
384415
385− my %hash;
386− my $exact_pattern_match = 0;
387− my $tvi = find_first_section();
388− while ($tvi < @typevalue) {
389− my $start = find_starting_index($tvi);
390− my $end = find_ending_index($tvi);
391− my $exclude = 0;
392− my $i;
393−
394− #Do not match excluded file patterns
395−
396− for ($i = $start; $i < $end; $i++) {
397− my $line = $typevalue[$i];
398− if ($line =~ m/^(\C):\s*(.*)/) {
399− my $type = $1;
400− my $value = $2;
401− if ($type eq 'X') {
402− if (file_match_pattern($file, $value)) {
403− $exclude = 1;
404− last;
405− }
406− }
407− }
408− }
416+output(@to) if (@to);
417+
418+if ($scm) {
419+ @scm = uniq(@scm);
420+ output(@scm);
421+}
422+
423+if ($status) {
424+ @status = uniq(@status);
425+ output(@status);
426+}
427+
428+if ($subsystem) {
429+ @subsystem = uniq(@subsystem);
430+ output(@subsystem);
431+}
432+
433+if ($web) {
434+ @web = uniq(@web);
435+ output(@web);
436+}
437+
438+exit($exit);
439+
440+sub 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
409473
410− if (!$exclude) {
411474 for ($i = $start; $i < $end; $i++) {
412475 my $line = $typevalue[$i];
413476 if ($line =~ m/^(\C):\s*(.*)/) {
414477 my $type = $1;
415478 my $value = $2;
416− if ($type eq 'F') {
479+ if ($type eq 'X') {
417480 if (file_match_pattern($file, $value)) {
418− my $value_pd = ($value =~ tr@/@@);
419− my $file_pd = ($file =~ tr@/@@);
420− $value_pd++ if (substr($value,-1,1) ne "/");
421− $value_pd = -1 if ($value =~ /^\.\*/);
422− $exact_pattern_match = 1 if ($value_pd >= $file_pd);
423− if ($pattern_depth == 0 ||
424− (($file_pd - $value_pd) < $pattern_depth)) {
425− $hash{$tvi} = $value_pd;
481+ $exclude = 1;
482+ last;
483+ }
484+ }
485+ }
486+ }
487+
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+ }
426505 }
427506 }
428507 }
429508 }
430509 }
510+ $tvi = $end + 1;
431511 }
432512
433− $tvi = $end + 1;
434− }
435−
436− foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
437− add_categories($line);
438− if ($sections) {
439− my $i;
440− my $start = find_starting_index($line);
441− my $end = find_ending_index($line);
442− for ($i = $start; $i < $end; $i++) {
443− my $line = $typevalue[$i];
444− if ($line =~ /^[FX]:/) { ##Restore file patterns
445− $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
446− $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
447− $line =~ s/\\\./\./g; ##Convert \. to .
448− $line =~ s/\.\*/\*/g; ##Convert .* to *
513+ foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
514+ add_categories($line);
515+ if ($sections) {
516+ my $i;
517+ my $start = find_starting_index($line);
518+ my $end = find_ending_index($line);
519+ for ($i = $start; $i < $end; $i++) {
520+ my $line = $typevalue[$i];
521+ if ($line =~ /^[FX]:/) { ##Restore file patterns
522+ $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
523+ $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
524+ $line =~ s/\\\./\./g; ##Convert \. to .
525+ $line =~ s/\.\*/\*/g; ##Convert .* to *
526+ }
527+ $line =~ s/^([A-Z]):/$1:\t/g;
528+ print("$line\n");
449529 }
450− $line =~ s/^([A-Z]):/$1:\t/g;
451− print("$line\n");
530+ print("\n");
452531 }
453− print("\n");
454532 }
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);
466533
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+ }
467541 }
468−}
469542
470−if ($keywords) {
471− @keyword_tvi = sort_and_uniq(@keyword_tvi);
472− foreach my $line (@keyword_tvi) {
473− add_categories($line);
543+ if ($keywords) {
544+ @keyword_tvi = sort_and_uniq(@keyword_tvi);
545+ foreach my $line (@keyword_tvi) {
546+ add_categories($line);
547+ }
474548 }
475−}
476549
477−if ($email) {
478− foreach my $chief (@penguin_chief) {
479− if ($chief =~ m/^(.*):(.*)/) {
480− my $email_address;
550+ if ($email) {
551+ foreach my $chief (@penguin_chief) {
552+ if ($chief =~ m/^(.*):(.*)/) {
553+ my $email_address;
481554
482− $email_address = format_email($1, $2, $email_usename);
483− if ($email_git_penguin_chiefs) {
484− push(@email_to, [$email_address, 'chief penguin']);
485− } else {
486− @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
555+ $email_address = format_email($1, $2, $email_usename);
556+ if ($email_git_penguin_chiefs) {
557+ push(@email_to, [$email_address, 'chief penguin']);
558+ } else {
559+ @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
560+ }
487561 }
488562 }
489− }
490563
491− foreach my $email (@file_emails) {
492− my ($name, $address) = parse_email($email);
564+ foreach my $email (@file_emails) {
565+ my ($name, $address) = parse_email($email);
493566
494− my $tmp_email = format_email($name, $address, $email_usename);
495− push_email_address($tmp_email, '');
496− add_role($tmp_email, 'in file');
567+ my $tmp_email = format_email($name, $address, $email_usename);
568+ push_email_address($tmp_email, '');
569+ add_role($tmp_email, 'in file');
570+ }
497571 }
498−}
499−
500572
501−if ($email || $email_list) {
502573 my @to = ();
503− if ($email) {
504− if ($interactive) {
505− @email_to = @{vcs_interactive_menu(\@email_to)};
574+ if ($email || $email_list) {
575+ if ($email) {
576+ @to = (@to, @email_to);
577+ }
578+ if ($email_list) {
579+ @to = (@to, @list_to);
506580 }
507− @to = (@to, @email_to);
508− }
509− if ($email_list) {
510− @to = (@to, @list_to);
511581 }
512− output(merge_email(@to));
513−}
514582
515−if ($scm) {
516− @scm = uniq(@scm);
517− output(@scm);
518−}
519−if ($status) {
520− @status = uniq(@status);
521− output(@status);
522−}
583+ @to = interactive_get_maintainer(\@to) if ($interactive);
523584
524−if ($subsystem) {
525− @subsystem = uniq(@subsystem);
526− output(@subsystem);
585+ return @to;
527586 }
528587
529−if ($web) {
530− @web = uniq(@web);
531− output(@web);
532−}
533−
534−exit($exit);
535−
536588 sub file_match_pattern {
537589 my ($file, $pattern) = @_;
538590 if (substr($pattern, -1) eq "/") {
@@ -561,7 +613,7 @@ MAINTAINER field selection options: · 22 unmodified lines
561613 --email => print email address(es) if any
562614 --git => include recent git \*-by: signers
563615 --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)
565617 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
566618 --git-chief-penguins => include ${penguin_chiefs}
567619 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
@@ -847,11 +899,19 @@ sub add_categories { · 279 unmodified lines
847899 }
848900 if ($list_additional =~ m/subscribers-only/) {
849901 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+ }
851907 }
852908 } else {
853909 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+ }
855915 }
856916 }
857917 } elsif ($ptype eq "M") {
@@ -882,9 +942,6 @@ sub add_categories { · 24 unmodified lines
882942 }
883943 }
884944
885−my %email_hash_name;
886−my %email_hash_address;
887−
888945 sub email_inuse {
889946 my ($name, $address) = @_;
890947
@@ -1037,10 +1094,31 @@ sub hg_execute_cmd { · 146 unmodified lines
10371094 return @lines;
10381095 }
10391096
1097+sub 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+
10401117 sub vcs_find_signers {
10411118 my ($cmd) = @_;
1042− my @lines = ();
10431119 my $commits;
1120+ my @lines = ();
1121+ my @signatures = ();
10441122
10451123 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
10461124
@@ -1048,24 +1126,20 @@ sub vcs_find_signers { · 1 unmodified line
10481126
10491127 $commits = grep(/$pattern/, @lines); # of commits
10501128
1051− @lines = grep(/^[ \t]*${signaturePattern}.*\@.*$/, @lines);
1052− if (!$email_git_penguin_chiefs) {
1053− @lines = grep(!/${penguin_chiefs}/i, @lines);
1054− }
1129+ @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
10551130
1056− return (0, @lines) if !@lines;
1131+ return (0, @signatures) if !@signatures;
10571132
1058− # cut -f2- -d":"
1059− s/.*:\s*(.+)\s*/$1/ for (@lines);
1133+ save_commits_by_author(@lines) if ($interactive);
1134+ save_commits_by_signer(@lines) if ($interactive);
10601135
1061−## Reformat email addresses (with names) to avoid badly written signatures
1062−
1063− foreach my $line (@lines) {
1064− my ($name, $address) = parse_email($line);
1065− $line = format_email($name, $address, 1);
1136+ if (!$email_git_penguin_chiefs) {
1137+ @signatures = grep(!/${penguin_chiefs}/i, @signatures);
10661138 }
10671139
1068− return ($commits, @lines);
1140+ my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1141+
1142+ return ($commits, @$signers_ref);
10691143 }
10701144
10711145 sub vcs_find_author {
@@ -1080,14 +1154,20 @@ sub vcs_find_author { · 8 unmodified lines
10801154
10811155 return @lines if !@lines;
10821156
1083−## Reformat email addresses (with names) to avoid badly written signatures
1084−
1157+ my @authors = ();
10851158 foreach my $line (@lines) {
1086− my ($name, $address) = parse_email($line);
1087− $line = format_email($name, $address, 1);
1159+ if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1160+ my $author = $1;
1161+ my ($name, $address) = parse_email($author);
1162+ $author = format_email($name, $address, 1);
1163+ push(@authors, $author);
1164+ }
10881165 }
10891166
1090− return @lines;
1167+ save_commits_by_author(@lines) if ($interactive);
1168+ save_commits_by_signer(@lines) if ($interactive);
1169+
1170+ return @authors;
10911171 }
10921172
10931173 sub vcs_save_commits {
@@ -1159,7 +1239,7 @@ sub vcs_exists { · 65 unmodified lines
11591239 %VCS_cmds = %VCS_cmds_git;
11601240 return 1 if eval $VCS_cmds{"available"};
11611241 %VCS_cmds = %VCS_cmds_hg;
1162− return 1 if eval $VCS_cmds{"available"};
1242+ return 2 if eval $VCS_cmds{"available"};
11631243 %VCS_cmds = ();
11641244 if (!$printed_novcs) {
11651245 warn("$P: No supported VCS found. Add --nogit to options?\n");
@@ -1171,125 +1251,309 @@ sub vcs_exists { · 5 unmodified lines
11711251 return 0;
11721252 }
11731253
1174−sub vcs_interactive_menu {
1175− my $list_ref = shift;
1254+sub vcs_is_git {
1255+ return $vcs_used == 1;
1256+}
1257+
1258+sub vcs_is_hg {
1259+ return $vcs_used == 2;
1260+}
1261+
1262+sub interactive_get_maintainer {
1263+ my ($list_ref) = @_;
11761264 my @list = @$list_ref;
11771265
1178− return if (!vcs_exists());
1266+ vcs_exists();
11791267
11801268 my %selected;
1181− my %shortlog;
1182− my $input;
1269+ my %authored;
1270+ my %signed;
11831271 my $count = 0;
11841272
11851273 #select maintainers by default
11861274 foreach my $entry (@list){
1187− my $role = $entry->[1];
1188− $selected{$count} = ($role =~ /maintainer:|supporter:/);
1189− $count++;
1275+ my $role = $entry->[1];
1276+ $selected{$count} = ($role =~ /^(maintainer|supporter|open list)/);
1277+ $authored{$count} = 0;
1278+ $signed{$count} = 0;
1279+ $count++;
11901280 }
11911281
11921282 #menu loop
1193− do {
1194− my $count = 0;
1195− foreach my $entry (@list){
1196− my $email = $entry->[0];
1197− my $role = $entry->[1];
1198− if ($selected{$count}){
1199− print STDERR "* ";
1200− } else {
1201− print STDERR " ";
1202− }
1203− print STDERR "$count: $email,\t\t $role";
1204− print STDERR "\n";
1205− if ($shortlog{$count}){
1206− my $entries_ref = vcs_get_shortlog($email);
1207− foreach my $entry_ref (@{$entries_ref}){
1208− my $filename = @{$entry_ref}[0];
1209− my @shortlog = @{@{$entry_ref}[1]};
1210− print STDERR "\tshortlog for $filename (authored commits: " . @shortlog . ").\n";
1211− foreach my $commit (@shortlog){
1212− print STDERR "\t $commit\n";
1283+ my $done = 0;
1284+ my $print_options = 0;
1285+ my $redraw = 1;
1286+ while (!$done) {
1287+ $count = 0;
1288+ if ($redraw) {
1289+ printf STDERR "\n%1s %2s %-65sauth sign\n",
1290+ "*", "#", "email/list and role:stats";
1291+ foreach my $entry (@list) {
1292+ my $email = $entry->[0];
1293+ my $role = $entry->[1];
1294+ my $sel = "";
1295+ $sel = "*" if ($selected{$count});
1296+ my $commit_author = $commit_author_hash{$email};
1297+ my $commit_signer = $commit_signer_hash{$email};
1298+ my $authored = 0;
1299+ my $signed = 0;
1300+ $authored++ for (@{$commit_author});
1301+ $signed++ for (@{$commit_signer});
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";
12131310 }
1214− print STDERR "\n";
12151311 }
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++;
12161320 }
1217− $count++;
12181321 }
1219− print STDERR "\n";
1220− print STDERR "Choose whom to cc by entering a commaseperated list of numbers and hitting enter.\n";
1221− print STDERR "To show a short list of commits, precede the number by a '?',\n";
1222− print STDERR "A blank line indicates that you are satisfied with your choice.\n";
1223− $input = <STDIN>;
1322+ my $date_ref = \$email_git_since;
1323+ $date_ref = \$email_hg_since if (vcs_is_hg());
1324+ if ($print_options) {
1325+ $print_options = 0;
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>;
12241350 chomp($input);
12251351
1226− my @wish = split(/[, ]+/,$input);
1227− foreach my $nr (@wish){
1228− my $logtoggle = 0;
1229− if ($nr =~ /\?/){
1230− $nr =~ s/\?//;
1231− $logtoggle = 1;
1352+ $redraw = 1;
1353+ my $rerun = 0;
1354+ my @wish = split(/[, ]+/, $input);
1355+ foreach my $nr (@wish) {
1356+ $nr = lc($nr);
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;
12321375 }
1233−
1234− #skip out of bounds numbers
1235− next unless ($nr <= $count && $nr >= 0);
1236−
1237− if ($logtoggle){
1238− $shortlog{$nr} = !$shortlog{$nr};
1376+ } elsif ($sel eq "0") {
1377+ for (my $i = 0; $i < $count; $i++) {
1378+ $selected{$i} = !$selected{$i};
1379+ }
1380+ } elsif ($sel eq "a") {
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);
12391406 } else {
1240− $selected{$nr} = !$selected{$nr};
1241−
1242− #switch shortlog on if an entry get's selected
1243− if ($selected{$nr}){
1244− $shortlog{$nr}=1;
1245− }
1407+ bool_invert(\$email_git);
1408+ }
1409+ $rerun = 1;
1410+ } elsif ($sel eq "b") {
1411+ if ($str eq "s") {
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;
12461431 }
1247− };
1248− } while(length($input) > 0);
1432+ } elsif ($sel eq "d") {
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+ }
12491467
12501468 #drop not selected entries
12511469 $count = 0;
1252− my @new_emailto;
1253− foreach my $entry (@list){
1254− if ($selected{$count}){
1255− 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";
1470+ my @new_emailto = ();
1471+ foreach my $entry (@list) {
1472+ if ($selected{$count}) {
1473+ push(@new_emailto, $list[$count]);
12611474 }
12621475 $count++;
12631476 }
1264− return \@new_emailto;
1477+ return @new_emailto;
12651478 }
12661479
1267−sub vcs_get_shortlog {
1268− my $arg = shift;
1269− my ($name, $address) = parse_email($arg);
1270− return $shortlog_buffer{$address};
1480+sub bool_invert {
1481+ my ($bool_ref) = @_;
1482+
1483+ if ($$bool_ref) {
1484+ $$bool_ref = 0;
1485+ } else {
1486+ $$bool_ref = 1;
1487+ }
12711488 }
12721489
1273−sub vcs_file_shortlogs {
1274− my ($file) = @_;
1275− print STDERR "shortlog processing $file:";
1276− foreach my $entry (@email_to){
1277− my ($name, $address) = parse_email($entry->[0]);
1278− print STDERR ".";
1279− my $commits_ref = vcs_email_shortlog($address, $file);
1280− push(@{$shortlog_buffer{$address}}, [ $file, $commits_ref ]);
1490+sub save_commits_by_author {
1491+ my (@lines) = @_;
1492+
1493+ my @authors = ();
1494+ my @commits = ();
1495+ my @subjects = ();
1496+
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+ }
12811521 }
1282− print STDERR "\n";
12831522 }
12841523
1285−sub vcs_email_shortlog {
1286− my $email = shift;
1287− my ($file) = @_;
1524+sub save_commits_by_signer {
1525+ my (@lines) = @_;
1526+
1527+ my $commit = "";
1528+ my $subject = "";
12881529
1289− my $cmd = $VCS_cmds{"shortlog_cmd"};
1290− $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables
1291− my @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1292− return \@lines;
1530+ foreach my $line (@lines) {
1531+ $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1532+ $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
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+ }
12931557 }
12941558
12951559 sub vcs_assign {
@@ -1342,7 +1606,8 @@ sub vcs_file_signoffs { · 46 unmodified lines
13421606 my @signers = ();
13431607 my $commits;
13441608
1345− return if (!vcs_exists());
1609+ $vcs_used = vcs_exists();
1610+ return if (!$vcs_used);
13461611
13471612 my $cmd = $VCS_cmds{"find_signers_cmd"};
13481613 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
@@ -1360,37 +1625,93 @@ sub vcs_file_blame { · 11 unmodified lines
13601625 my $total_commits;
13611626 my $total_lines;
13621627
1363− return if (!vcs_exists());
1628+ $vcs_used = vcs_exists();
1629+ return if (!$vcs_used);
13641630
13651631 @all_commits = vcs_blame($file);
13661632 @commits = uniq(@all_commits);
13671633 $total_commits = @commits;
13681634 $total_lines = @all_commits;
13691635
1370− foreach my $commit (@commits) {
1371− my $commit_count;
1372− my @commit_signers = ();
1636+ if ($email_git_blame_signatures) {
1637+ if (vcs_is_hg()) {
1638+ my $commit_count;
1639+ my @commit_signers = ();
1640+ my $commit = join(" -r ", @commits);
1641+ my $cmd;
13731642
1374− my $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1375− $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1643+ $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1644+ $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
13761645
1377− ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1646+ ($commit_count, @commit_signers) = vcs_find_signers($cmd);
13781647
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+ }
13801663 }
13811664
13821665 if ($from_filename) {
13831666 if ($output_rolestats) {
13841667 my @blame_signers;
1385− foreach my $commit (@commits) {
1386− my $i;
1387− my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1388− $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1389− my @author = vcs_find_author($cmd);
1390− next if !@author;
1391− my $count = grep(/$commit/, @all_commits);
1392− for ($i = 0; $i < $count ; $i++) {
1393− push(@blame_signers, $author[0]);
1668+ if (vcs_is_hg()) {{ # Double brace for last exit
1669+ my $commit_count;
1670+ my @commit_signers = ();
1671+ @commits = uniq(@commits);
1672+ @commits = sort(@commits);
1673+ my $commit = join(" -r ", @commits);
1674+ my $cmd;
1675+
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+ }
13941715 }
13951716 }
13961717 if (@blame_signers) {