]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blame - scripts/get_maintainer.pl
kernel/configs.c: include MODULE_*() when CONFIG_IKCONFIG_PROC=n
[mirror_ubuntu-bionic-kernel.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#
3bd7bf5f
RK
8# usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
9# perl scripts/get_maintainer.pl [OPTIONS] -f <file>
cb7301c7
JP
10#
11# Licensed under the terms of the GNU GPL License version 2
12
13use strict;
14
15my $P = $0;
7e1863af 16my $V = '0.26';
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;
cb7301c7 26my $email_git_penguin_chiefs = 0;
e3e9d114 27my $email_git = 0;
0fa05599 28my $email_git_all_signature_types = 0;
60db31ac 29my $email_git_blame = 0;
683c6f8f 30my $email_git_blame_signatures = 1;
e3e9d114 31my $email_git_fallback = 1;
cb7301c7
JP
32my $email_git_min_signatures = 1;
33my $email_git_max_maintainers = 5;
afa81ee1 34my $email_git_min_percent = 5;
cb7301c7 35my $email_git_since = "1-year-ago";
60db31ac 36my $email_hg_since = "-365";
dace8e30 37my $interactive = 0;
11ecf53c 38my $email_remove_duplicates = 1;
b9e2331d 39my $email_use_mailmap = 1;
cb7301c7
JP
40my $output_multiline = 1;
41my $output_separator = ", ";
3c7385b8 42my $output_roles = 0;
7e1863af 43my $output_rolestats = 1;
cb7301c7
JP
44my $scm = 0;
45my $web = 0;
46my $subsystem = 0;
47my $status = 0;
dcf36a92 48my $keywords = 1;
4b76c9da 49my $sections = 0;
03372dbb 50my $file_emails = 0;
4a7fdb5f 51my $from_filename = 0;
3fb55652 52my $pattern_depth = 0;
cb7301c7
JP
53my $version = 0;
54my $help = 0;
55
683c6f8f
JP
56my $vcs_used = 0;
57
cb7301c7
JP
58my $exit = 0;
59
683c6f8f
JP
60my %commit_author_hash;
61my %commit_signer_hash;
dace8e30 62
cb7301c7 63my @penguin_chief = ();
e4d26b02 64push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
cb7301c7 65#Andrew wants in on most everything - 2009/01/14
e4d26b02 66#push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
cb7301c7
JP
67
68my @penguin_chief_names = ();
69foreach my $chief (@penguin_chief) {
70 if ($chief =~ m/^(.*):(.*)/) {
71 my $chief_name = $1;
72 my $chief_addr = $2;
73 push(@penguin_chief_names, $chief_name);
74 }
75}
e4d26b02
JP
76my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
77
78# Signature types of people who are either
79# a) responsible for the code in question, or
80# b) familiar enough with it to give relevant feedback
81my @signature_tags = ();
82push(@signature_tags, "Signed-off-by:");
83push(@signature_tags, "Reviewed-by:");
84push(@signature_tags, "Acked-by:");
cb7301c7 85
5f2441e9 86# rfc822 email address - preloaded methods go here.
1b5e1cf6 87my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
df4cc036 88my $rfc822_char = '[\\000-\\377]';
1b5e1cf6 89
60db31ac
JP
90# VCS command support: class-like functions and strings
91
92my %VCS_cmds;
93
94my %VCS_cmds_git = (
95 "execute_cmd" => \&git_execute_cmd,
96 "available" => '(which("git") ne "") && (-d ".git")',
683c6f8f
JP
97 "find_signers_cmd" =>
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",
60db31ac
JP
120 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
121 "blame_file_cmd" => "git blame -l \$file",
683c6f8f 122 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
dace8e30 123 "blame_commit_pattern" => "^([0-9a-f]+) ",
683c6f8f
JP
124 "author_pattern" => "^GitAuthor: (.*)",
125 "subject_pattern" => "^GitSubject: (.*)",
60db31ac
JP
126);
127
128my %VCS_cmds_hg = (
129 "execute_cmd" => \&hg_execute_cmd,
130 "available" => '(which("hg") ne "") && (-d ".hg")',
131 "find_signers_cmd" =>
683c6f8f
JP
132 "hg log --date=\$email_hg_since " .
133 "--template='HgCommit: {node}\\n" .
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",
60db31ac 147 "blame_range_cmd" => "", # not supported
683c6f8f
JP
148 "blame_file_cmd" => "hg blame -n \$file",
149 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
150 "blame_commit_pattern" => "^([ 0-9a-f]+):",
151 "author_pattern" => "^HgAuthor: (.*)",
152 "subject_pattern" => "^HgSubject: (.*)",
60db31ac
JP
153);
154
bcde44ed
JP
155my $conf = which_conf(".get_maintainer.conf");
156if (-f $conf) {
368669da 157 my @conf_args;
bcde44ed
JP
158 open(my $conffile, '<', "$conf")
159 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
160
368669da
JP
161 while (<$conffile>) {
162 my $line = $_;
163
164 $line =~ s/\s*\n?$//g;
165 $line =~ s/^\s*//g;
166 $line =~ s/\s+/ /g;
167
168 next if ($line =~ m/^\s*#/);
169 next if ($line =~ m/^\s*$/);
170
171 my @words = split(" ", $line);
172 foreach my $word (@words) {
173 last if ($word =~ m/^#/);
174 push (@conf_args, $word);
175 }
176 }
177 close($conffile);
178 unshift(@ARGV, @conf_args) if @conf_args;
179}
180
cb7301c7
JP
181if (!GetOptions(
182 'email!' => \$email,
183 'git!' => \$email_git,
e4d26b02 184 'git-all-signature-types!' => \$email_git_all_signature_types,
60db31ac 185 'git-blame!' => \$email_git_blame,
683c6f8f 186 'git-blame-signatures!' => \$email_git_blame_signatures,
e3e9d114 187 'git-fallback!' => \$email_git_fallback,
cb7301c7
JP
188 'git-chief-penguins!' => \$email_git_penguin_chiefs,
189 'git-min-signatures=i' => \$email_git_min_signatures,
190 'git-max-maintainers=i' => \$email_git_max_maintainers,
afa81ee1 191 'git-min-percent=i' => \$email_git_min_percent,
cb7301c7 192 'git-since=s' => \$email_git_since,
60db31ac 193 'hg-since=s' => \$email_hg_since,
dace8e30 194 'i|interactive!' => \$interactive,
11ecf53c 195 'remove-duplicates!' => \$email_remove_duplicates,
b9e2331d 196 'mailmap!' => \$email_use_mailmap,
cb7301c7
JP
197 'm!' => \$email_maintainer,
198 'n!' => \$email_usename,
199 'l!' => \$email_list,
200 's!' => \$email_subscriber_list,
201 'multiline!' => \$output_multiline,
3c7385b8
JP
202 'roles!' => \$output_roles,
203 'rolestats!' => \$output_rolestats,
cb7301c7
JP
204 'separator=s' => \$output_separator,
205 'subsystem!' => \$subsystem,
206 'status!' => \$status,
207 'scm!' => \$scm,
208 'web!' => \$web,
3fb55652 209 'pattern-depth=i' => \$pattern_depth,
dcf36a92 210 'k|keywords!' => \$keywords,
4b76c9da 211 'sections!' => \$sections,
03372dbb 212 'fe|file-emails!' => \$file_emails,
4a7fdb5f 213 'f|file' => \$from_filename,
cb7301c7 214 'v|version' => \$version,
64f77f31 215 'h|help|usage' => \$help,
cb7301c7 216 )) {
3c7385b8 217 die "$P: invalid argument - use --help if necessary\n";
cb7301c7
JP
218}
219
220if ($help != 0) {
221 usage();
222 exit 0;
223}
224
225if ($version != 0) {
226 print("${P} ${V}\n");
227 exit 0;
228}
229
64f77f31
JP
230if (-t STDIN && !@ARGV) {
231 # We're talking to a terminal, but have no command line arguments.
232 die "$P: missing patchfile or -f file - use --help if necessary\n";
cb7301c7
JP
233}
234
683c6f8f
JP
235$output_multiline = 0 if ($output_separator ne ", ");
236$output_rolestats = 1 if ($interactive);
237$output_roles = 1 if ($output_rolestats);
3c7385b8 238
4b76c9da
JP
239if ($sections) {
240 $email = 0;
241 $email_list = 0;
242 $scm = 0;
243 $status = 0;
244 $subsystem = 0;
245 $web = 0;
246 $keywords = 0;
6ef1c52e 247 $interactive = 0;
4b76c9da
JP
248} else {
249 my $selections = $email + $scm + $status + $subsystem + $web;
250 if ($selections == 0) {
4b76c9da
JP
251 die "$P: Missing required option: email, scm, status, subsystem or web\n";
252 }
cb7301c7
JP
253}
254
f5492666
JP
255if ($email &&
256 ($email_maintainer + $email_list + $email_subscriber_list +
257 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
cb7301c7
JP
258 die "$P: Please select at least 1 email option\n";
259}
260
261if (!top_of_kernel_tree($lk_path)) {
262 die "$P: The current directory does not appear to be "
263 . "a linux kernel source tree.\n";
264}
265
266## Read MAINTAINERS for type/value pairs
267
268my @typevalue = ();
dcf36a92
JP
269my %keyword_hash;
270
22dd5b0c
SH
271open (my $maint, '<', "${lk_path}MAINTAINERS")
272 or die "$P: Can't open MAINTAINERS: $!\n";
273while (<$maint>) {
cb7301c7
JP
274 my $line = $_;
275
276 if ($line =~ m/^(\C):\s*(.*)/) {
277 my $type = $1;
278 my $value = $2;
279
280 ##Filename pattern matching
281 if ($type eq "F" || $type eq "X") {
282 $value =~ s@\.@\\\.@g; ##Convert . to \.
283 $value =~ s/\*/\.\*/g; ##Convert * to .*
284 $value =~ s/\?/\./g; ##Convert ? to .
870020f9
JP
285 ##if pattern is a directory and it lacks a trailing slash, add one
286 if ((-d $value)) {
287 $value =~ s@([^/])$@$1/@;
288 }
dcf36a92
JP
289 } elsif ($type eq "K") {
290 $keyword_hash{@typevalue} = $value;
cb7301c7
JP
291 }
292 push(@typevalue, "$type:$value");
293 } elsif (!/^(\s)*$/) {
294 $line =~ s/\n$//g;
295 push(@typevalue, $line);
296 }
297}
22dd5b0c 298close($maint);
cb7301c7 299
8cbb3a77 300
7fa8ff2e
FM
301#
302# Read mail address map
303#
304
b9e2331d
JP
305my $mailmap;
306
307read_mailmap();
7fa8ff2e
FM
308
309sub read_mailmap {
b9e2331d 310 $mailmap = {
7fa8ff2e
FM
311 names => {},
312 addresses => {}
47abc722 313 };
7fa8ff2e 314
b9e2331d 315 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
7fa8ff2e
FM
316
317 open(my $mailmap_file, '<', "${lk_path}.mailmap")
22dd5b0c 318 or warn "$P: Can't open .mailmap: $!\n";
8cbb3a77 319
7fa8ff2e
FM
320 while (<$mailmap_file>) {
321 s/#.*$//; #strip comments
322 s/^\s+|\s+$//g; #trim
8cbb3a77 323
7fa8ff2e
FM
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 (/^(.+)<(.+)>$/) {
47abc722
JP
332 my $real_name = $1;
333 my $address = $2;
8cbb3a77 334
47abc722 335 $real_name =~ s/\s+$//;
b9e2331d 336 ($real_name, $address) = parse_email("$real_name <$address>");
47abc722 337 $mailmap->{names}->{$address} = $real_name;
8cbb3a77 338
7fa8ff2e 339 } elsif (/^<([^\s]+)>\s*<([^\s]+)>$/) {
47abc722
JP
340 my $real_address = $1;
341 my $wrong_address = $2;
7fa8ff2e 342
47abc722 343 $mailmap->{addresses}->{$wrong_address} = $real_address;
7fa8ff2e
FM
344
345 } elsif (/^(.+)<([^\s]+)>\s*<([^\s]+)>$/) {
b9e2331d 346 my $real_name = $1;
47abc722
JP
347 my $real_address = $2;
348 my $wrong_address = $3;
7fa8ff2e 349
47abc722 350 $real_name =~ s/\s+$//;
b9e2331d
JP
351 ($real_name, $real_address) =
352 parse_email("$real_name <$real_address>");
47abc722
JP
353 $mailmap->{names}->{$wrong_address} = $real_name;
354 $mailmap->{addresses}->{$wrong_address} = $real_address;
7fa8ff2e
FM
355
356 } elsif (/^(.+)<([^\s]+)>\s*([^\s].*)<([^\s]+)>$/) {
47abc722
JP
357 my $real_name = $1;
358 my $real_address = $2;
359 my $wrong_name = $3;
360 my $wrong_address = $4;
7fa8ff2e 361
47abc722 362 $real_name =~ s/\s+$//;
b9e2331d
JP
363 ($real_name, $real_address) =
364 parse_email("$real_name <$real_address>");
365
47abc722 366 $wrong_name =~ s/\s+$//;
b9e2331d
JP
367 ($wrong_name, $wrong_address) =
368 parse_email("$wrong_name <$wrong_address>");
7fa8ff2e 369
b9e2331d
JP
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;
11ecf53c 373 }
8cbb3a77 374 }
7fa8ff2e 375 close($mailmap_file);
8cbb3a77
JP
376}
377
4a7fdb5f 378## use the filenames on the command line or find the filenames in the patchfiles
cb7301c7
JP
379
380my @files = ();
f5492666 381my @range = ();
dcf36a92 382my @keyword_tvi = ();
03372dbb 383my @file_emails = ();
cb7301c7 384
64f77f31
JP
385if (!@ARGV) {
386 push(@ARGV, "&STDIN");
387}
388
4a7fdb5f 389foreach my $file (@ARGV) {
64f77f31
JP
390 if ($file ne "&STDIN") {
391 ##if $file is a directory and it lacks a trailing slash, add one
392 if ((-d $file)) {
393 $file =~ s@([^/])$@$1/@;
394 } elsif (!(-f $file)) {
395 die "$P: file '${file}' not found\n";
396 }
cb7301c7 397 }
4a7fdb5f
JP
398 if ($from_filename) {
399 push(@files, $file);
fab9ed12 400 if ($file ne "MAINTAINERS" && -f $file && ($keywords || $file_emails)) {
22dd5b0c
SH
401 open(my $f, '<', $file)
402 or die "$P: Can't open $file: $!\n";
403 my $text = do { local($/) ; <$f> };
404 close($f);
03372dbb
JP
405 if ($keywords) {
406 foreach my $line (keys %keyword_hash) {
407 if ($text =~ m/$keyword_hash{$line}/x) {
408 push(@keyword_tvi, $line);
409 }
dcf36a92
JP
410 }
411 }
03372dbb
JP
412 if ($file_emails) {
413 my @poss_addr = $text =~ m$[A-Za-zÀ-ÿ\"\' \,\.\+-]*\s*[\,]*\s*[\(\<\{]{0,1}[A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+\.[A-Za-z0-9]+[\)\>\}]{0,1}$g;
414 push(@file_emails, clean_file_emails(@poss_addr));
415 }
dcf36a92 416 }
4a7fdb5f
JP
417 } else {
418 my $file_cnt = @files;
f5492666 419 my $lastfile;
22dd5b0c 420
3a4df13d 421 open(my $patch, "< $file")
22dd5b0c 422 or die "$P: Can't open $file: $!\n";
7764dcb5
JP
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
22dd5b0c 431 while (<$patch>) {
dcf36a92 432 my $patch_line = $_;
4a7fdb5f
JP
433 if (m/^\+\+\+\s+(\S+)/) {
434 my $filename = $1;
435 $filename =~ s@^[^/]*/@@;
436 $filename =~ s@\n@@;
f5492666 437 $lastfile = $filename;
4a7fdb5f 438 push(@files, $filename);
7764dcb5 439 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
f5492666
JP
440 } elsif (m/^\@\@ -(\d+),(\d+)/) {
441 if ($email_git_blame) {
442 push(@range, "$lastfile:$1:$2");
443 }
dcf36a92
JP
444 } elsif ($keywords) {
445 foreach my $line (keys %keyword_hash) {
7764dcb5 446 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
dcf36a92
JP
447 push(@keyword_tvi, $line);
448 }
449 }
4a7fdb5f 450 }
cb7301c7 451 }
22dd5b0c
SH
452 close($patch);
453
4a7fdb5f 454 if ($file_cnt == @files) {
7f29fd27 455 warn "$P: file '${file}' doesn't appear to be a patch. "
4a7fdb5f
JP
456 . "Add -f to options?\n";
457 }
458 @files = sort_and_uniq(@files);
cb7301c7 459 }
cb7301c7
JP
460}
461
03372dbb
JP
462@file_emails = uniq(@file_emails);
463
683c6f8f
JP
464my %email_hash_name;
465my %email_hash_address;
cb7301c7 466my @email_to = ();
683c6f8f 467my %hash_list_to;
290603c1 468my @list_to = ();
cb7301c7
JP
469my @scm = ();
470my @web = ();
471my @subsystem = ();
472my @status = ();
b9e2331d
JP
473my %deduplicate_name_hash = ();
474my %deduplicate_address_hash = ();
683c6f8f 475my $signature_pattern;
cb7301c7 476
6ef1c52e 477my @maintainers = get_maintainers();
cb7301c7 478
6ef1c52e
JP
479if (@maintainers) {
480 @maintainers = merge_email(@maintainers);
481 output(@maintainers);
482}
683c6f8f
JP
483
484if ($scm) {
485 @scm = uniq(@scm);
486 output(@scm);
487}
488
489if ($status) {
490 @status = uniq(@status);
491 output(@status);
492}
493
494if ($subsystem) {
495 @subsystem = uniq(@subsystem);
496 output(@subsystem);
497}
498
499if ($web) {
500 @web = uniq(@web);
501 output(@web);
502}
503
504exit($exit);
505
ab6c937d
JP
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;
517 }
518 }
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
6ef1c52e 540sub get_maintainers {
683c6f8f
JP
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 = ();
b9e2331d
JP
552 %deduplicate_name_hash = ();
553 %deduplicate_address_hash = ();
683c6f8f
JP
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
b9e2331d 562 my %exact_pattern_match_hash = ();
6ef1c52e 563
683c6f8f
JP
564 foreach my $file (@files) {
565
566 my %hash;
683c6f8f
JP
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
272a8979 575
272a8979
JP
576 for ($i = $start; $i < $end; $i++) {
577 my $line = $typevalue[$i];
578 if ($line =~ m/^(\C):\s*(.*)/) {
579 my $type = $1;
580 my $value = $2;
683c6f8f 581 if ($type eq 'X') {
272a8979 582 if (file_match_pattern($file, $value)) {
683c6f8f
JP
583 $exclude = 1;
584 last;
585 }
586 }
587 }
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 =~ /^\.\*/);
ab6c937d
JP
602 if ($value_pd >= $file_pd &&
603 range_is_maintained($start, $end) &&
604 range_has_maintainer($start, $end)) {
6ef1c52e
JP
605 $exact_pattern_match_hash{$file} = 1;
606 }
683c6f8f
JP
607 if ($pattern_depth == 0 ||
608 (($file_pd - $value_pd) < $pattern_depth)) {
609 $hash{$tvi} = $value_pd;
610 }
272a8979
JP
611 }
612 }
613 }
614 }
615 }
683c6f8f 616 $tvi = $end + 1;
1d606b4e 617 }
272a8979 618
683c6f8f
JP
619 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
620 add_categories($line);
621 if ($sections) {
622 my $i;
623 my $start = find_starting_index($line);
624 my $end = find_ending_index($line);
625 for ($i = $start; $i < $end; $i++) {
626 my $line = $typevalue[$i];
627 if ($line =~ /^[FX]:/) { ##Restore file patterns
628 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
629 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
630 $line =~ s/\\\./\./g; ##Convert \. to .
631 $line =~ s/\.\*/\*/g; ##Convert .* to *
632 }
633 $line =~ s/^([A-Z]):/$1:\t/g;
634 print("$line\n");
4b76c9da 635 }
683c6f8f 636 print("\n");
4b76c9da 637 }
6ffd9485 638 }
dace8e30 639 }
cb7301c7 640
683c6f8f
JP
641 if ($keywords) {
642 @keyword_tvi = sort_and_uniq(@keyword_tvi);
643 foreach my $line (@keyword_tvi) {
644 add_categories($line);
645 }
dcf36a92 646 }
dcf36a92 647
b9e2331d
JP
648 foreach my $email (@email_to, @list_to) {
649 $email->[0] = deduplicate_email($email->[0]);
650 }
6ef1c52e
JP
651
652 foreach my $file (@files) {
653 if ($email &&
654 ($email_git || ($email_git_fallback &&
655 !$exact_pattern_match_hash{$file}))) {
656 vcs_file_signoffs($file);
657 }
658 if ($email && $email_git_blame) {
659 vcs_file_blame($file);
660 }
661 }
662
683c6f8f
JP
663 if ($email) {
664 foreach my $chief (@penguin_chief) {
665 if ($chief =~ m/^(.*):(.*)/) {
666 my $email_address;
0e70e83d 667
683c6f8f
JP
668 $email_address = format_email($1, $2, $email_usename);
669 if ($email_git_penguin_chiefs) {
670 push(@email_to, [$email_address, 'chief penguin']);
671 } else {
672 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
673 }
cb7301c7
JP
674 }
675 }
03372dbb 676
683c6f8f
JP
677 foreach my $email (@file_emails) {
678 my ($name, $address) = parse_email($email);
03372dbb 679
683c6f8f
JP
680 my $tmp_email = format_email($name, $address, $email_usename);
681 push_email_address($tmp_email, '');
682 add_role($tmp_email, 'in file');
683 }
03372dbb 684 }
cb7301c7 685
290603c1 686 my @to = ();
683c6f8f
JP
687 if ($email || $email_list) {
688 if ($email) {
689 @to = (@to, @email_to);
690 }
691 if ($email_list) {
692 @to = (@to, @list_to);
dace8e30 693 }
290603c1 694 }
cb7301c7 695
6ef1c52e 696 if ($interactive) {
b9e2331d 697 @to = interactive_get_maintainers(\@to);
6ef1c52e 698 }
cb7301c7 699
683c6f8f 700 return @to;
cb7301c7
JP
701}
702
cb7301c7
JP
703sub file_match_pattern {
704 my ($file, $pattern) = @_;
705 if (substr($pattern, -1) eq "/") {
706 if ($file =~ m@^$pattern@) {
707 return 1;
708 }
709 } else {
710 if ($file =~ m@^$pattern@) {
711 my $s1 = ($file =~ tr@/@@);
712 my $s2 = ($pattern =~ tr@/@@);
713 if ($s1 == $s2) {
714 return 1;
715 }
716 }
717 }
718 return 0;
719}
720
721sub usage {
722 print <<EOT;
723usage: $P [options] patchfile
870020f9 724 $P [options] -f file|directory
cb7301c7
JP
725version: $V
726
727MAINTAINER field selection options:
728 --email => print email address(es) if any
729 --git => include recent git \*-by: signers
e4d26b02 730 --git-all-signature-types => include signers regardless of signature type
683c6f8f 731 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
e3e9d114 732 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
cb7301c7 733 --git-chief-penguins => include ${penguin_chiefs}
e4d26b02
JP
734 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
735 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
736 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
f5492666 737 --git-blame => use git blame to find modified commits for patch or file
e4d26b02
JP
738 --git-since => git history to use (default: $email_git_since)
739 --hg-since => hg history to use (default: $email_hg_since)
dace8e30 740 --interactive => display a menu (mostly useful if used with the --git option)
cb7301c7
JP
741 --m => include maintainer(s) if any
742 --n => include name 'Full Name <addr\@domain.tld>'
743 --l => include list(s) if any
744 --s => include subscriber only list(s) if any
11ecf53c 745 --remove-duplicates => minimize duplicate email names/addresses
3c7385b8
JP
746 --roles => show roles (status:subsystem, git-signer, list, etc...)
747 --rolestats => show roles and statistics (commits/total_commits, %)
03372dbb 748 --file-emails => add email addresses found in -f file (default: 0 (off))
cb7301c7
JP
749 --scm => print SCM tree(s) if any
750 --status => print status if any
751 --subsystem => print subsystem name if any
752 --web => print website(s) if any
753
754Output type options:
755 --separator [, ] => separator for multiple entries on 1 line
42498316 756 using --separator also sets --nomultiline if --separator is not [, ]
cb7301c7
JP
757 --multiline => print 1 entry per line
758
cb7301c7 759Other options:
3fb55652 760 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
b9e2331d
JP
761 --keywords => scan patch for keywords (default: $keywords)
762 --sections => print all of the subsystem sections with pattern matches
763 --mailmap => use .mailmap file (default: $email_use_mailmap)
f5f5078d 764 --version => show version
cb7301c7
JP
765 --help => show this help information
766
3fb55652 767Default options:
7e1863af
JP
768 [--email --nogit --git-fallback --m --n --l --multiline -pattern-depth=0
769 --remove-duplicates --rolestats]
3fb55652 770
870020f9
JP
771Notes:
772 Using "-f directory" may give unexpected results:
f5492666
JP
773 Used with "--git", git signators for _all_ files in and below
774 directory are examined as git recurses directories.
775 Any specified X: (exclude) pattern matches are _not_ ignored.
776 Used with "--nogit", directory is used as a pattern match,
60db31ac
JP
777 no individual file within the directory or subdirectory
778 is matched.
f5492666
JP
779 Used with "--git-blame", does not iterate all files in directory
780 Using "--git-blame" is slow and may add old committers and authors
781 that are no longer active maintainers to the output.
3c7385b8
JP
782 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
783 other automated tools that expect only ["name"] <email address>
784 may not work because of additional output after <email address>.
785 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
786 not the percentage of the entire file authored. # of commits is
787 not a good measure of amount of code authored. 1 major commit may
788 contain a thousand lines, 5 trivial commits may modify a single line.
60db31ac
JP
789 If git is not installed, but mercurial (hg) is installed and an .hg
790 repository exists, the following options apply to mercurial:
791 --git,
792 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
793 --git-blame
794 Use --hg-since not --git-since to control date selection
368669da
JP
795 File ".get_maintainer.conf", if it exists in the linux kernel source root
796 directory, can change whatever get_maintainer defaults are desired.
797 Entries in this file can be any command line argument.
798 This file is prepended to any additional command line arguments.
799 Multiple lines and # comments are allowed.
cb7301c7
JP
800EOT
801}
802
803sub top_of_kernel_tree {
47abc722 804 my ($lk_path) = @_;
cb7301c7 805
47abc722
JP
806 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
807 $lk_path .= "/";
808 }
809 if ( (-f "${lk_path}COPYING")
810 && (-f "${lk_path}CREDITS")
811 && (-f "${lk_path}Kbuild")
812 && (-f "${lk_path}MAINTAINERS")
813 && (-f "${lk_path}Makefile")
814 && (-f "${lk_path}README")
815 && (-d "${lk_path}Documentation")
816 && (-d "${lk_path}arch")
817 && (-d "${lk_path}include")
818 && (-d "${lk_path}drivers")
819 && (-d "${lk_path}fs")
820 && (-d "${lk_path}init")
821 && (-d "${lk_path}ipc")
822 && (-d "${lk_path}kernel")
823 && (-d "${lk_path}lib")
824 && (-d "${lk_path}scripts")) {
825 return 1;
826 }
827 return 0;
cb7301c7
JP
828}
829
0e70e83d
JP
830sub parse_email {
831 my ($formatted_email) = @_;
832
833 my $name = "";
834 my $address = "";
835
11ecf53c 836 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
0e70e83d
JP
837 $name = $1;
838 $address = $2;
11ecf53c 839 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
0e70e83d 840 $address = $1;
b781655a 841 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
0e70e83d
JP
842 $address = $1;
843 }
cb7301c7
JP
844
845 $name =~ s/^\s+|\s+$//g;
d789504a 846 $name =~ s/^\"|\"$//g;
0e70e83d 847 $address =~ s/^\s+|\s+$//g;
cb7301c7 848
a63ceb4c 849 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
0e70e83d
JP
850 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
851 $name = "\"$name\"";
852 }
853
854 return ($name, $address);
855}
856
857sub format_email {
a8af2430 858 my ($name, $address, $usename) = @_;
0e70e83d
JP
859
860 my $formatted_email;
861
862 $name =~ s/^\s+|\s+$//g;
863 $name =~ s/^\"|\"$//g;
864 $address =~ s/^\s+|\s+$//g;
cb7301c7 865
a63ceb4c 866 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
cb7301c7 867 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
0e70e83d
JP
868 $name = "\"$name\"";
869 }
870
a8af2430 871 if ($usename) {
0e70e83d
JP
872 if ("$name" eq "") {
873 $formatted_email = "$address";
874 } else {
a8af2430 875 $formatted_email = "$name <$address>";
0e70e83d 876 }
cb7301c7 877 } else {
0e70e83d 878 $formatted_email = $address;
cb7301c7 879 }
0e70e83d 880
cb7301c7
JP
881 return $formatted_email;
882}
883
272a8979
JP
884sub find_first_section {
885 my $index = 0;
886
887 while ($index < @typevalue) {
888 my $tv = $typevalue[$index];
889 if (($tv =~ m/^(\C):\s*(.*)/)) {
890 last;
891 }
892 $index++;
893 }
894
895 return $index;
896}
897
b781655a 898sub find_starting_index {
b781655a
JP
899 my ($index) = @_;
900
901 while ($index > 0) {
902 my $tv = $typevalue[$index];
903 if (!($tv =~ m/^(\C):\s*(.*)/)) {
904 last;
905 }
906 $index--;
907 }
908
909 return $index;
910}
911
912sub find_ending_index {
cb7301c7
JP
913 my ($index) = @_;
914
b781655a 915 while ($index < @typevalue) {
cb7301c7 916 my $tv = $typevalue[$index];
b781655a
JP
917 if (!($tv =~ m/^(\C):\s*(.*)/)) {
918 last;
919 }
920 $index++;
921 }
922
923 return $index;
924}
925
3c7385b8
JP
926sub get_maintainer_role {
927 my ($index) = @_;
928
929 my $i;
930 my $start = find_starting_index($index);
931 my $end = find_ending_index($index);
932
933 my $role;
934 my $subsystem = $typevalue[$start];
935 if (length($subsystem) > 20) {
936 $subsystem = substr($subsystem, 0, 17);
937 $subsystem =~ s/\s*$//;
938 $subsystem = $subsystem . "...";
939 }
940
941 for ($i = $start + 1; $i < $end; $i++) {
942 my $tv = $typevalue[$i];
943 if ($tv =~ m/^(\C):\s*(.*)/) {
944 my $ptype = $1;
945 my $pvalue = $2;
946 if ($ptype eq "S") {
947 $role = $pvalue;
948 }
949 }
950 }
951
952 $role = lc($role);
953 if ($role eq "supported") {
954 $role = "supporter";
955 } elsif ($role eq "maintained") {
956 $role = "maintainer";
957 } elsif ($role eq "odd fixes") {
958 $role = "odd fixer";
959 } elsif ($role eq "orphan") {
960 $role = "orphan minder";
961 } elsif ($role eq "obsolete") {
962 $role = "obsolete minder";
963 } elsif ($role eq "buried alive in reporters") {
964 $role = "chief penguin";
965 }
966
967 return $role . ":" . $subsystem;
968}
969
970sub get_list_role {
971 my ($index) = @_;
972
973 my $i;
974 my $start = find_starting_index($index);
975 my $end = find_ending_index($index);
976
977 my $subsystem = $typevalue[$start];
978 if (length($subsystem) > 20) {
979 $subsystem = substr($subsystem, 0, 17);
980 $subsystem =~ s/\s*$//;
981 $subsystem = $subsystem . "...";
982 }
983
984 if ($subsystem eq "THE REST") {
985 $subsystem = "";
986 }
987
988 return $subsystem;
989}
990
b781655a
JP
991sub add_categories {
992 my ($index) = @_;
993
994 my $i;
995 my $start = find_starting_index($index);
996 my $end = find_ending_index($index);
997
998 push(@subsystem, $typevalue[$start]);
999
1000 for ($i = $start + 1; $i < $end; $i++) {
1001 my $tv = $typevalue[$i];
290603c1 1002 if ($tv =~ m/^(\C):\s*(.*)/) {
cb7301c7
JP
1003 my $ptype = $1;
1004 my $pvalue = $2;
1005 if ($ptype eq "L") {
290603c1
JP
1006 my $list_address = $pvalue;
1007 my $list_additional = "";
3c7385b8
JP
1008 my $list_role = get_list_role($i);
1009
1010 if ($list_role ne "") {
1011 $list_role = ":" . $list_role;
1012 }
290603c1
JP
1013 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1014 $list_address = $1;
1015 $list_additional = $2;
1016 }
bdf7c685 1017 if ($list_additional =~ m/subscribers-only/) {
cb7301c7 1018 if ($email_subscriber_list) {
6ef1c52e
JP
1019 if (!$hash_list_to{lc($list_address)}) {
1020 $hash_list_to{lc($list_address)} = 1;
683c6f8f
JP
1021 push(@list_to, [$list_address,
1022 "subscriber list${list_role}"]);
1023 }
cb7301c7
JP
1024 }
1025 } else {
1026 if ($email_list) {
6ef1c52e
JP
1027 if (!$hash_list_to{lc($list_address)}) {
1028 $hash_list_to{lc($list_address)} = 1;
683c6f8f
JP
1029 push(@list_to, [$list_address,
1030 "open list${list_role}"]);
1031 }
cb7301c7
JP
1032 }
1033 }
1034 } elsif ($ptype eq "M") {
0e70e83d
JP
1035 my ($name, $address) = parse_email($pvalue);
1036 if ($name eq "") {
b781655a
JP
1037 if ($i > 0) {
1038 my $tv = $typevalue[$i - 1];
0e70e83d
JP
1039 if ($tv =~ m/^(\C):\s*(.*)/) {
1040 if ($1 eq "P") {
1041 $name = $2;
a8af2430 1042 $pvalue = format_email($name, $address, $email_usename);
5f2441e9
JP
1043 }
1044 }
1045 }
1046 }
0e70e83d 1047 if ($email_maintainer) {
3c7385b8
JP
1048 my $role = get_maintainer_role($i);
1049 push_email_addresses($pvalue, $role);
cb7301c7
JP
1050 }
1051 } elsif ($ptype eq "T") {
1052 push(@scm, $pvalue);
1053 } elsif ($ptype eq "W") {
1054 push(@web, $pvalue);
1055 } elsif ($ptype eq "S") {
1056 push(@status, $pvalue);
1057 }
cb7301c7
JP
1058 }
1059 }
1060}
1061
11ecf53c
JP
1062sub email_inuse {
1063 my ($name, $address) = @_;
1064
1065 return 1 if (($name eq "") && ($address eq ""));
6ef1c52e
JP
1066 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1067 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
0e70e83d 1068
0e70e83d
JP
1069 return 0;
1070}
1071
1b5e1cf6 1072sub push_email_address {
3c7385b8 1073 my ($line, $role) = @_;
1b5e1cf6 1074
0e70e83d 1075 my ($name, $address) = parse_email($line);
1b5e1cf6 1076
b781655a
JP
1077 if ($address eq "") {
1078 return 0;
1079 }
1080
11ecf53c 1081 if (!$email_remove_duplicates) {
a8af2430 1082 push(@email_to, [format_email($name, $address, $email_usename), $role]);
11ecf53c 1083 } elsif (!email_inuse($name, $address)) {
a8af2430 1084 push(@email_to, [format_email($name, $address, $email_usename), $role]);
fae99206 1085 $email_hash_name{lc($name)}++ if ($name ne "");
6ef1c52e 1086 $email_hash_address{lc($address)}++;
1b5e1cf6 1087 }
b781655a
JP
1088
1089 return 1;
1b5e1cf6
JP
1090}
1091
1092sub push_email_addresses {
3c7385b8 1093 my ($address, $role) = @_;
1b5e1cf6
JP
1094
1095 my @address_list = ();
1096
5f2441e9 1097 if (rfc822_valid($address)) {
3c7385b8 1098 push_email_address($address, $role);
5f2441e9 1099 } elsif (@address_list = rfc822_validlist($address)) {
1b5e1cf6
JP
1100 my $array_count = shift(@address_list);
1101 while (my $entry = shift(@address_list)) {
3c7385b8 1102 push_email_address($entry, $role);
1b5e1cf6 1103 }
5f2441e9 1104 } else {
3c7385b8 1105 if (!push_email_address($address, $role)) {
b781655a
JP
1106 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1107 }
1b5e1cf6 1108 }
1b5e1cf6
JP
1109}
1110
3c7385b8
JP
1111sub add_role {
1112 my ($line, $role) = @_;
1113
1114 my ($name, $address) = parse_email($line);
a8af2430 1115 my $email = format_email($name, $address, $email_usename);
3c7385b8
JP
1116
1117 foreach my $entry (@email_to) {
1118 if ($email_remove_duplicates) {
1119 my ($entry_name, $entry_address) = parse_email($entry->[0]);
03372dbb
JP
1120 if (($name eq $entry_name || $address eq $entry_address)
1121 && ($role eq "" || !($entry->[1] =~ m/$role/))
1122 ) {
3c7385b8
JP
1123 if ($entry->[1] eq "") {
1124 $entry->[1] = "$role";
1125 } else {
1126 $entry->[1] = "$entry->[1],$role";
1127 }
1128 }
1129 } else {
03372dbb
JP
1130 if ($email eq $entry->[0]
1131 && ($role eq "" || !($entry->[1] =~ m/$role/))
1132 ) {
3c7385b8
JP
1133 if ($entry->[1] eq "") {
1134 $entry->[1] = "$role";
1135 } else {
1136 $entry->[1] = "$entry->[1],$role";
1137 }
1138 }
1139 }
1140 }
1141}
1142
cb7301c7
JP
1143sub which {
1144 my ($bin) = @_;
1145
f5f5078d 1146 foreach my $path (split(/:/, $ENV{PATH})) {
cb7301c7
JP
1147 if (-e "$path/$bin") {
1148 return "$path/$bin";
1149 }
1150 }
1151
1152 return "";
1153}
1154
bcde44ed
JP
1155sub which_conf {
1156 my ($conf) = @_;
1157
1158 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1159 if (-e "$path/$conf") {
1160 return "$path/$conf";
1161 }
1162 }
1163
1164 return "";
1165}
1166
7fa8ff2e 1167sub mailmap_email {
b9e2331d 1168 my ($line) = @_;
7fa8ff2e 1169
47abc722
JP
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};
8cbb3a77 1189 }
47abc722
JP
1190 }
1191 return format_email($real_name, $real_address, 1);
7fa8ff2e
FM
1192}
1193
1194sub mailmap {
1195 my (@addresses) = @_;
1196
b9e2331d 1197 my @mapped_emails = ();
7fa8ff2e 1198 foreach my $line (@addresses) {
b9e2331d 1199 push(@mapped_emails, mailmap_email($line));
8cbb3a77 1200 }
b9e2331d
JP
1201 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1202 return @mapped_emails;
7fa8ff2e
FM
1203}
1204
1205sub merge_by_realname {
47abc722
JP
1206 my %address_map;
1207 my (@emails) = @_;
b9e2331d 1208
47abc722
JP
1209 foreach my $email (@emails) {
1210 my ($name, $address) = parse_email($email);
b9e2331d 1211 if (exists $address_map{$name}) {
47abc722 1212 $address = $address_map{$name};
b9e2331d
JP
1213 $email = format_email($name, $address, 1);
1214 } else {
1215 $address_map{$name} = $address;
7fa8ff2e 1216 }
47abc722 1217 }
8cbb3a77
JP
1218}
1219
60db31ac
JP
1220sub git_execute_cmd {
1221 my ($cmd) = @_;
1222 my @lines = ();
cb7301c7 1223
60db31ac
JP
1224 my $output = `$cmd`;
1225 $output =~ s/^\s*//gm;
1226 @lines = split("\n", $output);
1227
1228 return @lines;
a8af2430
JP
1229}
1230
60db31ac 1231sub hg_execute_cmd {
a8af2430 1232 my ($cmd) = @_;
60db31ac
JP
1233 my @lines = ();
1234
1235 my $output = `$cmd`;
1236 @lines = split("\n", $output);
a8af2430 1237
60db31ac
JP
1238 return @lines;
1239}
1240
683c6f8f
JP
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) {
b9e2331d 1254 $signer = deduplicate_email($signer);
683c6f8f
JP
1255 }
1256
1257 return (\@type, \@signature_lines);
1258}
1259
60db31ac
JP
1260sub vcs_find_signers {
1261 my ($cmd) = @_;
a8af2430 1262 my $commits;
683c6f8f
JP
1263 my @lines = ();
1264 my @signatures = ();
a8af2430 1265
60db31ac 1266 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
cb7301c7 1267
60db31ac 1268 my $pattern = $VCS_cmds{"commit_pattern"};
cb7301c7 1269
60db31ac 1270 $commits = grep(/$pattern/, @lines); # of commits
afa81ee1 1271
683c6f8f 1272 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
63ab52db 1273
683c6f8f 1274 return (0, @signatures) if !@signatures;
63ab52db 1275
683c6f8f
JP
1276 save_commits_by_author(@lines) if ($interactive);
1277 save_commits_by_signer(@lines) if ($interactive);
0e70e83d 1278
683c6f8f
JP
1279 if (!$email_git_penguin_chiefs) {
1280 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
a8af2430
JP
1281 }
1282
683c6f8f
JP
1283 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1284
1285 return ($commits, @$signers_ref);
a8af2430
JP
1286}
1287
63ab52db
JP
1288sub vcs_find_author {
1289 my ($cmd) = @_;
1290 my @lines = ();
1291
1292 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1293
1294 if (!$email_git_penguin_chiefs) {
1295 @lines = grep(!/${penguin_chiefs}/i, @lines);
1296 }
1297
1298 return @lines if !@lines;
1299
683c6f8f 1300 my @authors = ();
63ab52db 1301 foreach my $line (@lines) {
683c6f8f
JP
1302 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1303 my $author = $1;
1304 my ($name, $address) = parse_email($author);
1305 $author = format_email($name, $address, 1);
1306 push(@authors, $author);
1307 }
63ab52db
JP
1308 }
1309
683c6f8f
JP
1310 save_commits_by_author(@lines) if ($interactive);
1311 save_commits_by_signer(@lines) if ($interactive);
1312
1313 return @authors;
63ab52db
JP
1314}
1315
60db31ac
JP
1316sub vcs_save_commits {
1317 my ($cmd) = @_;
1318 my @lines = ();
1319 my @commits = ();
1320
1321 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1322
1323 foreach my $line (@lines) {
1324 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1325 push(@commits, $1);
1326 }
1327 }
1328
1329 return @commits;
1330}
1331
1332sub vcs_blame {
1333 my ($file) = @_;
1334 my $cmd;
1335 my @commits = ();
1336
1337 return @commits if (!(-f $file));
1338
1339 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1340 my @all_commits = ();
1341
1342 $cmd = $VCS_cmds{"blame_file_cmd"};
1343 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1344 @all_commits = vcs_save_commits($cmd);
1345
1346 foreach my $file_range_diff (@range) {
1347 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1348 my $diff_file = $1;
1349 my $diff_start = $2;
1350 my $diff_length = $3;
1351 next if ("$file" ne "$diff_file");
1352 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1353 push(@commits, $all_commits[$i]);
1354 }
1355 }
1356 } elsif (@range) {
1357 foreach my $file_range_diff (@range) {
1358 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1359 my $diff_file = $1;
1360 my $diff_start = $2;
1361 my $diff_length = $3;
1362 next if ("$file" ne "$diff_file");
1363 $cmd = $VCS_cmds{"blame_range_cmd"};
1364 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1365 push(@commits, vcs_save_commits($cmd));
1366 }
1367 } else {
1368 $cmd = $VCS_cmds{"blame_file_cmd"};
1369 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1370 @commits = vcs_save_commits($cmd);
1371 }
1372
63ab52db
JP
1373 foreach my $commit (@commits) {
1374 $commit =~ s/^\^//g;
1375 }
1376
60db31ac
JP
1377 return @commits;
1378}
1379
1380my $printed_novcs = 0;
1381sub vcs_exists {
1382 %VCS_cmds = %VCS_cmds_git;
1383 return 1 if eval $VCS_cmds{"available"};
1384 %VCS_cmds = %VCS_cmds_hg;
683c6f8f 1385 return 2 if eval $VCS_cmds{"available"};
60db31ac
JP
1386 %VCS_cmds = ();
1387 if (!$printed_novcs) {
1388 warn("$P: No supported VCS found. Add --nogit to options?\n");
1389 warn("Using a git repository produces better results.\n");
1390 warn("Try Linus Torvalds' latest git repository using:\n");
1391 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
1392 $printed_novcs = 1;
1393 }
1394 return 0;
1395}
1396
683c6f8f 1397sub vcs_is_git {
b9e2331d 1398 vcs_exists();
683c6f8f
JP
1399 return $vcs_used == 1;
1400}
1401
1402sub vcs_is_hg {
1403 return $vcs_used == 2;
1404}
1405
6ef1c52e 1406sub interactive_get_maintainers {
683c6f8f 1407 my ($list_ref) = @_;
dace8e30
FM
1408 my @list = @$list_ref;
1409
683c6f8f 1410 vcs_exists();
dace8e30
FM
1411
1412 my %selected;
683c6f8f
JP
1413 my %authored;
1414 my %signed;
dace8e30 1415 my $count = 0;
6ef1c52e 1416 my $maintained = 0;
6ef1c52e 1417 foreach my $entry (@list) {
b9e2331d
JP
1418 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1419 $selected{$count} = 1;
683c6f8f
JP
1420 $authored{$count} = 0;
1421 $signed{$count} = 0;
1422 $count++;
dace8e30
FM
1423 }
1424
1425 #menu loop
683c6f8f
JP
1426 my $done = 0;
1427 my $print_options = 0;
1428 my $redraw = 1;
1429 while (!$done) {
1430 $count = 0;
1431 if ($redraw) {
6ef1c52e
JP
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";
683c6f8f
JP
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";
dace8e30 1459 }
dace8e30 1460 }
683c6f8f
JP
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()) {
b9e2331d
JP
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
dace8e30 1490 }
b9e2331d
JP
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
dace8e30 1504 }
683c6f8f
JP
1505 print STDERR
1506"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1507
1508 my $input = <STDIN>;
dace8e30
FM
1509 chomp($input);
1510
683c6f8f
JP
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;
dace8e30 1534 }
683c6f8f
JP
1535 } elsif ($sel eq "0") {
1536 for (my $i = 0; $i < $count; $i++) {
1537 $selected{$i} = !$selected{$i};
1538 }
b9e2331d
JP
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 }
683c6f8f
JP
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);
dace8e30 1587 } else {
683c6f8f
JP
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;
dace8e30 1612 }
683c6f8f
JP
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;
b9e2331d
JP
1629 } elsif ($sel eq "m") {
1630 bool_invert(\$email_use_mailmap);
1631 read_mailmap();
1632 $rerun = 1;
683c6f8f
JP
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 }
6ef1c52e
JP
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
47abc722 1649If you have git or hg installed, you can choose to summarize the commit
6ef1c52e
JP
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
683c6f8f
JP
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);
6ef1c52e 1670 goto &get_maintainers;
683c6f8f
JP
1671 }
1672 }
dace8e30
FM
1673
1674 #drop not selected entries
1675 $count = 0;
683c6f8f
JP
1676 my @new_emailto = ();
1677 foreach my $entry (@list) {
1678 if ($selected{$count}) {
1679 push(@new_emailto, $list[$count]);
dace8e30
FM
1680 }
1681 $count++;
1682 }
683c6f8f 1683 return @new_emailto;
dace8e30
FM
1684}
1685
683c6f8f
JP
1686sub bool_invert {
1687 my ($bool_ref) = @_;
1688
1689 if ($$bool_ref) {
1690 $$bool_ref = 0;
1691 } else {
1692 $$bool_ref = 1;
1693 }
dace8e30
FM
1694}
1695
b9e2331d
JP
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
fae99206 1708 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
b9e2331d
JP
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
683c6f8f
JP
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;
b9e2331d 1736 $author = deduplicate_email($author);
683c6f8f
JP
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 }
dace8e30 1756 }
dace8e30
FM
1757}
1758
683c6f8f
JP
1759sub save_commits_by_signer {
1760 my (@lines) = @_;
1761
1762 my $commit = "";
1763 my $subject = "";
dace8e30 1764
683c6f8f
JP
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
b9e2331d 1777 $signer = deduplicate_email($signer);
6ef1c52e 1778
683c6f8f
JP
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 }
dace8e30
FM
1794}
1795
60db31ac 1796sub vcs_assign {
a8af2430
JP
1797 my ($role, $divisor, @lines) = @_;
1798
1799 my %hash;
1800 my $count = 0;
1801
a8af2430
JP
1802 return if (@lines <= 0);
1803
1804 if ($divisor <= 0) {
60db31ac 1805 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
a8af2430 1806 $divisor = 1;
3c7385b8 1807 }
8cbb3a77 1808
7fa8ff2e 1809 @lines = mailmap(@lines);
0e70e83d 1810
63ab52db
JP
1811 return if (@lines <= 0);
1812
0e70e83d 1813 @lines = sort(@lines);
11ecf53c 1814
0e70e83d 1815 # uniq -c
11ecf53c
JP
1816 $hash{$_}++ for @lines;
1817
0e70e83d 1818 # sort -rn
0e70e83d 1819 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
11ecf53c 1820 my $sign_offs = $hash{$line};
a8af2430 1821 my $percent = $sign_offs * 100 / $divisor;
3c7385b8 1822
a8af2430 1823 $percent = 100 if ($percent > 100);
11ecf53c
JP
1824 $count++;
1825 last if ($sign_offs < $email_git_min_signatures ||
1826 $count > $email_git_max_maintainers ||
a8af2430 1827 $percent < $email_git_min_percent);
3c7385b8 1828 push_email_address($line, '');
3c7385b8 1829 if ($output_rolestats) {
a8af2430
JP
1830 my $fmt_percent = sprintf("%.0f", $percent);
1831 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1832 } else {
1833 add_role($line, $role);
3c7385b8 1834 }
f5492666
JP
1835 }
1836}
1837
60db31ac 1838sub vcs_file_signoffs {
a8af2430
JP
1839 my ($file) = @_;
1840
1841 my @signers = ();
60db31ac 1842 my $commits;
f5492666 1843
683c6f8f
JP
1844 $vcs_used = vcs_exists();
1845 return if (!$vcs_used);
a8af2430 1846
60db31ac
JP
1847 my $cmd = $VCS_cmds{"find_signers_cmd"};
1848 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
f5492666 1849
60db31ac 1850 ($commits, @signers) = vcs_find_signers($cmd);
b9e2331d
JP
1851
1852 foreach my $signer (@signers) {
1853 $signer = deduplicate_email($signer);
1854 }
1855
60db31ac 1856 vcs_assign("commit_signer", $commits, @signers);
f5492666
JP
1857}
1858
60db31ac 1859sub vcs_file_blame {
f5492666
JP
1860 my ($file) = @_;
1861
a8af2430 1862 my @signers = ();
63ab52db 1863 my @all_commits = ();
60db31ac 1864 my @commits = ();
a8af2430 1865 my $total_commits;
63ab52db 1866 my $total_lines;
f5492666 1867
683c6f8f
JP
1868 $vcs_used = vcs_exists();
1869 return if (!$vcs_used);
f5492666 1870
63ab52db
JP
1871 @all_commits = vcs_blame($file);
1872 @commits = uniq(@all_commits);
a8af2430 1873 $total_commits = @commits;
63ab52db 1874 $total_lines = @all_commits;
8cbb3a77 1875
683c6f8f
JP
1876 if ($email_git_blame_signatures) {
1877 if (vcs_is_hg()) {
1878 my $commit_count;
1879 my @commit_signers = ();
1880 my $commit = join(" -r ", @commits);
1881 my $cmd;
8cbb3a77 1882
683c6f8f
JP
1883 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1884 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
60db31ac 1885
683c6f8f 1886 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
63ab52db 1887
683c6f8f
JP
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
1897
1898 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1899
1900 push(@signers, @commit_signers);
1901 }
1902 }
f5492666
JP
1903 }
1904
a8af2430 1905 if ($from_filename) {
63ab52db
JP
1906 if ($output_rolestats) {
1907 my @blame_signers;
683c6f8f
JP
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;
b9e2331d
JP
1933 $author = deduplicate_email($author);
1934 push(@authors, $author);
683c6f8f
JP
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;
b9e2331d
JP
1950
1951 my $formatted_author = deduplicate_email($author[0]);
1952
683c6f8f
JP
1953 my $count = grep(/$commit/, @all_commits);
1954 for ($i = 0; $i < $count ; $i++) {
b9e2331d 1955 push(@blame_signers, $formatted_author);
683c6f8f 1956 }
63ab52db
JP
1957 }
1958 }
1959 if (@blame_signers) {
1960 vcs_assign("authored lines", $total_lines, @blame_signers);
1961 }
1962 }
b9e2331d
JP
1963 foreach my $signer (@signers) {
1964 $signer = deduplicate_email($signer);
1965 }
60db31ac 1966 vcs_assign("commits", $total_commits, @signers);
a8af2430 1967 } else {
b9e2331d
JP
1968 foreach my $signer (@signers) {
1969 $signer = deduplicate_email($signer);
1970 }
60db31ac 1971 vcs_assign("modified commits", $total_commits, @signers);
cb7301c7 1972 }
cb7301c7
JP
1973}
1974
1975sub uniq {
a8af2430 1976 my (@parms) = @_;
cb7301c7
JP
1977
1978 my %saw;
1979 @parms = grep(!$saw{$_}++, @parms);
1980 return @parms;
1981}
1982
1983sub sort_and_uniq {
a8af2430 1984 my (@parms) = @_;
cb7301c7
JP
1985
1986 my %saw;
1987 @parms = sort @parms;
1988 @parms = grep(!$saw{$_}++, @parms);
1989 return @parms;
1990}
1991
03372dbb
JP
1992sub clean_file_emails {
1993 my (@file_emails) = @_;
1994 my @fmt_emails = ();
1995
1996 foreach my $email (@file_emails) {
1997 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
1998 my ($name, $address) = parse_email($email);
1999 if ($name eq '"[,\.]"') {
2000 $name = "";
2001 }
2002
2003 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
2004 if (@nw > 2) {
2005 my $first = $nw[@nw - 3];
2006 my $middle = $nw[@nw - 2];
2007 my $last = $nw[@nw - 1];
2008
2009 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2010 (length($first) == 2 && substr($first, -1) eq ".")) ||
2011 (length($middle) == 1 ||
2012 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2013 $name = "$first $middle $last";
2014 } else {
2015 $name = "$middle $last";
2016 }
2017 }
2018
2019 if (substr($name, -1) =~ /[,\.]/) {
2020 $name = substr($name, 0, length($name) - 1);
2021 } elsif (substr($name, -2) =~ /[,\.]"/) {
2022 $name = substr($name, 0, length($name) - 2) . '"';
2023 }
2024
2025 if (substr($name, 0, 1) =~ /[,\.]/) {
2026 $name = substr($name, 1, length($name) - 1);
2027 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2028 $name = '"' . substr($name, 2, length($name) - 2);
2029 }
2030
2031 my $fmt_email = format_email($name, $address, $email_usename);
2032 push(@fmt_emails, $fmt_email);
2033 }
2034 return @fmt_emails;
2035}
2036
3c7385b8
JP
2037sub merge_email {
2038 my @lines;
2039 my %saw;
2040
2041 for (@_) {
2042 my ($address, $role) = @$_;
2043 if (!$saw{$address}) {
2044 if ($output_roles) {
60db31ac 2045 push(@lines, "$address ($role)");
3c7385b8 2046 } else {
60db31ac 2047 push(@lines, $address);
3c7385b8
JP
2048 }
2049 $saw{$address} = 1;
2050 }
2051 }
2052
2053 return @lines;
2054}
2055
cb7301c7 2056sub output {
a8af2430 2057 my (@parms) = @_;
cb7301c7
JP
2058
2059 if ($output_multiline) {
2060 foreach my $line (@parms) {
2061 print("${line}\n");
2062 }
2063 } else {
2064 print(join($output_separator, @parms));
2065 print("\n");
2066 }
2067}
1b5e1cf6
JP
2068
2069my $rfc822re;
2070
2071sub make_rfc822re {
2072# Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2073# comment. We must allow for rfc822_lwsp (or comments) after each of these.
2074# This regexp will only work on addresses which have had comments stripped
2075# and replaced with rfc822_lwsp.
2076
2077 my $specials = '()<>@,;:\\\\".\\[\\]';
2078 my $controls = '\\000-\\037\\177';
2079
2080 my $dtext = "[^\\[\\]\\r\\\\]";
2081 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2082
2083 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2084
2085# Use zero-width assertion to spot the limit of an atom. A simple
2086# $rfc822_lwsp* causes the regexp engine to hang occasionally.
2087 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2088 my $word = "(?:$atom|$quoted_string)";
2089 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2090
2091 my $sub_domain = "(?:$atom|$domain_literal)";
2092 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2093
2094 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2095
2096 my $phrase = "$word*";
2097 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2098 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2099 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2100
2101 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2102 my $address = "(?:$mailbox|$group)";
2103
2104 return "$rfc822_lwsp*$address";
2105}
2106
2107sub rfc822_strip_comments {
2108 my $s = shift;
2109# Recursively remove comments, and replace with a single space. The simpler
2110# regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2111# chars in atoms, for example.
2112
2113 while ($s =~ s/^((?:[^"\\]|\\.)*
2114 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2115 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2116 return $s;
2117}
2118
2119# valid: returns true if the parameter is an RFC822 valid address
2120#
22dd5b0c 2121sub rfc822_valid {
1b5e1cf6
JP
2122 my $s = rfc822_strip_comments(shift);
2123
2124 if (!$rfc822re) {
2125 $rfc822re = make_rfc822re();
2126 }
2127
2128 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2129}
2130
2131# validlist: In scalar context, returns true if the parameter is an RFC822
2132# valid list of addresses.
2133#
2134# In list context, returns an empty list on failure (an invalid
2135# address was found); otherwise a list whose first element is the
2136# number of addresses found and whose remaining elements are the
2137# addresses. This is needed to disambiguate failure (invalid)
2138# from success with no addresses found, because an empty string is
2139# a valid list.
2140
22dd5b0c 2141sub rfc822_validlist {
1b5e1cf6
JP
2142 my $s = rfc822_strip_comments(shift);
2143
2144 if (!$rfc822re) {
2145 $rfc822re = make_rfc822re();
2146 }
2147 # * null list items are valid according to the RFC
2148 # * the '1' business is to aid in distinguishing failure from no results
2149
2150 my @r;
2151 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2152 $s =~ m/^$rfc822_char*$/) {
5f2441e9 2153 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
60db31ac 2154 push(@r, $1);
1b5e1cf6
JP
2155 }
2156 return wantarray ? (scalar(@r), @r) : 1;
2157 }
60db31ac 2158 return wantarray ? () : 0;
1b5e1cf6 2159}