aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/get_maintainer.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/get_maintainer.pl')
-rwxr-xr-xscripts/get_maintainer.pl1208
1 files changed, 994 insertions, 214 deletions
diff --git a/scripts/get_maintainer.pl b/scripts/get_maintainer.pl
index b2281982f52f..d29a8d75cb22 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';
17 17
18use Getopt::Long qw(:config no_auto_abbrev); 18use Getopt::Long qw(:config no_auto_abbrev);
19 19
@@ -24,19 +24,23 @@ 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;
39my $output_rolestats = 0; 43my $output_rolestats = 1;
40my $scm = 0; 44my $scm = 0;
41my $web = 0; 45my $web = 0;
42my $subsystem = 0; 46my $subsystem = 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> };
@@ -325,6 +420,14 @@ foreach my $file (@ARGV) {
325 420
326 open(my $patch, "< $file") 421 open(my $patch, "< $file")
327 or die "$P: Can't open $file: $!\n"; 422 or die "$P: Can't open $file: $!\n";
423
424 # We can check arbitrary information before the patch
425 # like the commit message, mail headers, etc...
426 # This allows us to match arbitrary keywords against any part
427 # of a git format-patch generated file (subject tags, etc...)
428
429 my $patch_prefix = ""; #Parsing the intro
430
328 while (<$patch>) { 431 while (<$patch>) {
329 my $patch_line = $_; 432 my $patch_line = $_;
330 if (m/^\+\+\+\s+(\S+)/) { 433 if (m/^\+\+\+\s+(\S+)/) {
@@ -333,13 +436,14 @@ foreach my $file (@ARGV) {
333 $filename =~ s@\n@@; 436 $filename =~ s@\n@@;
334 $lastfile = $filename; 437 $lastfile = $filename;
335 push(@files, $filename); 438 push(@files, $filename);
439 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
336 } elsif (m/^\@\@ -(\d+),(\d+)/) { 440 } elsif (m/^\@\@ -(\d+),(\d+)/) {
337 if ($email_git_blame) { 441 if ($email_git_blame) {
338 push(@range, "$lastfile:$1:$2"); 442 push(@range, "$lastfile:$1:$2");
339 } 443 }
340 } elsif ($keywords) { 444 } elsif ($keywords) {
341 foreach my $line (keys %keyword_hash) { 445 foreach my $line (keys %keyword_hash) {
342 if ($patch_line =~ m/^[+-].*$keyword_hash{$line}/x) { 446 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
343 push(@keyword_tvi, $line); 447 push(@keyword_tvi, $line);
344 } 448 }
345 } 449 }
@@ -357,67 +461,163 @@ foreach my $file (@ARGV) {
357 461
358@file_emails = uniq(@file_emails); 462@file_emails = uniq(@file_emails);
359 463
464my %email_hash_name;
465my %email_hash_address;
360my @email_to = (); 466my @email_to = ();
467my %hash_list_to;
361my @list_to = (); 468my @list_to = ();
362my @scm = (); 469my @scm = ();
363my @web = (); 470my @web = ();
364my @subsystem = (); 471my @subsystem = ();
365my @status = (); 472my @status = ();
473my %deduplicate_name_hash = ();
474my %deduplicate_address_hash = ();
475my $signature_pattern;
366 476
367# Find responsible parties 477my @maintainers = get_maintainers();
368 478
369foreach my $file (@files) { 479if (@maintainers) {
480 @maintainers = merge_email(@maintainers);
481 output(@maintainers);
482}
370 483
371 my %hash; 484if ($scm) {
372 my $tvi = find_first_section(); 485 @scm = uniq(@scm);
373 while ($tvi < @typevalue) { 486 output(@scm);
374 my $start = find_starting_index($tvi); 487}
375 my $end = find_ending_index($tvi); 488
376 my $exclude = 0; 489if ($status) {
377 my $i; 490 @status = uniq(@status);
378 491 output(@status);
379 #Do not match excluded file patterns 492}
380 493
381 for ($i = $start; $i < $end; $i++) { 494if ($subsystem) {
382 my $line = $typevalue[$i]; 495 @subsystem = uniq(@subsystem);
383 if ($line =~ m/^(\C):\s*(.*)/) { 496 output(@subsystem);
384 my $type = $1; 497}
385 my $value = $2; 498
386 if ($type eq 'X') { 499if ($web) {
387 if (file_match_pattern($file, $value)) { 500 @web = uniq(@web);
388 $exclude = 1; 501 output(@web);
389 last; 502}
390 } 503
504exit($exit);
505
506sub range_is_maintained {
507 my ($start, $end) = @_;
508
509 for (my $i = $start; $i < $end; $i++) {
510 my $line = $typevalue[$i];
511 if ($line =~ m/^(\C):\s*(.*)/) {
512 my $type = $1;
513 my $value = $2;
514 if ($type eq 'S') {
515 if ($value =~ /(maintain|support)/i) {
516 return 1;
391 } 517 }
392 } 518 }
393 } 519 }
520 }
521 return 0;
522}
523
524sub range_has_maintainer {
525 my ($start, $end) = @_;
526
527 for (my $i = $start; $i < $end; $i++) {
528 my $line = $typevalue[$i];
529 if ($line =~ m/^(\C):\s*(.*)/) {
530 my $type = $1;
531 my $value = $2;
532 if ($type eq 'M') {
533 return 1;
534 }
535 }
536 }
537 return 0;
538}
539
540sub get_maintainers {
541 %email_hash_name = ();
542 %email_hash_address = ();
543 %commit_author_hash = ();
544 %commit_signer_hash = ();
545 @email_to = ();
546 %hash_list_to = ();
547 @list_to = ();
548 @scm = ();
549 @web = ();
550 @subsystem = ();
551 @status = ();
552 %deduplicate_name_hash = ();
553 %deduplicate_address_hash = ();
554 if ($email_git_all_signature_types) {
555 $signature_pattern = "(.+?)[Bb][Yy]:";
556 } else {
557 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
558 }
559
560 # Find responsible parties
561
562 my %exact_pattern_match_hash = ();
563
564 foreach my $file (@files) {
565
566 my %hash;
567 my $tvi = find_first_section();
568 while ($tvi < @typevalue) {
569 my $start = find_starting_index($tvi);
570 my $end = find_ending_index($tvi);
571 my $exclude = 0;
572 my $i;
573
574 #Do not match excluded file patterns
394 575
395 if (!$exclude) {
396 for ($i = $start; $i < $end; $i++) { 576 for ($i = $start; $i < $end; $i++) {
397 my $line = $typevalue[$i]; 577 my $line = $typevalue[$i];
398 if ($line =~ m/^(\C):\s*(.*)/) { 578 if ($line =~ m/^(\C):\s*(.*)/) {
399 my $type = $1; 579 my $type = $1;
400 my $value = $2; 580 my $value = $2;
401 if ($type eq 'F') { 581 if ($type eq 'X') {
402 if (file_match_pattern($file, $value)) { 582 if (file_match_pattern($file, $value)) {
403 my $value_pd = ($value =~ tr@/@@); 583 $exclude = 1;
404 my $file_pd = ($file =~ tr@/@@); 584 last;
405 $value_pd++ if (substr($value,-1,1) ne "/"); 585 }
406 if ($pattern_depth == 0 || 586 }
407 (($file_pd - $value_pd) < $pattern_depth)) { 587 }
408 $hash{$tvi} = $value_pd; 588 }
589
590 if (!$exclude) {
591 for ($i = $start; $i < $end; $i++) {
592 my $line = $typevalue[$i];
593 if ($line =~ m/^(\C):\s*(.*)/) {
594 my $type = $1;
595 my $value = $2;
596 if ($type eq 'F') {
597 if (file_match_pattern($file, $value)) {
598 my $value_pd = ($value =~ tr@/@@);
599 my $file_pd = ($file =~ tr@/@@);
600 $value_pd++ if (substr($value,-1,1) ne "/");
601 $value_pd = -1 if ($value =~ /^\.\*/);
602 if ($value_pd >= $file_pd &&
603 range_is_maintained($start, $end) &&
604 range_has_maintainer($start, $end)) {
605 $exact_pattern_match_hash{$file} = 1;
606 }
607 if ($pattern_depth == 0 ||
608 (($file_pd - $value_pd) < $pattern_depth)) {
609 $hash{$tvi} = $value_pd;
610 }
409 } 611 }
410 } 612 }
411 } 613 }
412 } 614 }
413 } 615 }
616 $tvi = $end + 1;
414 } 617 }
415 618
416 $tvi = $end + 1; 619 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
417 } 620 add_categories($line);
418
419 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
420 add_categories($line);
421 if ($sections) { 621 if ($sections) {
422 my $i; 622 my $i;
423 my $start = find_starting_index($line); 623 my $start = find_starting_index($line);
@@ -435,80 +635,71 @@ foreach my $file (@files) {
435 } 635 }
436 print("\n"); 636 print("\n");
437 } 637 }
638 }
438 } 639 }
439 640
440 if ($email && $email_git) { 641 if ($keywords) {
441 vcs_file_signoffs($file); 642 @keyword_tvi = sort_and_uniq(@keyword_tvi);
643 foreach my $line (@keyword_tvi) {
644 add_categories($line);
645 }
442 } 646 }
443 647
444 if ($email && $email_git_blame) { 648 foreach my $email (@email_to, @list_to) {
445 vcs_file_blame($file); 649 $email->[0] = deduplicate_email($email->[0]);
446 } 650 }
447}
448 651
449if ($keywords) { 652 foreach my $file (@files) {
450 @keyword_tvi = sort_and_uniq(@keyword_tvi); 653 if ($email &&
451 foreach my $line (@keyword_tvi) { 654 ($email_git || ($email_git_fallback &&
452 add_categories($line); 655 !$exact_pattern_match_hash{$file}))) {
656 vcs_file_signoffs($file);
657 }
658 if ($email && $email_git_blame) {
659 vcs_file_blame($file);
660 }
453 } 661 }
454}
455 662
456if ($email) { 663 if ($email) {
457 foreach my $chief (@penguin_chief) { 664 foreach my $chief (@penguin_chief) {
458 if ($chief =~ m/^(.*):(.*)/) { 665 if ($chief =~ m/^(.*):(.*)/) {
459 my $email_address; 666 my $email_address;
460 667
461 $email_address = format_email($1, $2, $email_usename); 668 $email_address = format_email($1, $2, $email_usename);
462 if ($email_git_penguin_chiefs) { 669 if ($email_git_penguin_chiefs) {
463 push(@email_to, [$email_address, 'chief penguin']); 670 push(@email_to, [$email_address, 'chief penguin']);
464 } else { 671 } else {
465 @email_to = grep($_->[0] !~ /${email_address}/, @email_to); 672 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
673 }
466 } 674 }
467 } 675 }
468 }
469 676
470 foreach my $email (@file_emails) { 677 foreach my $email (@file_emails) {
471 my ($name, $address) = parse_email($email); 678 my ($name, $address) = parse_email($email);
472 679
473 my $tmp_email = format_email($name, $address, $email_usename); 680 my $tmp_email = format_email($name, $address, $email_usename);
474 push_email_address($tmp_email, ''); 681 push_email_address($tmp_email, '');
475 add_role($tmp_email, 'in file'); 682 add_role($tmp_email, 'in file');
683 }
476 } 684 }
477}
478 685
479if ($email || $email_list) {
480 my @to = (); 686 my @to = ();
481 if ($email) { 687 if ($email || $email_list) {
482 @to = (@to, @email_to); 688 if ($email) {
483 } 689 @to = (@to, @email_to);
484 if ($email_list) { 690 }
485 @to = (@to, @list_to); 691 if ($email_list) {
692 @to = (@to, @list_to);
693 }
486 } 694 }
487 output(merge_email(@to));
488}
489 695
490if ($scm) { 696 if ($interactive) {
491 @scm = uniq(@scm); 697 @to = interactive_get_maintainers(\@to);
492 output(@scm); 698 }
493}
494
495if ($status) {
496 @status = uniq(@status);
497 output(@status);
498}
499
500if ($subsystem) {
501 @subsystem = uniq(@subsystem);
502 output(@subsystem);
503}
504 699
505if ($web) { 700 return @to;
506 @web = uniq(@web);
507 output(@web);
508} 701}
509 702
510exit($exit);
511
512sub file_match_pattern { 703sub file_match_pattern {
513 my ($file, $pattern) = @_; 704 my ($file, $pattern) = @_;
514 if (substr($pattern, -1) eq "/") { 705 if (substr($pattern, -1) eq "/") {
@@ -537,7 +728,8 @@ MAINTAINER field selection options:
537 --email => print email address(es) if any 728 --email => print email address(es) if any
538 --git => include recent git \*-by: signers 729 --git => include recent git \*-by: signers
539 --git-all-signature-types => include signers regardless of signature type 730 --git-all-signature-types => include signers regardless of signature type
540 or use only ${signaturePattern} signers (default: $email_git_all_signature_types) 731 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
732 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
541 --git-chief-penguins => include ${penguin_chiefs} 733 --git-chief-penguins => include ${penguin_chiefs}
542 --git-min-signatures => number of signatures required (default: $email_git_min_signatures) 734 --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) 735 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
@@ -545,6 +737,7 @@ MAINTAINER field selection options:
545 --git-blame => use git blame to find modified commits for patch or file 737 --git-blame => use git blame to find modified commits for patch or file
546 --git-since => git history to use (default: $email_git_since) 738 --git-since => git history to use (default: $email_git_since)
547 --hg-since => hg history to use (default: $email_hg_since) 739 --hg-since => hg history to use (default: $email_hg_since)
740 --interactive => display a menu (mostly useful if used with the --git option)
548 --m => include maintainer(s) if any 741 --m => include maintainer(s) if any
549 --n => include name 'Full Name <addr\@domain.tld>' 742 --n => include name 'Full Name <addr\@domain.tld>'
550 --l => include list(s) if any 743 --l => include list(s) if any
@@ -565,13 +758,15 @@ Output type options:
565 758
566Other options: 759Other options:
567 --pattern-depth => Number of pattern directory traversals (default: 0 (all)) 760 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
568 --keywords => scan patch for keywords (default: 1 (on)) 761 --keywords => scan patch for keywords (default: $keywords)
569 --sections => print the entire subsystem sections with pattern matches 762 --sections => print all of the subsystem sections with pattern matches
763 --mailmap => use .mailmap file (default: $email_use_mailmap)
570 --version => show version 764 --version => show version
571 --help => show this help information 765 --help => show this help information
572 766
573Default options: 767Default options:
574 [--email --git --m --n --l --multiline --pattern-depth=0 --remove-duplicates] 768 [--email --nogit --git-fallback --m --n --l --multiline -pattern-depth=0
769 --remove-duplicates --rolestats]
575 770
576Notes: 771Notes:
577 Using "-f directory" may give unexpected results: 772 Using "-f directory" may give unexpected results:
@@ -606,30 +801,30 @@ EOT
606} 801}
607 802
608sub top_of_kernel_tree { 803sub top_of_kernel_tree {
609 my ($lk_path) = @_; 804 my ($lk_path) = @_;
610 805
611 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") { 806 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
612 $lk_path .= "/"; 807 $lk_path .= "/";
613 } 808 }
614 if ( (-f "${lk_path}COPYING") 809 if ( (-f "${lk_path}COPYING")
615 && (-f "${lk_path}CREDITS") 810 && (-f "${lk_path}CREDITS")
616 && (-f "${lk_path}Kbuild") 811 && (-f "${lk_path}Kbuild")
617 && (-f "${lk_path}MAINTAINERS") 812 && (-f "${lk_path}MAINTAINERS")
618 && (-f "${lk_path}Makefile") 813 && (-f "${lk_path}Makefile")
619 && (-f "${lk_path}README") 814 && (-f "${lk_path}README")
620 && (-d "${lk_path}Documentation") 815 && (-d "${lk_path}Documentation")
621 && (-d "${lk_path}arch") 816 && (-d "${lk_path}arch")
622 && (-d "${lk_path}include") 817 && (-d "${lk_path}include")
623 && (-d "${lk_path}drivers") 818 && (-d "${lk_path}drivers")
624 && (-d "${lk_path}fs") 819 && (-d "${lk_path}fs")
625 && (-d "${lk_path}init") 820 && (-d "${lk_path}init")
626 && (-d "${lk_path}ipc") 821 && (-d "${lk_path}ipc")
627 && (-d "${lk_path}kernel") 822 && (-d "${lk_path}kernel")
628 && (-d "${lk_path}lib") 823 && (-d "${lk_path}lib")
629 && (-d "${lk_path}scripts")) { 824 && (-d "${lk_path}scripts")) {
630 return 1; 825 return 1;
631 } 826 }
632 return 0; 827 return 0;
633} 828}
634 829
635sub parse_email { 830sub parse_email {
@@ -821,11 +1016,19 @@ sub add_categories {
821 } 1016 }
822 if ($list_additional =~ m/subscribers-only/) { 1017 if ($list_additional =~ m/subscribers-only/) {
823 if ($email_subscriber_list) { 1018 if ($email_subscriber_list) {
824 push(@list_to, [$list_address, "subscriber list${list_role}"]); 1019 if (!$hash_list_to{lc($list_address)}) {
1020 $hash_list_to{lc($list_address)} = 1;
1021 push(@list_to, [$list_address,
1022 "subscriber list${list_role}"]);
1023 }
825 } 1024 }
826 } else { 1025 } else {
827 if ($email_list) { 1026 if ($email_list) {
828 push(@list_to, [$list_address, "open list${list_role}"]); 1027 if (!$hash_list_to{lc($list_address)}) {
1028 $hash_list_to{lc($list_address)} = 1;
1029 push(@list_to, [$list_address,
1030 "open list${list_role}"]);
1031 }
829 } 1032 }
830 } 1033 }
831 } elsif ($ptype eq "M") { 1034 } elsif ($ptype eq "M") {
@@ -856,15 +1059,12 @@ sub add_categories {
856 } 1059 }
857} 1060}
858 1061
859my %email_hash_name;
860my %email_hash_address;
861
862sub email_inuse { 1062sub email_inuse {
863 my ($name, $address) = @_; 1063 my ($name, $address) = @_;
864 1064
865 return 1 if (($name eq "") && ($address eq "")); 1065 return 1 if (($name eq "") && ($address eq ""));
866 return 1 if (($name ne "") && exists($email_hash_name{$name})); 1066 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
867 return 1 if (($address ne "") && exists($email_hash_address{$address})); 1067 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
868 1068
869 return 0; 1069 return 0;
870} 1070}
@@ -882,8 +1082,8 @@ sub push_email_address {
882 push(@email_to, [format_email($name, $address, $email_usename), $role]); 1082 push(@email_to, [format_email($name, $address, $email_usename), $role]);
883 } elsif (!email_inuse($name, $address)) { 1083 } elsif (!email_inuse($name, $address)) {
884 push(@email_to, [format_email($name, $address, $email_usename), $role]); 1084 push(@email_to, [format_email($name, $address, $email_usename), $role]);
885 $email_hash_name{$name}++; 1085 $email_hash_name{lc($name)}++ if ($name ne "");
886 $email_hash_address{$address}++; 1086 $email_hash_address{lc($address)}++;
887 } 1087 }
888 1088
889 return 1; 1089 return 1;
@@ -952,30 +1152,69 @@ sub which {
952 return ""; 1152 return "";
953} 1153}
954 1154
955sub mailmap { 1155sub which_conf {
956 my (@lines) = @_; 1156 my ($conf) = @_;
957 my %hash;
958 1157
959 foreach my $line (@lines) { 1158 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
960 my ($name, $address) = parse_email($line); 1159 if (-e "$path/$conf") {
961 if (!exists($hash{$name})) { 1160 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 } 1161 }
967 if (exists($mailmap{$name})) { 1162 }
968 my $obj = $mailmap{$name}; 1163
969 foreach my $map_address (@$obj) { 1164 return "";
970 if (($map_address eq $address) && 1165}
971 ($map_address ne $hash{$name})) { 1166
972 $line = format_email($name, $hash{$name}, $email_usename); 1167sub mailmap_email {
973 } 1168 my ($line) = @_;
974 } 1169
1170 my ($name, $address) = parse_email($line);
1171 my $email = format_email($name, $address, 1);
1172 my $real_name = $name;
1173 my $real_address = $address;
1174
1175 if (exists $mailmap->{names}->{$email} ||
1176 exists $mailmap->{addresses}->{$email}) {
1177 if (exists $mailmap->{names}->{$email}) {
1178 $real_name = $mailmap->{names}->{$email};
1179 }
1180 if (exists $mailmap->{addresses}->{$email}) {
1181 $real_address = $mailmap->{addresses}->{$email};
1182 }
1183 } else {
1184 if (exists $mailmap->{names}->{$address}) {
1185 $real_name = $mailmap->{names}->{$address};
1186 }
1187 if (exists $mailmap->{addresses}->{$address}) {
1188 $real_address = $mailmap->{addresses}->{$address};
975 } 1189 }
976 } 1190 }
1191 return format_email($real_name, $real_address, 1);
1192}
977 1193
978 return @lines; 1194sub mailmap {
1195 my (@addresses) = @_;
1196
1197 my @mapped_emails = ();
1198 foreach my $line (@addresses) {
1199 push(@mapped_emails, mailmap_email($line));
1200 }
1201 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1202 return @mapped_emails;
1203}
1204
1205sub merge_by_realname {
1206 my %address_map;
1207 my (@emails) = @_;
1208
1209 foreach my $email (@emails) {
1210 my ($name, $address) = parse_email($email);
1211 if (exists $address_map{$name}) {
1212 $address = $address_map{$name};
1213 $email = format_email($name, $address, 1);
1214 } else {
1215 $address_map{$name} = $address;
1216 }
1217 }
979} 1218}
980 1219
981sub git_execute_cmd { 1220sub git_execute_cmd {
@@ -999,10 +1238,30 @@ sub hg_execute_cmd {
999 return @lines; 1238 return @lines;
1000} 1239}
1001 1240
1241sub extract_formatted_signatures {
1242 my (@signature_lines) = @_;
1243
1244 my @type = @signature_lines;
1245
1246 s/\s*(.*):.*/$1/ for (@type);
1247
1248 # cut -f2- -d":"
1249 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1250
1251## Reformat email addresses (with names) to avoid badly written signatures
1252
1253 foreach my $signer (@signature_lines) {
1254 $signer = deduplicate_email($signer);
1255 }
1256
1257 return (\@type, \@signature_lines);
1258}
1259
1002sub vcs_find_signers { 1260sub vcs_find_signers {
1003 my ($cmd) = @_; 1261 my ($cmd) = @_;
1004 my @lines = ();
1005 my $commits; 1262 my $commits;
1263 my @lines = ();
1264 my @signatures = ();
1006 1265
1007 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd); 1266 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1008 1267
@@ -1010,21 +1269,48 @@ sub vcs_find_signers {
1010 1269
1011 $commits = grep(/$pattern/, @lines); # of commits 1270 $commits = grep(/$pattern/, @lines); # of commits
1012 1271
1013 @lines = grep(/^[ \t]*${signaturePattern}.*\@.*$/, @lines); 1272 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1273
1274 return (0, @signatures) if !@signatures;
1275
1276 save_commits_by_author(@lines) if ($interactive);
1277 save_commits_by_signer(@lines) if ($interactive);
1278
1279 if (!$email_git_penguin_chiefs) {
1280 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1281 }
1282
1283 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1284
1285 return ($commits, @$signers_ref);
1286}
1287
1288sub vcs_find_author {
1289 my ($cmd) = @_;
1290 my @lines = ();
1291
1292 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1293
1014 if (!$email_git_penguin_chiefs) { 1294 if (!$email_git_penguin_chiefs) {
1015 @lines = grep(!/${penguin_chiefs}/i, @lines); 1295 @lines = grep(!/${penguin_chiefs}/i, @lines);
1016 } 1296 }
1017 # cut -f2- -d":"
1018 s/.*:\s*(.+)\s*/$1/ for (@lines);
1019 1297
1020## Reformat email addresses (with names) to avoid badly written signatures 1298 return @lines if !@lines;
1021 1299
1300 my @authors = ();
1022 foreach my $line (@lines) { 1301 foreach my $line (@lines) {
1023 my ($name, $address) = parse_email($line); 1302 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1024 $line = format_email($name, $address, 1); 1303 my $author = $1;
1304 my ($name, $address) = parse_email($author);
1305 $author = format_email($name, $address, 1);
1306 push(@authors, $author);
1307 }
1025 } 1308 }
1026 1309
1027 return ($commits, @lines); 1310 save_commits_by_author(@lines) if ($interactive);
1311 save_commits_by_signer(@lines) if ($interactive);
1312
1313 return @authors;
1028} 1314}
1029 1315
1030sub vcs_save_commits { 1316sub vcs_save_commits {
@@ -1084,6 +1370,10 @@ sub vcs_blame {
1084 @commits = vcs_save_commits($cmd); 1370 @commits = vcs_save_commits($cmd);
1085 } 1371 }
1086 1372
1373 foreach my $commit (@commits) {
1374 $commit =~ s/^\^//g;
1375 }
1376
1087 return @commits; 1377 return @commits;
1088} 1378}
1089 1379
@@ -1092,7 +1382,7 @@ sub vcs_exists {
1092 %VCS_cmds = %VCS_cmds_git; 1382 %VCS_cmds = %VCS_cmds_git;
1093 return 1 if eval $VCS_cmds{"available"}; 1383 return 1 if eval $VCS_cmds{"available"};
1094 %VCS_cmds = %VCS_cmds_hg; 1384 %VCS_cmds = %VCS_cmds_hg;
1095 return 1 if eval $VCS_cmds{"available"}; 1385 return 2 if eval $VCS_cmds{"available"};
1096 %VCS_cmds = (); 1386 %VCS_cmds = ();
1097 if (!$printed_novcs) { 1387 if (!$printed_novcs) {
1098 warn("$P: No supported VCS found. Add --nogit to options?\n"); 1388 warn("$P: No supported VCS found. Add --nogit to options?\n");
@@ -1104,6 +1394,405 @@ sub vcs_exists {
1104 return 0; 1394 return 0;
1105} 1395}
1106 1396
1397sub vcs_is_git {
1398 vcs_exists();
1399 return $vcs_used == 1;
1400}
1401
1402sub vcs_is_hg {
1403 return $vcs_used == 2;
1404}
1405
1406sub interactive_get_maintainers {
1407 my ($list_ref) = @_;
1408 my @list = @$list_ref;
1409
1410 vcs_exists();
1411
1412 my %selected;
1413 my %authored;
1414 my %signed;
1415 my $count = 0;
1416 my $maintained = 0;
1417 foreach my $entry (@list) {
1418 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1419 $selected{$count} = 1;
1420 $authored{$count} = 0;
1421 $signed{$count} = 0;
1422 $count++;
1423 }
1424
1425 #menu loop
1426 my $done = 0;
1427 my $print_options = 0;
1428 my $redraw = 1;
1429 while (!$done) {
1430 $count = 0;
1431 if ($redraw) {
1432 printf STDERR "\n%1s %2s %-65s",
1433 "*", "#", "email/list and role:stats";
1434 if ($email_git ||
1435 ($email_git_fallback && !$maintained) ||
1436 $email_git_blame) {
1437 print STDERR "auth sign";
1438 }
1439 print STDERR "\n";
1440 foreach my $entry (@list) {
1441 my $email = $entry->[0];
1442 my $role = $entry->[1];
1443 my $sel = "";
1444 $sel = "*" if ($selected{$count});
1445 my $commit_author = $commit_author_hash{$email};
1446 my $commit_signer = $commit_signer_hash{$email};
1447 my $authored = 0;
1448 my $signed = 0;
1449 $authored++ for (@{$commit_author});
1450 $signed++ for (@{$commit_signer});
1451 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1452 printf STDERR "%4d %4d", $authored, $signed
1453 if ($authored > 0 || $signed > 0);
1454 printf STDERR "\n %s\n", $role;
1455 if ($authored{$count}) {
1456 my $commit_author = $commit_author_hash{$email};
1457 foreach my $ref (@{$commit_author}) {
1458 print STDERR " Author: @{$ref}[1]\n";
1459 }
1460 }
1461 if ($signed{$count}) {
1462 my $commit_signer = $commit_signer_hash{$email};
1463 foreach my $ref (@{$commit_signer}) {
1464 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1465 }
1466 }
1467
1468 $count++;
1469 }
1470 }
1471 my $date_ref = \$email_git_since;
1472 $date_ref = \$email_hg_since if (vcs_is_hg());
1473 if ($print_options) {
1474 $print_options = 0;
1475 if (vcs_exists()) {
1476 print STDERR <<EOT
1477
1478Version Control options:
1479g use git history [$email_git]
1480gf use git-fallback [$email_git_fallback]
1481b use git blame [$email_git_blame]
1482bs use blame signatures [$email_git_blame_signatures]
1483c# minimum commits [$email_git_min_signatures]
1484%# min percent [$email_git_min_percent]
1485d# history to use [$$date_ref]
1486x# max maintainers [$email_git_max_maintainers]
1487t all signature types [$email_git_all_signature_types]
1488m use .mailmap [$email_use_mailmap]
1489EOT
1490 }
1491 print STDERR <<EOT
1492
1493Additional options:
14940 toggle all
1495tm toggle maintainers
1496tg toggle git entries
1497tl toggle open list entries
1498ts toggle subscriber list entries
1499f emails in file [$file_emails]
1500k keywords in file [$keywords]
1501r remove duplicates [$email_remove_duplicates]
1502p# pattern match depth [$pattern_depth]
1503EOT
1504 }
1505 print STDERR
1506"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1507
1508 my $input = <STDIN>;
1509 chomp($input);
1510
1511 $redraw = 1;
1512 my $rerun = 0;
1513 my @wish = split(/[, ]+/, $input);
1514 foreach my $nr (@wish) {
1515 $nr = lc($nr);
1516 my $sel = substr($nr, 0, 1);
1517 my $str = substr($nr, 1);
1518 my $val = 0;
1519 $val = $1 if $str =~ /^(\d+)$/;
1520
1521 if ($sel eq "y") {
1522 $interactive = 0;
1523 $done = 1;
1524 $output_rolestats = 0;
1525 $output_roles = 0;
1526 last;
1527 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1528 $selected{$nr - 1} = !$selected{$nr - 1};
1529 } elsif ($sel eq "*" || $sel eq '^') {
1530 my $toggle = 0;
1531 $toggle = 1 if ($sel eq '*');
1532 for (my $i = 0; $i < $count; $i++) {
1533 $selected{$i} = $toggle;
1534 }
1535 } elsif ($sel eq "0") {
1536 for (my $i = 0; $i < $count; $i++) {
1537 $selected{$i} = !$selected{$i};
1538 }
1539 } elsif ($sel eq "t") {
1540 if (lc($str) eq "m") {
1541 for (my $i = 0; $i < $count; $i++) {
1542 $selected{$i} = !$selected{$i}
1543 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1544 }
1545 } elsif (lc($str) eq "g") {
1546 for (my $i = 0; $i < $count; $i++) {
1547 $selected{$i} = !$selected{$i}
1548 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1549 }
1550 } elsif (lc($str) eq "l") {
1551 for (my $i = 0; $i < $count; $i++) {
1552 $selected{$i} = !$selected{$i}
1553 if ($list[$i]->[1] =~ /^(open list)/i);
1554 }
1555 } elsif (lc($str) eq "s") {
1556 for (my $i = 0; $i < $count; $i++) {
1557 $selected{$i} = !$selected{$i}
1558 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1559 }
1560 }
1561 } elsif ($sel eq "a") {
1562 if ($val > 0 && $val <= $count) {
1563 $authored{$val - 1} = !$authored{$val - 1};
1564 } elsif ($str eq '*' || $str eq '^') {
1565 my $toggle = 0;
1566 $toggle = 1 if ($str eq '*');
1567 for (my $i = 0; $i < $count; $i++) {
1568 $authored{$i} = $toggle;
1569 }
1570 }
1571 } elsif ($sel eq "s") {
1572 if ($val > 0 && $val <= $count) {
1573 $signed{$val - 1} = !$signed{$val - 1};
1574 } elsif ($str eq '*' || $str eq '^') {
1575 my $toggle = 0;
1576 $toggle = 1 if ($str eq '*');
1577 for (my $i = 0; $i < $count; $i++) {
1578 $signed{$i} = $toggle;
1579 }
1580 }
1581 } elsif ($sel eq "o") {
1582 $print_options = 1;
1583 $redraw = 1;
1584 } elsif ($sel eq "g") {
1585 if ($str eq "f") {
1586 bool_invert(\$email_git_fallback);
1587 } else {
1588 bool_invert(\$email_git);
1589 }
1590 $rerun = 1;
1591 } elsif ($sel eq "b") {
1592 if ($str eq "s") {
1593 bool_invert(\$email_git_blame_signatures);
1594 } else {
1595 bool_invert(\$email_git_blame);
1596 }
1597 $rerun = 1;
1598 } elsif ($sel eq "c") {
1599 if ($val > 0) {
1600 $email_git_min_signatures = $val;
1601 $rerun = 1;
1602 }
1603 } elsif ($sel eq "x") {
1604 if ($val > 0) {
1605 $email_git_max_maintainers = $val;
1606 $rerun = 1;
1607 }
1608 } elsif ($sel eq "%") {
1609 if ($str ne "" && $val >= 0) {
1610 $email_git_min_percent = $val;
1611 $rerun = 1;
1612 }
1613 } elsif ($sel eq "d") {
1614 if (vcs_is_git()) {
1615 $email_git_since = $str;
1616 } elsif (vcs_is_hg()) {
1617 $email_hg_since = $str;
1618 }
1619 $rerun = 1;
1620 } elsif ($sel eq "t") {
1621 bool_invert(\$email_git_all_signature_types);
1622 $rerun = 1;
1623 } elsif ($sel eq "f") {
1624 bool_invert(\$file_emails);
1625 $rerun = 1;
1626 } elsif ($sel eq "r") {
1627 bool_invert(\$email_remove_duplicates);
1628 $rerun = 1;
1629 } elsif ($sel eq "m") {
1630 bool_invert(\$email_use_mailmap);
1631 read_mailmap();
1632 $rerun = 1;
1633 } elsif ($sel eq "k") {
1634 bool_invert(\$keywords);
1635 $rerun = 1;
1636 } elsif ($sel eq "p") {
1637 if ($str ne "" && $val >= 0) {
1638 $pattern_depth = $val;
1639 $rerun = 1;
1640 }
1641 } elsif ($sel eq "h" || $sel eq "?") {
1642 print STDERR <<EOT
1643
1644Interactive mode allows you to select the various maintainers, submitters,
1645commit signers and mailing lists that could be CC'd on a patch.
1646
1647Any *'d entry is selected.
1648
1649If you have git or hg installed, you can choose to summarize the commit
1650history of files in the patch. Also, each line of the current file can
1651be matched to its commit author and that commits signers with blame.
1652
1653Various knobs exist to control the length of time for active commit
1654tracking, the maximum number of commit authors and signers to add,
1655and such.
1656
1657Enter selections at the prompt until you are satisfied that the selected
1658maintainers are appropriate. You may enter multiple selections separated
1659by either commas or spaces.
1660
1661EOT
1662 } else {
1663 print STDERR "invalid option: '$nr'\n";
1664 $redraw = 0;
1665 }
1666 }
1667 if ($rerun) {
1668 print STDERR "git-blame can be very slow, please have patience..."
1669 if ($email_git_blame);
1670 goto &get_maintainers;
1671 }
1672 }
1673
1674 #drop not selected entries
1675 $count = 0;
1676 my @new_emailto = ();
1677 foreach my $entry (@list) {
1678 if ($selected{$count}) {
1679 push(@new_emailto, $list[$count]);
1680 }
1681 $count++;
1682 }
1683 return @new_emailto;
1684}
1685
1686sub bool_invert {
1687 my ($bool_ref) = @_;
1688
1689 if ($$bool_ref) {
1690 $$bool_ref = 0;
1691 } else {
1692 $$bool_ref = 1;
1693 }
1694}
1695
1696sub deduplicate_email {
1697 my ($email) = @_;
1698
1699 my $matched = 0;
1700 my ($name, $address) = parse_email($email);
1701 $email = format_email($name, $address, 1);
1702 $email = mailmap_email($email);
1703
1704 return $email if (!$email_remove_duplicates);
1705
1706 ($name, $address) = parse_email($email);
1707
1708 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
1709 $name = $deduplicate_name_hash{lc($name)}->[0];
1710 $address = $deduplicate_name_hash{lc($name)}->[1];
1711 $matched = 1;
1712 } elsif ($deduplicate_address_hash{lc($address)}) {
1713 $name = $deduplicate_address_hash{lc($address)}->[0];
1714 $address = $deduplicate_address_hash{lc($address)}->[1];
1715 $matched = 1;
1716 }
1717 if (!$matched) {
1718 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1719 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1720 }
1721 $email = format_email($name, $address, 1);
1722 $email = mailmap_email($email);
1723 return $email;
1724}
1725
1726sub save_commits_by_author {
1727 my (@lines) = @_;
1728
1729 my @authors = ();
1730 my @commits = ();
1731 my @subjects = ();
1732
1733 foreach my $line (@lines) {
1734 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1735 my $author = $1;
1736 $author = deduplicate_email($author);
1737 push(@authors, $author);
1738 }
1739 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1740 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1741 }
1742
1743 for (my $i = 0; $i < @authors; $i++) {
1744 my $exists = 0;
1745 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1746 if (@{$ref}[0] eq $commits[$i] &&
1747 @{$ref}[1] eq $subjects[$i]) {
1748 $exists = 1;
1749 last;
1750 }
1751 }
1752 if (!$exists) {
1753 push(@{$commit_author_hash{$authors[$i]}},
1754 [ ($commits[$i], $subjects[$i]) ]);
1755 }
1756 }
1757}
1758
1759sub save_commits_by_signer {
1760 my (@lines) = @_;
1761
1762 my $commit = "";
1763 my $subject = "";
1764
1765 foreach my $line (@lines) {
1766 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1767 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1768 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1769 my @signatures = ($line);
1770 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1771 my @types = @$types_ref;
1772 my @signers = @$signers_ref;
1773
1774 my $type = $types[0];
1775 my $signer = $signers[0];
1776
1777 $signer = deduplicate_email($signer);
1778
1779 my $exists = 0;
1780 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1781 if (@{$ref}[0] eq $commit &&
1782 @{$ref}[1] eq $subject &&
1783 @{$ref}[2] eq $type) {
1784 $exists = 1;
1785 last;
1786 }
1787 }
1788 if (!$exists) {
1789 push(@{$commit_signer_hash{$signer}},
1790 [ ($commit, $subject, $type) ]);
1791 }
1792 }
1793 }
1794}
1795
1107sub vcs_assign { 1796sub vcs_assign {
1108 my ($role, $divisor, @lines) = @_; 1797 my ($role, $divisor, @lines) = @_;
1109 1798
@@ -1117,9 +1806,9 @@ sub vcs_assign {
1117 $divisor = 1; 1806 $divisor = 1;
1118 } 1807 }
1119 1808
1120 if ($email_remove_duplicates) { 1809 @lines = mailmap(@lines);
1121 @lines = mailmap(@lines); 1810
1122 } 1811 return if (@lines <= 0);
1123 1812
1124 @lines = sort(@lines); 1813 @lines = sort(@lines);
1125 1814
@@ -1152,12 +1841,18 @@ sub vcs_file_signoffs {
1152 my @signers = (); 1841 my @signers = ();
1153 my $commits; 1842 my $commits;
1154 1843
1155 return if (!vcs_exists()); 1844 $vcs_used = vcs_exists();
1845 return if (!$vcs_used);
1156 1846
1157 my $cmd = $VCS_cmds{"find_signers_cmd"}; 1847 my $cmd = $VCS_cmds{"find_signers_cmd"};
1158 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd 1848 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1159 1849
1160 ($commits, @signers) = vcs_find_signers($cmd); 1850 ($commits, @signers) = vcs_find_signers($cmd);
1851
1852 foreach my $signer (@signers) {
1853 $signer = deduplicate_email($signer);
1854 }
1855
1161 vcs_assign("commit_signer", $commits, @signers); 1856 vcs_assign("commit_signer", $commits, @signers);
1162} 1857}
1163 1858
@@ -1165,29 +1860,114 @@ sub vcs_file_blame {
1165 my ($file) = @_; 1860 my ($file) = @_;
1166 1861
1167 my @signers = (); 1862 my @signers = ();
1863 my @all_commits = ();
1168 my @commits = (); 1864 my @commits = ();
1169 my $total_commits; 1865 my $total_commits;
1866 my $total_lines;
1170 1867
1171 return if (!vcs_exists()); 1868 $vcs_used = vcs_exists();
1869 return if (!$vcs_used);
1172 1870
1173 @commits = vcs_blame($file); 1871 @all_commits = vcs_blame($file);
1174 @commits = uniq(@commits); 1872 @commits = uniq(@all_commits);
1175 $total_commits = @commits; 1873 $total_commits = @commits;
1874 $total_lines = @all_commits;
1176 1875
1177 foreach my $commit (@commits) { 1876 if ($email_git_blame_signatures) {
1178 my $commit_count; 1877 if (vcs_is_hg()) {
1179 my @commit_signers = (); 1878 my $commit_count;
1879 my @commit_signers = ();
1880 my $commit = join(" -r ", @commits);
1881 my $cmd;
1882
1883 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1884 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1885
1886 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1887
1888 push(@signers, @commit_signers);
1889 } else {
1890 foreach my $commit (@commits) {
1891 my $commit_count;
1892 my @commit_signers = ();
1893 my $cmd;
1894
1895 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1896 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1180 1897
1181 my $cmd = $VCS_cmds{"find_commit_signers_cmd"}; 1898 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1182 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1183 1899
1184 ($commit_count, @commit_signers) = vcs_find_signers($cmd); 1900 push(@signers, @commit_signers);
1185 push(@signers, @commit_signers); 1901 }
1902 }
1186 } 1903 }
1187 1904
1188 if ($from_filename) { 1905 if ($from_filename) {
1906 if ($output_rolestats) {
1907 my @blame_signers;
1908 if (vcs_is_hg()) {{ # Double brace for last exit
1909 my $commit_count;
1910 my @commit_signers = ();
1911 @commits = uniq(@commits);
1912 @commits = sort(@commits);
1913 my $commit = join(" -r ", @commits);
1914 my $cmd;
1915
1916 $cmd = $VCS_cmds{"find_commit_author_cmd"};
1917 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1918
1919 my @lines = ();
1920
1921 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1922
1923 if (!$email_git_penguin_chiefs) {
1924 @lines = grep(!/${penguin_chiefs}/i, @lines);
1925 }
1926
1927 last if !@lines;
1928
1929 my @authors = ();
1930 foreach my $line (@lines) {
1931 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1932 my $author = $1;
1933 $author = deduplicate_email($author);
1934 push(@authors, $author);
1935 }
1936 }
1937
1938 save_commits_by_author(@lines) if ($interactive);
1939 save_commits_by_signer(@lines) if ($interactive);
1940
1941 push(@signers, @authors);
1942 }}
1943 else {
1944 foreach my $commit (@commits) {
1945 my $i;
1946 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1947 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1948 my @author = vcs_find_author($cmd);
1949 next if !@author;
1950
1951 my $formatted_author = deduplicate_email($author[0]);
1952
1953 my $count = grep(/$commit/, @all_commits);
1954 for ($i = 0; $i < $count ; $i++) {
1955 push(@blame_signers, $formatted_author);
1956 }
1957 }
1958 }
1959 if (@blame_signers) {
1960 vcs_assign("authored lines", $total_lines, @blame_signers);
1961 }
1962 }
1963 foreach my $signer (@signers) {
1964 $signer = deduplicate_email($signer);
1965 }
1189 vcs_assign("commits", $total_commits, @signers); 1966 vcs_assign("commits", $total_commits, @signers);
1190 } else { 1967 } else {
1968 foreach my $signer (@signers) {
1969 $signer = deduplicate_email($signer);
1970 }
1191 vcs_assign("modified commits", $total_commits, @signers); 1971 vcs_assign("modified commits", $total_commits, @signers);
1192 } 1972 }
1193} 1973}