]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blame - scripts/get_maintainer.pl
printk: allow setting DEFAULT_MESSAGE_LEVEL via Kconfig
[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
SH
422 or die "$P: Can't open $file: $!\n";
423 while (<$patch>) {
dcf36a92 424 my $patch_line = $_;
4a7fdb5f
JP
425 if (m/^\+\+\+\s+(\S+)/) {
426 my $filename = $1;
427 $filename =~ s@^[^/]*/@@;
428 $filename =~ s@\n@@;
f5492666 429 $lastfile = $filename;
4a7fdb5f 430 push(@files, $filename);
f5492666
JP
431 } elsif (m/^\@\@ -(\d+),(\d+)/) {
432 if ($email_git_blame) {
433 push(@range, "$lastfile:$1:$2");
434 }
dcf36a92
JP
435 } elsif ($keywords) {
436 foreach my $line (keys %keyword_hash) {
437 if ($patch_line =~ m/^[+-].*$keyword_hash{$line}/x) {
438 push(@keyword_tvi, $line);
439 }
440 }
4a7fdb5f 441 }
cb7301c7 442 }
22dd5b0c
SH
443 close($patch);
444
4a7fdb5f 445 if ($file_cnt == @files) {
7f29fd27 446 warn "$P: file '${file}' doesn't appear to be a patch. "
4a7fdb5f
JP
447 . "Add -f to options?\n";
448 }
449 @files = sort_and_uniq(@files);
cb7301c7 450 }
cb7301c7
JP
451}
452
03372dbb
JP
453@file_emails = uniq(@file_emails);
454
683c6f8f
JP
455my %email_hash_name;
456my %email_hash_address;
cb7301c7 457my @email_to = ();
683c6f8f 458my %hash_list_to;
290603c1 459my @list_to = ();
cb7301c7
JP
460my @scm = ();
461my @web = ();
462my @subsystem = ();
463my @status = ();
b9e2331d
JP
464my %deduplicate_name_hash = ();
465my %deduplicate_address_hash = ();
683c6f8f 466my $signature_pattern;
cb7301c7 467
6ef1c52e 468my @maintainers = get_maintainers();
cb7301c7 469
6ef1c52e
JP
470if (@maintainers) {
471 @maintainers = merge_email(@maintainers);
472 output(@maintainers);
473}
683c6f8f
JP
474
475if ($scm) {
476 @scm = uniq(@scm);
477 output(@scm);
478}
479
480if ($status) {
481 @status = uniq(@status);
482 output(@status);
483}
484
485if ($subsystem) {
486 @subsystem = uniq(@subsystem);
487 output(@subsystem);
488}
489
490if ($web) {
491 @web = uniq(@web);
492 output(@web);
493}
494
495exit($exit);
496
ab6c937d
JP
497sub range_is_maintained {
498 my ($start, $end) = @_;
499
500 for (my $i = $start; $i < $end; $i++) {
501 my $line = $typevalue[$i];
502 if ($line =~ m/^(\C):\s*(.*)/) {
503 my $type = $1;
504 my $value = $2;
505 if ($type eq 'S') {
506 if ($value =~ /(maintain|support)/i) {
507 return 1;
508 }
509 }
510 }
511 }
512 return 0;
513}
514
515sub range_has_maintainer {
516 my ($start, $end) = @_;
517
518 for (my $i = $start; $i < $end; $i++) {
519 my $line = $typevalue[$i];
520 if ($line =~ m/^(\C):\s*(.*)/) {
521 my $type = $1;
522 my $value = $2;
523 if ($type eq 'M') {
524 return 1;
525 }
526 }
527 }
528 return 0;
529}
530
6ef1c52e 531sub get_maintainers {
683c6f8f
JP
532 %email_hash_name = ();
533 %email_hash_address = ();
534 %commit_author_hash = ();
535 %commit_signer_hash = ();
536 @email_to = ();
537 %hash_list_to = ();
538 @list_to = ();
539 @scm = ();
540 @web = ();
541 @subsystem = ();
542 @status = ();
b9e2331d
JP
543 %deduplicate_name_hash = ();
544 %deduplicate_address_hash = ();
683c6f8f
JP
545 if ($email_git_all_signature_types) {
546 $signature_pattern = "(.+?)[Bb][Yy]:";
547 } else {
548 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
549 }
550
551 # Find responsible parties
552
b9e2331d 553 my %exact_pattern_match_hash = ();
6ef1c52e 554
683c6f8f
JP
555 foreach my $file (@files) {
556
557 my %hash;
683c6f8f
JP
558 my $tvi = find_first_section();
559 while ($tvi < @typevalue) {
560 my $start = find_starting_index($tvi);
561 my $end = find_ending_index($tvi);
562 my $exclude = 0;
563 my $i;
564
565 #Do not match excluded file patterns
272a8979 566
272a8979
JP
567 for ($i = $start; $i < $end; $i++) {
568 my $line = $typevalue[$i];
569 if ($line =~ m/^(\C):\s*(.*)/) {
570 my $type = $1;
571 my $value = $2;
683c6f8f 572 if ($type eq 'X') {
272a8979 573 if (file_match_pattern($file, $value)) {
683c6f8f
JP
574 $exclude = 1;
575 last;
576 }
577 }
578 }
579 }
580
581 if (!$exclude) {
582 for ($i = $start; $i < $end; $i++) {
583 my $line = $typevalue[$i];
584 if ($line =~ m/^(\C):\s*(.*)/) {
585 my $type = $1;
586 my $value = $2;
587 if ($type eq 'F') {
588 if (file_match_pattern($file, $value)) {
589 my $value_pd = ($value =~ tr@/@@);
590 my $file_pd = ($file =~ tr@/@@);
591 $value_pd++ if (substr($value,-1,1) ne "/");
592 $value_pd = -1 if ($value =~ /^\.\*/);
ab6c937d
JP
593 if ($value_pd >= $file_pd &&
594 range_is_maintained($start, $end) &&
595 range_has_maintainer($start, $end)) {
6ef1c52e
JP
596 $exact_pattern_match_hash{$file} = 1;
597 }
683c6f8f
JP
598 if ($pattern_depth == 0 ||
599 (($file_pd - $value_pd) < $pattern_depth)) {
600 $hash{$tvi} = $value_pd;
601 }
272a8979
JP
602 }
603 }
604 }
605 }
606 }
683c6f8f 607 $tvi = $end + 1;
1d606b4e 608 }
272a8979 609
683c6f8f
JP
610 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
611 add_categories($line);
612 if ($sections) {
613 my $i;
614 my $start = find_starting_index($line);
615 my $end = find_ending_index($line);
616 for ($i = $start; $i < $end; $i++) {
617 my $line = $typevalue[$i];
618 if ($line =~ /^[FX]:/) { ##Restore file patterns
619 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
620 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
621 $line =~ s/\\\./\./g; ##Convert \. to .
622 $line =~ s/\.\*/\*/g; ##Convert .* to *
623 }
624 $line =~ s/^([A-Z]):/$1:\t/g;
625 print("$line\n");
4b76c9da 626 }
683c6f8f 627 print("\n");
4b76c9da 628 }
6ffd9485 629 }
dace8e30 630 }
cb7301c7 631
683c6f8f
JP
632 if ($keywords) {
633 @keyword_tvi = sort_and_uniq(@keyword_tvi);
634 foreach my $line (@keyword_tvi) {
635 add_categories($line);
636 }
dcf36a92 637 }
dcf36a92 638
b9e2331d
JP
639 foreach my $email (@email_to, @list_to) {
640 $email->[0] = deduplicate_email($email->[0]);
641 }
6ef1c52e
JP
642
643 foreach my $file (@files) {
644 if ($email &&
645 ($email_git || ($email_git_fallback &&
646 !$exact_pattern_match_hash{$file}))) {
647 vcs_file_signoffs($file);
648 }
649 if ($email && $email_git_blame) {
650 vcs_file_blame($file);
651 }
652 }
653
683c6f8f
JP
654 if ($email) {
655 foreach my $chief (@penguin_chief) {
656 if ($chief =~ m/^(.*):(.*)/) {
657 my $email_address;
0e70e83d 658
683c6f8f
JP
659 $email_address = format_email($1, $2, $email_usename);
660 if ($email_git_penguin_chiefs) {
661 push(@email_to, [$email_address, 'chief penguin']);
662 } else {
663 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
664 }
cb7301c7
JP
665 }
666 }
03372dbb 667
683c6f8f
JP
668 foreach my $email (@file_emails) {
669 my ($name, $address) = parse_email($email);
03372dbb 670
683c6f8f
JP
671 my $tmp_email = format_email($name, $address, $email_usename);
672 push_email_address($tmp_email, '');
673 add_role($tmp_email, 'in file');
674 }
03372dbb 675 }
cb7301c7 676
290603c1 677 my @to = ();
683c6f8f
JP
678 if ($email || $email_list) {
679 if ($email) {
680 @to = (@to, @email_to);
681 }
682 if ($email_list) {
683 @to = (@to, @list_to);
dace8e30 684 }
290603c1 685 }
cb7301c7 686
6ef1c52e 687 if ($interactive) {
b9e2331d 688 @to = interactive_get_maintainers(\@to);
6ef1c52e 689 }
cb7301c7 690
683c6f8f 691 return @to;
cb7301c7
JP
692}
693
cb7301c7
JP
694sub file_match_pattern {
695 my ($file, $pattern) = @_;
696 if (substr($pattern, -1) eq "/") {
697 if ($file =~ m@^$pattern@) {
698 return 1;
699 }
700 } else {
701 if ($file =~ m@^$pattern@) {
702 my $s1 = ($file =~ tr@/@@);
703 my $s2 = ($pattern =~ tr@/@@);
704 if ($s1 == $s2) {
705 return 1;
706 }
707 }
708 }
709 return 0;
710}
711
712sub usage {
713 print <<EOT;
714usage: $P [options] patchfile
870020f9 715 $P [options] -f file|directory
cb7301c7
JP
716version: $V
717
718MAINTAINER field selection options:
719 --email => print email address(es) if any
720 --git => include recent git \*-by: signers
e4d26b02 721 --git-all-signature-types => include signers regardless of signature type
683c6f8f 722 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
e3e9d114 723 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
cb7301c7 724 --git-chief-penguins => include ${penguin_chiefs}
e4d26b02
JP
725 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
726 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
727 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
f5492666 728 --git-blame => use git blame to find modified commits for patch or file
e4d26b02
JP
729 --git-since => git history to use (default: $email_git_since)
730 --hg-since => hg history to use (default: $email_hg_since)
dace8e30 731 --interactive => display a menu (mostly useful if used with the --git option)
cb7301c7
JP
732 --m => include maintainer(s) if any
733 --n => include name 'Full Name <addr\@domain.tld>'
734 --l => include list(s) if any
735 --s => include subscriber only list(s) if any
11ecf53c 736 --remove-duplicates => minimize duplicate email names/addresses
3c7385b8
JP
737 --roles => show roles (status:subsystem, git-signer, list, etc...)
738 --rolestats => show roles and statistics (commits/total_commits, %)
03372dbb 739 --file-emails => add email addresses found in -f file (default: 0 (off))
cb7301c7
JP
740 --scm => print SCM tree(s) if any
741 --status => print status if any
742 --subsystem => print subsystem name if any
743 --web => print website(s) if any
744
745Output type options:
746 --separator [, ] => separator for multiple entries on 1 line
42498316 747 using --separator also sets --nomultiline if --separator is not [, ]
cb7301c7
JP
748 --multiline => print 1 entry per line
749
cb7301c7 750Other options:
3fb55652 751 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
b9e2331d
JP
752 --keywords => scan patch for keywords (default: $keywords)
753 --sections => print all of the subsystem sections with pattern matches
754 --mailmap => use .mailmap file (default: $email_use_mailmap)
f5f5078d 755 --version => show version
cb7301c7
JP
756 --help => show this help information
757
3fb55652 758Default options:
7e1863af
JP
759 [--email --nogit --git-fallback --m --n --l --multiline -pattern-depth=0
760 --remove-duplicates --rolestats]
3fb55652 761
870020f9
JP
762Notes:
763 Using "-f directory" may give unexpected results:
f5492666
JP
764 Used with "--git", git signators for _all_ files in and below
765 directory are examined as git recurses directories.
766 Any specified X: (exclude) pattern matches are _not_ ignored.
767 Used with "--nogit", directory is used as a pattern match,
60db31ac
JP
768 no individual file within the directory or subdirectory
769 is matched.
f5492666
JP
770 Used with "--git-blame", does not iterate all files in directory
771 Using "--git-blame" is slow and may add old committers and authors
772 that are no longer active maintainers to the output.
3c7385b8
JP
773 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
774 other automated tools that expect only ["name"] <email address>
775 may not work because of additional output after <email address>.
776 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
777 not the percentage of the entire file authored. # of commits is
778 not a good measure of amount of code authored. 1 major commit may
779 contain a thousand lines, 5 trivial commits may modify a single line.
60db31ac
JP
780 If git is not installed, but mercurial (hg) is installed and an .hg
781 repository exists, the following options apply to mercurial:
782 --git,
783 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
784 --git-blame
785 Use --hg-since not --git-since to control date selection
368669da
JP
786 File ".get_maintainer.conf", if it exists in the linux kernel source root
787 directory, can change whatever get_maintainer defaults are desired.
788 Entries in this file can be any command line argument.
789 This file is prepended to any additional command line arguments.
790 Multiple lines and # comments are allowed.
cb7301c7
JP
791EOT
792}
793
794sub top_of_kernel_tree {
47abc722 795 my ($lk_path) = @_;
cb7301c7 796
47abc722
JP
797 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
798 $lk_path .= "/";
799 }
800 if ( (-f "${lk_path}COPYING")
801 && (-f "${lk_path}CREDITS")
802 && (-f "${lk_path}Kbuild")
803 && (-f "${lk_path}MAINTAINERS")
804 && (-f "${lk_path}Makefile")
805 && (-f "${lk_path}README")
806 && (-d "${lk_path}Documentation")
807 && (-d "${lk_path}arch")
808 && (-d "${lk_path}include")
809 && (-d "${lk_path}drivers")
810 && (-d "${lk_path}fs")
811 && (-d "${lk_path}init")
812 && (-d "${lk_path}ipc")
813 && (-d "${lk_path}kernel")
814 && (-d "${lk_path}lib")
815 && (-d "${lk_path}scripts")) {
816 return 1;
817 }
818 return 0;
cb7301c7
JP
819}
820
0e70e83d
JP
821sub parse_email {
822 my ($formatted_email) = @_;
823
824 my $name = "";
825 my $address = "";
826
11ecf53c 827 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
0e70e83d
JP
828 $name = $1;
829 $address = $2;
11ecf53c 830 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
0e70e83d 831 $address = $1;
b781655a 832 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
0e70e83d
JP
833 $address = $1;
834 }
cb7301c7
JP
835
836 $name =~ s/^\s+|\s+$//g;
d789504a 837 $name =~ s/^\"|\"$//g;
0e70e83d 838 $address =~ s/^\s+|\s+$//g;
cb7301c7 839
a63ceb4c 840 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
0e70e83d
JP
841 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
842 $name = "\"$name\"";
843 }
844
845 return ($name, $address);
846}
847
848sub format_email {
a8af2430 849 my ($name, $address, $usename) = @_;
0e70e83d
JP
850
851 my $formatted_email;
852
853 $name =~ s/^\s+|\s+$//g;
854 $name =~ s/^\"|\"$//g;
855 $address =~ s/^\s+|\s+$//g;
cb7301c7 856
a63ceb4c 857 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
cb7301c7 858 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
0e70e83d
JP
859 $name = "\"$name\"";
860 }
861
a8af2430 862 if ($usename) {
0e70e83d
JP
863 if ("$name" eq "") {
864 $formatted_email = "$address";
865 } else {
a8af2430 866 $formatted_email = "$name <$address>";
0e70e83d 867 }
cb7301c7 868 } else {
0e70e83d 869 $formatted_email = $address;
cb7301c7 870 }
0e70e83d 871
cb7301c7
JP
872 return $formatted_email;
873}
874
272a8979
JP
875sub find_first_section {
876 my $index = 0;
877
878 while ($index < @typevalue) {
879 my $tv = $typevalue[$index];
880 if (($tv =~ m/^(\C):\s*(.*)/)) {
881 last;
882 }
883 $index++;
884 }
885
886 return $index;
887}
888
b781655a 889sub find_starting_index {
b781655a
JP
890 my ($index) = @_;
891
892 while ($index > 0) {
893 my $tv = $typevalue[$index];
894 if (!($tv =~ m/^(\C):\s*(.*)/)) {
895 last;
896 }
897 $index--;
898 }
899
900 return $index;
901}
902
903sub find_ending_index {
cb7301c7
JP
904 my ($index) = @_;
905
b781655a 906 while ($index < @typevalue) {
cb7301c7 907 my $tv = $typevalue[$index];
b781655a
JP
908 if (!($tv =~ m/^(\C):\s*(.*)/)) {
909 last;
910 }
911 $index++;
912 }
913
914 return $index;
915}
916
3c7385b8
JP
917sub get_maintainer_role {
918 my ($index) = @_;
919
920 my $i;
921 my $start = find_starting_index($index);
922 my $end = find_ending_index($index);
923
924 my $role;
925 my $subsystem = $typevalue[$start];
926 if (length($subsystem) > 20) {
927 $subsystem = substr($subsystem, 0, 17);
928 $subsystem =~ s/\s*$//;
929 $subsystem = $subsystem . "...";
930 }
931
932 for ($i = $start + 1; $i < $end; $i++) {
933 my $tv = $typevalue[$i];
934 if ($tv =~ m/^(\C):\s*(.*)/) {
935 my $ptype = $1;
936 my $pvalue = $2;
937 if ($ptype eq "S") {
938 $role = $pvalue;
939 }
940 }
941 }
942
943 $role = lc($role);
944 if ($role eq "supported") {
945 $role = "supporter";
946 } elsif ($role eq "maintained") {
947 $role = "maintainer";
948 } elsif ($role eq "odd fixes") {
949 $role = "odd fixer";
950 } elsif ($role eq "orphan") {
951 $role = "orphan minder";
952 } elsif ($role eq "obsolete") {
953 $role = "obsolete minder";
954 } elsif ($role eq "buried alive in reporters") {
955 $role = "chief penguin";
956 }
957
958 return $role . ":" . $subsystem;
959}
960
961sub get_list_role {
962 my ($index) = @_;
963
964 my $i;
965 my $start = find_starting_index($index);
966 my $end = find_ending_index($index);
967
968 my $subsystem = $typevalue[$start];
969 if (length($subsystem) > 20) {
970 $subsystem = substr($subsystem, 0, 17);
971 $subsystem =~ s/\s*$//;
972 $subsystem = $subsystem . "...";
973 }
974
975 if ($subsystem eq "THE REST") {
976 $subsystem = "";
977 }
978
979 return $subsystem;
980}
981
b781655a
JP
982sub add_categories {
983 my ($index) = @_;
984
985 my $i;
986 my $start = find_starting_index($index);
987 my $end = find_ending_index($index);
988
989 push(@subsystem, $typevalue[$start]);
990
991 for ($i = $start + 1; $i < $end; $i++) {
992 my $tv = $typevalue[$i];
290603c1 993 if ($tv =~ m/^(\C):\s*(.*)/) {
cb7301c7
JP
994 my $ptype = $1;
995 my $pvalue = $2;
996 if ($ptype eq "L") {
290603c1
JP
997 my $list_address = $pvalue;
998 my $list_additional = "";
3c7385b8
JP
999 my $list_role = get_list_role($i);
1000
1001 if ($list_role ne "") {
1002 $list_role = ":" . $list_role;
1003 }
290603c1
JP
1004 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1005 $list_address = $1;
1006 $list_additional = $2;
1007 }
bdf7c685 1008 if ($list_additional =~ m/subscribers-only/) {
cb7301c7 1009 if ($email_subscriber_list) {
6ef1c52e
JP
1010 if (!$hash_list_to{lc($list_address)}) {
1011 $hash_list_to{lc($list_address)} = 1;
683c6f8f
JP
1012 push(@list_to, [$list_address,
1013 "subscriber list${list_role}"]);
1014 }
cb7301c7
JP
1015 }
1016 } else {
1017 if ($email_list) {
6ef1c52e
JP
1018 if (!$hash_list_to{lc($list_address)}) {
1019 $hash_list_to{lc($list_address)} = 1;
683c6f8f
JP
1020 push(@list_to, [$list_address,
1021 "open list${list_role}"]);
1022 }
cb7301c7
JP
1023 }
1024 }
1025 } elsif ($ptype eq "M") {
0e70e83d
JP
1026 my ($name, $address) = parse_email($pvalue);
1027 if ($name eq "") {
b781655a
JP
1028 if ($i > 0) {
1029 my $tv = $typevalue[$i - 1];
0e70e83d
JP
1030 if ($tv =~ m/^(\C):\s*(.*)/) {
1031 if ($1 eq "P") {
1032 $name = $2;
a8af2430 1033 $pvalue = format_email($name, $address, $email_usename);
5f2441e9
JP
1034 }
1035 }
1036 }
1037 }
0e70e83d 1038 if ($email_maintainer) {
3c7385b8
JP
1039 my $role = get_maintainer_role($i);
1040 push_email_addresses($pvalue, $role);
cb7301c7
JP
1041 }
1042 } elsif ($ptype eq "T") {
1043 push(@scm, $pvalue);
1044 } elsif ($ptype eq "W") {
1045 push(@web, $pvalue);
1046 } elsif ($ptype eq "S") {
1047 push(@status, $pvalue);
1048 }
cb7301c7
JP
1049 }
1050 }
1051}
1052
11ecf53c
JP
1053sub email_inuse {
1054 my ($name, $address) = @_;
1055
1056 return 1 if (($name eq "") && ($address eq ""));
6ef1c52e
JP
1057 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1058 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
0e70e83d 1059
0e70e83d
JP
1060 return 0;
1061}
1062
1b5e1cf6 1063sub push_email_address {
3c7385b8 1064 my ($line, $role) = @_;
1b5e1cf6 1065
0e70e83d 1066 my ($name, $address) = parse_email($line);
1b5e1cf6 1067
b781655a
JP
1068 if ($address eq "") {
1069 return 0;
1070 }
1071
11ecf53c 1072 if (!$email_remove_duplicates) {
a8af2430 1073 push(@email_to, [format_email($name, $address, $email_usename), $role]);
11ecf53c 1074 } elsif (!email_inuse($name, $address)) {
a8af2430 1075 push(@email_to, [format_email($name, $address, $email_usename), $role]);
fae99206 1076 $email_hash_name{lc($name)}++ if ($name ne "");
6ef1c52e 1077 $email_hash_address{lc($address)}++;
1b5e1cf6 1078 }
b781655a
JP
1079
1080 return 1;
1b5e1cf6
JP
1081}
1082
1083sub push_email_addresses {
3c7385b8 1084 my ($address, $role) = @_;
1b5e1cf6
JP
1085
1086 my @address_list = ();
1087
5f2441e9 1088 if (rfc822_valid($address)) {
3c7385b8 1089 push_email_address($address, $role);
5f2441e9 1090 } elsif (@address_list = rfc822_validlist($address)) {
1b5e1cf6
JP
1091 my $array_count = shift(@address_list);
1092 while (my $entry = shift(@address_list)) {
3c7385b8 1093 push_email_address($entry, $role);
1b5e1cf6 1094 }
5f2441e9 1095 } else {
3c7385b8 1096 if (!push_email_address($address, $role)) {
b781655a
JP
1097 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1098 }
1b5e1cf6 1099 }
1b5e1cf6
JP
1100}
1101
3c7385b8
JP
1102sub add_role {
1103 my ($line, $role) = @_;
1104
1105 my ($name, $address) = parse_email($line);
a8af2430 1106 my $email = format_email($name, $address, $email_usename);
3c7385b8
JP
1107
1108 foreach my $entry (@email_to) {
1109 if ($email_remove_duplicates) {
1110 my ($entry_name, $entry_address) = parse_email($entry->[0]);
03372dbb
JP
1111 if (($name eq $entry_name || $address eq $entry_address)
1112 && ($role eq "" || !($entry->[1] =~ m/$role/))
1113 ) {
3c7385b8
JP
1114 if ($entry->[1] eq "") {
1115 $entry->[1] = "$role";
1116 } else {
1117 $entry->[1] = "$entry->[1],$role";
1118 }
1119 }
1120 } else {
03372dbb
JP
1121 if ($email eq $entry->[0]
1122 && ($role eq "" || !($entry->[1] =~ m/$role/))
1123 ) {
3c7385b8
JP
1124 if ($entry->[1] eq "") {
1125 $entry->[1] = "$role";
1126 } else {
1127 $entry->[1] = "$entry->[1],$role";
1128 }
1129 }
1130 }
1131 }
1132}
1133
cb7301c7
JP
1134sub which {
1135 my ($bin) = @_;
1136
f5f5078d 1137 foreach my $path (split(/:/, $ENV{PATH})) {
cb7301c7
JP
1138 if (-e "$path/$bin") {
1139 return "$path/$bin";
1140 }
1141 }
1142
1143 return "";
1144}
1145
bcde44ed
JP
1146sub which_conf {
1147 my ($conf) = @_;
1148
1149 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1150 if (-e "$path/$conf") {
1151 return "$path/$conf";
1152 }
1153 }
1154
1155 return "";
1156}
1157
7fa8ff2e 1158sub mailmap_email {
b9e2331d 1159 my ($line) = @_;
7fa8ff2e 1160
47abc722
JP
1161 my ($name, $address) = parse_email($line);
1162 my $email = format_email($name, $address, 1);
1163 my $real_name = $name;
1164 my $real_address = $address;
1165
1166 if (exists $mailmap->{names}->{$email} ||
1167 exists $mailmap->{addresses}->{$email}) {
1168 if (exists $mailmap->{names}->{$email}) {
1169 $real_name = $mailmap->{names}->{$email};
1170 }
1171 if (exists $mailmap->{addresses}->{$email}) {
1172 $real_address = $mailmap->{addresses}->{$email};
1173 }
1174 } else {
1175 if (exists $mailmap->{names}->{$address}) {
1176 $real_name = $mailmap->{names}->{$address};
1177 }
1178 if (exists $mailmap->{addresses}->{$address}) {
1179 $real_address = $mailmap->{addresses}->{$address};
8cbb3a77 1180 }
47abc722
JP
1181 }
1182 return format_email($real_name, $real_address, 1);
7fa8ff2e
FM
1183}
1184
1185sub mailmap {
1186 my (@addresses) = @_;
1187
b9e2331d 1188 my @mapped_emails = ();
7fa8ff2e 1189 foreach my $line (@addresses) {
b9e2331d 1190 push(@mapped_emails, mailmap_email($line));
8cbb3a77 1191 }
b9e2331d
JP
1192 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1193 return @mapped_emails;
7fa8ff2e
FM
1194}
1195
1196sub merge_by_realname {
47abc722
JP
1197 my %address_map;
1198 my (@emails) = @_;
b9e2331d 1199
47abc722
JP
1200 foreach my $email (@emails) {
1201 my ($name, $address) = parse_email($email);
b9e2331d 1202 if (exists $address_map{$name}) {
47abc722 1203 $address = $address_map{$name};
b9e2331d
JP
1204 $email = format_email($name, $address, 1);
1205 } else {
1206 $address_map{$name} = $address;
7fa8ff2e 1207 }
47abc722 1208 }
8cbb3a77
JP
1209}
1210
60db31ac
JP
1211sub git_execute_cmd {
1212 my ($cmd) = @_;
1213 my @lines = ();
cb7301c7 1214
60db31ac
JP
1215 my $output = `$cmd`;
1216 $output =~ s/^\s*//gm;
1217 @lines = split("\n", $output);
1218
1219 return @lines;
a8af2430
JP
1220}
1221
60db31ac 1222sub hg_execute_cmd {
a8af2430 1223 my ($cmd) = @_;
60db31ac
JP
1224 my @lines = ();
1225
1226 my $output = `$cmd`;
1227 @lines = split("\n", $output);
a8af2430 1228
60db31ac
JP
1229 return @lines;
1230}
1231
683c6f8f
JP
1232sub extract_formatted_signatures {
1233 my (@signature_lines) = @_;
1234
1235 my @type = @signature_lines;
1236
1237 s/\s*(.*):.*/$1/ for (@type);
1238
1239 # cut -f2- -d":"
1240 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1241
1242## Reformat email addresses (with names) to avoid badly written signatures
1243
1244 foreach my $signer (@signature_lines) {
b9e2331d 1245 $signer = deduplicate_email($signer);
683c6f8f
JP
1246 }
1247
1248 return (\@type, \@signature_lines);
1249}
1250
60db31ac
JP
1251sub vcs_find_signers {
1252 my ($cmd) = @_;
a8af2430 1253 my $commits;
683c6f8f
JP
1254 my @lines = ();
1255 my @signatures = ();
a8af2430 1256
60db31ac 1257 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
cb7301c7 1258
60db31ac 1259 my $pattern = $VCS_cmds{"commit_pattern"};
cb7301c7 1260
60db31ac 1261 $commits = grep(/$pattern/, @lines); # of commits
afa81ee1 1262
683c6f8f 1263 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
63ab52db 1264
683c6f8f 1265 return (0, @signatures) if !@signatures;
63ab52db 1266
683c6f8f
JP
1267 save_commits_by_author(@lines) if ($interactive);
1268 save_commits_by_signer(@lines) if ($interactive);
0e70e83d 1269
683c6f8f
JP
1270 if (!$email_git_penguin_chiefs) {
1271 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
a8af2430
JP
1272 }
1273
683c6f8f
JP
1274 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1275
1276 return ($commits, @$signers_ref);
a8af2430
JP
1277}
1278
63ab52db
JP
1279sub vcs_find_author {
1280 my ($cmd) = @_;
1281 my @lines = ();
1282
1283 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1284
1285 if (!$email_git_penguin_chiefs) {
1286 @lines = grep(!/${penguin_chiefs}/i, @lines);
1287 }
1288
1289 return @lines if !@lines;
1290
683c6f8f 1291 my @authors = ();
63ab52db 1292 foreach my $line (@lines) {
683c6f8f
JP
1293 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1294 my $author = $1;
1295 my ($name, $address) = parse_email($author);
1296 $author = format_email($name, $address, 1);
1297 push(@authors, $author);
1298 }
63ab52db
JP
1299 }
1300
683c6f8f
JP
1301 save_commits_by_author(@lines) if ($interactive);
1302 save_commits_by_signer(@lines) if ($interactive);
1303
1304 return @authors;
63ab52db
JP
1305}
1306
60db31ac
JP
1307sub vcs_save_commits {
1308 my ($cmd) = @_;
1309 my @lines = ();
1310 my @commits = ();
1311
1312 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1313
1314 foreach my $line (@lines) {
1315 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1316 push(@commits, $1);
1317 }
1318 }
1319
1320 return @commits;
1321}
1322
1323sub vcs_blame {
1324 my ($file) = @_;
1325 my $cmd;
1326 my @commits = ();
1327
1328 return @commits if (!(-f $file));
1329
1330 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1331 my @all_commits = ();
1332
1333 $cmd = $VCS_cmds{"blame_file_cmd"};
1334 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1335 @all_commits = vcs_save_commits($cmd);
1336
1337 foreach my $file_range_diff (@range) {
1338 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1339 my $diff_file = $1;
1340 my $diff_start = $2;
1341 my $diff_length = $3;
1342 next if ("$file" ne "$diff_file");
1343 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1344 push(@commits, $all_commits[$i]);
1345 }
1346 }
1347 } elsif (@range) {
1348 foreach my $file_range_diff (@range) {
1349 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1350 my $diff_file = $1;
1351 my $diff_start = $2;
1352 my $diff_length = $3;
1353 next if ("$file" ne "$diff_file");
1354 $cmd = $VCS_cmds{"blame_range_cmd"};
1355 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1356 push(@commits, vcs_save_commits($cmd));
1357 }
1358 } else {
1359 $cmd = $VCS_cmds{"blame_file_cmd"};
1360 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1361 @commits = vcs_save_commits($cmd);
1362 }
1363
63ab52db
JP
1364 foreach my $commit (@commits) {
1365 $commit =~ s/^\^//g;
1366 }
1367
60db31ac
JP
1368 return @commits;
1369}
1370
1371my $printed_novcs = 0;
1372sub vcs_exists {
1373 %VCS_cmds = %VCS_cmds_git;
1374 return 1 if eval $VCS_cmds{"available"};
1375 %VCS_cmds = %VCS_cmds_hg;
683c6f8f 1376 return 2 if eval $VCS_cmds{"available"};
60db31ac
JP
1377 %VCS_cmds = ();
1378 if (!$printed_novcs) {
1379 warn("$P: No supported VCS found. Add --nogit to options?\n");
1380 warn("Using a git repository produces better results.\n");
1381 warn("Try Linus Torvalds' latest git repository using:\n");
1382 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
1383 $printed_novcs = 1;
1384 }
1385 return 0;
1386}
1387
683c6f8f 1388sub vcs_is_git {
b9e2331d 1389 vcs_exists();
683c6f8f
JP
1390 return $vcs_used == 1;
1391}
1392
1393sub vcs_is_hg {
1394 return $vcs_used == 2;
1395}
1396
6ef1c52e 1397sub interactive_get_maintainers {
683c6f8f 1398 my ($list_ref) = @_;
dace8e30
FM
1399 my @list = @$list_ref;
1400
683c6f8f 1401 vcs_exists();
dace8e30
FM
1402
1403 my %selected;
683c6f8f
JP
1404 my %authored;
1405 my %signed;
dace8e30 1406 my $count = 0;
6ef1c52e 1407 my $maintained = 0;
6ef1c52e 1408 foreach my $entry (@list) {
b9e2331d
JP
1409 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1410 $selected{$count} = 1;
683c6f8f
JP
1411 $authored{$count} = 0;
1412 $signed{$count} = 0;
1413 $count++;
dace8e30
FM
1414 }
1415
1416 #menu loop
683c6f8f
JP
1417 my $done = 0;
1418 my $print_options = 0;
1419 my $redraw = 1;
1420 while (!$done) {
1421 $count = 0;
1422 if ($redraw) {
6ef1c52e
JP
1423 printf STDERR "\n%1s %2s %-65s",
1424 "*", "#", "email/list and role:stats";
1425 if ($email_git ||
1426 ($email_git_fallback && !$maintained) ||
1427 $email_git_blame) {
1428 print STDERR "auth sign";
1429 }
1430 print STDERR "\n";
683c6f8f
JP
1431 foreach my $entry (@list) {
1432 my $email = $entry->[0];
1433 my $role = $entry->[1];
1434 my $sel = "";
1435 $sel = "*" if ($selected{$count});
1436 my $commit_author = $commit_author_hash{$email};
1437 my $commit_signer = $commit_signer_hash{$email};
1438 my $authored = 0;
1439 my $signed = 0;
1440 $authored++ for (@{$commit_author});
1441 $signed++ for (@{$commit_signer});
1442 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1443 printf STDERR "%4d %4d", $authored, $signed
1444 if ($authored > 0 || $signed > 0);
1445 printf STDERR "\n %s\n", $role;
1446 if ($authored{$count}) {
1447 my $commit_author = $commit_author_hash{$email};
1448 foreach my $ref (@{$commit_author}) {
1449 print STDERR " Author: @{$ref}[1]\n";
dace8e30 1450 }
dace8e30 1451 }
683c6f8f
JP
1452 if ($signed{$count}) {
1453 my $commit_signer = $commit_signer_hash{$email};
1454 foreach my $ref (@{$commit_signer}) {
1455 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1456 }
1457 }
1458
1459 $count++;
1460 }
1461 }
1462 my $date_ref = \$email_git_since;
1463 $date_ref = \$email_hg_since if (vcs_is_hg());
1464 if ($print_options) {
1465 $print_options = 0;
1466 if (vcs_exists()) {
b9e2331d
JP
1467 print STDERR <<EOT
1468
1469Version Control options:
1470g use git history [$email_git]
1471gf use git-fallback [$email_git_fallback]
1472b use git blame [$email_git_blame]
1473bs use blame signatures [$email_git_blame_signatures]
1474c# minimum commits [$email_git_min_signatures]
1475%# min percent [$email_git_min_percent]
1476d# history to use [$$date_ref]
1477x# max maintainers [$email_git_max_maintainers]
1478t all signature types [$email_git_all_signature_types]
1479m use .mailmap [$email_use_mailmap]
1480EOT
dace8e30 1481 }
b9e2331d
JP
1482 print STDERR <<EOT
1483
1484Additional options:
14850 toggle all
1486tm toggle maintainers
1487tg toggle git entries
1488tl toggle open list entries
1489ts toggle subscriber list entries
1490f emails in file [$file_emails]
1491k keywords in file [$keywords]
1492r remove duplicates [$email_remove_duplicates]
1493p# pattern match depth [$pattern_depth]
1494EOT
dace8e30 1495 }
683c6f8f
JP
1496 print STDERR
1497"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1498
1499 my $input = <STDIN>;
dace8e30
FM
1500 chomp($input);
1501
683c6f8f
JP
1502 $redraw = 1;
1503 my $rerun = 0;
1504 my @wish = split(/[, ]+/, $input);
1505 foreach my $nr (@wish) {
1506 $nr = lc($nr);
1507 my $sel = substr($nr, 0, 1);
1508 my $str = substr($nr, 1);
1509 my $val = 0;
1510 $val = $1 if $str =~ /^(\d+)$/;
1511
1512 if ($sel eq "y") {
1513 $interactive = 0;
1514 $done = 1;
1515 $output_rolestats = 0;
1516 $output_roles = 0;
1517 last;
1518 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1519 $selected{$nr - 1} = !$selected{$nr - 1};
1520 } elsif ($sel eq "*" || $sel eq '^') {
1521 my $toggle = 0;
1522 $toggle = 1 if ($sel eq '*');
1523 for (my $i = 0; $i < $count; $i++) {
1524 $selected{$i} = $toggle;
dace8e30 1525 }
683c6f8f
JP
1526 } elsif ($sel eq "0") {
1527 for (my $i = 0; $i < $count; $i++) {
1528 $selected{$i} = !$selected{$i};
1529 }
b9e2331d
JP
1530 } elsif ($sel eq "t") {
1531 if (lc($str) eq "m") {
1532 for (my $i = 0; $i < $count; $i++) {
1533 $selected{$i} = !$selected{$i}
1534 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1535 }
1536 } elsif (lc($str) eq "g") {
1537 for (my $i = 0; $i < $count; $i++) {
1538 $selected{$i} = !$selected{$i}
1539 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1540 }
1541 } elsif (lc($str) eq "l") {
1542 for (my $i = 0; $i < $count; $i++) {
1543 $selected{$i} = !$selected{$i}
1544 if ($list[$i]->[1] =~ /^(open list)/i);
1545 }
1546 } elsif (lc($str) eq "s") {
1547 for (my $i = 0; $i < $count; $i++) {
1548 $selected{$i} = !$selected{$i}
1549 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1550 }
1551 }
683c6f8f
JP
1552 } elsif ($sel eq "a") {
1553 if ($val > 0 && $val <= $count) {
1554 $authored{$val - 1} = !$authored{$val - 1};
1555 } elsif ($str eq '*' || $str eq '^') {
1556 my $toggle = 0;
1557 $toggle = 1 if ($str eq '*');
1558 for (my $i = 0; $i < $count; $i++) {
1559 $authored{$i} = $toggle;
1560 }
1561 }
1562 } elsif ($sel eq "s") {
1563 if ($val > 0 && $val <= $count) {
1564 $signed{$val - 1} = !$signed{$val - 1};
1565 } elsif ($str eq '*' || $str eq '^') {
1566 my $toggle = 0;
1567 $toggle = 1 if ($str eq '*');
1568 for (my $i = 0; $i < $count; $i++) {
1569 $signed{$i} = $toggle;
1570 }
1571 }
1572 } elsif ($sel eq "o") {
1573 $print_options = 1;
1574 $redraw = 1;
1575 } elsif ($sel eq "g") {
1576 if ($str eq "f") {
1577 bool_invert(\$email_git_fallback);
dace8e30 1578 } else {
683c6f8f
JP
1579 bool_invert(\$email_git);
1580 }
1581 $rerun = 1;
1582 } elsif ($sel eq "b") {
1583 if ($str eq "s") {
1584 bool_invert(\$email_git_blame_signatures);
1585 } else {
1586 bool_invert(\$email_git_blame);
1587 }
1588 $rerun = 1;
1589 } elsif ($sel eq "c") {
1590 if ($val > 0) {
1591 $email_git_min_signatures = $val;
1592 $rerun = 1;
1593 }
1594 } elsif ($sel eq "x") {
1595 if ($val > 0) {
1596 $email_git_max_maintainers = $val;
1597 $rerun = 1;
1598 }
1599 } elsif ($sel eq "%") {
1600 if ($str ne "" && $val >= 0) {
1601 $email_git_min_percent = $val;
1602 $rerun = 1;
dace8e30 1603 }
683c6f8f
JP
1604 } elsif ($sel eq "d") {
1605 if (vcs_is_git()) {
1606 $email_git_since = $str;
1607 } elsif (vcs_is_hg()) {
1608 $email_hg_since = $str;
1609 }
1610 $rerun = 1;
1611 } elsif ($sel eq "t") {
1612 bool_invert(\$email_git_all_signature_types);
1613 $rerun = 1;
1614 } elsif ($sel eq "f") {
1615 bool_invert(\$file_emails);
1616 $rerun = 1;
1617 } elsif ($sel eq "r") {
1618 bool_invert(\$email_remove_duplicates);
1619 $rerun = 1;
b9e2331d
JP
1620 } elsif ($sel eq "m") {
1621 bool_invert(\$email_use_mailmap);
1622 read_mailmap();
1623 $rerun = 1;
683c6f8f
JP
1624 } elsif ($sel eq "k") {
1625 bool_invert(\$keywords);
1626 $rerun = 1;
1627 } elsif ($sel eq "p") {
1628 if ($str ne "" && $val >= 0) {
1629 $pattern_depth = $val;
1630 $rerun = 1;
1631 }
6ef1c52e
JP
1632 } elsif ($sel eq "h" || $sel eq "?") {
1633 print STDERR <<EOT
1634
1635Interactive mode allows you to select the various maintainers, submitters,
1636commit signers and mailing lists that could be CC'd on a patch.
1637
1638Any *'d entry is selected.
1639
47abc722 1640If you have git or hg installed, you can choose to summarize the commit
6ef1c52e
JP
1641history of files in the patch. Also, each line of the current file can
1642be matched to its commit author and that commits signers with blame.
1643
1644Various knobs exist to control the length of time for active commit
1645tracking, the maximum number of commit authors and signers to add,
1646and such.
1647
1648Enter selections at the prompt until you are satisfied that the selected
1649maintainers are appropriate. You may enter multiple selections separated
1650by either commas or spaces.
1651
1652EOT
683c6f8f
JP
1653 } else {
1654 print STDERR "invalid option: '$nr'\n";
1655 $redraw = 0;
1656 }
1657 }
1658 if ($rerun) {
1659 print STDERR "git-blame can be very slow, please have patience..."
1660 if ($email_git_blame);
6ef1c52e 1661 goto &get_maintainers;
683c6f8f
JP
1662 }
1663 }
dace8e30
FM
1664
1665 #drop not selected entries
1666 $count = 0;
683c6f8f
JP
1667 my @new_emailto = ();
1668 foreach my $entry (@list) {
1669 if ($selected{$count}) {
1670 push(@new_emailto, $list[$count]);
dace8e30
FM
1671 }
1672 $count++;
1673 }
683c6f8f 1674 return @new_emailto;
dace8e30
FM
1675}
1676
683c6f8f
JP
1677sub bool_invert {
1678 my ($bool_ref) = @_;
1679
1680 if ($$bool_ref) {
1681 $$bool_ref = 0;
1682 } else {
1683 $$bool_ref = 1;
1684 }
dace8e30
FM
1685}
1686
b9e2331d
JP
1687sub deduplicate_email {
1688 my ($email) = @_;
1689
1690 my $matched = 0;
1691 my ($name, $address) = parse_email($email);
1692 $email = format_email($name, $address, 1);
1693 $email = mailmap_email($email);
1694
1695 return $email if (!$email_remove_duplicates);
1696
1697 ($name, $address) = parse_email($email);
1698
fae99206 1699 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
b9e2331d
JP
1700 $name = $deduplicate_name_hash{lc($name)}->[0];
1701 $address = $deduplicate_name_hash{lc($name)}->[1];
1702 $matched = 1;
1703 } elsif ($deduplicate_address_hash{lc($address)}) {
1704 $name = $deduplicate_address_hash{lc($address)}->[0];
1705 $address = $deduplicate_address_hash{lc($address)}->[1];
1706 $matched = 1;
1707 }
1708 if (!$matched) {
1709 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
1710 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
1711 }
1712 $email = format_email($name, $address, 1);
1713 $email = mailmap_email($email);
1714 return $email;
1715}
1716
683c6f8f
JP
1717sub save_commits_by_author {
1718 my (@lines) = @_;
1719
1720 my @authors = ();
1721 my @commits = ();
1722 my @subjects = ();
1723
1724 foreach my $line (@lines) {
1725 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1726 my $author = $1;
b9e2331d 1727 $author = deduplicate_email($author);
683c6f8f
JP
1728 push(@authors, $author);
1729 }
1730 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1731 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1732 }
1733
1734 for (my $i = 0; $i < @authors; $i++) {
1735 my $exists = 0;
1736 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
1737 if (@{$ref}[0] eq $commits[$i] &&
1738 @{$ref}[1] eq $subjects[$i]) {
1739 $exists = 1;
1740 last;
1741 }
1742 }
1743 if (!$exists) {
1744 push(@{$commit_author_hash{$authors[$i]}},
1745 [ ($commits[$i], $subjects[$i]) ]);
1746 }
dace8e30 1747 }
dace8e30
FM
1748}
1749
683c6f8f
JP
1750sub save_commits_by_signer {
1751 my (@lines) = @_;
1752
1753 my $commit = "";
1754 my $subject = "";
dace8e30 1755
683c6f8f
JP
1756 foreach my $line (@lines) {
1757 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1758 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1759 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1760 my @signatures = ($line);
1761 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1762 my @types = @$types_ref;
1763 my @signers = @$signers_ref;
1764
1765 my $type = $types[0];
1766 my $signer = $signers[0];
1767
b9e2331d 1768 $signer = deduplicate_email($signer);
6ef1c52e 1769
683c6f8f
JP
1770 my $exists = 0;
1771 foreach my $ref(@{$commit_signer_hash{$signer}}) {
1772 if (@{$ref}[0] eq $commit &&
1773 @{$ref}[1] eq $subject &&
1774 @{$ref}[2] eq $type) {
1775 $exists = 1;
1776 last;
1777 }
1778 }
1779 if (!$exists) {
1780 push(@{$commit_signer_hash{$signer}},
1781 [ ($commit, $subject, $type) ]);
1782 }
1783 }
1784 }
dace8e30
FM
1785}
1786
60db31ac 1787sub vcs_assign {
a8af2430
JP
1788 my ($role, $divisor, @lines) = @_;
1789
1790 my %hash;
1791 my $count = 0;
1792
a8af2430
JP
1793 return if (@lines <= 0);
1794
1795 if ($divisor <= 0) {
60db31ac 1796 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
a8af2430 1797 $divisor = 1;
3c7385b8 1798 }
8cbb3a77 1799
7fa8ff2e 1800 @lines = mailmap(@lines);
0e70e83d 1801
63ab52db
JP
1802 return if (@lines <= 0);
1803
0e70e83d 1804 @lines = sort(@lines);
11ecf53c 1805
0e70e83d 1806 # uniq -c
11ecf53c
JP
1807 $hash{$_}++ for @lines;
1808
0e70e83d 1809 # sort -rn
0e70e83d 1810 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
11ecf53c 1811 my $sign_offs = $hash{$line};
a8af2430 1812 my $percent = $sign_offs * 100 / $divisor;
3c7385b8 1813
a8af2430 1814 $percent = 100 if ($percent > 100);
11ecf53c
JP
1815 $count++;
1816 last if ($sign_offs < $email_git_min_signatures ||
1817 $count > $email_git_max_maintainers ||
a8af2430 1818 $percent < $email_git_min_percent);
3c7385b8 1819 push_email_address($line, '');
3c7385b8 1820 if ($output_rolestats) {
a8af2430
JP
1821 my $fmt_percent = sprintf("%.0f", $percent);
1822 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1823 } else {
1824 add_role($line, $role);
3c7385b8 1825 }
f5492666
JP
1826 }
1827}
1828
60db31ac 1829sub vcs_file_signoffs {
a8af2430
JP
1830 my ($file) = @_;
1831
1832 my @signers = ();
60db31ac 1833 my $commits;
f5492666 1834
683c6f8f
JP
1835 $vcs_used = vcs_exists();
1836 return if (!$vcs_used);
a8af2430 1837
60db31ac
JP
1838 my $cmd = $VCS_cmds{"find_signers_cmd"};
1839 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
f5492666 1840
60db31ac 1841 ($commits, @signers) = vcs_find_signers($cmd);
b9e2331d
JP
1842
1843 foreach my $signer (@signers) {
1844 $signer = deduplicate_email($signer);
1845 }
1846
60db31ac 1847 vcs_assign("commit_signer", $commits, @signers);
f5492666
JP
1848}
1849
60db31ac 1850sub vcs_file_blame {
f5492666
JP
1851 my ($file) = @_;
1852
a8af2430 1853 my @signers = ();
63ab52db 1854 my @all_commits = ();
60db31ac 1855 my @commits = ();
a8af2430 1856 my $total_commits;
63ab52db 1857 my $total_lines;
f5492666 1858
683c6f8f
JP
1859 $vcs_used = vcs_exists();
1860 return if (!$vcs_used);
f5492666 1861
63ab52db
JP
1862 @all_commits = vcs_blame($file);
1863 @commits = uniq(@all_commits);
a8af2430 1864 $total_commits = @commits;
63ab52db 1865 $total_lines = @all_commits;
8cbb3a77 1866
683c6f8f
JP
1867 if ($email_git_blame_signatures) {
1868 if (vcs_is_hg()) {
1869 my $commit_count;
1870 my @commit_signers = ();
1871 my $commit = join(" -r ", @commits);
1872 my $cmd;
8cbb3a77 1873
683c6f8f
JP
1874 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1875 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
60db31ac 1876
683c6f8f 1877 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
63ab52db 1878
683c6f8f
JP
1879 push(@signers, @commit_signers);
1880 } else {
1881 foreach my $commit (@commits) {
1882 my $commit_count;
1883 my @commit_signers = ();
1884 my $cmd;
1885
1886 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1887 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1888
1889 ($commit_count, @commit_signers) = vcs_find_signers($cmd);
1890
1891 push(@signers, @commit_signers);
1892 }
1893 }
f5492666
JP
1894 }
1895
a8af2430 1896 if ($from_filename) {
63ab52db
JP
1897 if ($output_rolestats) {
1898 my @blame_signers;
683c6f8f
JP
1899 if (vcs_is_hg()) {{ # Double brace for last exit
1900 my $commit_count;
1901 my @commit_signers = ();
1902 @commits = uniq(@commits);
1903 @commits = sort(@commits);
1904 my $commit = join(" -r ", @commits);
1905 my $cmd;
1906
1907 $cmd = $VCS_cmds{"find_commit_author_cmd"};
1908 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1909
1910 my @lines = ();
1911
1912 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1913
1914 if (!$email_git_penguin_chiefs) {
1915 @lines = grep(!/${penguin_chiefs}/i, @lines);
1916 }
1917
1918 last if !@lines;
1919
1920 my @authors = ();
1921 foreach my $line (@lines) {
1922 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1923 my $author = $1;
b9e2331d
JP
1924 $author = deduplicate_email($author);
1925 push(@authors, $author);
683c6f8f
JP
1926 }
1927 }
1928
1929 save_commits_by_author(@lines) if ($interactive);
1930 save_commits_by_signer(@lines) if ($interactive);
1931
1932 push(@signers, @authors);
1933 }}
1934 else {
1935 foreach my $commit (@commits) {
1936 my $i;
1937 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1938 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1939 my @author = vcs_find_author($cmd);
1940 next if !@author;
b9e2331d
JP
1941
1942 my $formatted_author = deduplicate_email($author[0]);
1943
683c6f8f
JP
1944 my $count = grep(/$commit/, @all_commits);
1945 for ($i = 0; $i < $count ; $i++) {
b9e2331d 1946 push(@blame_signers, $formatted_author);
683c6f8f 1947 }
63ab52db
JP
1948 }
1949 }
1950 if (@blame_signers) {
1951 vcs_assign("authored lines", $total_lines, @blame_signers);
1952 }
1953 }
b9e2331d
JP
1954 foreach my $signer (@signers) {
1955 $signer = deduplicate_email($signer);
1956 }
60db31ac 1957 vcs_assign("commits", $total_commits, @signers);
a8af2430 1958 } else {
b9e2331d
JP
1959 foreach my $signer (@signers) {
1960 $signer = deduplicate_email($signer);
1961 }
60db31ac 1962 vcs_assign("modified commits", $total_commits, @signers);
cb7301c7 1963 }
cb7301c7
JP
1964}
1965
1966sub uniq {
a8af2430 1967 my (@parms) = @_;
cb7301c7
JP
1968
1969 my %saw;
1970 @parms = grep(!$saw{$_}++, @parms);
1971 return @parms;
1972}
1973
1974sub sort_and_uniq {
a8af2430 1975 my (@parms) = @_;
cb7301c7
JP
1976
1977 my %saw;
1978 @parms = sort @parms;
1979 @parms = grep(!$saw{$_}++, @parms);
1980 return @parms;
1981}
1982
03372dbb
JP
1983sub clean_file_emails {
1984 my (@file_emails) = @_;
1985 my @fmt_emails = ();
1986
1987 foreach my $email (@file_emails) {
1988 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
1989 my ($name, $address) = parse_email($email);
1990 if ($name eq '"[,\.]"') {
1991 $name = "";
1992 }
1993
1994 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
1995 if (@nw > 2) {
1996 my $first = $nw[@nw - 3];
1997 my $middle = $nw[@nw - 2];
1998 my $last = $nw[@nw - 1];
1999
2000 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
2001 (length($first) == 2 && substr($first, -1) eq ".")) ||
2002 (length($middle) == 1 ||
2003 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2004 $name = "$first $middle $last";
2005 } else {
2006 $name = "$middle $last";
2007 }
2008 }
2009
2010 if (substr($name, -1) =~ /[,\.]/) {
2011 $name = substr($name, 0, length($name) - 1);
2012 } elsif (substr($name, -2) =~ /[,\.]"/) {
2013 $name = substr($name, 0, length($name) - 2) . '"';
2014 }
2015
2016 if (substr($name, 0, 1) =~ /[,\.]/) {
2017 $name = substr($name, 1, length($name) - 1);
2018 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
2019 $name = '"' . substr($name, 2, length($name) - 2);
2020 }
2021
2022 my $fmt_email = format_email($name, $address, $email_usename);
2023 push(@fmt_emails, $fmt_email);
2024 }
2025 return @fmt_emails;
2026}
2027
3c7385b8
JP
2028sub merge_email {
2029 my @lines;
2030 my %saw;
2031
2032 for (@_) {
2033 my ($address, $role) = @$_;
2034 if (!$saw{$address}) {
2035 if ($output_roles) {
60db31ac 2036 push(@lines, "$address ($role)");
3c7385b8 2037 } else {
60db31ac 2038 push(@lines, $address);
3c7385b8
JP
2039 }
2040 $saw{$address} = 1;
2041 }
2042 }
2043
2044 return @lines;
2045}
2046
cb7301c7 2047sub output {
a8af2430 2048 my (@parms) = @_;
cb7301c7
JP
2049
2050 if ($output_multiline) {
2051 foreach my $line (@parms) {
2052 print("${line}\n");
2053 }
2054 } else {
2055 print(join($output_separator, @parms));
2056 print("\n");
2057 }
2058}
1b5e1cf6
JP
2059
2060my $rfc822re;
2061
2062sub make_rfc822re {
2063# Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2064# comment. We must allow for rfc822_lwsp (or comments) after each of these.
2065# This regexp will only work on addresses which have had comments stripped
2066# and replaced with rfc822_lwsp.
2067
2068 my $specials = '()<>@,;:\\\\".\\[\\]';
2069 my $controls = '\\000-\\037\\177';
2070
2071 my $dtext = "[^\\[\\]\\r\\\\]";
2072 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2073
2074 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2075
2076# Use zero-width assertion to spot the limit of an atom. A simple
2077# $rfc822_lwsp* causes the regexp engine to hang occasionally.
2078 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2079 my $word = "(?:$atom|$quoted_string)";
2080 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2081
2082 my $sub_domain = "(?:$atom|$domain_literal)";
2083 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2084
2085 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2086
2087 my $phrase = "$word*";
2088 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2089 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2090 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2091
2092 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2093 my $address = "(?:$mailbox|$group)";
2094
2095 return "$rfc822_lwsp*$address";
2096}
2097
2098sub rfc822_strip_comments {
2099 my $s = shift;
2100# Recursively remove comments, and replace with a single space. The simpler
2101# regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2102# chars in atoms, for example.
2103
2104 while ($s =~ s/^((?:[^"\\]|\\.)*
2105 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2106 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2107 return $s;
2108}
2109
2110# valid: returns true if the parameter is an RFC822 valid address
2111#
22dd5b0c 2112sub rfc822_valid {
1b5e1cf6
JP
2113 my $s = rfc822_strip_comments(shift);
2114
2115 if (!$rfc822re) {
2116 $rfc822re = make_rfc822re();
2117 }
2118
2119 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2120}
2121
2122# validlist: In scalar context, returns true if the parameter is an RFC822
2123# valid list of addresses.
2124#
2125# In list context, returns an empty list on failure (an invalid
2126# address was found); otherwise a list whose first element is the
2127# number of addresses found and whose remaining elements are the
2128# addresses. This is needed to disambiguate failure (invalid)
2129# from success with no addresses found, because an empty string is
2130# a valid list.
2131
22dd5b0c 2132sub rfc822_validlist {
1b5e1cf6
JP
2133 my $s = rfc822_strip_comments(shift);
2134
2135 if (!$rfc822re) {
2136 $rfc822re = make_rfc822re();
2137 }
2138 # * null list items are valid according to the RFC
2139 # * the '1' business is to aid in distinguishing failure from no results
2140
2141 my @r;
2142 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2143 $s =~ m/^$rfc822_char*$/) {
5f2441e9 2144 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
60db31ac 2145 push(@r, $1);
1b5e1cf6
JP
2146 }
2147 return wantarray ? (scalar(@r), @r) : 1;
2148 }
60db31ac 2149 return wantarray ? () : 0;
1b5e1cf6 2150}