]> bbs.cooldavid.org Git - net-next-2.6.git/blame - scripts/get_maintainer.pl
scripts/get_maintainer.pl: add .mailmap use, shell and email cleanups
[net-next-2.6.git] / scripts / get_maintainer.pl
CommitLineData
cb7301c7
JP
1#!/usr/bin/perl -w
2# (c) 2007, Joe Perches <joe@perches.com>
3# created from checkpatch.pl
4#
5# Print selected MAINTAINERS information for
6# the files modified in a patch or for a file
7#
8# usage: perl scripts/get_maintainers.pl [OPTIONS] <patch>
9# perl scripts/get_maintainers.pl [OPTIONS] -f <file>
10#
11# Licensed under the terms of the GNU GPL License version 2
12
13use strict;
14
15my $P = $0;
0e70e83d 16my $V = '0.20';
cb7301c7
JP
17
18use Getopt::Long qw(:config no_auto_abbrev);
19
20my $lk_path = "./";
21my $email = 1;
22my $email_usename = 1;
23my $email_maintainer = 1;
24my $email_list = 1;
25my $email_subscriber_list = 0;
26my $email_git = 1;
27my $email_git_penguin_chiefs = 0;
28my $email_git_min_signatures = 1;
29my $email_git_max_maintainers = 5;
afa81ee1 30my $email_git_min_percent = 5;
cb7301c7 31my $email_git_since = "1-year-ago";
f5492666 32my $email_git_blame = 0;
cb7301c7
JP
33my $output_multiline = 1;
34my $output_separator = ", ";
35my $scm = 0;
36my $web = 0;
37my $subsystem = 0;
38my $status = 0;
4a7fdb5f 39my $from_filename = 0;
3fb55652 40my $pattern_depth = 0;
cb7301c7
JP
41my $version = 0;
42my $help = 0;
43
44my $exit = 0;
45
46my @penguin_chief = ();
47push(@penguin_chief,"Linus Torvalds:torvalds\@linux-foundation.org");
48#Andrew wants in on most everything - 2009/01/14
49#push(@penguin_chief,"Andrew Morton:akpm\@linux-foundation.org");
50
51my @penguin_chief_names = ();
52foreach my $chief (@penguin_chief) {
53 if ($chief =~ m/^(.*):(.*)/) {
54 my $chief_name = $1;
55 my $chief_addr = $2;
56 push(@penguin_chief_names, $chief_name);
57 }
58}
59my $penguin_chiefs = "\(" . join("|",@penguin_chief_names) . "\)";
60
5f2441e9 61# rfc822 email address - preloaded methods go here.
1b5e1cf6 62my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
df4cc036 63my $rfc822_char = '[\\000-\\377]';
1b5e1cf6 64
cb7301c7
JP
65if (!GetOptions(
66 'email!' => \$email,
67 'git!' => \$email_git,
68 'git-chief-penguins!' => \$email_git_penguin_chiefs,
69 'git-min-signatures=i' => \$email_git_min_signatures,
70 'git-max-maintainers=i' => \$email_git_max_maintainers,
afa81ee1 71 'git-min-percent=i' => \$email_git_min_percent,
cb7301c7 72 'git-since=s' => \$email_git_since,
f5492666 73 'git-blame!' => \$email_git_blame,
cb7301c7
JP
74 'm!' => \$email_maintainer,
75 'n!' => \$email_usename,
76 'l!' => \$email_list,
77 's!' => \$email_subscriber_list,
78 'multiline!' => \$output_multiline,
79 'separator=s' => \$output_separator,
80 'subsystem!' => \$subsystem,
81 'status!' => \$status,
82 'scm!' => \$scm,
83 'web!' => \$web,
3fb55652 84 'pattern-depth=i' => \$pattern_depth,
4a7fdb5f 85 'f|file' => \$from_filename,
cb7301c7
JP
86 'v|version' => \$version,
87 'h|help' => \$help,
88 )) {
89 usage();
90 die "$P: invalid argument\n";
91}
92
93if ($help != 0) {
94 usage();
95 exit 0;
96}
97
98if ($version != 0) {
99 print("${P} ${V}\n");
100 exit 0;
101}
102
cb7301c7
JP
103if ($#ARGV < 0) {
104 usage();
105 die "$P: argument missing: patchfile or -f file please\n";
106}
107
108my $selections = $email + $scm + $status + $subsystem + $web;
109if ($selections == 0) {
110 usage();
111 die "$P: Missing required option: email, scm, status, subsystem or web\n";
112}
113
f5492666
JP
114if ($email &&
115 ($email_maintainer + $email_list + $email_subscriber_list +
116 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
cb7301c7
JP
117 usage();
118 die "$P: Please select at least 1 email option\n";
119}
120
121if (!top_of_kernel_tree($lk_path)) {
122 die "$P: The current directory does not appear to be "
123 . "a linux kernel source tree.\n";
124}
125
126## Read MAINTAINERS for type/value pairs
127
128my @typevalue = ();
129open(MAINT, "<${lk_path}MAINTAINERS") || die "$P: Can't open MAINTAINERS\n";
130while (<MAINT>) {
131 my $line = $_;
132
133 if ($line =~ m/^(\C):\s*(.*)/) {
134 my $type = $1;
135 my $value = $2;
136
137 ##Filename pattern matching
138 if ($type eq "F" || $type eq "X") {
139 $value =~ s@\.@\\\.@g; ##Convert . to \.
140 $value =~ s/\*/\.\*/g; ##Convert * to .*
141 $value =~ s/\?/\./g; ##Convert ? to .
870020f9
JP
142 ##if pattern is a directory and it lacks a trailing slash, add one
143 if ((-d $value)) {
144 $value =~ s@([^/])$@$1/@;
145 }
cb7301c7
JP
146 }
147 push(@typevalue, "$type:$value");
148 } elsif (!/^(\s)*$/) {
149 $line =~ s/\n$//g;
150 push(@typevalue, $line);
151 }
152}
153close(MAINT);
154
8cbb3a77
JP
155my %mailmap;
156
157open(MAILMAP, "<${lk_path}.mailmap") || warn "$P: Can't open .mailmap\n";
158while (<MAILMAP>) {
159 my $line = $_;
160
161 next if ($line =~ m/^\s*#/);
162 next if ($line =~ m/^\s*$/);
163
164 my ($name, $address) = parse_email($line);
165 $line = format_email($name, $address);
166
167 next if ($line =~ m/^\s*$/);
168
169 if (exists($mailmap{$name})) {
170 my $obj = $mailmap{$name};
171 push(@$obj, $address);
172 } else {
173 my @arr = ($address);
174 $mailmap{$name} = \@arr;
175 }
176}
177close(MAILMAP);
178
179foreach my $name (sort {$mailmap{$a} <=> $mailmap{$b}} keys %mailmap) {
180 my $obj = $mailmap{$name};
181 foreach my $address (@$obj) {
182 }
183}
184
4a7fdb5f 185## use the filenames on the command line or find the filenames in the patchfiles
cb7301c7
JP
186
187my @files = ();
f5492666 188my @range = ();
cb7301c7 189
4a7fdb5f 190foreach my $file (@ARGV) {
870020f9
JP
191 ##if $file is a directory and it lacks a trailing slash, add one
192 if ((-d $file)) {
193 $file =~ s@([^/])$@$1/@;
194 } elsif (!(-f $file)) {
4a7fdb5f 195 die "$P: file '${file}' not found\n";
cb7301c7 196 }
4a7fdb5f
JP
197 if ($from_filename) {
198 push(@files, $file);
199 } else {
200 my $file_cnt = @files;
f5492666 201 my $lastfile;
4a7fdb5f
JP
202 open(PATCH, "<$file") or die "$P: Can't open ${file}\n";
203 while (<PATCH>) {
204 if (m/^\+\+\+\s+(\S+)/) {
205 my $filename = $1;
206 $filename =~ s@^[^/]*/@@;
207 $filename =~ s@\n@@;
f5492666 208 $lastfile = $filename;
4a7fdb5f 209 push(@files, $filename);
f5492666
JP
210 } elsif (m/^\@\@ -(\d+),(\d+)/) {
211 if ($email_git_blame) {
212 push(@range, "$lastfile:$1:$2");
213 }
4a7fdb5f 214 }
cb7301c7 215 }
4a7fdb5f
JP
216 close(PATCH);
217 if ($file_cnt == @files) {
7f29fd27 218 warn "$P: file '${file}' doesn't appear to be a patch. "
4a7fdb5f
JP
219 . "Add -f to options?\n";
220 }
221 @files = sort_and_uniq(@files);
cb7301c7 222 }
cb7301c7
JP
223}
224
225my @email_to = ();
290603c1 226my @list_to = ();
cb7301c7
JP
227my @scm = ();
228my @web = ();
229my @subsystem = ();
230my @status = ();
231
232# Find responsible parties
233
234foreach my $file (@files) {
235
236#Do not match excluded file patterns
237
238 my $exclude = 0;
239 foreach my $line (@typevalue) {
290603c1 240 if ($line =~ m/^(\C):\s*(.*)/) {
cb7301c7
JP
241 my $type = $1;
242 my $value = $2;
243 if ($type eq 'X') {
244 if (file_match_pattern($file, $value)) {
245 $exclude = 1;
1d606b4e 246 last;
cb7301c7
JP
247 }
248 }
249 }
250 }
251
252 if (!$exclude) {
253 my $tvi = 0;
1d606b4e 254 my %hash;
cb7301c7 255 foreach my $line (@typevalue) {
290603c1 256 if ($line =~ m/^(\C):\s*(.*)/) {
cb7301c7
JP
257 my $type = $1;
258 my $value = $2;
259 if ($type eq 'F') {
260 if (file_match_pattern($file, $value)) {
3fb55652
JP
261 my $value_pd = ($value =~ tr@/@@);
262 my $file_pd = ($file =~ tr@/@@);
263 $value_pd++ if (substr($value,-1,1) ne "/");
264 if ($pattern_depth == 0 ||
265 (($file_pd - $value_pd) < $pattern_depth)) {
266 $hash{$tvi} = $value_pd;
267 }
cb7301c7
JP
268 }
269 }
270 }
271 $tvi++;
272 }
1d606b4e
JP
273 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
274 add_categories($line);
275 }
cb7301c7
JP
276 }
277
4a7fdb5f 278 if ($email && $email_git) {
cb7301c7
JP
279 recent_git_signoffs($file);
280 }
281
f5492666
JP
282 if ($email && $email_git_blame) {
283 git_assign_blame($file);
284 }
cb7301c7
JP
285}
286
f5f5078d 287if ($email) {
cb7301c7
JP
288 foreach my $chief (@penguin_chief) {
289 if ($chief =~ m/^(.*):(.*)/) {
f5f5078d 290 my $email_address;
0e70e83d
JP
291
292 $email_address = format_email($1, $2);
f5f5078d
JP
293 if ($email_git_penguin_chiefs) {
294 push(@email_to, $email_address);
295 } else {
296 @email_to = grep(!/${email_address}/, @email_to);
cb7301c7
JP
297 }
298 }
299 }
300}
301
290603c1
JP
302if ($email || $email_list) {
303 my @to = ();
304 if ($email) {
305 @to = (@to, @email_to);
cb7301c7 306 }
290603c1 307 if ($email_list) {
290603c1 308 @to = (@to, @list_to);
290603c1
JP
309 }
310 output(uniq(@to));
cb7301c7
JP
311}
312
313if ($scm) {
4a7fdb5f 314 @scm = sort_and_uniq(@scm);
cb7301c7
JP
315 output(@scm);
316}
317
318if ($status) {
4a7fdb5f 319 @status = sort_and_uniq(@status);
cb7301c7
JP
320 output(@status);
321}
322
323if ($subsystem) {
4a7fdb5f 324 @subsystem = sort_and_uniq(@subsystem);
cb7301c7
JP
325 output(@subsystem);
326}
327
328if ($web) {
4a7fdb5f 329 @web = sort_and_uniq(@web);
cb7301c7
JP
330 output(@web);
331}
332
333exit($exit);
334
335sub file_match_pattern {
336 my ($file, $pattern) = @_;
337 if (substr($pattern, -1) eq "/") {
338 if ($file =~ m@^$pattern@) {
339 return 1;
340 }
341 } else {
342 if ($file =~ m@^$pattern@) {
343 my $s1 = ($file =~ tr@/@@);
344 my $s2 = ($pattern =~ tr@/@@);
345 if ($s1 == $s2) {
346 return 1;
347 }
348 }
349 }
350 return 0;
351}
352
353sub usage {
354 print <<EOT;
355usage: $P [options] patchfile
870020f9 356 $P [options] -f file|directory
cb7301c7
JP
357version: $V
358
359MAINTAINER field selection options:
360 --email => print email address(es) if any
361 --git => include recent git \*-by: signers
362 --git-chief-penguins => include ${penguin_chiefs}
363 --git-min-signatures => number of signatures required (default: 1)
364 --git-max-maintainers => maximum maintainers to add (default: 5)
3d202aeb 365 --git-min-percent => minimum percentage of commits required (default: 5)
cb7301c7 366 --git-since => git history to use (default: 1-year-ago)
f5492666 367 --git-blame => use git blame to find modified commits for patch or file
cb7301c7
JP
368 --m => include maintainer(s) if any
369 --n => include name 'Full Name <addr\@domain.tld>'
370 --l => include list(s) if any
371 --s => include subscriber only list(s) if any
372 --scm => print SCM tree(s) if any
373 --status => print status if any
374 --subsystem => print subsystem name if any
375 --web => print website(s) if any
376
377Output type options:
378 --separator [, ] => separator for multiple entries on 1 line
379 --multiline => print 1 entry per line
380
cb7301c7 381Other options:
3fb55652 382 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
f5f5078d 383 --version => show version
cb7301c7
JP
384 --help => show this help information
385
3fb55652
JP
386Default options:
387 [--email --git --m --n --l --multiline --pattern-depth=0]
388
870020f9
JP
389Notes:
390 Using "-f directory" may give unexpected results:
f5492666
JP
391 Used with "--git", git signators for _all_ files in and below
392 directory are examined as git recurses directories.
393 Any specified X: (exclude) pattern matches are _not_ ignored.
394 Used with "--nogit", directory is used as a pattern match,
395 no individual file within the directory or subdirectory
396 is matched.
397 Used with "--git-blame", does not iterate all files in directory
398 Using "--git-blame" is slow and may add old committers and authors
399 that are no longer active maintainers to the output.
cb7301c7
JP
400EOT
401}
402
403sub top_of_kernel_tree {
404 my ($lk_path) = @_;
405
406 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
407 $lk_path .= "/";
408 }
409 if ( (-f "${lk_path}COPYING")
410 && (-f "${lk_path}CREDITS")
411 && (-f "${lk_path}Kbuild")
412 && (-f "${lk_path}MAINTAINERS")
413 && (-f "${lk_path}Makefile")
414 && (-f "${lk_path}README")
415 && (-d "${lk_path}Documentation")
416 && (-d "${lk_path}arch")
417 && (-d "${lk_path}include")
418 && (-d "${lk_path}drivers")
419 && (-d "${lk_path}fs")
420 && (-d "${lk_path}init")
421 && (-d "${lk_path}ipc")
422 && (-d "${lk_path}kernel")
423 && (-d "${lk_path}lib")
424 && (-d "${lk_path}scripts")) {
425 return 1;
426 }
427 return 0;
428}
429
0e70e83d
JP
430sub parse_email {
431 my ($formatted_email) = @_;
432
433 my $name = "";
434 my $address = "";
435
8cbb3a77 436 if ($formatted_email =~ /^([^<]+)<(.*\@.*)>.*$/) {
0e70e83d
JP
437 $name = $1;
438 $address = $2;
8cbb3a77 439 } elsif ($formatted_email =~ /^\s*<(.*\@.*)>.*$/) {
0e70e83d 440 $address = $1;
8cbb3a77 441 } elsif ($formatted_email =~ /^\s*(.*\@.*)$/) {
0e70e83d
JP
442 $address = $1;
443 }
cb7301c7
JP
444
445 $name =~ s/^\s+|\s+$//g;
d789504a 446 $name =~ s/^\"|\"$//g;
0e70e83d 447 $address =~ s/^\s+|\s+$//g;
cb7301c7 448
0e70e83d
JP
449 if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars
450 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
451 $name = "\"$name\"";
452 }
453
454 return ($name, $address);
455}
456
457sub format_email {
458 my ($name, $address) = @_;
459
460 my $formatted_email;
461
462 $name =~ s/^\s+|\s+$//g;
463 $name =~ s/^\"|\"$//g;
464 $address =~ s/^\s+|\s+$//g;
cb7301c7
JP
465
466 if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars
467 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
0e70e83d
JP
468 $name = "\"$name\"";
469 }
470
471 if ($email_usename) {
472 if ("$name" eq "") {
473 $formatted_email = "$address";
474 } else {
475 $formatted_email = "$name <${address}>";
476 }
cb7301c7 477 } else {
0e70e83d 478 $formatted_email = $address;
cb7301c7 479 }
0e70e83d 480
cb7301c7
JP
481 return $formatted_email;
482}
483
484sub add_categories {
485 my ($index) = @_;
486
487 $index = $index - 1;
488 while ($index >= 0) {
489 my $tv = $typevalue[$index];
290603c1 490 if ($tv =~ m/^(\C):\s*(.*)/) {
cb7301c7
JP
491 my $ptype = $1;
492 my $pvalue = $2;
493 if ($ptype eq "L") {
290603c1
JP
494 my $list_address = $pvalue;
495 my $list_additional = "";
496 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
497 $list_address = $1;
498 $list_additional = $2;
499 }
bdf7c685 500 if ($list_additional =~ m/subscribers-only/) {
cb7301c7 501 if ($email_subscriber_list) {
290603c1 502 push(@list_to, $list_address);
cb7301c7
JP
503 }
504 } else {
505 if ($email_list) {
290603c1 506 push(@list_to, $list_address);
cb7301c7
JP
507 }
508 }
509 } elsif ($ptype eq "M") {
0e70e83d
JP
510 my ($name, $address) = parse_email($pvalue);
511 if ($name eq "") {
512 if ($index >= 0) {
513 my $tv = $typevalue[$index - 1];
514 if ($tv =~ m/^(\C):\s*(.*)/) {
515 if ($1 eq "P") {
516 $name = $2;
5f2441e9
JP
517 }
518 }
519 }
520 }
0e70e83d 521 if ($email_maintainer) {
1b5e1cf6 522 push_email_addresses($pvalue);
cb7301c7
JP
523 }
524 } elsif ($ptype eq "T") {
525 push(@scm, $pvalue);
526 } elsif ($ptype eq "W") {
527 push(@web, $pvalue);
528 } elsif ($ptype eq "S") {
529 push(@status, $pvalue);
530 }
531
532 $index--;
533 } else {
534 push(@subsystem,$tv);
535 $index = -1;
536 }
537 }
538}
539
0e70e83d
JP
540sub email_address_inuse {
541 my ($test_address) = @_;
542
543 foreach my $line (@email_to) {
544 my ($name, $address) = parse_email($line);
545
546 return 1 if ($address eq $test_address);
547 }
548 return 0;
549}
550
1b5e1cf6 551sub push_email_address {
0e70e83d 552 my ($line) = @_;
1b5e1cf6 553
0e70e83d 554 my ($name, $address) = parse_email($line);
1b5e1cf6 555
0e70e83d
JP
556 if (!email_address_inuse($address)) {
557 push(@email_to, format_email($name, $address));
1b5e1cf6
JP
558 }
559}
560
561sub push_email_addresses {
562 my ($address) = @_;
563
564 my @address_list = ();
565
5f2441e9
JP
566 if (rfc822_valid($address)) {
567 push_email_address($address);
568 } elsif (@address_list = rfc822_validlist($address)) {
1b5e1cf6
JP
569 my $array_count = shift(@address_list);
570 while (my $entry = shift(@address_list)) {
571 push_email_address($entry);
572 }
5f2441e9
JP
573 } else {
574 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1b5e1cf6 575 }
1b5e1cf6
JP
576}
577
cb7301c7
JP
578sub which {
579 my ($bin) = @_;
580
f5f5078d 581 foreach my $path (split(/:/, $ENV{PATH})) {
cb7301c7
JP
582 if (-e "$path/$bin") {
583 return "$path/$bin";
584 }
585 }
586
587 return "";
588}
589
8cbb3a77
JP
590sub mailmap {
591 my @lines = @_;
592 my %hash;
593
594 foreach my $line (@lines) {
595 my ($name, $address) = parse_email($line);
596 if (!exists($hash{$name})) {
597 $hash{$name} = $address;
598 }
599 if (exists($mailmap{$name})) {
600 my $obj = $mailmap{$name};
601 foreach my $map_address (@$obj) {
602 if (($map_address eq $address) &&
603 ($map_address ne $hash{$name})) {
604 $line = format_email($name, $hash{$name});
605 }
606 }
607 }
608 }
609
610 return @lines;
611}
612
cb7301c7
JP
613sub recent_git_signoffs {
614 my ($file) = @_;
615
616 my $sign_offs = "";
617 my $cmd = "";
618 my $output = "";
619 my $count = 0;
620 my @lines = ();
0e70e83d 621 my %hash;
afa81ee1 622 my $total_sign_offs;
cb7301c7
JP
623
624 if (which("git") eq "") {
de2fc492
JP
625 warn("$P: git not found. Add --nogit to options?\n");
626 return;
627 }
628 if (!(-d ".git")) {
5f2441e9
JP
629 warn("$P: .git directory not found. Use a git repository for better results.\n");
630 warn("$P: perhaps 'git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git'\n");
de2fc492 631 return;
cb7301c7
JP
632 }
633
634 $cmd = "git log --since=${email_git_since} -- ${file}";
cb7301c7
JP
635
636 $output = `${cmd}`;
637 $output =~ s/^\s*//gm;
638
639 @lines = split("\n", $output);
afa81ee1 640
0e70e83d
JP
641 @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines);
642 if (!$email_git_penguin_chiefs) {
643 @lines = grep(!/${penguin_chiefs}/i, @lines);
644 }
645 # cut -f2- -d":"
646 s/.*:\s*(.+)\s*/$1/ for (@lines);
647
8cbb3a77
JP
648 $total_sign_offs = @lines;
649
0e70e83d
JP
650 @lines = mailmap(@lines);
651
0e70e83d
JP
652 @lines = sort(@lines);
653 # uniq -c
afa81ee1 654 foreach my $line (@lines) {
0e70e83d
JP
655 $hash{$line}++;
656 }
657 # sort -rn
658 @lines = ();
659 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
660 push(@lines,"$hash{$line} $line");
afa81ee1
JP
661 }
662
cb7301c7 663 foreach my $line (@lines) {
4a7fdb5f 664 if ($line =~ m/([0-9]+)\s+(.*)/) {
cb7301c7 665 my $sign_offs = $1;
4a7fdb5f 666 $line = $2;
cb7301c7
JP
667 $count++;
668 if ($sign_offs < $email_git_min_signatures ||
afa81ee1
JP
669 $count > $email_git_max_maintainers ||
670 $sign_offs * 100 / $total_sign_offs < $email_git_min_percent) {
cb7301c7
JP
671 last;
672 }
0e70e83d 673 push_email_address($line);
cb7301c7 674 }
f5492666
JP
675 }
676}
677
678sub save_commits {
679 my ($cmd, @commits) = @_;
680 my $output;
681 my @lines = ();
682
683 $output = `${cmd}`;
684
685 @lines = split("\n", $output);
686 foreach my $line (@lines) {
687 if ($line =~ m/^(\w+) /) {
688 push (@commits, $1);
689 }
690 }
691 return @commits;
692}
693
694sub git_assign_blame {
695 my ($file) = @_;
696
697 my @lines = ();
698 my @commits = ();
699 my $cmd;
700 my $output;
701 my %hash;
702 my $total_sign_offs;
703 my $count;
704
705 if (@range) {
706 foreach my $file_range_diff (@range) {
707 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
708 my $diff_file = $1;
709 my $diff_start = $2;
710 my $diff_length = $3;
711 next if (!("$file" eq "$diff_file"));
8cbb3a77 712 $cmd = "git blame -l -L $diff_start,+$diff_length $file";
f5492666 713 @commits = save_commits($cmd, @commits);
cb7301c7 714 }
f5492666
JP
715 } else {
716 if (-f $file) {
8cbb3a77 717 $cmd = "git blame -l $file";
f5492666
JP
718 @commits = save_commits($cmd, @commits);
719 }
720 }
721
722 $total_sign_offs = 0;
723 @commits = uniq(@commits);
724 foreach my $commit (@commits) {
725 $cmd = "git log -1 ${commit}";
f5492666
JP
726
727 $output = `${cmd}`;
728 $output =~ s/^\s*//gm;
729 @lines = split("\n", $output);
0e70e83d
JP
730
731 @lines = grep(/^[-_ a-z]+by:.*\@.*$/i, @lines);
732 if (!$email_git_penguin_chiefs) {
733 @lines = grep(!/${penguin_chiefs}/i, @lines);
734 }
8cbb3a77 735
0e70e83d
JP
736 # cut -f2- -d":"
737 s/.*:\s*(.+)\s*/$1/ for (@lines);
738
f5492666 739 $total_sign_offs += @lines;
8cbb3a77
JP
740
741 @lines = mailmap(@lines);
742
743 $hash{$_}++ for @lines;
f5492666
JP
744 }
745
746 $count = 0;
747 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
748 my $sign_offs = $hash{$line};
749 $count++;
750 last if ($sign_offs < $email_git_min_signatures ||
751 $count > $email_git_max_maintainers ||
752 $sign_offs * 100 / $total_sign_offs < $email_git_min_percent);
753 push_email_address($line);
cb7301c7 754 }
cb7301c7
JP
755}
756
757sub uniq {
758 my @parms = @_;
759
760 my %saw;
761 @parms = grep(!$saw{$_}++, @parms);
762 return @parms;
763}
764
765sub sort_and_uniq {
766 my @parms = @_;
767
768 my %saw;
769 @parms = sort @parms;
770 @parms = grep(!$saw{$_}++, @parms);
771 return @parms;
772}
773
774sub output {
775 my @parms = @_;
776
777 if ($output_multiline) {
778 foreach my $line (@parms) {
779 print("${line}\n");
780 }
781 } else {
782 print(join($output_separator, @parms));
783 print("\n");
784 }
785}
1b5e1cf6
JP
786
787my $rfc822re;
788
789sub make_rfc822re {
790# Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
791# comment. We must allow for rfc822_lwsp (or comments) after each of these.
792# This regexp will only work on addresses which have had comments stripped
793# and replaced with rfc822_lwsp.
794
795 my $specials = '()<>@,;:\\\\".\\[\\]';
796 my $controls = '\\000-\\037\\177';
797
798 my $dtext = "[^\\[\\]\\r\\\\]";
799 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
800
801 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
802
803# Use zero-width assertion to spot the limit of an atom. A simple
804# $rfc822_lwsp* causes the regexp engine to hang occasionally.
805 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
806 my $word = "(?:$atom|$quoted_string)";
807 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
808
809 my $sub_domain = "(?:$atom|$domain_literal)";
810 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
811
812 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
813
814 my $phrase = "$word*";
815 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
816 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
817 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
818
819 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
820 my $address = "(?:$mailbox|$group)";
821
822 return "$rfc822_lwsp*$address";
823}
824
825sub rfc822_strip_comments {
826 my $s = shift;
827# Recursively remove comments, and replace with a single space. The simpler
828# regexps in the Email Addressing FAQ are imperfect - they will miss escaped
829# chars in atoms, for example.
830
831 while ($s =~ s/^((?:[^"\\]|\\.)*
832 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
833 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
834 return $s;
835}
836
837# valid: returns true if the parameter is an RFC822 valid address
838#
839sub rfc822_valid ($) {
840 my $s = rfc822_strip_comments(shift);
841
842 if (!$rfc822re) {
843 $rfc822re = make_rfc822re();
844 }
845
846 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
847}
848
849# validlist: In scalar context, returns true if the parameter is an RFC822
850# valid list of addresses.
851#
852# In list context, returns an empty list on failure (an invalid
853# address was found); otherwise a list whose first element is the
854# number of addresses found and whose remaining elements are the
855# addresses. This is needed to disambiguate failure (invalid)
856# from success with no addresses found, because an empty string is
857# a valid list.
858
859sub rfc822_validlist ($) {
860 my $s = rfc822_strip_comments(shift);
861
862 if (!$rfc822re) {
863 $rfc822re = make_rfc822re();
864 }
865 # * null list items are valid according to the RFC
866 # * the '1' business is to aid in distinguishing failure from no results
867
868 my @r;
869 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
870 $s =~ m/^$rfc822_char*$/) {
5f2441e9 871 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
1b5e1cf6
JP
872 push @r, $1;
873 }
874 return wantarray ? (scalar(@r), @r) : 1;
875 }
876 else {
877 return wantarray ? () : 0;
878 }
879}