aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/get_maintainer.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/get_maintainer.pl')
-rwxr-xr-xscripts/get_maintainer.pl1162
1 files changed, 948 insertions, 214 deletions
diff --git a/scripts/get_maintainer.pl b/scripts/get_maintainer.pl
index b2281982f52f..d21ec3a89603 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.24'; 16my $V = '0.26-beta6';
17 17
18use Getopt::Long qw(:config no_auto_abbrev); 18use Getopt::Long qw(:config no_auto_abbrev);
19 19
@@ -24,15 +24,19 @@ my $email_maintainer = 1;
24my $email_list = 1; 24my $email_list = 1;
25my $email_subscriber_list = 0; 25my $email_subscriber_list = 0;
26my $email_git_penguin_chiefs = 0; 26my $email_git_penguin_chiefs = 0;
27my $email_git = 1; 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;
31my $email_git_fallback = 1;
30my $email_git_min_signatures = 1; 32my $email_git_min_signatures = 1;
31my $email_git_max_maintainers = 5; 33my $email_git_max_maintainers = 5;
32my $email_git_min_percent = 5; 34my $email_git_min_percent = 5;
33my $email_git_since = "1-year-ago"; 35my $email_git_since = "1-year-ago";
34my $email_hg_since = "-365"; 36my $email_hg_since = "-365";
37my $interactive = 0;
35my $email_remove_duplicates = 1; 38my $email_remove_duplicates = 1;
39my $email_use_mailmap = 1;
36my $output_multiline = 1; 40my $output_multiline = 1;
37my $output_separator = ", "; 41my $output_separator = ", ";
38my $output_roles = 0; 42my $output_roles = 0;
@@ -49,8 +53,13 @@ my $pattern_depth = 0;
49my $version = 0; 53my $version = 0;
50my $help = 0; 54my $help = 0;
51 55
56my $vcs_used = 0;
57
52my $exit = 0; 58my $exit = 0;
53 59
60my %commit_author_hash;
61my %commit_signer_hash;
62
54my @penguin_chief = (); 63my @penguin_chief = ();
55push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org"); 64push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
56#Andrew wants in on most everything - 2009/01/14 65#Andrew wants in on most everything - 2009/01/14
@@ -73,7 +82,6 @@ my @signature_tags = ();
73push(@signature_tags, "Signed-off-by:"); 82push(@signature_tags, "Signed-off-by:");
74push(@signature_tags, "Reviewed-by:"); 83push(@signature_tags, "Reviewed-by:");
75push(@signature_tags, "Acked-by:"); 84push(@signature_tags, "Acked-by:");
76my $signaturePattern = "\(" . join("|", @signature_tags) . "\)";
77 85
78# rfc822 email address - preloaded methods go here. 86# rfc822 email address - preloaded methods go here.
79my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])"; 87my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
@@ -86,31 +94,70 @@ my %VCS_cmds;
86my %VCS_cmds_git = ( 94my %VCS_cmds_git = (
87 "execute_cmd" => \&git_execute_cmd, 95 "execute_cmd" => \&git_execute_cmd,
88 "available" => '(which("git") ne "") && (-d ".git")', 96 "available" => '(which("git") ne "") && (-d ".git")',
89 "find_signers_cmd" => "git log --no-color --since=\$email_git_since -- \$file", 97 "find_signers_cmd" =>
90 "find_commit_signers_cmd" => "git log --no-color -1 \$commit", 98 "git log --no-color --since=\$email_git_since " .
99 '--format="GitCommit: %H%n' .
100 'GitAuthor: %an <%ae>%n' .
101 'GitDate: %aD%n' .
102 'GitSubject: %s%n' .
103 '%b%n"' .
104 " -- \$file",
105 "find_commit_signers_cmd" =>
106 "git log --no-color " .
107 '--format="GitCommit: %H%n' .
108 'GitAuthor: %an <%ae>%n' .
109 'GitDate: %aD%n' .
110 'GitSubject: %s%n' .
111 '%b%n"' .
112 " -1 \$commit",
113 "find_commit_author_cmd" =>
114 "git log --no-color " .
115 '--format="GitCommit: %H%n' .
116 'GitAuthor: %an <%ae>%n' .
117 'GitDate: %aD%n' .
118 'GitSubject: %s%n"' .
119 " -1 \$commit",
91 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file", 120 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
92 "blame_file_cmd" => "git blame -l \$file", 121 "blame_file_cmd" => "git blame -l \$file",
93 "commit_pattern" => "^commit [0-9a-f]{40,40}", 122 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
94 "blame_commit_pattern" => "^([0-9a-f]+) " 123 "blame_commit_pattern" => "^([0-9a-f]+) ",
124 "author_pattern" => "^GitAuthor: (.*)",
125 "subject_pattern" => "^GitSubject: (.*)",
95); 126);
96 127
97my %VCS_cmds_hg = ( 128my %VCS_cmds_hg = (
98 "execute_cmd" => \&hg_execute_cmd, 129 "execute_cmd" => \&hg_execute_cmd,
99 "available" => '(which("hg") ne "") && (-d ".hg")', 130 "available" => '(which("hg") ne "") && (-d ".hg")',
100 "find_signers_cmd" => 131 "find_signers_cmd" =>
101 "hg log --date=\$email_hg_since" . 132 "hg log --date=\$email_hg_since " .
102 " --template='commit {node}\\n{desc}\\n' -- \$file", 133 "--template='HgCommit: {node}\\n" .
103 "find_commit_signers_cmd" => "hg log --template='{desc}\\n' -r \$commit", 134 "HgAuthor: {author}\\n" .
135 "HgSubject: {desc}\\n'" .
136 " -- \$file",
137 "find_commit_signers_cmd" =>
138 "hg log " .
139 "--template='HgSubject: {desc}\\n'" .
140 " -r \$commit",
141 "find_commit_author_cmd" =>
142 "hg log " .
143 "--template='HgCommit: {node}\\n" .
144 "HgAuthor: {author}\\n" .
145 "HgSubject: {desc|firstline}\\n'" .
146 " -r \$commit",
104 "blame_range_cmd" => "", # not supported 147 "blame_range_cmd" => "", # not supported
105 "blame_file_cmd" => "hg blame -c \$file", 148 "blame_file_cmd" => "hg blame -n \$file",
106 "commit_pattern" => "^commit [0-9a-f]{40,40}", 149 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
107 "blame_commit_pattern" => "^([0-9a-f]+):" 150 "blame_commit_pattern" => "^([ 0-9a-f]+):",
151 "author_pattern" => "^HgAuthor: (.*)",
152 "subject_pattern" => "^HgSubject: (.*)",
108); 153);
109 154
110if (-f "${lk_path}.get_maintainer.conf") { 155my $conf = which_conf(".get_maintainer.conf");
156if (-f $conf) {
111 my @conf_args; 157 my @conf_args;
112 open(my $conffile, '<', "${lk_path}.get_maintainer.conf") 158 open(my $conffile, '<', "$conf")
113 or warn "$P: Can't open .get_maintainer.conf: $!\n"; 159 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
160
114 while (<$conffile>) { 161 while (<$conffile>) {
115 my $line = $_; 162 my $line = $_;
116 163
@@ -136,13 +183,17 @@ if (!GetOptions(
136 'git!' => \$email_git, 183 'git!' => \$email_git,
137 'git-all-signature-types!' => \$email_git_all_signature_types, 184 'git-all-signature-types!' => \$email_git_all_signature_types,
138 'git-blame!' => \$email_git_blame, 185 'git-blame!' => \$email_git_blame,
186 'git-blame-signatures!' => \$email_git_blame_signatures,
187 'git-fallback!' => \$email_git_fallback,
139 'git-chief-penguins!' => \$email_git_penguin_chiefs, 188 'git-chief-penguins!' => \$email_git_penguin_chiefs,
140 'git-min-signatures=i' => \$email_git_min_signatures, 189 'git-min-signatures=i' => \$email_git_min_signatures,
141 'git-max-maintainers=i' => \$email_git_max_maintainers, 190 'git-max-maintainers=i' => \$email_git_max_maintainers,
142 'git-min-percent=i' => \$email_git_min_percent, 191 'git-min-percent=i' => \$email_git_min_percent,
143 'git-since=s' => \$email_git_since, 192 'git-since=s' => \$email_git_since,
144 'hg-since=s' => \$email_hg_since, 193 'hg-since=s' => \$email_hg_since,
194 'i|interactive!' => \$interactive,
145 'remove-duplicates!' => \$email_remove_duplicates, 195 'remove-duplicates!' => \$email_remove_duplicates,
196 'mailmap!' => \$email_use_mailmap,
146 'm!' => \$email_maintainer, 197 'm!' => \$email_maintainer,
147 'n!' => \$email_usename, 198 'n!' => \$email_usename,
148 'l!' => \$email_list, 199 'l!' => \$email_list,
@@ -181,13 +232,9 @@ if (-t STDIN && !@ARGV) {
181 die "$P: missing patchfile or -f file - use --help if necessary\n"; 232 die "$P: missing patchfile or -f file - use --help if necessary\n";
182} 233}
183 234
184if ($output_separator ne ", ") { 235$output_multiline = 0 if ($output_separator ne ", ");
185 $output_multiline = 0; 236$output_rolestats = 1 if ($interactive);
186} 237$output_roles = 1 if ($output_rolestats);
187
188if ($output_rolestats) {
189 $output_roles = 1;
190}
191 238
192if ($sections) { 239if ($sections) {
193 $email = 0; 240 $email = 0;
@@ -197,6 +244,7 @@ if ($sections) {
197 $subsystem = 0; 244 $subsystem = 0;
198 $web = 0; 245 $web = 0;
199 $keywords = 0; 246 $keywords = 0;
247 $interactive = 0;
200} else { 248} else {
201 my $selections = $email + $scm + $status + $subsystem + $web; 249 my $selections = $email + $scm + $status + $subsystem + $web;
202 if ($selections == 0) { 250 if ($selections == 0) {
@@ -215,10 +263,6 @@ if (!top_of_kernel_tree($lk_path)) {
215 . "a linux kernel source tree.\n"; 263 . "a linux kernel source tree.\n";
216} 264}
217 265
218if ($email_git_all_signature_types) {
219 $signaturePattern = "(.+?)[Bb][Yy]:";
220}
221
222## Read MAINTAINERS for type/value pairs 266## Read MAINTAINERS for type/value pairs
223 267
224my @typevalue = (); 268my @typevalue = ();
@@ -253,31 +297,82 @@ while (<$maint>) {
253} 297}
254close($maint); 298close($maint);
255 299
256my %mailmap;
257 300
258if ($email_remove_duplicates) { 301#
259 open(my $mailmap, '<', "${lk_path}.mailmap") 302# Read mail address map
260 or warn "$P: Can't open .mailmap: $!\n"; 303#
261 while (<$mailmap>) {
262 my $line = $_;
263 304
264 next if ($line =~ m/^\s*#/); 305my $mailmap;
265 next if ($line =~ m/^\s*$/);
266 306
267 my ($name, $address) = parse_email($line); 307read_mailmap();
268 $line = format_email($name, $address, $email_usename);
269 308
270 next if ($line =~ m/^\s*$/); 309sub read_mailmap {
310 $mailmap = {
311 names => {},
312 addresses => {}
313 };
271 314
272 if (exists($mailmap{$name})) { 315 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
273 my $obj = $mailmap{$name}; 316
274 push(@$obj, $address); 317 open(my $mailmap_file, '<', "${lk_path}.mailmap")
275 } else { 318 or warn "$P: Can't open .mailmap: $!\n";
276 my @arr = ($address); 319
277 $mailmap{$name} = \@arr; 320 while (<$mailmap_file>) {
321 s/#.*$//; #strip comments
322 s/^\s+|\s+$//g; #trim
323
324 next if (/^\s*$/); #skip empty lines
325 #entries have one of the following formats:
326 # name1 <mail1>
327 # <mail1> <mail2>
328 # name1 <mail1> <mail2>
329 # name1 <mail1> name2 <mail2>
330 # (see man git-shortlog)
331 if (/^(.+)<(.+)>$/) {
332 my $real_name = $1;
333 my $address = $2;
334
335 $real_name =~ s/\s+$//;
336 ($real_name, $address) = parse_email("$real_name <$address>");
337 $mailmap->{names}->{$address} = $real_name;
338
339 } elsif (/^<([^\s]+)>\s*<([^\s]+)>$/) {
340 my $real_address = $1;
341 my $wrong_address = $2;
342
343 $mailmap->{addresses}->{$wrong_address} = $real_address;
344
345 } elsif (/^(.+)<([^\s]+)>\s*<([^\s]+)>$/) {
346 my $real_name = $1;
347 my $real_address = $2;
348 my $wrong_address = $3;
349
350 $real_name =~ s/\s+$//;
351 ($real_name, $real_address) =
352 parse_email("$real_name <$real_address>");
353 $mailmap->{names}->{$wrong_address} = $real_name;
354 $mailmap->{addresses}->{$wrong_address} = $real_address;
355
356 } elsif (/^(.+)<([^\s]+)>\s*([^\s].*)<([^\s]+)>$/) {
357 my $real_name = $1;
358 my $real_address = $2;
359 my $wrong_name = $3;
360 my $wrong_address = $4;
361
362 $real_name =~ s/\s+$//;
363 ($real_name, $real_address) =
364 parse_email("$real_name <$real_address>");
365
366 $wrong_name =~ s/\s+$//;
367 ($wrong_name, $wrong_address) =
368 parse_email("$wrong_name <$wrong_address>");
369
370 my $wrong_email = format_email($wrong_name, $wrong_address, 1);
371 $mailmap->{names}->{$wrong_email} = $real_name;
372 $mailmap->{addresses}->{$wrong_email} = $real_address;
278 } 373 }
279 } 374 }
280 close($mailmap); 375 close($mailmap_file);
281} 376}
282 377
283## use the filenames on the command line or find the filenames in the patchfiles 378## use the filenames on the command line or find the filenames in the patchfiles
@@ -302,7 +397,7 @@ foreach my $file (@ARGV) {
302 } 397 }
303 if ($from_filename) { 398 if ($from_filename) {
304 push(@files, $file); 399 push(@files, $file);
305 if (-f $file && ($keywords || $file_emails)) { 400 if ($file ne "MAINTAINERS" && -f $file && ($keywords || $file_emails)) {
306 open(my $f, '<', $file) 401 open(my $f, '<', $file)
307 or die "$P: Can't open $file: $!\n"; 402 or die "$P: Can't open $file: $!\n";
308 my $text = do { local($/) ; <$f> }; 403 my $text = do { local($/) ; <$f> };
@@ -357,67 +452,127 @@ foreach my $file (@ARGV) {
357 452
358@file_emails = uniq(@file_emails); 453@file_emails = uniq(@file_emails);
359 454
455my %email_hash_name;
456my %email_hash_address;
360my @email_to = (); 457my @email_to = ();
458my %hash_list_to;
361my @list_to = (); 459my @list_to = ();
362my @scm = (); 460my @scm = ();
363my @web = (); 461my @web = ();
364my @subsystem = (); 462my @subsystem = ();
365my @status = (); 463my @status = ();
464my %deduplicate_name_hash = ();
465my %deduplicate_address_hash = ();
466my $signature_pattern;
366 467
367# Find responsible parties 468my @maintainers = get_maintainers();
368 469
369foreach my $file (@files) { 470if (@maintainers) {
471 @maintainers = merge_email(@maintainers);
472 output(@maintainers);
473}
370 474
371 my %hash; 475if ($scm) {
372 my $tvi = find_first_section(); 476 @scm = uniq(@scm);
373 while ($tvi < @typevalue) { 477 output(@scm);
374 my $start = find_starting_index($tvi); 478}
375 my $end = find_ending_index($tvi); 479
376 my $exclude = 0; 480if ($status) {
377 my $i; 481 @status = uniq(@status);
378 482 output(@status);
379 #Do not match excluded file patterns 483}
380 484
381 for ($i = $start; $i < $end; $i++) { 485if ($subsystem) {
382 my $line = $typevalue[$i]; 486 @subsystem = uniq(@subsystem);
383 if ($line =~ m/^(\C):\s*(.*)/) { 487 output(@subsystem);
384 my $type = $1; 488}
385 my $value = $2; 489
386 if ($type eq 'X') { 490if ($web) {
387 if (file_match_pattern($file, $value)) { 491 @web = uniq(@web);
388 $exclude = 1; 492 output(@web);
389 last; 493}
390 } 494
391 } 495exit($exit);
392 } 496
393 } 497sub get_maintainers {
498 %email_hash_name = ();
499 %email_hash_address = ();
500 %commit_author_hash = ();
501 %commit_signer_hash = ();
502 @email_to = ();
503 %hash_list_to = ();
504 @list_to = ();
505 @scm = ();
506 @web = ();
507 @subsystem = ();
508 @status = ();
509 %deduplicate_name_hash = ();
510 %deduplicate_address_hash = ();
511 if ($email_git_all_signature_types) {
512 $signature_pattern = "(.+?)[Bb][Yy]:";
513 } else {
514 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
515 }
516
517 # Find responsible parties
518
519 my %exact_pattern_match_hash = ();
520
521 foreach my $file (@files) {
522
523 my %hash;
524 my $tvi = find_first_section();
525 while ($tvi < @typevalue) {
526 my $start = find_starting_index($tvi);
527 my $end = find_ending_index($tvi);
528 my $exclude = 0;
529 my $i;
530
531 #Do not match excluded file patterns
394 532
395 if (!$exclude) {
396 for ($i = $start; $i < $end; $i++) { 533 for ($i = $start; $i < $end; $i++) {
397 my $line = $typevalue[$i]; 534 my $line = $typevalue[$i];
398 if ($line =~ m/^(\C):\s*(.*)/) { 535 if ($line =~ m/^(\C):\s*(.*)/) {
399 my $type = $1; 536 my $type = $1;
400 my $value = $2; 537 my $value = $2;
401 if ($type eq 'F') { 538 if ($type eq 'X') {
402 if (file_match_pattern($file, $value)) { 539 if (file_match_pattern($file, $value)) {
403 my $value_pd = ($value =~ tr@/@@); 540 $exclude = 1;
404 my $file_pd = ($file =~ tr@/@@); 541 last;
405 $value_pd++ if (substr($value,-1,1) ne "/"); 542 }
406 if ($pattern_depth == 0 || 543 }
407 (($file_pd - $value_pd) < $pattern_depth)) { 544 }
408 $hash{$tvi} = $value_pd; 545 }
546
547 if (!$exclude) {
548 for ($i = $start; $i < $end; $i++) {
549 my $line = $typevalue[$i];
550 if ($line =~ m/^(\C):\s*(.*)/) {
551 my $type = $1;
552 my $value = $2;
553 if ($type eq 'F') {
554 if (file_match_pattern($file, $value)) {
555 my $value_pd = ($value =~ tr@/@@);
556 my $file_pd = ($file =~ tr@/@@);
557 $value_pd++ if (substr($value,-1,1) ne "/");
558 $value_pd = -1 if ($value =~ /^\.\*/);
559 if ($value_pd >= $file_pd) {
560 $exact_pattern_match_hash{$file} = 1;
561 }
562 if ($pattern_depth == 0 ||
563 (($file_pd - $value_pd) < $pattern_depth)) {
564 $hash{$tvi} = $value_pd;
565 }
409 } 566 }
410 } 567 }
411 } 568 }
412 } 569 }
413 } 570 }
571 $tvi = $end + 1;
414 } 572 }
415 573
416 $tvi = $end + 1; 574 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
417 } 575 add_categories($line);
418
419 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
420 add_categories($line);
421 if ($sections) { 576 if ($sections) {
422 my $i; 577 my $i;
423 my $start = find_starting_index($line); 578 my $start = find_starting_index($line);
@@ -435,80 +590,71 @@ foreach my $file (@files) {
435 } 590 }
436 print("\n"); 591 print("\n");
437 } 592 }
593 }
438 } 594 }
439 595
440 if ($email && $email_git) { 596 if ($keywords) {
441 vcs_file_signoffs($file); 597 @keyword_tvi = sort_and_uniq(@keyword_tvi);
598 foreach my $line (@keyword_tvi) {
599 add_categories($line);
600 }
442 } 601 }
443 602
444 if ($email && $email_git_blame) { 603 foreach my $email (@email_to, @list_to) {
445 vcs_file_blame($file); 604 $email->[0] = deduplicate_email($email->[0]);
446 } 605 }
447}
448 606
449if ($keywords) { 607 foreach my $file (@files) {
450 @keyword_tvi = sort_and_uniq(@keyword_tvi); 608 if ($email &&
451 foreach my $line (@keyword_tvi) { 609 ($email_git || ($email_git_fallback &&
452 add_categories($line); 610 !$exact_pattern_match_hash{$file}))) {
611 vcs_file_signoffs($file);
612 }
613 if ($email && $email_git_blame) {
614 vcs_file_blame($file);
615 }
453 } 616 }
454}
455 617
456if ($email) { 618 if ($email) {
457 foreach my $chief (@penguin_chief) { 619 foreach my $chief (@penguin_chief) {
458 if ($chief =~ m/^(.*):(.*)/) { 620 if ($chief =~ m/^(.*):(.*)/) {
459 my $email_address; 621 my $email_address;
460 622
461 $email_address = format_email($1, $2, $email_usename); 623 $email_address = format_email($1, $2, $email_usename);
462 if ($email_git_penguin_chiefs) { 624 if ($email_git_penguin_chiefs) {
463 push(@email_to, [$email_address, 'chief penguin']); 625 push(@email_to, [$email_address, 'chief penguin']);
464 } else { 626 } else {
465 @email_to = grep($_->[0] !~ /${email_address}/, @email_to); 627 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
628 }
466 } 629 }
467 } 630 }
468 }
469 631
470 foreach my $email (@file_emails) { 632 foreach my $email (@file_emails) {
471 my ($name, $address) = parse_email($email); 633 my ($name, $address) = parse_email($email);
472 634
473 my $tmp_email = format_email($name, $address, $email_usename); 635 my $tmp_email = format_email($name, $address, $email_usename);
474 push_email_address($tmp_email, ''); 636 push_email_address($tmp_email, '');
475 add_role($tmp_email, 'in file'); 637 add_role($tmp_email, 'in file');
638 }
476 } 639 }
477}
478 640
479if ($email || $email_list) {
480 my @to = (); 641 my @to = ();
481 if ($email) { 642 if ($email || $email_list) {
482 @to = (@to, @email_to); 643 if ($email) {
483 } 644 @to = (@to, @email_to);
484 if ($email_list) { 645 }
485 @to = (@to, @list_to); 646 if ($email_list) {
647 @to = (@to, @list_to);
648 }
486 } 649 }
487 output(merge_email(@to));
488}
489
490if ($scm) {
491 @scm = uniq(@scm);
492 output(@scm);
493}
494
495if ($status) {
496 @status = uniq(@status);
497 output(@status);
498}
499 650
500if ($subsystem) { 651 if ($interactive) {
501 @subsystem = uniq(@subsystem); 652 @to = interactive_get_maintainers(\@to);
502 output(@subsystem); 653 }
503}
504 654
505if ($web) { 655 return @to;
506 @web = uniq(@web);
507 output(@web);
508} 656}
509 657
510exit($exit);
511
512sub file_match_pattern { 658sub file_match_pattern {
513 my ($file, $pattern) = @_; 659 my ($file, $pattern) = @_;
514 if (substr($pattern, -1) eq "/") { 660 if (substr($pattern, -1) eq "/") {
@@ -537,7 +683,8 @@ MAINTAINER field selection options:
537 --email => print email address(es) if any 683 --email => print email address(es) if any
538 --git => include recent git \*-by: signers 684 --git => include recent git \*-by: signers
539 --git-all-signature-types => include signers regardless of signature type 685 --git-all-signature-types => include signers regardless of signature type
540 or use only ${signaturePattern} signers (default: $email_git_all_signature_types) 686 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
687 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
541 --git-chief-penguins => include ${penguin_chiefs} 688 --git-chief-penguins => include ${penguin_chiefs}
542 --git-min-signatures => number of signatures required (default: $email_git_min_signatures) 689 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
543 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers) 690 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
@@ -545,6 +692,7 @@ MAINTAINER field selection options:
545 --git-blame => use git blame to find modified commits for patch or file 692 --git-blame => use git blame to find modified commits for patch or file
546 --git-since => git history to use (default: $email_git_since) 693 --git-since => git history to use (default: $email_git_since)
547 --hg-since => hg history to use (default: $email_hg_since) 694 --hg-since => hg history to use (default: $email_hg_since)
695 --interactive => display a menu (mostly useful if used with the --git option)
548 --m => include maintainer(s) if any 696 --m => include maintainer(s) if any
549 --n => include name 'Full Name <addr\@domain.tld>' 697 --n => include name 'Full Name <addr\@domain.tld>'
550 --l => include list(s) if any 698 --l => include list(s) if any
@@ -565,8 +713,9 @@ Output type options:
565 713
566Other options: 714Other options:
567 --pattern-depth => Number of pattern directory traversals (default: 0 (all)) 715 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
568 --keywords => scan patch for keywords (default: 1 (on)) 716 --keywords => scan patch for keywords (default: $keywords)
569 --sections => print the entire subsystem sections with pattern matches 717 --sections => print all of the subsystem sections with pattern matches
718 --mailmap => use .mailmap file (default: $email_use_mailmap)
570 --version => show version 719 --version => show version
571 --help => show this help information 720 --help => show this help information
572 721
@@ -606,30 +755,30 @@ EOT
606} 755}
607 756
608sub top_of_kernel_tree { 757sub top_of_kernel_tree {
609 my ($lk_path) = @_; 758 my ($lk_path) = @_;
610 759
611 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") { 760 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
612 $lk_path .= "/"; 761 $lk_path .= "/";
613 } 762 }
614 if ( (-f "${lk_path}COPYING") 763 if ( (-f "${lk_path}COPYING")
615 && (-f "${lk_path}CREDITS") 764 && (-f "${lk_path}CREDITS")
616 && (-f "${lk_path}Kbuild") 765 && (-f "${lk_path}Kbuild")
617 && (-f "${lk_path}MAINTAINERS") 766 && (-f "${lk_path}MAINTAINERS")
618 && (-f "${lk_path}Makefile") 767 && (-f "${lk_path}Makefile")
619 && (-f "${lk_path}README") 768 && (-f "${lk_path}README")
620 && (-d "${lk_path}Documentation") 769 && (-d "${lk_path}Documentation")
621 && (-d "${lk_path}arch") 770 && (-d "${lk_path}arch")
622 && (-d "${lk_path}include") 771 && (-d "${lk_path}include")
623 && (-d "${lk_path}drivers") 772 && (-d "${lk_path}drivers")
624 && (-d "${lk_path}fs") 773 && (-d "${lk_path}fs")
625 && (-d "${lk_path}init") 774 && (-d "${lk_path}init")
626 && (-d "${lk_path}ipc") 775 && (-d "${lk_path}ipc")
627 && (-d "${lk_path}kernel") 776 && (-d "${lk_path}kernel")
628 && (-d "${lk_path}lib") 777 && (-d "${lk_path}lib")
629 && (-d "${lk_path}scripts")) { 778 && (-d "${lk_path}scripts")) {
630 return 1; 779 return 1;
631 } 780 }
632 return 0; 781 return 0;
633} 782}
634 783
635sub parse_email { 784sub parse_email {
@@ -821,11 +970,19 @@ sub add_categories {
821 } 970 }
822 if ($list_additional =~ m/subscribers-only/) { 971 if ($list_additional =~ m/subscribers-only/) {
823 if ($email_subscriber_list) { 972 if ($email_subscriber_list) {
824 push(@list_to, [$list_address, "subscriber list${list_role}"]); 973 if (!$hash_list_to{lc($list_address)}) {
974 $hash_list_to{lc($list_address)} = 1;
975 push(@list_to, [$list_address,
976 "subscriber list${list_role}"]);
977 }
825 } 978 }
826 } else { 979 } else {
827 if ($email_list) { 980 if ($email_list) {
828 push(@list_to, [$list_address, "open list${list_role}"]); 981 if (!$hash_list_to{lc($list_address)}) {
982 $hash_list_to{lc($list_address)} = 1;
983 push(@list_to, [$list_address,
984 "open list${list_role}"]);
985 }
829 } 986 }
830 } 987 }
831 } elsif ($ptype eq "M") { 988 } elsif ($ptype eq "M") {
@@ -856,15 +1013,12 @@ sub add_categories {
856 } 1013 }
857} 1014}
858 1015
859my %email_hash_name;
860my %email_hash_address;
861
862sub email_inuse { 1016sub email_inuse {
863 my ($name, $address) = @_; 1017 my ($name, $address) = @_;
864 1018
865 return 1 if (($name eq "") && ($address eq "")); 1019 return 1 if (($name eq "") && ($address eq ""));
866 return 1 if (($name ne "") && exists($email_hash_name{$name})); 1020 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
867 return 1 if (($address ne "") && exists($email_hash_address{$address})); 1021 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
868 1022
869 return 0; 1023 return 0;
870} 1024}
@@ -882,8 +1036,8 @@ sub push_email_address {
882 push(@email_to, [format_email($name, $address, $email_usename), $role]); 1036 push(@email_to, [format_email($name, $address, $email_usename), $role]);
883 } elsif (!email_inuse($name, $address)) { 1037 } elsif (!email_inuse($name, $address)) {
884 push(@email_to, [format_email($name, $address, $email_usename), $role]); 1038 push(@email_to, [format_email($name, $address, $email_usename), $role]);
885 $email_hash_name{$name}++; 1039 $email_hash_name{lc($name)}++ if ($name ne "");
886 $email_hash_address{$address}++; 1040 $email_hash_address{lc($address)}++;
887 } 1041 }
888 1042
889 return 1; 1043 return 1;
@@ -952,30 +1106,69 @@ sub which {
952 return ""; 1106 return "";
953} 1107}
954 1108
955sub mailmap { 1109sub which_conf {
956 my (@lines) = @_; 1110 my ($conf) = @_;
957 my %hash;
958 1111
959 foreach my $line (@lines) { 1112 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
960 my ($name, $address) = parse_email($line); 1113 if (-e "$path/$conf") {
961 if (!exists($hash{$name})) { 1114 return "$path/$conf";
962 $hash{$name} = $address;
963 } elsif ($address ne $hash{$name}) {
964 $address = $hash{$name};
965 $line = format_email($name, $address, $email_usename);
966 } 1115 }
967 if (exists($mailmap{$name})) { 1116 }
968 my $obj = $mailmap{$name}; 1117
969 foreach my $map_address (@$obj) { 1118 return "";
970 if (($map_address eq $address) && 1119}
971 ($map_address ne $hash{$name})) { 1120
972 $line = format_email($name, $hash{$name}, $email_usename); 1121sub mailmap_email {
973 } 1122 my ($line) = @_;
974 } 1123
1124 my ($name, $address) = parse_email($line);
1125 my $email = format_email($name, $address, 1);
1126 my $real_name = $name;
1127 my $real_address = $address;
1128
1129 if (exists $mailmap->{names}->{$email} ||
1130 exists $mailmap->{addresses}->{$email}) {
1131 if (exists $mailmap->{names}->{$email}) {
1132 $real_name = $mailmap->{names}->{$email};
1133 }
1134 if (exists $mailmap->{addresses}->{$email}) {
1135 $real_address = $mailmap->{addresses}->{$email};
1136 }
1137 } else {
1138 if (exists $mailmap->{names}->{$address}) {
1139 $real_name = $mailmap->{names}->{$address};
1140 }
1141 if (exists $mailmap->{addresses}->{$address}) {
1142 $real_address = $mailmap->{addresses}->{$address};
975 } 1143 }
976 } 1144 }
1145 return format_email($real_name, $real_address, 1);
1146}
977 1147
978 return @lines; 1148sub mailmap {
1149 my (@addresses) = @_;
1150
1151 my @mapped_emails = ();
1152 foreach my $line (@addresses) {
1153 push(@mapped_emails, mailmap_email($line));
1154 }
1155 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1156 return @mapped_emails;
1157}
1158
1159sub merge_by_realname {
1160 my %address_map;
1161 my (@emails) = @_;
1162
1163 foreach my $email (@emails) {
1164 my ($name, $address) = parse_email($email);
1165 if (exists $address_map{$name}) {
1166 $address = $address_map{$name};
1167 $email = format_email($name, $address, 1);
1168 } else {
1169 $address_map{$name} = $address;
1170 }
1171 }
979} 1172}
980 1173
981sub git_execute_cmd { 1174sub git_execute_cmd {
@@ -999,10 +1192,30 @@ sub hg_execute_cmd {
999 return @lines; 1192 return @lines;
1000} 1193}
1001 1194
1195sub extract_formatted_signatures {
1196 my (@signature_lines) = @_;
1197
1198 my @type = @signature_lines;
1199
1200 s/\s*(.*):.*/$1/ for (@type);
1201
1202 # cut -f2- -d":"
1203 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1204
1205## Reformat email addresses (with names) to avoid badly written signatures
1206
1207 foreach my $signer (@signature_lines) {
1208 $signer = deduplicate_email($signer);
1209 }
1210
1211 return (\@type, \@signature_lines);
1212}
1213
1002sub vcs_find_signers { 1214sub vcs_find_signers {
1003 my ($cmd) = @_; 1215 my ($cmd) = @_;
1004 my @lines = ();
1005 my $commits; 1216 my $commits;
1217 my @lines = ();
1218 my @signatures = ();
1006 1219
1007 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd); 1220 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1008 1221
@@ -1010,21 +1223,48 @@ sub vcs_find_signers {
1010 1223
1011 $commits = grep(/$pattern/, @lines); # of commits 1224 $commits = grep(/$pattern/, @lines); # of commits
1012 1225
1013 @lines = grep(/^[ \t]*${signaturePattern}.*\@.*$/, @lines); 1226 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1227
1228 return (0, @signatures) if !@signatures;
1229
1230 save_commits_by_author(@lines) if ($interactive);
1231 save_commits_by_signer(@lines) if ($interactive);
1232
1233 if (!$email_git_penguin_chiefs) {
1234 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1235 }
1236
1237 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1238
1239 return ($commits, @$signers_ref);
1240}
1241
1242sub vcs_find_author {
1243 my ($cmd) = @_;
1244 my @lines = ();
1245
1246 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1247
1014 if (!$email_git_penguin_chiefs) { 1248 if (!$email_git_penguin_chiefs) {
1015 @lines = grep(!/${penguin_chiefs}/i, @lines); 1249 @lines = grep(!/${penguin_chiefs}/i, @lines);
1016 } 1250 }
1017 # cut -f2- -d":"
1018 s/.*:\s*(.+)\s*/$1/ for (@lines);
1019 1251
1020## Reformat email addresses (with names) to avoid badly written signatures 1252 return @lines if !@lines;
1021 1253
1254 my @authors = ();
1022 foreach my $line (@lines) { 1255 foreach my $line (@lines) {
1023 my ($name, $address) = parse_email($line); 1256 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1024 $line = format_email($name, $address, 1); 1257 my $author = $1;
1258 my ($name, $address) = parse_email($author);
1259 $author = format_email($name, $address, 1);
1260 push(@authors, $author);
1261 }
1025 } 1262 }
1026 1263
1027 return ($commits, @lines); 1264 save_commits_by_author(@lines) if ($interactive);
1265 save_commits_by_signer(@lines) if ($interactive);
1266
1267 return @authors;
1028} 1268}
1029 1269
1030sub vcs_save_commits { 1270sub vcs_save_commits {
@@ -1084,6 +1324,10 @@ sub vcs_blame {
1084 @commits = vcs_save_commits($cmd); 1324 @commits = vcs_save_commits($cmd);
1085 } 1325 }
1086 1326
1327 foreach my $commit (@commits) {
1328 $commit =~ s/^\^//g;
1329 }
1330
1087 return @commits; 1331 return @commits;
1088} 1332}
1089 1333
@@ -1092,7 +1336,7 @@ sub vcs_exists {
1092 %VCS_cmds = %VCS_cmds_git; 1336 %VCS_cmds = %VCS_cmds_git;
1093 return 1 if eval $VCS_cmds{"available"}; 1337 return 1 if eval $VCS_cmds{"available"};
1094 %VCS_cmds = %VCS_cmds_hg; 1338 %VCS_cmds = %VCS_cmds_hg;
1095 return 1 if eval $VCS_cmds{"available"}; 1339 return 2 if eval $VCS_cmds{"available"};
1096 %VCS_cmds = (); 1340 %VCS_cmds = ();
1097 if (!$printed_novcs) { 1341 if (!$printed_novcs) {
1098 warn("$P: No supported VCS found. Add --nogit to options?\n"); 1342 warn("$P: No supported VCS found. Add --nogit to options?\n");
@@ -1104,6 +1348,405 @@ sub vcs_exists {
1104 return 0; 1348 return 0;
1105} 1349}
1106 1350
1351sub vcs_is_git {
1352 vcs_exists();
1353 return $vcs_used == 1;
1354}
1355
1356sub vcs_is_hg {
1357 return $vcs_used == 2;
1358}
1359
1360sub interactive_get_maintainers {
1361 my ($list_ref) = @_;
1362 my @list = @$list_ref;
1363
1364 vcs_exists();
1365
1366 my %selected;
1367 my %authored;
1368 my %signed;
1369 my $count = 0;
1370 my $maintained = 0;
1371 foreach my $entry (@list) {
1372 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1373 $selected{$count} = 1;
1374 $authored{$count} = 0;
1375 $signed{$count} = 0;
1376 $count++;
1377 }
1378
1379 #menu loop
1380 my $done = 0;
1381 my $print_options = 0;
1382 my $redraw = 1;
1383 while (!$done) {
1384 $count = 0;
1385 if ($redraw) {
1386 printf STDERR "\n%1s %2s %-65s",
1387 "*", "#", "email/list and role:stats";
1388 if ($email_git ||
1389 ($email_git_fallback && !$maintained) ||
1390 $email_git_blame) {
1391 print STDERR "auth sign";
1392 }
1393 print STDERR "\n";
1394 foreach my $entry (@list) {
1395 my $email = $entry->[0];
1396 my $role = $entry->[1];
1397 my $sel = "";
1398 $sel = "*" if ($selected{$count});
1399 my $commit_author = $commit_author_hash{$email};
1400 my $commit_signer = $commit_signer_hash{$email};
1401 my $authored = 0;
1402 my $signed = 0;
1403 $authored++ for (@{$commit_author});
1404 $signed++ for (@{$commit_signer});
1405 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1406 printf STDERR "%4d %4d", $authored, $signed
1407 if ($authored > 0 || $signed > 0);
1408 printf STDERR "\n %s\n", $role;
1409 if ($authored{$count}) {
1410 my $commit_author = $commit_author_hash{$email};
1411 foreach my $ref (@{$commit_author}) {
1412 print STDERR " Author: @{$ref}[1]\n";
1413 }
1414 }
1415 if ($signed{$count}) {
1416 my $commit_signer = $commit_signer_hash{$email};
1417 foreach my $ref (@{$commit_signer}) {
1418 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1419 }
1420 }
1421
1422 $count++;
1423 }
1424 }
1425 my $date_ref = \$email_git_since;
1426 $date_ref = \$email_hg_since if (vcs_is_hg());
1427 if ($print_options) {
1428 $print_options = 0;
1429 if (vcs_exists()) {
1430 print STDERR <<EOT
1431
1432Version Control options:
1433g use git history [$email_git]
1434gf use git-fallback [$email_git_fallback]
1435b use git blame [$email_git_blame]
1436bs use blame signatures [$email_git_blame_signatures]
1437c# minimum commits [$email_git_min_signatures]
1438%# min percent [$email_git_min_percent]
1439d# history to use [$$date_ref]
1440x# max maintainers [$email_git_max_maintainers]
1441t all signature types [$email_git_all_signature_types]
1442m use .mailmap [$email_use_mailmap]
1443EOT
1444 }
1445 print STDERR <<EOT
1446
1447Additional options:
14480 toggle all
1449tm toggle maintainers
1450tg toggle git entries
1451tl toggle open list entries
1452ts toggle subscriber list entries
1453f emails in file [$file_emails]
1454k keywords in file [$keywords]
1455r remove duplicates [$email_remove_duplicates]
1456p# pattern match depth [$pattern_depth]
1457EOT
1458 }
1459 print STDERR
1460"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1461
1462 my $input = <STDIN>;
1463 chomp($input);
1464
1465 $redraw = 1;
1466 my $rerun = 0;
1467 my @wish = split(/[, ]+/, $input);
1468 foreach my $nr (@wish) {
1469 $nr = lc($nr);
1470 my $sel = substr($nr, 0, 1);
1471 my $str = substr($nr, 1);
1472 my $val = 0;
1473 $val = $1 if $str =~ /^(\d+)$/;
1474
1475 if ($sel eq "y") {
1476 $interactive = 0;
1477 $done = 1;
1478 $output_rolestats = 0;
1479 $output_roles = 0;
1480 last;
1481 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1482 $selected{$nr - 1} = !$selected{$nr - 1};
1483 } elsif ($sel eq "*" || $sel eq '^') {
1484 my $toggle = 0;
1485 $toggle = 1 if ($sel eq '*');
1486 for (my $i = 0; $i < $count; $i++) {
1487 $selected{$i} = $toggle;
1488 }
1489 } elsif ($sel eq "0") {
1490 for (my $i = 0; $i < $count; $i++) {
1491 $selected{$i} = !$selected{$i};
1492 }
1493 } elsif ($sel eq "t") {
1494 if (lc($str) eq "m") {
1495 for (my $i = 0; $i < $count; $i++) {
1496 $selected{$i} = !$selected{$i}
1497 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1498 }
1499 } elsif (lc($str) eq "g") {
1500 for (my $i = 0; $i < $count; $i++) {
1501 $selected{$i} = !$selected{$i}
1502 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1503 }
1504 } elsif (lc($str) eq "l") {
1505 for (my $i = 0; $i < $count; $i++) {
1506 $selected{$i} = !$selected{$i}
1507 if ($list[$i]->[1] =~ /^(open list)/i);
1508 }
1509 } elsif (lc($str) eq "s") {
1510 for (my $i = 0; $i < $count; $i++) {
1511 $selected{$i} = !$selected{$i}
1512 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1513 }
1514 }
1515 } elsif ($sel eq "a") {
1516 if ($val > 0 && $val <= $count) {
1517 $authored{$val - 1} = !$authored{$val - 1};
1518 } elsif ($str eq '*' || $str eq '^') {
1519 my $toggle = 0;
1520 $toggle = 1 if ($str eq '*');
1521 for (my $i = 0; $i < $count; $i++) {
1522 $authored{$i} = $toggle;
1523 }
1524 }
1525 } elsif ($sel eq "s") {
1526 if ($val > 0 && $val <= $count) {
1527 $signed{$val - 1} = !$signed{$val - 1};
1528 } elsif ($str eq '*' || $str eq '^') {
1529 my $toggle = 0;
1530 $toggle = 1 if ($str eq '*');
1531 for (my $i = 0; $i < $count; $i++) {
1532 $signed{$i} = $toggle;
1533 }
1534 }
1535 } elsif ($sel eq "o") {
1536 $print_options = 1;
1537 $redraw = 1;
1538 } elsif ($sel eq "g") {
1539 if ($str eq "f") {
1540 bool_invert(\$email_git_fallback);
1541 } else {
1542 bool_invert(\$email_git);
1543 }
1544 $rerun = 1;
1545 } elsif ($sel eq "b") {
1546 if ($str eq "s") {
1547 bool_invert(\$email_git_blame_signatures);
1548 } else {
1549 bool_invert(\$email_git_blame);
1550 }
1551 $rerun = 1;
1552 } elsif ($sel eq "c") {
1553 if ($val > 0) {
1554 $email_git_min_signatures = $val;
1555 $rerun = 1;
1556 }
1557 } elsif ($sel eq "x") {
1558 if ($val > 0) {
1559 $email_git_max_maintainers = $val;
1560 $rerun = 1;
1561 }
1562 } elsif ($sel eq "%") {
1563 if ($str ne "" && $val >= 0) {
1564 $email_git_min_percent = $val;
1565 $rerun = 1;
1566 }
1567 } elsif ($sel eq "d") {
1568 if (vcs_is_git()) {
1569 $email_git_since = $str;
1570 } elsif (vcs_is_hg()) {
1571 $email_hg_since = $str;
1572 }
1573 $rerun = 1;
1574 } elsif ($sel eq "t") {
1575 bool_invert(\$email_git_all_signature_types);
1576 $rerun = 1;
1577 } elsif ($sel eq "f") {
1578 bool_invert(\$file_emails);
1579 $rerun = 1;
1580 } elsif ($sel eq "r") {
1581 bool_invert(\$email_remove_duplicates);
1582 $rerun = 1;
1583 } elsif ($sel eq "m") {
1584 bool_invert(\$email_use_mailmap);
1585 read_mailmap();
1586 $rerun = 1;
1587 } elsif ($sel eq "k") {
1588 bool_invert(\$keywords);
1589 $rerun = 1;
1590 } elsif ($sel eq "p") {
1591 if ($str ne "" && $val >= 0) {
1592 $pattern_depth = $val;
1593 $rerun = 1;
1594 }
1595 } elsif ($sel eq "h" || $sel eq "?") {
1596 print STDERR <<EOT
1597
1598Interactive mode allows you to select the various maintainers, submitters,
1599commit signers and mailing lists that could be CC'd on a patch.
1600
1601Any *'d entry is selected.
1602
1603If you have git or hg installed, you can choose to summarize the commit
1604history of files in the patch. Also, each line of the current file can
1605be matched to its commit author and that commits signers with blame.
1606
1607Various knobs exist to control the length of time for active commit
1608tracking, the maximum number of commit authors and signers to add,
1609and such.
1610
1611Enter selections at the prompt until you are satisfied that the selected
1612maintainers are appropriate. You may enter multiple selections separated
1613by either commas or spaces.
1614
1615EOT
1616 } else {
1617 print STDERR "invalid option: '$nr'\n";
1618 $redraw = 0;
1619 }
1620 }
1621 if ($rerun) {
1622 print STDERR "git-blame can be very slow, please have patience..."
1623 if ($email_git_blame);
1624 goto &get_maintainers;
1625 }
1626 }
1627
1628 #drop not selected entries
1629 $count = 0;
1630 my @new_emailto = ();
1631 foreach my $entry (@list) {
1632 if ($selected{$count}) {
1633 push(@new_emailto, $list[$count]);
1634 }
1635 $count++;
1636 }
1637 return @new_emailto;
1638}
1639
1640sub bool_invert {
1641 my ($bool_ref) = @_;
1642
1643 if ($$bool_ref) {
1644 $$bool_ref = 0;
1645 } else {
1646 $$bool_ref = 1;
1647 }
1648}
1649
1650sub deduplicate_email {
1651 my ($email) = @_;
1652
1653 my $matched = 0;
1654 my ($name, $address) = parse_email($email);
1655 $email = format_email($name, $address, 1);
1656 $email = mailmap_email($email);
1657
1658 return $email if (!$email_remove_duplicates);
1659
1660 ($name, $address) = parse_email($email);
1661
1662 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1663 $name = $deduplicate_name_hash{lc($name)}->[0];
1664 $address = $deduplicate_name_hash{lc($name)}->[1];
1665 $matched = 1;
1666 } elsif ($deduplicate_address_hash{lc($address)}) {
1667 $name = $deduplicate_address_hash{lc($address)}->[0];
1668 $address = $deduplicate_address_hash{lc($address)}->[1];
1669 $matched = 1;
1670 }
1671 if (!$matched) {
1672 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1673 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1674 }
1675 $email = format_email($name, $address, 1);
1676 $email = mailmap_email($email);
1677 return $email;
1678}
1679
1680sub save_commits_by_author {
1681 my (@lines) = @_;
1682
1683 my @authors = ();
1684 my @commits = ();
1685 my @subjects = ();
1686
1687 foreach my $line (@lines) {
1688 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1689 my $author = $1;
1690 $author = deduplicate_email($author);
1691 push(@authors, $author);
1692 }
1693 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1694 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1695 }
1696
1697 for (my $i = 0; $i < @authors; $i++) {
1698 my $exists = 0;
1699 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1700 if (@{$ref}[0] eq $commits[$i] &&
1701 @{$ref}[1] eq $subjects[$i]) {
1702 $exists = 1;
1703 last;
1704 }
1705 }
1706 if (!$exists) {
1707 push(@{$commit_author_hash{$authors[$i]}},
1708 [ ($commits[$i], $subjects[$i]) ]);
1709 }
1710 }
1711}
1712
1713sub save_commits_by_signer {
1714 my (@lines) = @_;
1715
1716 my $commit = "";
1717 my $subject = "";
1718
1719 foreach my $line (@lines) {
1720 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1721 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1722 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1723 my @signatures = ($line);
1724 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1725 my @types = @$types_ref;
1726 my @signers = @$signers_ref;
1727
1728 my $type = $types[0];
1729 my $signer = $signers[0];
1730
1731 $signer = deduplicate_email($signer);
1732
1733 my $exists = 0;
1734 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1735 if (@{$ref}[0] eq $commit &&
1736 @{$ref}[1] eq $subject &&
1737 @{$ref}[2] eq $type) {
1738 $exists = 1;
1739 last;
1740 }
1741 }
1742 if (!$exists) {
1743 push(@{$commit_signer_hash{$signer}},
1744 [ ($commit, $subject, $type) ]);
1745 }
1746 }
1747 }
1748}
1749
1107sub vcs_assign { 1750sub vcs_assign {
1108 my ($role, $divisor, @lines) = @_; 1751 my ($role, $divisor, @lines) = @_;
1109 1752
@@ -1117,9 +1760,9 @@ sub vcs_assign {
1117 $divisor = 1; 1760 $divisor = 1;
1118 } 1761 }
1119 1762
1120 if ($email_remove_duplicates) { 1763 @lines = mailmap(@lines);
1121 @lines = mailmap(@lines); 1764
1122 } 1765 return if (@lines <= 0);
1123 1766
1124 @lines = sort(@lines); 1767 @lines = sort(@lines);
1125 1768
@@ -1152,12 +1795,18 @@ sub vcs_file_signoffs {
1152 my @signers = (); 1795 my @signers = ();
1153 my $commits; 1796 my $commits;
1154 1797
1155 return if (!vcs_exists()); 1798 $vcs_used = vcs_exists();
1799 return if (!$vcs_used);
1156 1800
1157 my $cmd = $VCS_cmds{"find_signers_cmd"}; 1801 my $cmd = $VCS_cmds{"find_signers_cmd"};
1158 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd 1802 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1159 1803
1160 ($commits, @signers) = vcs_find_signers($cmd); 1804 ($commits, @signers) = vcs_find_signers($cmd);
1805
1806 foreach my $signer (@signers) {
1807 $signer = deduplicate_email($signer);
1808 }
1809
1161 vcs_assign("commit_signer", $commits, @signers); 1810 vcs_assign("commit_signer", $commits, @signers);
1162} 1811}
1163 1812
@@ -1165,29 +1814,114 @@ sub vcs_file_blame {
1165 my ($file) = @_; 1814 my ($file) = @_;
1166 1815
1167 my @signers = (); 1816 my @signers = ();
1817 my @all_commits = ();
1168 my @commits = (); 1818 my @commits = ();
1169 my $total_commits; 1819 my $total_commits;
1820 my $total_lines;
1170 1821
1171 return if (!vcs_exists()); 1822 $vcs_used = vcs_exists();
1823 return if (!$vcs_used);
1172 1824
1173 @commits = vcs_blame($file); 1825 @all_commits = vcs_blame($file);
1174 @commits = uniq(@commits); 1826 @commits = uniq(@all_commits);
1175 $total_commits = @commits; 1827 $total_commits = @commits;
1828 $total_lines = @all_commits;
1176 1829
1177 foreach my $commit (@commits) { 1830 if ($email_git_blame_signatures) {
1178 my $commit_count; 1831 if (vcs_is_hg()) {
1179 my @commit_signers = (); 1832 my $commit_count;
1833 my @commit_signers = ();
1834 my $commit = join(" -r ", @commits);
1835 my $cmd;
1836
1837 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1838 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1839
1840 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1841
1842 push(@signers, @commit_signers);
1843 } else {
1844 foreach my $commit (@commits) {
1845 my $commit_count;
1846 my @commit_signers = ();
1847 my $cmd;
1180 1848
1181 my $cmd = $VCS_cmds{"find_commit_signers_cmd"}; 1849 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1182 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd 1850 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1183 1851
1184 ($commit_count, @commit_signers) = vcs_find_signers($cmd); 1852 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1185 push(@signers, @commit_signers); 1853
1854 push(@signers, @commit_signers);
1855 }
1856 }
1186 } 1857 }
1187 1858
1188 if ($from_filename) { 1859 if ($from_filename) {
1860 if ($output_rolestats) {
1861 my @blame_signers;
1862 if (vcs_is_hg()) {{ # Double brace for last exit
1863 my $commit_count;
1864 my @commit_signers = ();
1865 @commits = uniq(@commits);
1866 @commits = sort(@commits);
1867 my $commit = join(" -r ", @commits);
1868 my $cmd;
1869
1870 $cmd = $VCS_cmds{"find_commit_author_cmd"};
1871 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1872
1873 my @lines = ();
1874
1875 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1876
1877 if (!$email_git_penguin_chiefs) {
1878 @lines = grep(!/${penguin_chiefs}/i, @lines);
1879 }
1880
1881 last if !@lines;
1882
1883 my @authors = ();
1884 foreach my $line (@lines) {
1885 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1886 my $author = $1;
1887 $author = deduplicate_email($author);
1888 push(@authors, $author);
1889 }
1890 }
1891
1892 save_commits_by_author(@lines) if ($interactive);
1893 save_commits_by_signer(@lines) if ($interactive);
1894
1895 push(@signers, @authors);
1896 }}
1897 else {
1898 foreach my $commit (@commits) {
1899 my $i;
1900 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1901 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1902 my @author = vcs_find_author($cmd);
1903 next if !@author;
1904
1905 my $formatted_author = deduplicate_email($author[0]);
1906
1907 my $count = grep(/$commit/, @all_commits);
1908 for ($i = 0; $i < $count ; $i++) {
1909 push(@blame_signers, $formatted_author);
1910 }
1911 }
1912 }
1913 if (@blame_signers) {
1914 vcs_assign("authored lines", $total_lines, @blame_signers);
1915 }
1916 }
1917 foreach my $signer (@signers) {
1918 $signer = deduplicate_email($signer);
1919 }
1189 vcs_assign("commits", $total_commits, @signers); 1920 vcs_assign("commits", $total_commits, @signers);
1190 } else { 1921 } else {
1922 foreach my $signer (@signers) {
1923 $signer = deduplicate_email($signer);
1924 }
1191 vcs_assign("modified commits", $total_commits, @signers); 1925 vcs_assign("modified commits", $total_commits, @signers);
1192 } 1926 }
1193} 1927}