aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/get_maintainer.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/get_maintainer.pl')
-rwxr-xr-xscripts/get_maintainer.pl421
1 files changed, 329 insertions, 92 deletions
diff --git a/scripts/get_maintainer.pl b/scripts/get_maintainer.pl
index 278a45bd45a5..cdb44b63342e 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.17'; 16my $V = '0.20';
17 17
18use Getopt::Long qw(:config no_auto_abbrev); 18use Getopt::Long qw(:config no_auto_abbrev);
19 19
@@ -29,6 +29,8 @@ my $email_git_min_signatures = 1;
29my $email_git_max_maintainers = 5; 29my $email_git_max_maintainers = 5;
30my $email_git_min_percent = 5; 30my $email_git_min_percent = 5;
31my $email_git_since = "1-year-ago"; 31my $email_git_since = "1-year-ago";
32my $email_git_blame = 0;
33my $email_remove_duplicates = 1;
32my $output_multiline = 1; 34my $output_multiline = 1;
33my $output_separator = ", "; 35my $output_separator = ", ";
34my $scm = 0; 36my $scm = 0;
@@ -36,6 +38,7 @@ my $web = 0;
36my $subsystem = 0; 38my $subsystem = 0;
37my $status = 0; 39my $status = 0;
38my $from_filename = 0; 40my $from_filename = 0;
41my $pattern_depth = 0;
39my $version = 0; 42my $version = 0;
40my $help = 0; 43my $help = 0;
41 44
@@ -68,6 +71,8 @@ if (!GetOptions(
68 'git-max-maintainers=i' => \$email_git_max_maintainers, 71 'git-max-maintainers=i' => \$email_git_max_maintainers,
69 'git-min-percent=i' => \$email_git_min_percent, 72 'git-min-percent=i' => \$email_git_min_percent,
70 'git-since=s' => \$email_git_since, 73 'git-since=s' => \$email_git_since,
74 'git-blame!' => \$email_git_blame,
75 'remove-duplicates!' => \$email_remove_duplicates,
71 'm!' => \$email_maintainer, 76 'm!' => \$email_maintainer,
72 'n!' => \$email_usename, 77 'n!' => \$email_usename,
73 'l!' => \$email_list, 78 'l!' => \$email_list,
@@ -78,6 +83,7 @@ if (!GetOptions(
78 'status!' => \$status, 83 'status!' => \$status,
79 'scm!' => \$scm, 84 'scm!' => \$scm,
80 'web!' => \$web, 85 'web!' => \$web,
86 'pattern-depth=i' => \$pattern_depth,
81 'f|file' => \$from_filename, 87 'f|file' => \$from_filename,
82 'v|version' => \$version, 88 'v|version' => \$version,
83 'h|help' => \$help, 89 'h|help' => \$help,
@@ -101,14 +107,19 @@ if ($#ARGV < 0) {
101 die "$P: argument missing: patchfile or -f file please\n"; 107 die "$P: argument missing: patchfile or -f file please\n";
102} 108}
103 109
110if ($output_separator ne ", ") {
111 $output_multiline = 0;
112}
113
104my $selections = $email + $scm + $status + $subsystem + $web; 114my $selections = $email + $scm + $status + $subsystem + $web;
105if ($selections == 0) { 115if ($selections == 0) {
106 usage(); 116 usage();
107 die "$P: Missing required option: email, scm, status, subsystem or web\n"; 117 die "$P: Missing required option: email, scm, status, subsystem or web\n";
108} 118}
109 119
110if ($email && ($email_maintainer + $email_list + $email_subscriber_list 120if ($email &&
111 + $email_git + $email_git_penguin_chiefs) == 0) { 121 ($email_maintainer + $email_list + $email_subscriber_list +
122 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
112 usage(); 123 usage();
113 die "$P: Please select at least 1 email option\n"; 124 die "$P: Please select at least 1 email option\n";
114} 125}
@@ -147,9 +158,36 @@ while (<MAINT>) {
147} 158}
148close(MAINT); 159close(MAINT);
149 160
161my %mailmap;
162
163if ($email_remove_duplicates) {
164 open(MAILMAP, "<${lk_path}.mailmap") || warn "$P: Can't open .mailmap\n";
165 while (<MAILMAP>) {
166 my $line = $_;
167
168 next if ($line =~ m/^\s*#/);
169 next if ($line =~ m/^\s*$/);
170
171 my ($name, $address) = parse_email($line);
172 $line = format_email($name, $address);
173
174 next if ($line =~ m/^\s*$/);
175
176 if (exists($mailmap{$name})) {
177 my $obj = $mailmap{$name};
178 push(@$obj, $address);
179 } else {
180 my @arr = ($address);
181 $mailmap{$name} = \@arr;
182 }
183 }
184 close(MAILMAP);
185}
186
150## use the filenames on the command line or find the filenames in the patchfiles 187## use the filenames on the command line or find the filenames in the patchfiles
151 188
152my @files = (); 189my @files = ();
190my @range = ();
153 191
154foreach my $file (@ARGV) { 192foreach my $file (@ARGV) {
155 ##if $file is a directory and it lacks a trailing slash, add one 193 ##if $file is a directory and it lacks a trailing slash, add one
@@ -162,13 +200,19 @@ foreach my $file (@ARGV) {
162 push(@files, $file); 200 push(@files, $file);
163 } else { 201 } else {
164 my $file_cnt = @files; 202 my $file_cnt = @files;
203 my $lastfile;
165 open(PATCH, "<$file") or die "$P: Can't open ${file}\n"; 204 open(PATCH, "<$file") or die "$P: Can't open ${file}\n";
166 while (<PATCH>) { 205 while (<PATCH>) {
167 if (m/^\+\+\+\s+(\S+)/) { 206 if (m/^\+\+\+\s+(\S+)/) {
168 my $filename = $1; 207 my $filename = $1;
169 $filename =~ s@^[^/]*/@@; 208 $filename =~ s@^[^/]*/@@;
170 $filename =~ s@\n@@; 209 $filename =~ s@\n@@;
210 $lastfile = $filename;
171 push(@files, $filename); 211 push(@files, $filename);
212 } elsif (m/^\@\@ -(\d+),(\d+)/) {
213 if ($email_git_blame) {
214 push(@range, "$lastfile:$1:$2");
215 }
172 } 216 }
173 } 217 }
174 close(PATCH); 218 close(PATCH);
@@ -201,6 +245,7 @@ foreach my $file (@files) {
201 if ($type eq 'X') { 245 if ($type eq 'X') {
202 if (file_match_pattern($file, $value)) { 246 if (file_match_pattern($file, $value)) {
203 $exclude = 1; 247 $exclude = 1;
248 last;
204 } 249 }
205 } 250 }
206 } 251 }
@@ -208,35 +253,45 @@ foreach my $file (@files) {
208 253
209 if (!$exclude) { 254 if (!$exclude) {
210 my $tvi = 0; 255 my $tvi = 0;
256 my %hash;
211 foreach my $line (@typevalue) { 257 foreach my $line (@typevalue) {
212 if ($line =~ m/^(\C):\s*(.*)/) { 258 if ($line =~ m/^(\C):\s*(.*)/) {
213 my $type = $1; 259 my $type = $1;
214 my $value = $2; 260 my $value = $2;
215 if ($type eq 'F') { 261 if ($type eq 'F') {
216 if (file_match_pattern($file, $value)) { 262 if (file_match_pattern($file, $value)) {
217 add_categories($tvi); 263 my $value_pd = ($value =~ tr@/@@);
264 my $file_pd = ($file =~ tr@/@@);
265 $value_pd++ if (substr($value,-1,1) ne "/");
266 if ($pattern_depth == 0 ||
267 (($file_pd - $value_pd) < $pattern_depth)) {
268 $hash{$tvi} = $value_pd;
269 }
218 } 270 }
219 } 271 }
220 } 272 }
221 $tvi++; 273 $tvi++;
222 } 274 }
275 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
276 add_categories($line);
277 }
223 } 278 }
224 279
225 if ($email && $email_git) { 280 if ($email && $email_git) {
226 recent_git_signoffs($file); 281 recent_git_signoffs($file);
227 } 282 }
228 283
284 if ($email && $email_git_blame) {
285 git_assign_blame($file);
286 }
229} 287}
230 288
231if ($email) { 289if ($email) {
232 foreach my $chief (@penguin_chief) { 290 foreach my $chief (@penguin_chief) {
233 if ($chief =~ m/^(.*):(.*)/) { 291 if ($chief =~ m/^(.*):(.*)/) {
234 my $email_address; 292 my $email_address;
235 if ($email_usename) { 293
236 $email_address = format_email($1, $2); 294 $email_address = format_email($1, $2);
237 } else {
238 $email_address = $2;
239 }
240 if ($email_git_penguin_chiefs) { 295 if ($email_git_penguin_chiefs) {
241 push(@email_to, $email_address); 296 push(@email_to, $email_address);
242 } else { 297 } else {
@@ -258,22 +313,22 @@ if ($email || $email_list) {
258} 313}
259 314
260if ($scm) { 315if ($scm) {
261 @scm = sort_and_uniq(@scm); 316 @scm = uniq(@scm);
262 output(@scm); 317 output(@scm);
263} 318}
264 319
265if ($status) { 320if ($status) {
266 @status = sort_and_uniq(@status); 321 @status = uniq(@status);
267 output(@status); 322 output(@status);
268} 323}
269 324
270if ($subsystem) { 325if ($subsystem) {
271 @subsystem = sort_and_uniq(@subsystem); 326 @subsystem = uniq(@subsystem);
272 output(@subsystem); 327 output(@subsystem);
273} 328}
274 329
275if ($web) { 330if ($web) {
276 @web = sort_and_uniq(@web); 331 @web = uniq(@web);
277 output(@web); 332 output(@web);
278} 333}
279 334
@@ -311,10 +366,12 @@ MAINTAINER field selection options:
311 --git-max-maintainers => maximum maintainers to add (default: 5) 366 --git-max-maintainers => maximum maintainers to add (default: 5)
312 --git-min-percent => minimum percentage of commits required (default: 5) 367 --git-min-percent => minimum percentage of commits required (default: 5)
313 --git-since => git history to use (default: 1-year-ago) 368 --git-since => git history to use (default: 1-year-ago)
369 --git-blame => use git blame to find modified commits for patch or file
314 --m => include maintainer(s) if any 370 --m => include maintainer(s) if any
315 --n => include name 'Full Name <addr\@domain.tld>' 371 --n => include name 'Full Name <addr\@domain.tld>'
316 --l => include list(s) if any 372 --l => include list(s) if any
317 --s => include subscriber only list(s) if any 373 --s => include subscriber only list(s) if any
374 --remove-duplicates => minimize duplicate email names/addresses
318 --scm => print SCM tree(s) if any 375 --scm => print SCM tree(s) if any
319 --status => print status if any 376 --status => print status if any
320 --subsystem => print subsystem name if any 377 --subsystem => print subsystem name if any
@@ -322,24 +379,28 @@ MAINTAINER field selection options:
322 379
323Output type options: 380Output type options:
324 --separator [, ] => separator for multiple entries on 1 line 381 --separator [, ] => separator for multiple entries on 1 line
382 using --separator also sets --nomultiline if --separator is not [, ]
325 --multiline => print 1 entry per line 383 --multiline => print 1 entry per line
326 384
327Default options:
328 [--email --git --m --n --l --multiline]
329
330Other options: 385Other options:
386 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
331 --version => show version 387 --version => show version
332 --help => show this help information 388 --help => show this help information
333 389
390Default options:
391 [--email --git --m --n --l --multiline --pattern-depth=0 --remove-duplicates]
392
334Notes: 393Notes:
335 Using "-f directory" may give unexpected results: 394 Using "-f directory" may give unexpected results:
336 395 Used with "--git", git signators for _all_ files in and below
337 Used with "--git", git signators for _all_ files in and below 396 directory are examined as git recurses directories.
338 directory are examined as git recurses directories. 397 Any specified X: (exclude) pattern matches are _not_ ignored.
339 Any specified X: (exclude) pattern matches are _not_ ignored. 398 Used with "--nogit", directory is used as a pattern match,
340 Used with "--nogit", directory is used as a pattern match, 399 no individual file within the directory or subdirectory
341 no individual file within the directory or subdirectory 400 is matched.
342 is matched. 401 Used with "--git-blame", does not iterate all files in directory
402 Using "--git-blame" is slow and may add old committers and authors
403 that are no longer active maintainers to the output.
343EOT 404EOT
344} 405}
345 406
@@ -370,30 +431,100 @@ sub top_of_kernel_tree {
370 return 0; 431 return 0;
371} 432}
372 433
373sub format_email { 434sub parse_email {
374 my ($name, $email) = @_; 435 my ($formatted_email) = @_;
436
437 my $name = "";
438 my $address = "";
439
440 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
441 $name = $1;
442 $address = $2;
443 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
444 $address = $1;
445 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
446 $address = $1;
447 }
375 448
376 $name =~ s/^\s+|\s+$//g; 449 $name =~ s/^\s+|\s+$//g;
377 $name =~ s/^\"|\"$//g; 450 $name =~ s/^\"|\"$//g;
378 $email =~ s/^\s+|\s+$//g; 451 $address =~ s/^\s+|\s+$//g;
452
453 if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars
454 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
455 $name = "\"$name\"";
456 }
457
458 return ($name, $address);
459}
460
461sub format_email {
462 my ($name, $address) = @_;
379 463
380 my $formatted_email = ""; 464 my $formatted_email;
465
466 $name =~ s/^\s+|\s+$//g;
467 $name =~ s/^\"|\"$//g;
468 $address =~ s/^\s+|\s+$//g;
381 469
382 if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars 470 if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars
383 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes 471 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
384 $formatted_email = "\"${name}\"\ \<${email}\>"; 472 $name = "\"$name\"";
473 }
474
475 if ($email_usename) {
476 if ("$name" eq "") {
477 $formatted_email = "$address";
478 } else {
479 $formatted_email = "$name <${address}>";
480 }
385 } else { 481 } else {
386 $formatted_email = "${name} \<${email}\>"; 482 $formatted_email = $address;
387 } 483 }
484
388 return $formatted_email; 485 return $formatted_email;
389} 486}
390 487
391sub add_categories { 488sub find_starting_index {
489
392 my ($index) = @_; 490 my ($index) = @_;
393 491
394 $index = $index - 1; 492 while ($index > 0) {
395 while ($index >= 0) {
396 my $tv = $typevalue[$index]; 493 my $tv = $typevalue[$index];
494 if (!($tv =~ m/^(\C):\s*(.*)/)) {
495 last;
496 }
497 $index--;
498 }
499
500 return $index;
501}
502
503sub find_ending_index {
504 my ($index) = @_;
505
506 while ($index < @typevalue) {
507 my $tv = $typevalue[$index];
508 if (!($tv =~ m/^(\C):\s*(.*)/)) {
509 last;
510 }
511 $index++;
512 }
513
514 return $index;
515}
516
517sub add_categories {
518 my ($index) = @_;
519
520 my $i;
521 my $start = find_starting_index($index);
522 my $end = find_ending_index($index);
523
524 push(@subsystem, $typevalue[$start]);
525
526 for ($i = $start + 1; $i < $end; $i++) {
527 my $tv = $typevalue[$i];
397 if ($tv =~ m/^(\C):\s*(.*)/) { 528 if ($tv =~ m/^(\C):\s*(.*)/) {
398 my $ptype = $1; 529 my $ptype = $1;
399 my $pvalue = $2; 530 my $pvalue = $2;
@@ -414,19 +545,19 @@ sub add_categories {
414 } 545 }
415 } 546 }
416 } elsif ($ptype eq "M") { 547 } elsif ($ptype eq "M") {
417 my $p_used = 0; 548 my ($name, $address) = parse_email($pvalue);
418 if ($index >= 0) { 549 if ($name eq "") {
419 my $tv = $typevalue[$index - 1]; 550 if ($i > 0) {
420 if ($tv =~ m/^(\C):\s*(.*)/) { 551 my $tv = $typevalue[$i - 1];
421 if ($1 eq "P") { 552 if ($tv =~ m/^(\C):\s*(.*)/) {
422 if ($email_usename) { 553 if ($1 eq "P") {
423 push_email_address(format_email($2, $pvalue)); 554 $name = $2;
424 $p_used = 1; 555 $pvalue = format_email($name, $address);
425 } 556 }
426 } 557 }
427 } 558 }
428 } 559 }
429 if (!$p_used) { 560 if ($email_maintainer) {
430 push_email_addresses($pvalue); 561 push_email_addresses($pvalue);
431 } 562 }
432 } elsif ($ptype eq "T") { 563 } elsif ($ptype eq "T") {
@@ -436,31 +567,41 @@ sub add_categories {
436 } elsif ($ptype eq "S") { 567 } elsif ($ptype eq "S") {
437 push(@status, $pvalue); 568 push(@status, $pvalue);
438 } 569 }
439
440 $index--;
441 } else {
442 push(@subsystem,$tv);
443 $index = -1;
444 } 570 }
445 } 571 }
446} 572}
447 573
574my %email_hash_name;
575my %email_hash_address;
576
577sub email_inuse {
578 my ($name, $address) = @_;
579
580 return 1 if (($name eq "") && ($address eq ""));
581 return 1 if (($name ne "") && exists($email_hash_name{$name}));
582 return 1 if (($address ne "") && exists($email_hash_address{$address}));
583
584 return 0;
585}
586
448sub push_email_address { 587sub push_email_address {
449 my ($email_address) = @_; 588 my ($line) = @_;
589
590 my ($name, $address) = parse_email($line);
450 591
451 my $email_name = ""; 592 if ($address eq "") {
452 if ($email_address =~ m/([^<]+)<(.*\@.*)>$/) { 593 return 0;
453 $email_name = $1;
454 $email_address = $2;
455 } 594 }
456 595
457 if ($email_maintainer) { 596 if (!$email_remove_duplicates) {
458 if ($email_usename && $email_name) { 597 push(@email_to, format_email($name, $address));
459 push(@email_to, format_email($email_name, $email_address)); 598 } elsif (!email_inuse($name, $address)) {
460 } else { 599 push(@email_to, format_email($name, $address));
461 push(@email_to, $email_address); 600 $email_hash_name{$name}++;
462 } 601 $email_hash_address{$address}++;
463 } 602 }
603
604 return 1;
464} 605}
465 606
466sub push_email_addresses { 607sub push_email_addresses {
@@ -476,7 +617,9 @@ sub push_email_addresses {
476 push_email_address($entry); 617 push_email_address($entry);
477 } 618 }
478 } else { 619 } else {
479 warn("Invalid MAINTAINERS address: '" . $address . "'\n"); 620 if (!push_email_address($address)) {
621 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
622 }
480 } 623 }
481} 624}
482 625
@@ -492,6 +635,32 @@ sub which {
492 return ""; 635 return "";
493} 636}
494 637
638sub mailmap {
639 my @lines = @_;
640 my %hash;
641
642 foreach my $line (@lines) {
643 my ($name, $address) = parse_email($line);
644 if (!exists($hash{$name})) {
645 $hash{$name} = $address;
646 } elsif ($address ne $hash{$name}) {
647 $address = $hash{$name};
648 $line = format_email($name, $address);
649 }
650 if (exists($mailmap{$name})) {
651 my $obj = $mailmap{$name};
652 foreach my $map_address (@$obj) {
653 if (($map_address eq $address) &&
654 ($map_address ne $hash{$name})) {
655 $line = format_email($name, $hash{$name});
656 }
657 }
658 }
659 }
660
661 return @lines;
662}
663
495sub recent_git_signoffs { 664sub recent_git_signoffs {
496 my ($file) = @_; 665 my ($file) = @_;
497 666
@@ -500,6 +669,7 @@ sub recent_git_signoffs {
500 my $output = ""; 669 my $output = "";
501 my $count = 0; 670 my $count = 0;
502 my @lines = (); 671 my @lines = ();
672 my %hash;
503 my $total_sign_offs; 673 my $total_sign_offs;
504 674
505 if (which("git") eq "") { 675 if (which("git") eq "") {
@@ -513,52 +683,119 @@ sub recent_git_signoffs {
513 } 683 }
514 684
515 $cmd = "git log --since=${email_git_since} -- ${file}"; 685 $cmd = "git log --since=${email_git_since} -- ${file}";
516 $cmd .= " | grep -Ei \"^[-_ a-z]+by:.*\\\@.*\$\"";
517 if (!$email_git_penguin_chiefs) {
518 $cmd .= " | grep -Ev \"${penguin_chiefs}\"";
519 }
520 $cmd .= " | cut -f2- -d\":\"";
521 $cmd .= " | sort | uniq -c | sort -rn";
522 686
523 $output = `${cmd}`; 687 $output = `${cmd}`;
524 $output =~ s/^\s*//gm; 688 $output =~ s/^\s*//gm;
525 689
526 @lines = split("\n", $output); 690 @lines = split("\n", $output);
527 691
528 $total_sign_offs = 0; 692 @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines);
693 if (!$email_git_penguin_chiefs) {
694 @lines = grep(!/${penguin_chiefs}/i, @lines);
695 }
696 # cut -f2- -d":"
697 s/.*:\s*(.+)\s*/$1/ for (@lines);
698
699 $total_sign_offs = @lines;
700
701 if ($email_remove_duplicates) {
702 @lines = mailmap(@lines);
703 }
704
705 @lines = sort(@lines);
706
707 # uniq -c
708 $hash{$_}++ for @lines;
709
710 # sort -rn
711 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
712 my $sign_offs = $hash{$line};
713 $count++;
714 last if ($sign_offs < $email_git_min_signatures ||
715 $count > $email_git_max_maintainers ||
716 $sign_offs * 100 / $total_sign_offs < $email_git_min_percent);
717 push_email_address($line);
718 }
719}
720
721sub save_commits {
722 my ($cmd, @commits) = @_;
723 my $output;
724 my @lines = ();
725
726 $output = `${cmd}`;
727
728 @lines = split("\n", $output);
529 foreach my $line (@lines) { 729 foreach my $line (@lines) {
530 if ($line =~ m/([0-9]+)\s+(.*)/) { 730 if ($line =~ m/^(\w+) /) {
531 $total_sign_offs += $1; 731 push (@commits, $1);
532 } else {
533 die("$P: Unexpected git output: ${line}\n");
534 } 732 }
535 } 733 }
734 return @commits;
735}
536 736
537 foreach my $line (@lines) { 737sub git_assign_blame {
538 if ($line =~ m/([0-9]+)\s+(.*)/) { 738 my ($file) = @_;
539 my $sign_offs = $1; 739
540 $line = $2; 740 my @lines = ();
541 $count++; 741 my @commits = ();
542 if ($sign_offs < $email_git_min_signatures || 742 my $cmd;
543 $count > $email_git_max_maintainers || 743 my $output;
544 $sign_offs * 100 / $total_sign_offs < $email_git_min_percent) { 744 my %hash;
545 last; 745 my $total_sign_offs;
546 } 746 my $count;
747
748 if (@range) {
749 foreach my $file_range_diff (@range) {
750 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
751 my $diff_file = $1;
752 my $diff_start = $2;
753 my $diff_length = $3;
754 next if (!("$file" eq "$diff_file"));
755 $cmd = "git blame -l -L $diff_start,+$diff_length $file";
756 @commits = save_commits($cmd, @commits);
547 } 757 }
548 if ($line =~ m/(.+)<(.+)>/) { 758 } else {
549 my $git_name = $1; 759 if (-f $file) {
550 my $git_addr = $2; 760 $cmd = "git blame -l $file";
551 if ($email_usename) { 761 @commits = save_commits($cmd, @commits);
552 push(@email_to, format_email($git_name, $git_addr)); 762 }
553 } else { 763 }
554 push(@email_to, $git_addr); 764
555 } 765 $total_sign_offs = 0;
556 } elsif ($line =~ m/<(.+)>/) { 766 @commits = uniq(@commits);
557 my $git_addr = $1; 767 foreach my $commit (@commits) {
558 push(@email_to, $git_addr); 768 $cmd = "git log -1 ${commit}";
559 } else { 769
560 push(@email_to, $line); 770 $output = `${cmd}`;
771 $output =~ s/^\s*//gm;
772 @lines = split("\n", $output);
773
774 @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines);
775 if (!$email_git_penguin_chiefs) {
776 @lines = grep(!/${penguin_chiefs}/i, @lines);
777 }
778
779 # cut -f2- -d":"
780 s/.*:\s*(.+)\s*/$1/ for (@lines);
781
782 $total_sign_offs += @lines;
783
784 if ($email_remove_duplicates) {
785 @lines = mailmap(@lines);
561 } 786 }
787
788 $hash{$_}++ for @lines;
789 }
790
791 $count = 0;
792 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
793 my $sign_offs = $hash{$line};
794 $count++;
795 last if ($sign_offs < $email_git_min_signatures ||
796 $count > $email_git_max_maintainers ||
797 $sign_offs * 100 / $total_sign_offs < $email_git_min_percent);
798 push_email_address($line);
562 } 799 }
563} 800}
564 801