Merge tag 'pci-v6.16-fixes-3' of git://git.kernel.org/pub/scm/linux/kernel/git/pci/pci
[linux-block.git] / scripts / get_maintainer.pl
CommitLineData
cb77f0d6 1#!/usr/bin/env perl
882ea1d6
JP
2# SPDX-License-Identifier: GPL-2.0
3#
cb7301c7
JP
4# (c) 2007, Joe Perches <joe@perches.com>
5# created from checkpatch.pl
6#
7# Print selected MAINTAINERS information for
8# the files modified in a patch or for a file
9#
3bd7bf5f
RK
10# usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
11# perl scripts/get_maintainer.pl [OPTIONS] -f <file>
cb7301c7 12
cb77f0d6 13use warnings;
cb7301c7
JP
14use strict;
15
16my $P = $0;
7e1863af 17my $V = '0.26';
cb7301c7
JP
18
19use Getopt::Long qw(:config no_auto_abbrev);
be17bddc 20use Cwd;
6f7d98ec 21use File::Find;
e33c9fe8 22use File::Spec::Functions;
9c334eb9 23use open qw(:std :encoding(UTF-8));
cb7301c7 24
be17bddc 25my $cur_path = fastgetcwd() . '/';
cb7301c7
JP
26my $lk_path = "./";
27my $email = 1;
28my $email_usename = 1;
29my $email_maintainer = 1;
c1c3f2c9 30my $email_reviewer = 1;
2f5bd343 31my $email_fixes = 1;
cb7301c7 32my $email_list = 1;
49662503 33my $email_moderated_list = 1;
cb7301c7 34my $email_subscriber_list = 0;
cb7301c7 35my $email_git_penguin_chiefs = 0;
e3e9d114 36my $email_git = 0;
0fa05599 37my $email_git_all_signature_types = 0;
60db31ac 38my $email_git_blame = 0;
683c6f8f 39my $email_git_blame_signatures = 1;
e3e9d114 40my $email_git_fallback = 1;
cb7301c7
JP
41my $email_git_min_signatures = 1;
42my $email_git_max_maintainers = 5;
afa81ee1 43my $email_git_min_percent = 5;
cb7301c7 44my $email_git_since = "1-year-ago";
60db31ac 45my $email_hg_since = "-365";
dace8e30 46my $interactive = 0;
11ecf53c 47my $email_remove_duplicates = 1;
b9e2331d 48my $email_use_mailmap = 1;
cb7301c7
JP
49my $output_multiline = 1;
50my $output_separator = ", ";
3c7385b8 51my $output_roles = 0;
7e1863af 52my $output_rolestats = 1;
9ad18c85 53my $output_substatus = undef;
364f68dc 54my $output_section_maxlen = 50;
cb7301c7 55my $scm = 0;
31bb82c9 56my $tree = 1;
cb7301c7 57my $web = 0;
033964f1 58my $bug = 0;
cb7301c7
JP
59my $subsystem = 0;
60my $status = 0;
03aed214 61my $letters = "";
dcf36a92 62my $keywords = 1;
71ca5ee1 63my $keywords_in_file = 0;
4b76c9da 64my $sections = 0;
0c78c013 65my $email_file_emails = 0;
4a7fdb5f 66my $from_filename = 0;
3fb55652 67my $pattern_depth = 0;
083bf9c5 68my $self_test = undef;
cb7301c7
JP
69my $version = 0;
70my $help = 0;
6f7d98ec 71my $find_maintainer_files = 0;
5f0baf95 72my $maintainer_path;
683c6f8f
JP
73my $vcs_used = 0;
74
cb7301c7
JP
75my $exit = 0;
76
0c78c013
JP
77my @files = ();
78my @fixes = (); # If a patch description includes Fixes: lines
79my @range = ();
80my @keyword_tvi = ();
81my @file_emails = ();
82
683c6f8f
JP
83my %commit_author_hash;
84my %commit_signer_hash;
dace8e30 85
cb7301c7 86my @penguin_chief = ();
e4d26b02 87push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
cb7301c7 88#Andrew wants in on most everything - 2009/01/14
e4d26b02 89#push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
cb7301c7
JP
90
91my @penguin_chief_names = ();
92foreach my $chief (@penguin_chief) {
93 if ($chief =~ m/^(.*):(.*)/) {
94 my $chief_name = $1;
95 my $chief_addr = $2;
96 push(@penguin_chief_names, $chief_name);
97 }
98}
e4d26b02
JP
99my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
100
101# Signature types of people who are either
102# a) responsible for the code in question, or
103# b) familiar enough with it to give relevant feedback
104my @signature_tags = ();
105push(@signature_tags, "Signed-off-by:");
106push(@signature_tags, "Reviewed-by:");
107push(@signature_tags, "Acked-by:");
cb7301c7 108
7dea2681
JP
109my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
110
5f2441e9 111# rfc822 email address - preloaded methods go here.
1b5e1cf6 112my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
df4cc036 113my $rfc822_char = '[\\000-\\377]';
1b5e1cf6 114
60db31ac
JP
115# VCS command support: class-like functions and strings
116
117my %VCS_cmds;
118
119my %VCS_cmds_git = (
120 "execute_cmd" => \&git_execute_cmd,
ec83b616 121 "available" => '(which("git") ne "") && (-e ".git")',
683c6f8f 122 "find_signers_cmd" =>
ed128fea 123 "git log --no-color --follow --since=\$email_git_since " .
c9ecefea 124 '--numstat --no-merges ' .
683c6f8f
JP
125 '--format="GitCommit: %H%n' .
126 'GitAuthor: %an <%ae>%n' .
127 'GitDate: %aD%n' .
128 'GitSubject: %s%n' .
129 '%b%n"' .
130 " -- \$file",
131 "find_commit_signers_cmd" =>
132 "git log --no-color " .
c9ecefea 133 '--numstat ' .
683c6f8f
JP
134 '--format="GitCommit: %H%n' .
135 'GitAuthor: %an <%ae>%n' .
136 'GitDate: %aD%n' .
137 'GitSubject: %s%n' .
138 '%b%n"' .
139 " -1 \$commit",
140 "find_commit_author_cmd" =>
141 "git log --no-color " .
c9ecefea 142 '--numstat ' .
683c6f8f
JP
143 '--format="GitCommit: %H%n' .
144 'GitAuthor: %an <%ae>%n' .
145 'GitDate: %aD%n' .
146 'GitSubject: %s%n"' .
147 " -1 \$commit",
60db31ac
JP
148 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
149 "blame_file_cmd" => "git blame -l \$file",
683c6f8f 150 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
dace8e30 151 "blame_commit_pattern" => "^([0-9a-f]+) ",
683c6f8f
JP
152 "author_pattern" => "^GitAuthor: (.*)",
153 "subject_pattern" => "^GitSubject: (.*)",
c9ecefea 154 "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
4cad35a7 155 "file_exists_cmd" => "git ls-files \$file",
e1f75904 156 "list_files_cmd" => "git ls-files \$file",
60db31ac
JP
157);
158
159my %VCS_cmds_hg = (
160 "execute_cmd" => \&hg_execute_cmd,
161 "available" => '(which("hg") ne "") && (-d ".hg")',
162 "find_signers_cmd" =>
683c6f8f
JP
163 "hg log --date=\$email_hg_since " .
164 "--template='HgCommit: {node}\\n" .
165 "HgAuthor: {author}\\n" .
166 "HgSubject: {desc}\\n'" .
167 " -- \$file",
168 "find_commit_signers_cmd" =>
169 "hg log " .
170 "--template='HgSubject: {desc}\\n'" .
171 " -r \$commit",
172 "find_commit_author_cmd" =>
173 "hg log " .
174 "--template='HgCommit: {node}\\n" .
175 "HgAuthor: {author}\\n" .
176 "HgSubject: {desc|firstline}\\n'" .
177 " -r \$commit",
60db31ac 178 "blame_range_cmd" => "", # not supported
683c6f8f
JP
179 "blame_file_cmd" => "hg blame -n \$file",
180 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
181 "blame_commit_pattern" => "^([ 0-9a-f]+):",
182 "author_pattern" => "^HgAuthor: (.*)",
183 "subject_pattern" => "^HgSubject: (.*)",
c9ecefea 184 "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$",
4cad35a7 185 "file_exists_cmd" => "hg files \$file",
e1f75904 186 "list_files_cmd" => "hg manifest -R \$file",
60db31ac
JP
187);
188
bcde44ed
JP
189my $conf = which_conf(".get_maintainer.conf");
190if (-f $conf) {
368669da 191 my @conf_args;
bcde44ed
JP
192 open(my $conffile, '<', "$conf")
193 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
194
368669da
JP
195 while (<$conffile>) {
196 my $line = $_;
197
198 $line =~ s/\s*\n?$//g;
199 $line =~ s/^\s*//g;
200 $line =~ s/\s+/ /g;
201
202 next if ($line =~ m/^\s*#/);
203 next if ($line =~ m/^\s*$/);
204
205 my @words = split(" ", $line);
206 foreach my $word (@words) {
207 last if ($word =~ m/^#/);
208 push (@conf_args, $word);
209 }
210 }
211 close($conffile);
212 unshift(@ARGV, @conf_args) if @conf_args;
213}
214
435de078
JP
215my @ignore_emails = ();
216my $ignore_file = which_conf(".get_maintainer.ignore");
217if (-f $ignore_file) {
218 open(my $ignore, '<', "$ignore_file")
219 or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n";
220 while (<$ignore>) {
221 my $line = $_;
222
223 $line =~ s/\s*\n?$//;
224 $line =~ s/^\s*//;
225 $line =~ s/\s+$//;
226 $line =~ s/#.*$//;
227
228 next if ($line =~ m/^\s*$/);
229 if (rfc822_valid($line)) {
230 push(@ignore_emails, $line);
231 }
232 }
233 close($ignore);
234}
235
e1f75904
TS
236if ($#ARGV > 0) {
237 foreach (@ARGV) {
083bf9c5 238 if ($_ =~ /^-{1,2}self-test(?:=|$)/) {
e1f75904
TS
239 die "$P: using --self-test does not allow any other option or argument\n";
240 }
241 }
242}
243
cb7301c7
JP
244if (!GetOptions(
245 'email!' => \$email,
246 'git!' => \$email_git,
e4d26b02 247 'git-all-signature-types!' => \$email_git_all_signature_types,
60db31ac 248 'git-blame!' => \$email_git_blame,
683c6f8f 249 'git-blame-signatures!' => \$email_git_blame_signatures,
e3e9d114 250 'git-fallback!' => \$email_git_fallback,
cb7301c7
JP
251 'git-chief-penguins!' => \$email_git_penguin_chiefs,
252 'git-min-signatures=i' => \$email_git_min_signatures,
253 'git-max-maintainers=i' => \$email_git_max_maintainers,
afa81ee1 254 'git-min-percent=i' => \$email_git_min_percent,
cb7301c7 255 'git-since=s' => \$email_git_since,
60db31ac 256 'hg-since=s' => \$email_hg_since,
dace8e30 257 'i|interactive!' => \$interactive,
11ecf53c 258 'remove-duplicates!' => \$email_remove_duplicates,
b9e2331d 259 'mailmap!' => \$email_use_mailmap,
cb7301c7 260 'm!' => \$email_maintainer,
c1c3f2c9 261 'r!' => \$email_reviewer,
cb7301c7
JP
262 'n!' => \$email_usename,
263 'l!' => \$email_list,
2f5bd343 264 'fixes!' => \$email_fixes,
49662503 265 'moderated!' => \$email_moderated_list,
cb7301c7
JP
266 's!' => \$email_subscriber_list,
267 'multiline!' => \$output_multiline,
3c7385b8
JP
268 'roles!' => \$output_roles,
269 'rolestats!' => \$output_rolestats,
cb7301c7
JP
270 'separator=s' => \$output_separator,
271 'subsystem!' => \$subsystem,
272 'status!' => \$status,
9ad18c85 273 'substatus!' => \$output_substatus,
cb7301c7 274 'scm!' => \$scm,
31bb82c9 275 'tree!' => \$tree,
cb7301c7 276 'web!' => \$web,
033964f1 277 'bug!' => \$bug,
03aed214 278 'letters=s' => \$letters,
3fb55652 279 'pattern-depth=i' => \$pattern_depth,
dcf36a92 280 'k|keywords!' => \$keywords,
71ca5ee1 281 'kf|keywords-in-file!' => \$keywords_in_file,
4b76c9da 282 'sections!' => \$sections,
0c78c013 283 'fe|file-emails!' => \$email_file_emails,
4a7fdb5f 284 'f|file' => \$from_filename,
6f7d98ec 285 'find-maintainer-files' => \$find_maintainer_files,
5f0baf95 286 'mpath|maintainer-path=s' => \$maintainer_path,
083bf9c5 287 'self-test:s' => \$self_test,
cb7301c7 288 'v|version' => \$version,
64f77f31 289 'h|help|usage' => \$help,
cb7301c7 290 )) {
3c7385b8 291 die "$P: invalid argument - use --help if necessary\n";
cb7301c7
JP
292}
293
294if ($help != 0) {
295 usage();
296 exit 0;
297}
298
299if ($version != 0) {
300 print("${P} ${V}\n");
301 exit 0;
302}
303
083bf9c5 304if (defined $self_test) {
e1f75904 305 read_all_maintainer_files();
083bf9c5 306 self_test();
e1f75904
TS
307 exit 0;
308}
309
64f77f31
JP
310if (-t STDIN && !@ARGV) {
311 # We're talking to a terminal, but have no command line arguments.
312 die "$P: missing patchfile or -f file - use --help if necessary\n";
cb7301c7
JP
313}
314
683c6f8f
JP
315$output_multiline = 0 if ($output_separator ne ", ");
316$output_rolestats = 1 if ($interactive);
317$output_roles = 1 if ($output_rolestats);
3c7385b8 318
9ad18c85
VB
319if (!defined $output_substatus) {
320 $output_substatus = $email && $output_roles && -t STDOUT;
321}
322
03aed214
JP
323if ($sections || $letters ne "") {
324 $sections = 1;
4b76c9da
JP
325 $email = 0;
326 $email_list = 0;
327 $scm = 0;
328 $status = 0;
329 $subsystem = 0;
330 $web = 0;
033964f1 331 $bug = 0;
4b76c9da 332 $keywords = 0;
71ca5ee1 333 $keywords_in_file = 0;
6ef1c52e 334 $interactive = 0;
4b76c9da 335} else {
033964f1 336 my $selections = $email + $scm + $status + $subsystem + $web + $bug;
4b76c9da 337 if ($selections == 0) {
033964f1 338 die "$P: Missing required option: email, scm, status, subsystem, web or bug\n";
4b76c9da 339 }
cb7301c7
JP
340}
341
f5492666 342if ($email &&
c1c3f2c9
JP
343 ($email_maintainer + $email_reviewer +
344 $email_list + $email_subscriber_list +
f5492666 345 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
cb7301c7
JP
346 die "$P: Please select at least 1 email option\n";
347}
348
31bb82c9 349if ($tree && !top_of_kernel_tree($lk_path)) {
cb7301c7
JP
350 die "$P: The current directory does not appear to be "
351 . "a linux kernel source tree.\n";
352}
353
354## Read MAINTAINERS for type/value pairs
355
356my @typevalue = ();
dcf36a92 357my %keyword_hash;
6f7d98ec 358my @mfiles = ();
083bf9c5 359my @self_test_info = ();
dcf36a92 360
6f7d98ec
JP
361sub read_maintainer_file {
362 my ($file) = @_;
363
364 open (my $maint, '<', "$file")
365 or die "$P: Can't open MAINTAINERS file '$file': $!\n";
e1f75904 366 my $i = 1;
6f7d98ec
JP
367 while (<$maint>) {
368 my $line = $_;
083bf9c5 369 chomp $line;
6f7d98ec
JP
370
371 if ($line =~ m/^([A-Z]):\s*(.*)/) {
372 my $type = $1;
373 my $value = $2;
374
375 ##Filename pattern matching
376 if ($type eq "F" || $type eq "X") {
377 $value =~ s@\.@\\\.@g; ##Convert . to \.
378 $value =~ s/\*/\.\*/g; ##Convert * to .*
379 $value =~ s/\?/\./g; ##Convert ? to .
380 ##if pattern is a directory and it lacks a trailing slash, add one
381 if ((-d $value)) {
382 $value =~ s@([^/])$@$1/@;
383 }
384 } elsif ($type eq "K") {
385 $keyword_hash{@typevalue} = $value;
870020f9 386 }
6f7d98ec
JP
387 push(@typevalue, "$type:$value");
388 } elsif (!(/^\s*$/ || /^\s*\#/)) {
6f7d98ec 389 push(@typevalue, $line);
cb7301c7 390 }
083bf9c5
JP
391 if (defined $self_test) {
392 push(@self_test_info, {file=>$file, linenr=>$i, line=>$line});
393 }
e1f75904 394 $i++;
6f7d98ec
JP
395 }
396 close($maint);
397}
398
399sub find_is_maintainer_file {
400 my ($file) = $_;
401 return if ($file !~ m@/MAINTAINERS$@);
402 $file = $File::Find::name;
403 return if (! -f $file);
404 push(@mfiles, $file);
405}
406
407sub find_ignore_git {
408 return grep { $_ !~ /^\.git$/; } @_;
409}
410
e1f75904
TS
411read_all_maintainer_files();
412
413sub read_all_maintainer_files {
5f0baf95
JP
414 my $path = "${lk_path}MAINTAINERS";
415 if (defined $maintainer_path) {
416 $path = $maintainer_path;
417 # Perl Cookbook tilde expansion if necessary
418 $path =~ s@^~([^/]*)@ $1 ? (getpwnam($1))[7] : ( $ENV{HOME} || $ENV{LOGDIR} || (getpwuid($<))[7])@ex;
cb7301c7 419 }
cb7301c7 420
5f0baf95
JP
421 if (-d $path) {
422 $path .= '/' if ($path !~ m@/$@);
0fbd75fd
JP
423 if ($find_maintainer_files) {
424 find( { wanted => \&find_is_maintainer_file,
425 preprocess => \&find_ignore_git,
426 no_chdir => 1,
427 }, "$path");
428 } else {
5f0baf95
JP
429 opendir(DIR, "$path") or die $!;
430 my @files = readdir(DIR);
431 closedir(DIR);
432 foreach my $file (@files) {
433 push(@mfiles, "$path$file") if ($file !~ /^\./);
434 }
435 }
5f0baf95
JP
436 } elsif (-f "$path") {
437 push(@mfiles, "$path");
e1f75904 438 } else {
5f0baf95 439 die "$P: MAINTAINER file not found '$path'\n";
e1f75904 440 }
5f0baf95 441 die "$P: No MAINTAINER files found in '$path'\n" if (scalar(@mfiles) == 0);
e1f75904 442 foreach my $file (@mfiles) {
5f0baf95 443 read_maintainer_file("$file");
e1f75904 444 }
6f7d98ec 445}
8cbb3a77 446
0c78c013
JP
447sub maintainers_in_file {
448 my ($file) = @_;
449
450 return if ($file =~ m@\bMAINTAINERS$@);
451
452 if (-f $file && ($email_file_emails || $file =~ /\.yaml$/)) {
453 open(my $f, '<', $file)
454 or die "$P: Can't open $file: $!\n";
455 my $text = do { local($/) ; <$f> };
456 close($f);
457
9c334eb9 458 my @poss_addr = $text =~ m$[\p{L}\"\' \,\.\+-]*\s*[\,]*\s*[\(\<\{]{0,1}[A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+\.[A-Za-z0-9]+[\)\>\}]{0,1}$g;
0c78c013
JP
459 push(@file_emails, clean_file_emails(@poss_addr));
460 }
461}
462
7fa8ff2e
FM
463#
464# Read mail address map
465#
466
b9e2331d
JP
467my $mailmap;
468
469read_mailmap();
7fa8ff2e
FM
470
471sub read_mailmap {
b9e2331d 472 $mailmap = {
7fa8ff2e
FM
473 names => {},
474 addresses => {}
47abc722 475 };
7fa8ff2e 476
b9e2331d 477 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
7fa8ff2e
FM
478
479 open(my $mailmap_file, '<', "${lk_path}.mailmap")
22dd5b0c 480 or warn "$P: Can't open .mailmap: $!\n";
8cbb3a77 481
7fa8ff2e
FM
482 while (<$mailmap_file>) {
483 s/#.*$//; #strip comments
484 s/^\s+|\s+$//g; #trim
8cbb3a77 485
7fa8ff2e
FM
486 next if (/^\s*$/); #skip empty lines
487 #entries have one of the following formats:
488 # name1 <mail1>
489 # <mail1> <mail2>
490 # name1 <mail1> <mail2>
491 # name1 <mail1> name2 <mail2>
492 # (see man git-shortlog)
0334b382
JP
493
494 if (/^([^<]+)<([^>]+)>$/) {
47abc722
JP
495 my $real_name = $1;
496 my $address = $2;
8cbb3a77 497
47abc722 498 $real_name =~ s/\s+$//;
b9e2331d 499 ($real_name, $address) = parse_email("$real_name <$address>");
47abc722 500 $mailmap->{names}->{$address} = $real_name;
8cbb3a77 501
0334b382 502 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
47abc722
JP
503 my $real_address = $1;
504 my $wrong_address = $2;
7fa8ff2e 505
47abc722 506 $mailmap->{addresses}->{$wrong_address} = $real_address;
7fa8ff2e 507
0334b382 508 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
b9e2331d 509 my $real_name = $1;
47abc722
JP
510 my $real_address = $2;
511 my $wrong_address = $3;
7fa8ff2e 512
47abc722 513 $real_name =~ s/\s+$//;
b9e2331d
JP
514 ($real_name, $real_address) =
515 parse_email("$real_name <$real_address>");
47abc722
JP
516 $mailmap->{names}->{$wrong_address} = $real_name;
517 $mailmap->{addresses}->{$wrong_address} = $real_address;
7fa8ff2e 518
0334b382 519 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
47abc722
JP
520 my $real_name = $1;
521 my $real_address = $2;
522 my $wrong_name = $3;
523 my $wrong_address = $4;
7fa8ff2e 524
47abc722 525 $real_name =~ s/\s+$//;
b9e2331d
JP
526 ($real_name, $real_address) =
527 parse_email("$real_name <$real_address>");
528
47abc722 529 $wrong_name =~ s/\s+$//;
b9e2331d
JP
530 ($wrong_name, $wrong_address) =
531 parse_email("$wrong_name <$wrong_address>");
7fa8ff2e 532
b9e2331d
JP
533 my $wrong_email = format_email($wrong_name, $wrong_address, 1);
534 $mailmap->{names}->{$wrong_email} = $real_name;
535 $mailmap->{addresses}->{$wrong_email} = $real_address;
11ecf53c 536 }
8cbb3a77 537 }
7fa8ff2e 538 close($mailmap_file);
8cbb3a77
JP
539}
540
4a7fdb5f 541## use the filenames on the command line or find the filenames in the patchfiles
cb7301c7 542
64f77f31
JP
543if (!@ARGV) {
544 push(@ARGV, "&STDIN");
545}
546
4a7fdb5f 547foreach my $file (@ARGV) {
64f77f31 548 if ($file ne "&STDIN") {
e33c9fe8 549 $file = canonpath($file);
64f77f31
JP
550 ##if $file is a directory and it lacks a trailing slash, add one
551 if ((-d $file)) {
552 $file =~ s@([^/])$@$1/@;
553 } elsif (!(-f $file)) {
554 die "$P: file '${file}' not found\n";
555 }
cb7301c7 556 }
cdfe2d22
JP
557 if ($from_filename && (vcs_exists() && !vcs_file_exists($file))) {
558 warn "$P: file '$file' not found in version control $!\n";
559 }
aec742e8 560 if ($from_filename || ($file ne "&STDIN" && vcs_file_exists($file))) {
be17bddc
JP
561 $file =~ s/^\Q${cur_path}\E//; #strip any absolute path
562 $file =~ s/^\Q${lk_path}\E//; #or the path to the lk tree
4a7fdb5f 563 push(@files, $file);
71ca5ee1 564 if ($file ne "MAINTAINERS" && -f $file && $keywords && $keywords_in_file) {
22dd5b0c
SH
565 open(my $f, '<', $file)
566 or die "$P: Can't open $file: $!\n";
567 my $text = do { local($/) ; <$f> };
568 close($f);
71ca5ee1
JP
569 foreach my $line (keys %keyword_hash) {
570 if ($text =~ m/$keyword_hash{$line}/x) {
571 push(@keyword_tvi, $line);
dcf36a92
JP
572 }
573 }
dcf36a92 574 }
4a7fdb5f
JP
575 } else {
576 my $file_cnt = @files;
f5492666 577 my $lastfile;
22dd5b0c 578
3a4df13d 579 open(my $patch, "< $file")
22dd5b0c 580 or die "$P: Can't open $file: $!\n";
7764dcb5
JP
581
582 # We can check arbitrary information before the patch
583 # like the commit message, mail headers, etc...
584 # This allows us to match arbitrary keywords against any part
585 # of a git format-patch generated file (subject tags, etc...)
586
587 my $patch_prefix = ""; #Parsing the intro
588
22dd5b0c 589 while (<$patch>) {
dcf36a92 590 my $patch_line = $_;
0455c747
JP
591 if (m/^ mode change [0-7]+ => [0-7]+ (\S+)\s*$/) {
592 my $filename = $1;
593 push(@files, $filename);
594 } elsif (m/^rename (?:from|to) (\S+)\s*$/) {
595 my $filename = $1;
596 push(@files, $filename);
597 } elsif (m/^diff --git a\/(\S+) b\/(\S+)\s*$/) {
598 my $filename1 = $1;
599 my $filename2 = $2;
600 push(@files, $filename1);
601 push(@files, $filename2);
2f5bd343
JP
602 } elsif (m/^Fixes:\s+([0-9a-fA-F]{6,40})/) {
603 push(@fixes, $1) if ($email_fixes);
0455c747 604 } elsif (m/^\+\+\+\s+(\S+)/ or m/^---\s+(\S+)/) {
4a7fdb5f
JP
605 my $filename = $1;
606 $filename =~ s@^[^/]*/@@;
607 $filename =~ s@\n@@;
f5492666 608 $lastfile = $filename;
4a7fdb5f 609 push(@files, $filename);
7764dcb5 610 $patch_prefix = "^[+-].*"; #Now parsing the actual patch
f5492666
JP
611 } elsif (m/^\@\@ -(\d+),(\d+)/) {
612 if ($email_git_blame) {
613 push(@range, "$lastfile:$1:$2");
614 }
dcf36a92
JP
615 } elsif ($keywords) {
616 foreach my $line (keys %keyword_hash) {
7764dcb5 617 if ($patch_line =~ m/${patch_prefix}$keyword_hash{$line}/x) {
dcf36a92
JP
618 push(@keyword_tvi, $line);
619 }
620 }
4a7fdb5f 621 }
cb7301c7 622 }
22dd5b0c
SH
623 close($patch);
624
4a7fdb5f 625 if ($file_cnt == @files) {
7f29fd27 626 warn "$P: file '${file}' doesn't appear to be a patch. "
4a7fdb5f
JP
627 . "Add -f to options?\n";
628 }
629 @files = sort_and_uniq(@files);
cb7301c7 630 }
cb7301c7
JP
631}
632
03372dbb 633@file_emails = uniq(@file_emails);
2f5bd343 634@fixes = uniq(@fixes);
03372dbb 635
683c6f8f
JP
636my %email_hash_name;
637my %email_hash_address;
cb7301c7 638my @email_to = ();
683c6f8f 639my %hash_list_to;
290603c1 640my @list_to = ();
cb7301c7
JP
641my @scm = ();
642my @web = ();
033964f1 643my @bug = ();
cb7301c7
JP
644my @subsystem = ();
645my @status = ();
9ad18c85 646my @substatus = ();
b9e2331d
JP
647my %deduplicate_name_hash = ();
648my %deduplicate_address_hash = ();
cb7301c7 649
6ef1c52e 650my @maintainers = get_maintainers();
6ef1c52e
JP
651if (@maintainers) {
652 @maintainers = merge_email(@maintainers);
653 output(@maintainers);
654}
683c6f8f
JP
655
656if ($scm) {
657 @scm = uniq(@scm);
658 output(@scm);
659}
660
9ad18c85
VB
661if ($output_substatus) {
662 @substatus = uniq(@substatus);
663 output(@substatus);
664}
665
683c6f8f
JP
666if ($status) {
667 @status = uniq(@status);
668 output(@status);
669}
670
671if ($subsystem) {
672 @subsystem = uniq(@subsystem);
673 output(@subsystem);
674}
675
676if ($web) {
677 @web = uniq(@web);
678 output(@web);
679}
680
033964f1
JN
681if ($bug) {
682 @bug = uniq(@bug);
683 output(@bug);
684}
685
683c6f8f
JP
686exit($exit);
687
083bf9c5 688sub self_test {
e1f75904 689 my @lsfiles = ();
083bf9c5
JP
690 my @good_links = ();
691 my @bad_links = ();
692 my @section_headers = ();
693 my $index = 0;
e1f75904
TS
694
695 @lsfiles = vcs_list_files($lk_path);
696
083bf9c5
JP
697 for my $x (@self_test_info) {
698 $index++;
699
700 ## Section header duplication and missing section content
701 if (($self_test eq "" || $self_test =~ /\bsections\b/) &&
702 $x->{line} =~ /^\S[^:]/ &&
703 defined $self_test_info[$index] &&
704 $self_test_info[$index]->{line} =~ /^([A-Z]):\s*\S/) {
705 my $has_S = 0;
706 my $has_F = 0;
707 my $has_ML = 0;
708 my $status = "";
709 if (grep(m@^\Q$x->{line}\E@, @section_headers)) {
710 print("$x->{file}:$x->{linenr}: warning: duplicate section header\t$x->{line}\n");
711 } else {
712 push(@section_headers, $x->{line});
713 }
714 my $nextline = $index;
715 while (defined $self_test_info[$nextline] &&
716 $self_test_info[$nextline]->{line} =~ /^([A-Z]):\s*(\S.*)/) {
717 my $type = $1;
718 my $value = $2;
719 if ($type eq "S") {
720 $has_S = 1;
721 $status = $value;
722 } elsif ($type eq "F" || $type eq "N") {
723 $has_F = 1;
724 } elsif ($type eq "M" || $type eq "R" || $type eq "L") {
725 $has_ML = 1;
726 }
727 $nextline++;
728 }
729 if (!$has_ML && $status !~ /orphan|obsolete/i) {
730 print("$x->{file}:$x->{linenr}: warning: section without email address\t$x->{line}\n");
731 }
732 if (!$has_S) {
733 print("$x->{file}:$x->{linenr}: warning: section without status \t$x->{line}\n");
734 }
735 if (!$has_F) {
736 print("$x->{file}:$x->{linenr}: warning: section without file pattern\t$x->{line}\n");
737 }
738 }
739
740 next if ($x->{line} !~ /^([A-Z]):\s*(.*)/);
741
742 my $type = $1;
743 my $value = $2;
744
745 ## Filename pattern matching
746 if (($type eq "F" || $type eq "X") &&
747 ($self_test eq "" || $self_test =~ /\bpatterns\b/)) {
748 $value =~ s@\.@\\\.@g; ##Convert . to \.
749 $value =~ s/\*/\.\*/g; ##Convert * to .*
750 $value =~ s/\?/\./g; ##Convert ? to .
751 ##if pattern is a directory and it lacks a trailing slash, add one
752 if ((-d $value)) {
753 $value =~ s@([^/])$@$1/@;
754 }
755 if (!grep(m@^$value@, @lsfiles)) {
756 print("$x->{file}:$x->{linenr}: warning: no file matches\t$x->{line}\n");
757 }
758
759 ## Link reachability
760 } elsif (($type eq "W" || $type eq "Q" || $type eq "B") &&
761 $value =~ /^https?:/ &&
762 ($self_test eq "" || $self_test =~ /\blinks\b/)) {
763 next if (grep(m@^\Q$value\E$@, @good_links));
764 my $isbad = 0;
765 if (grep(m@^\Q$value\E$@, @bad_links)) {
766 $isbad = 1;
767 } else {
768 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $value`;
769 if ($? == 0) {
770 push(@good_links, $value);
771 } else {
772 push(@bad_links, $value);
773 $isbad = 1;
774 }
775 }
776 if ($isbad) {
777 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
778 }
779
780 ## SCM reachability
781 } elsif ($type eq "T" &&
782 ($self_test eq "" || $self_test =~ /\bscm\b/)) {
783 next if (grep(m@^\Q$value\E$@, @good_links));
784 my $isbad = 0;
785 if (grep(m@^\Q$value\E$@, @bad_links)) {
786 $isbad = 1;
787 } elsif ($value !~ /^(?:git|quilt|hg)\s+\S/) {
788 print("$x->{file}:$x->{linenr}: warning: malformed entry\t$x->{line}\n");
789 } elsif ($value =~ /^git\s+(\S+)(\s+([^\(]+\S+))?/) {
790 my $url = $1;
791 my $branch = "";
792 $branch = $3 if $3;
793 my $output = `git ls-remote --exit-code -h "$url" $branch > /dev/null 2>&1`;
794 if ($? == 0) {
795 push(@good_links, $value);
796 } else {
797 push(@bad_links, $value);
798 $isbad = 1;
799 }
800 } elsif ($value =~ /^(?:quilt|hg)\s+(https?:\S+)/) {
801 my $url = $1;
802 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $url`;
803 if ($? == 0) {
804 push(@good_links, $value);
805 } else {
806 push(@bad_links, $value);
807 $isbad = 1;
808 }
809 }
810 if ($isbad) {
811 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
812 }
813 }
e1f75904
TS
814 }
815}
816
435de078
JP
817sub ignore_email_address {
818 my ($address) = @_;
819
820 foreach my $ignore (@ignore_emails) {
821 return 1 if ($ignore eq $address);
822 }
823
824 return 0;
825}
826
ab6c937d
JP
827sub range_is_maintained {
828 my ($start, $end) = @_;
829
830 for (my $i = $start; $i < $end; $i++) {
831 my $line = $typevalue[$i];
ce8155f7 832 if ($line =~ m/^([A-Z]):\s*(.*)/) {
ab6c937d
JP
833 my $type = $1;
834 my $value = $2;
835 if ($type eq 'S') {
836 if ($value =~ /(maintain|support)/i) {
837 return 1;
838 }
839 }
840 }
841 }
842 return 0;
843}
844
845sub range_has_maintainer {
846 my ($start, $end) = @_;
847
848 for (my $i = $start; $i < $end; $i++) {
849 my $line = $typevalue[$i];
ce8155f7 850 if ($line =~ m/^([A-Z]):\s*(.*)/) {
ab6c937d
JP
851 my $type = $1;
852 my $value = $2;
853 if ($type eq 'M') {
854 return 1;
855 }
856 }
857 }
858 return 0;
859}
860
6ef1c52e 861sub get_maintainers {
683c6f8f
JP
862 %email_hash_name = ();
863 %email_hash_address = ();
864 %commit_author_hash = ();
865 %commit_signer_hash = ();
866 @email_to = ();
867 %hash_list_to = ();
868 @list_to = ();
869 @scm = ();
870 @web = ();
033964f1 871 @bug = ();
683c6f8f
JP
872 @subsystem = ();
873 @status = ();
9ad18c85 874 @substatus = ();
b9e2331d
JP
875 %deduplicate_name_hash = ();
876 %deduplicate_address_hash = ();
683c6f8f
JP
877 if ($email_git_all_signature_types) {
878 $signature_pattern = "(.+?)[Bb][Yy]:";
879 } else {
880 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
881 }
882
883 # Find responsible parties
884
b9e2331d 885 my %exact_pattern_match_hash = ();
6ef1c52e 886
683c6f8f
JP
887 foreach my $file (@files) {
888
889 my %hash;
683c6f8f
JP
890 my $tvi = find_first_section();
891 while ($tvi < @typevalue) {
892 my $start = find_starting_index($tvi);
893 my $end = find_ending_index($tvi);
894 my $exclude = 0;
895 my $i;
896
897 #Do not match excluded file patterns
272a8979 898
272a8979
JP
899 for ($i = $start; $i < $end; $i++) {
900 my $line = $typevalue[$i];
ce8155f7 901 if ($line =~ m/^([A-Z]):\s*(.*)/) {
272a8979
JP
902 my $type = $1;
903 my $value = $2;
683c6f8f 904 if ($type eq 'X') {
272a8979 905 if (file_match_pattern($file, $value)) {
683c6f8f
JP
906 $exclude = 1;
907 last;
908 }
909 }
910 }
911 }
912
913 if (!$exclude) {
914 for ($i = $start; $i < $end; $i++) {
915 my $line = $typevalue[$i];
ce8155f7 916 if ($line =~ m/^([A-Z]):\s*(.*)/) {
683c6f8f
JP
917 my $type = $1;
918 my $value = $2;
919 if ($type eq 'F') {
920 if (file_match_pattern($file, $value)) {
921 my $value_pd = ($value =~ tr@/@@);
922 my $file_pd = ($file =~ tr@/@@);
923 $value_pd++ if (substr($value,-1,1) ne "/");
924 $value_pd = -1 if ($value =~ /^\.\*/);
ab6c937d
JP
925 if ($value_pd >= $file_pd &&
926 range_is_maintained($start, $end) &&
927 range_has_maintainer($start, $end)) {
6ef1c52e
JP
928 $exact_pattern_match_hash{$file} = 1;
929 }
683c6f8f
JP
930 if ($pattern_depth == 0 ||
931 (($file_pd - $value_pd) < $pattern_depth)) {
932 $hash{$tvi} = $value_pd;
933 }
272a8979 934 }
bbbe96ed 935 } elsif ($type eq 'N') {
eb90d085
SW
936 if ($file =~ m/$value/x) {
937 $hash{$tvi} = 0;
938 }
272a8979
JP
939 }
940 }
941 }
942 }
683c6f8f 943 $tvi = $end + 1;
1d606b4e 944 }
272a8979 945
683c6f8f 946 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
71ca5ee1 947 add_categories($line, "");
683c6f8f
JP
948 if ($sections) {
949 my $i;
950 my $start = find_starting_index($line);
951 my $end = find_ending_index($line);
952 for ($i = $start; $i < $end; $i++) {
953 my $line = $typevalue[$i];
954 if ($line =~ /^[FX]:/) { ##Restore file patterns
955 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
956 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
957 $line =~ s/\\\./\./g; ##Convert \. to .
958 $line =~ s/\.\*/\*/g; ##Convert .* to *
959 }
03aed214
JP
960 my $count = $line =~ s/^([A-Z]):/$1:\t/g;
961 if ($letters eq "" || (!$count || $letters =~ /$1/i)) {
962 print("$line\n");
963 }
4b76c9da 964 }
683c6f8f 965 print("\n");
4b76c9da 966 }
6ffd9485 967 }
0c78c013
JP
968
969 maintainers_in_file($file);
dace8e30 970 }
cb7301c7 971
683c6f8f
JP
972 if ($keywords) {
973 @keyword_tvi = sort_and_uniq(@keyword_tvi);
974 foreach my $line (@keyword_tvi) {
71ca5ee1 975 add_categories($line, ":Keyword:$keyword_hash{$line}");
683c6f8f 976 }
dcf36a92 977 }
dcf36a92 978
b9e2331d
JP
979 foreach my $email (@email_to, @list_to) {
980 $email->[0] = deduplicate_email($email->[0]);
981 }
6ef1c52e
JP
982
983 foreach my $file (@files) {
984 if ($email &&
6343f6b7
JP
985 ($email_git ||
986 ($email_git_fallback &&
987 $file !~ /MAINTAINERS$/ &&
988 !$exact_pattern_match_hash{$file}))) {
6ef1c52e
JP
989 vcs_file_signoffs($file);
990 }
991 if ($email && $email_git_blame) {
992 vcs_file_blame($file);
993 }
994 }
995
683c6f8f
JP
996 if ($email) {
997 foreach my $chief (@penguin_chief) {
998 if ($chief =~ m/^(.*):(.*)/) {
999 my $email_address;
0e70e83d 1000
683c6f8f
JP
1001 $email_address = format_email($1, $2, $email_usename);
1002 if ($email_git_penguin_chiefs) {
1003 push(@email_to, [$email_address, 'chief penguin']);
1004 } else {
1005 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
1006 }
cb7301c7
JP
1007 }
1008 }
03372dbb 1009
683c6f8f 1010 foreach my $email (@file_emails) {
11fb4896 1011 $email = mailmap_email($email);
683c6f8f 1012 my ($name, $address) = parse_email($email);
03372dbb 1013
683c6f8f
JP
1014 my $tmp_email = format_email($name, $address, $email_usename);
1015 push_email_address($tmp_email, '');
1016 add_role($tmp_email, 'in file');
1017 }
03372dbb 1018 }
cb7301c7 1019
0ef82fce
DA
1020 foreach my $fix (@fixes) {
1021 vcs_add_commit_signers($fix, "blamed_fixes");
1022 }
1023
290603c1 1024 my @to = ();
683c6f8f
JP
1025 if ($email || $email_list) {
1026 if ($email) {
1027 @to = (@to, @email_to);
1028 }
1029 if ($email_list) {
1030 @to = (@to, @list_to);
dace8e30 1031 }
290603c1 1032 }
cb7301c7 1033
6ef1c52e 1034 if ($interactive) {
b9e2331d 1035 @to = interactive_get_maintainers(\@to);
6ef1c52e 1036 }
cb7301c7 1037
683c6f8f 1038 return @to;
cb7301c7
JP
1039}
1040
cb7301c7
JP
1041sub file_match_pattern {
1042 my ($file, $pattern) = @_;
1043 if (substr($pattern, -1) eq "/") {
1044 if ($file =~ m@^$pattern@) {
1045 return 1;
1046 }
1047 } else {
1048 if ($file =~ m@^$pattern@) {
1049 my $s1 = ($file =~ tr@/@@);
1050 my $s2 = ($pattern =~ tr@/@@);
1051 if ($s1 == $s2) {
1052 return 1;
1053 }
1054 }
1055 }
1056 return 0;
1057}
1058
1059sub usage {
1060 print <<EOT;
1061usage: $P [options] patchfile
870020f9 1062 $P [options] -f file|directory
cb7301c7
JP
1063version: $V
1064
1065MAINTAINER field selection options:
1066 --email => print email address(es) if any
1067 --git => include recent git \*-by: signers
e4d26b02 1068 --git-all-signature-types => include signers regardless of signature type
683c6f8f 1069 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
e3e9d114 1070 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
cb7301c7 1071 --git-chief-penguins => include ${penguin_chiefs}
e4d26b02
JP
1072 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
1073 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
1074 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
f5492666 1075 --git-blame => use git blame to find modified commits for patch or file
3cbcca8a 1076 --git-blame-signatures => when used with --git-blame, also include all commit signers
e4d26b02
JP
1077 --git-since => git history to use (default: $email_git_since)
1078 --hg-since => hg history to use (default: $email_hg_since)
dace8e30 1079 --interactive => display a menu (mostly useful if used with the --git option)
cb7301c7 1080 --m => include maintainer(s) if any
c1c3f2c9 1081 --r => include reviewer(s) if any
cb7301c7
JP
1082 --n => include name 'Full Name <addr\@domain.tld>'
1083 --l => include list(s) if any
49662503
JP
1084 --moderated => include moderated lists(s) if any (default: true)
1085 --s => include subscriber only list(s) if any (default: false)
11ecf53c 1086 --remove-duplicates => minimize duplicate email names/addresses
6ba31721 1087 --roles => show roles (role:subsystem, git-signer, list, etc...)
3c7385b8 1088 --rolestats => show roles and statistics (commits/total_commits, %)
9ad18c85 1089 --substatus => show subsystem status if not Maintained (default: match --roles when output is tty)"
03372dbb 1090 --file-emails => add email addresses found in -f file (default: 0 (off))
2f5bd343 1091 --fixes => for patches, add signatures of commits with 'Fixes: <commit>' (default: 1 (on))
cb7301c7
JP
1092 --scm => print SCM tree(s) if any
1093 --status => print status if any
1094 --subsystem => print subsystem name if any
1095 --web => print website(s) if any
033964f1 1096 --bug => print bug reporting info if any
cb7301c7
JP
1097
1098Output type options:
1099 --separator [, ] => separator for multiple entries on 1 line
42498316 1100 using --separator also sets --nomultiline if --separator is not [, ]
cb7301c7
JP
1101 --multiline => print 1 entry per line
1102
cb7301c7 1103Other options:
3fb55652 1104 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
b9e2331d 1105 --keywords => scan patch for keywords (default: $keywords)
71ca5ee1 1106 --keywords-in-file => scan file for keywords (default: $keywords_in_file)
b9e2331d 1107 --sections => print all of the subsystem sections with pattern matches
03aed214 1108 --letters => print all matching 'letter' types from all matching sections
b9e2331d 1109 --mailmap => use .mailmap file (default: $email_use_mailmap)
31bb82c9 1110 --no-tree => run without a kernel tree
e1f75904 1111 --self-test => show potential issues with MAINTAINERS file content
f5f5078d 1112 --version => show version
cb7301c7
JP
1113 --help => show this help information
1114
3fb55652 1115Default options:
31bb82c9 1116 [--email --tree --nogit --git-fallback --m --r --n --l --multiline
71ca5ee1 1117 --pattern-depth=0 --remove-duplicates --rolestats --keywords]
3fb55652 1118
870020f9
JP
1119Notes:
1120 Using "-f directory" may give unexpected results:
f5492666
JP
1121 Used with "--git", git signators for _all_ files in and below
1122 directory are examined as git recurses directories.
1123 Any specified X: (exclude) pattern matches are _not_ ignored.
1124 Used with "--nogit", directory is used as a pattern match,
60db31ac
JP
1125 no individual file within the directory or subdirectory
1126 is matched.
f5492666
JP
1127 Used with "--git-blame", does not iterate all files in directory
1128 Using "--git-blame" is slow and may add old committers and authors
1129 that are no longer active maintainers to the output.
3c7385b8
JP
1130 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
1131 other automated tools that expect only ["name"] <email address>
1132 may not work because of additional output after <email address>.
1133 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
1134 not the percentage of the entire file authored. # of commits is
1135 not a good measure of amount of code authored. 1 major commit may
1136 contain a thousand lines, 5 trivial commits may modify a single line.
60db31ac
JP
1137 If git is not installed, but mercurial (hg) is installed and an .hg
1138 repository exists, the following options apply to mercurial:
1139 --git,
1140 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
1141 --git-blame
1142 Use --hg-since not --git-since to control date selection
368669da
JP
1143 File ".get_maintainer.conf", if it exists in the linux kernel source root
1144 directory, can change whatever get_maintainer defaults are desired.
1145 Entries in this file can be any command line argument.
1146 This file is prepended to any additional command line arguments.
1147 Multiple lines and # comments are allowed.
b1312bfe
BN
1148 Most options have both positive and negative forms.
1149 The negative forms for --<foo> are --no<foo> and --no-<foo>.
1150
cb7301c7
JP
1151EOT
1152}
1153
1154sub top_of_kernel_tree {
47abc722 1155 my ($lk_path) = @_;
cb7301c7 1156
47abc722
JP
1157 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
1158 $lk_path .= "/";
1159 }
1160 if ( (-f "${lk_path}COPYING")
1161 && (-f "${lk_path}CREDITS")
1162 && (-f "${lk_path}Kbuild")
6f7d98ec 1163 && (-e "${lk_path}MAINTAINERS")
47abc722
JP
1164 && (-f "${lk_path}Makefile")
1165 && (-f "${lk_path}README")
1166 && (-d "${lk_path}Documentation")
1167 && (-d "${lk_path}arch")
1168 && (-d "${lk_path}include")
1169 && (-d "${lk_path}drivers")
1170 && (-d "${lk_path}fs")
1171 && (-d "${lk_path}init")
1172 && (-d "${lk_path}ipc")
1173 && (-d "${lk_path}kernel")
1174 && (-d "${lk_path}lib")
1175 && (-d "${lk_path}scripts")) {
1176 return 1;
1177 }
1178 return 0;
cb7301c7
JP
1179}
1180
9c334eb9
1181sub escape_name {
1182 my ($name) = @_;
1183
1184 if ($name =~ /[^\w \-]/ai) { ##has "must quote" chars
1185 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
1186 $name = "\"$name\"";
1187 }
1188
1189 return $name;
1190}
1191
0e70e83d
JP
1192sub parse_email {
1193 my ($formatted_email) = @_;
1194
1195 my $name = "";
1196 my $address = "";
1197
11ecf53c 1198 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
0e70e83d
JP
1199 $name = $1;
1200 $address = $2;
11ecf53c 1201 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
0e70e83d 1202 $address = $1;
b781655a 1203 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
0e70e83d
JP
1204 $address = $1;
1205 }
cb7301c7
JP
1206
1207 $name =~ s/^\s+|\s+$//g;
d789504a 1208 $name =~ s/^\"|\"$//g;
9c334eb9 1209 $name = escape_name($name);
0e70e83d 1210 $address =~ s/^\s+|\s+$//g;
cb7301c7 1211
0e70e83d
JP
1212 return ($name, $address);
1213}
1214
1215sub format_email {
a8af2430 1216 my ($name, $address, $usename) = @_;
0e70e83d
JP
1217
1218 my $formatted_email;
1219
1220 $name =~ s/^\s+|\s+$//g;
1221 $name =~ s/^\"|\"$//g;
9c334eb9 1222 $name = escape_name($name);
0e70e83d 1223 $address =~ s/^\s+|\s+$//g;
cb7301c7 1224
a8af2430 1225 if ($usename) {
0e70e83d
JP
1226 if ("$name" eq "") {
1227 $formatted_email = "$address";
1228 } else {
a8af2430 1229 $formatted_email = "$name <$address>";
0e70e83d 1230 }
cb7301c7 1231 } else {
0e70e83d 1232 $formatted_email = $address;
cb7301c7 1233 }
0e70e83d 1234
cb7301c7
JP
1235 return $formatted_email;
1236}
1237
272a8979
JP
1238sub find_first_section {
1239 my $index = 0;
1240
1241 while ($index < @typevalue) {
1242 my $tv = $typevalue[$index];
ce8155f7 1243 if (($tv =~ m/^([A-Z]):\s*(.*)/)) {
272a8979
JP
1244 last;
1245 }
1246 $index++;
1247 }
1248
1249 return $index;
1250}
1251
b781655a 1252sub find_starting_index {
b781655a
JP
1253 my ($index) = @_;
1254
1255 while ($index > 0) {
1256 my $tv = $typevalue[$index];
ce8155f7 1257 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
b781655a
JP
1258 last;
1259 }
1260 $index--;
1261 }
1262
1263 return $index;
1264}
1265
1266sub find_ending_index {
cb7301c7
JP
1267 my ($index) = @_;
1268
b781655a 1269 while ($index < @typevalue) {
cb7301c7 1270 my $tv = $typevalue[$index];
ce8155f7 1271 if (!($tv =~ m/^([A-Z]):\s*(.*)/)) {
b781655a
JP
1272 last;
1273 }
1274 $index++;
1275 }
1276
1277 return $index;
1278}
1279
2a7cb1dc 1280sub get_subsystem_name {
3c7385b8
JP
1281 my ($index) = @_;
1282
3c7385b8 1283 my $start = find_starting_index($index);
3c7385b8 1284
3c7385b8 1285 my $subsystem = $typevalue[$start];
364f68dc
JP
1286 if ($output_section_maxlen && length($subsystem) > $output_section_maxlen) {
1287 $subsystem = substr($subsystem, 0, $output_section_maxlen - 3);
3c7385b8
JP
1288 $subsystem =~ s/\s*$//;
1289 $subsystem = $subsystem . "...";
1290 }
2a7cb1dc
JP
1291 return $subsystem;
1292}
1293
1294sub get_maintainer_role {
1295 my ($index) = @_;
1296
1297 my $i;
1298 my $start = find_starting_index($index);
1299 my $end = find_ending_index($index);
1300
6ba31721 1301 my $role = "maintainer";
2a7cb1dc 1302 my $subsystem = get_subsystem_name($index);
6ba31721 1303 my $status = "unknown";
3c7385b8
JP
1304
1305 for ($i = $start + 1; $i < $end; $i++) {
1306 my $tv = $typevalue[$i];
ce8155f7 1307 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
3c7385b8
JP
1308 my $ptype = $1;
1309 my $pvalue = $2;
1310 if ($ptype eq "S") {
6ba31721 1311 $status = $pvalue;
3c7385b8
JP
1312 }
1313 }
1314 }
1315
6ba31721
VB
1316 $status = lc($status);
1317 if ($status eq "buried alive in reporters") {
3c7385b8
JP
1318 $role = "chief penguin";
1319 }
1320
1321 return $role . ":" . $subsystem;
1322}
1323
1324sub get_list_role {
1325 my ($index) = @_;
1326
2a7cb1dc 1327 my $subsystem = get_subsystem_name($index);
3c7385b8
JP
1328
1329 if ($subsystem eq "THE REST") {
1330 $subsystem = "";
1331 }
1332
1333 return $subsystem;
1334}
1335
b781655a 1336sub add_categories {
71ca5ee1 1337 my ($index, $suffix) = @_;
b781655a
JP
1338
1339 my $i;
1340 my $start = find_starting_index($index);
1341 my $end = find_ending_index($index);
1342
9ad18c85
VB
1343 my $subsystem = $typevalue[$start];
1344 push(@subsystem, $subsystem);
1345 my $status = "Unknown";
b781655a
JP
1346
1347 for ($i = $start + 1; $i < $end; $i++) {
1348 my $tv = $typevalue[$i];
ce8155f7 1349 if ($tv =~ m/^([A-Z]):\s*(.*)/) {
cb7301c7
JP
1350 my $ptype = $1;
1351 my $pvalue = $2;
1352 if ($ptype eq "L") {
290603c1
JP
1353 my $list_address = $pvalue;
1354 my $list_additional = "";
3c7385b8
JP
1355 my $list_role = get_list_role($i);
1356
1357 if ($list_role ne "") {
1358 $list_role = ":" . $list_role;
1359 }
290603c1
JP
1360 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
1361 $list_address = $1;
1362 $list_additional = $2;
1363 }
bdf7c685 1364 if ($list_additional =~ m/subscribers-only/) {
cb7301c7 1365 if ($email_subscriber_list) {
6ef1c52e
JP
1366 if (!$hash_list_to{lc($list_address)}) {
1367 $hash_list_to{lc($list_address)} = 1;
683c6f8f 1368 push(@list_to, [$list_address,
71ca5ee1 1369 "subscriber list${list_role}" . $suffix]);
683c6f8f 1370 }
cb7301c7
JP
1371 }
1372 } else {
1373 if ($email_list) {
6ef1c52e 1374 if (!$hash_list_to{lc($list_address)}) {
728f5a94 1375 if ($list_additional =~ m/moderated/) {
49662503
JP
1376 if ($email_moderated_list) {
1377 $hash_list_to{lc($list_address)} = 1;
1378 push(@list_to, [$list_address,
71ca5ee1 1379 "moderated list${list_role}" . $suffix]);
49662503 1380 }
728f5a94 1381 } else {
49662503 1382 $hash_list_to{lc($list_address)} = 1;
728f5a94 1383 push(@list_to, [$list_address,
71ca5ee1 1384 "open list${list_role}" . $suffix]);
728f5a94 1385 }
683c6f8f 1386 }
cb7301c7
JP
1387 }
1388 }
1389 } elsif ($ptype eq "M") {
0e70e83d 1390 if ($email_maintainer) {
3c7385b8 1391 my $role = get_maintainer_role($i);
71ca5ee1 1392 push_email_addresses($pvalue, $role . $suffix);
cb7301c7 1393 }
c1c3f2c9 1394 } elsif ($ptype eq "R") {
c1c3f2c9 1395 if ($email_reviewer) {
9ad18c85
VB
1396 my $subs = get_subsystem_name($i);
1397 push_email_addresses($pvalue, "reviewer:$subs" . $suffix);
c1c3f2c9 1398 }
cb7301c7 1399 } elsif ($ptype eq "T") {
71ca5ee1 1400 push(@scm, $pvalue . $suffix);
cb7301c7 1401 } elsif ($ptype eq "W") {
71ca5ee1 1402 push(@web, $pvalue . $suffix);
033964f1
JN
1403 } elsif ($ptype eq "B") {
1404 push(@bug, $pvalue . $suffix);
cb7301c7 1405 } elsif ($ptype eq "S") {
71ca5ee1 1406 push(@status, $pvalue . $suffix);
9ad18c85 1407 $status = $pvalue;
cb7301c7 1408 }
cb7301c7
JP
1409 }
1410 }
9ad18c85
VB
1411
1412 if ($subsystem ne "THE REST" and $status ne "Maintained") {
1413 push(@substatus, $subsystem . " status: " . $status . $suffix)
1414 }
cb7301c7
JP
1415}
1416
11ecf53c
JP
1417sub email_inuse {
1418 my ($name, $address) = @_;
1419
1420 return 1 if (($name eq "") && ($address eq ""));
6ef1c52e
JP
1421 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
1422 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
0e70e83d 1423
0e70e83d
JP
1424 return 0;
1425}
1426
1b5e1cf6 1427sub push_email_address {
3c7385b8 1428 my ($line, $role) = @_;
1b5e1cf6 1429
0e70e83d 1430 my ($name, $address) = parse_email($line);
1b5e1cf6 1431
b781655a
JP
1432 if ($address eq "") {
1433 return 0;
1434 }
1435
11ecf53c 1436 if (!$email_remove_duplicates) {
a8af2430 1437 push(@email_to, [format_email($name, $address, $email_usename), $role]);
11ecf53c 1438 } elsif (!email_inuse($name, $address)) {
a8af2430 1439 push(@email_to, [format_email($name, $address, $email_usename), $role]);
fae99206 1440 $email_hash_name{lc($name)}++ if ($name ne "");
6ef1c52e 1441 $email_hash_address{lc($address)}++;
1b5e1cf6 1442 }
b781655a
JP
1443
1444 return 1;
1b5e1cf6
JP
1445}
1446
1447sub push_email_addresses {
3c7385b8 1448 my ($address, $role) = @_;
1b5e1cf6
JP
1449
1450 my @address_list = ();
1451
5f2441e9 1452 if (rfc822_valid($address)) {
3c7385b8 1453 push_email_address($address, $role);
5f2441e9 1454 } elsif (@address_list = rfc822_validlist($address)) {
1b5e1cf6
JP
1455 my $array_count = shift(@address_list);
1456 while (my $entry = shift(@address_list)) {
3c7385b8 1457 push_email_address($entry, $role);
1b5e1cf6 1458 }
5f2441e9 1459 } else {
3c7385b8 1460 if (!push_email_address($address, $role)) {
b781655a
JP
1461 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1462 }
1b5e1cf6 1463 }
1b5e1cf6
JP
1464}
1465
3c7385b8
JP
1466sub add_role {
1467 my ($line, $role) = @_;
1468
1469 my ($name, $address) = parse_email($line);
a8af2430 1470 my $email = format_email($name, $address, $email_usename);
3c7385b8
JP
1471
1472 foreach my $entry (@email_to) {
1473 if ($email_remove_duplicates) {
1474 my ($entry_name, $entry_address) = parse_email($entry->[0]);
03372dbb
JP
1475 if (($name eq $entry_name || $address eq $entry_address)
1476 && ($role eq "" || !($entry->[1] =~ m/$role/))
1477 ) {
3c7385b8
JP
1478 if ($entry->[1] eq "") {
1479 $entry->[1] = "$role";
1480 } else {
1481 $entry->[1] = "$entry->[1],$role";
1482 }
1483 }
1484 } else {
03372dbb
JP
1485 if ($email eq $entry->[0]
1486 && ($role eq "" || !($entry->[1] =~ m/$role/))
1487 ) {
3c7385b8
JP
1488 if ($entry->[1] eq "") {
1489 $entry->[1] = "$role";
1490 } else {
1491 $entry->[1] = "$entry->[1],$role";
1492 }
1493 }
1494 }
1495 }
1496}
1497
cb7301c7
JP
1498sub which {
1499 my ($bin) = @_;
1500
f5f5078d 1501 foreach my $path (split(/:/, $ENV{PATH})) {
cb7301c7
JP
1502 if (-e "$path/$bin") {
1503 return "$path/$bin";
1504 }
1505 }
1506
1507 return "";
1508}
1509
bcde44ed
JP
1510sub which_conf {
1511 my ($conf) = @_;
1512
1513 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1514 if (-e "$path/$conf") {
1515 return "$path/$conf";
1516 }
1517 }
1518
1519 return "";
1520}
1521
7fa8ff2e 1522sub mailmap_email {
b9e2331d 1523 my ($line) = @_;
7fa8ff2e 1524
47abc722
JP
1525 my ($name, $address) = parse_email($line);
1526 my $email = format_email($name, $address, 1);
1527 my $real_name = $name;
1528 my $real_address = $address;
1529
1530 if (exists $mailmap->{names}->{$email} ||
1531 exists $mailmap->{addresses}->{$email}) {
1532 if (exists $mailmap->{names}->{$email}) {
1533 $real_name = $mailmap->{names}->{$email};
1534 }
1535 if (exists $mailmap->{addresses}->{$email}) {
1536 $real_address = $mailmap->{addresses}->{$email};
1537 }
1538 } else {
1539 if (exists $mailmap->{names}->{$address}) {
1540 $real_name = $mailmap->{names}->{$address};
1541 }
1542 if (exists $mailmap->{addresses}->{$address}) {
1543 $real_address = $mailmap->{addresses}->{$address};
8cbb3a77 1544 }
47abc722
JP
1545 }
1546 return format_email($real_name, $real_address, 1);
7fa8ff2e
FM
1547}
1548
1549sub mailmap {
1550 my (@addresses) = @_;
1551
b9e2331d 1552 my @mapped_emails = ();
7fa8ff2e 1553 foreach my $line (@addresses) {
b9e2331d 1554 push(@mapped_emails, mailmap_email($line));
8cbb3a77 1555 }
b9e2331d
JP
1556 merge_by_realname(@mapped_emails) if ($email_use_mailmap);
1557 return @mapped_emails;
7fa8ff2e
FM
1558}
1559
1560sub merge_by_realname {
47abc722
JP
1561 my %address_map;
1562 my (@emails) = @_;
b9e2331d 1563
47abc722
JP
1564 foreach my $email (@emails) {
1565 my ($name, $address) = parse_email($email);
b9e2331d 1566 if (exists $address_map{$name}) {
47abc722 1567 $address = $address_map{$name};
b9e2331d
JP
1568 $email = format_email($name, $address, 1);
1569 } else {
1570 $address_map{$name} = $address;
7fa8ff2e 1571 }
47abc722 1572 }
8cbb3a77
JP
1573}
1574
60db31ac
JP
1575sub git_execute_cmd {
1576 my ($cmd) = @_;
1577 my @lines = ();
cb7301c7 1578
60db31ac
JP
1579 my $output = `$cmd`;
1580 $output =~ s/^\s*//gm;
1581 @lines = split("\n", $output);
1582
1583 return @lines;
a8af2430
JP
1584}
1585
60db31ac 1586sub hg_execute_cmd {
a8af2430 1587 my ($cmd) = @_;
60db31ac
JP
1588 my @lines = ();
1589
1590 my $output = `$cmd`;
1591 @lines = split("\n", $output);
a8af2430 1592
60db31ac
JP
1593 return @lines;
1594}
1595
683c6f8f
JP
1596sub extract_formatted_signatures {
1597 my (@signature_lines) = @_;
1598
1599 my @type = @signature_lines;
1600
1601 s/\s*(.*):.*/$1/ for (@type);
1602
1603 # cut -f2- -d":"
1604 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1605
1606## Reformat email addresses (with names) to avoid badly written signatures
1607
1608 foreach my $signer (@signature_lines) {
b9e2331d 1609 $signer = deduplicate_email($signer);
683c6f8f
JP
1610 }
1611
1612 return (\@type, \@signature_lines);
1613}
1614
60db31ac 1615sub vcs_find_signers {
c9ecefea 1616 my ($cmd, $file) = @_;
a8af2430 1617 my $commits;
683c6f8f
JP
1618 my @lines = ();
1619 my @signatures = ();
c9ecefea
JP
1620 my @authors = ();
1621 my @stats = ();
a8af2430 1622
60db31ac 1623 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
cb7301c7 1624
60db31ac 1625 my $pattern = $VCS_cmds{"commit_pattern"};
c9ecefea
JP
1626 my $author_pattern = $VCS_cmds{"author_pattern"};
1627 my $stat_pattern = $VCS_cmds{"stat_pattern"};
1628
1629 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
cb7301c7 1630
60db31ac 1631 $commits = grep(/$pattern/, @lines); # of commits
afa81ee1 1632
c9ecefea 1633 @authors = grep(/$author_pattern/, @lines);
683c6f8f 1634 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
c9ecefea 1635 @stats = grep(/$stat_pattern/, @lines);
63ab52db 1636
c9ecefea
JP
1637# print("stats: <@stats>\n");
1638
1639 return (0, \@signatures, \@authors, \@stats) if !@signatures;
63ab52db 1640
683c6f8f
JP
1641 save_commits_by_author(@lines) if ($interactive);
1642 save_commits_by_signer(@lines) if ($interactive);
0e70e83d 1643
683c6f8f
JP
1644 if (!$email_git_penguin_chiefs) {
1645 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
a8af2430
JP
1646 }
1647
c9ecefea 1648 my ($author_ref, $authors_ref) = extract_formatted_signatures(@authors);
683c6f8f
JP
1649 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
1650
c9ecefea 1651 return ($commits, $signers_ref, $authors_ref, \@stats);
a8af2430
JP
1652}
1653
63ab52db
JP
1654sub vcs_find_author {
1655 my ($cmd) = @_;
1656 my @lines = ();
1657
1658 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1659
1660 if (!$email_git_penguin_chiefs) {
1661 @lines = grep(!/${penguin_chiefs}/i, @lines);
1662 }
1663
1664 return @lines if !@lines;
1665
683c6f8f 1666 my @authors = ();
63ab52db 1667 foreach my $line (@lines) {
683c6f8f
JP
1668 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1669 my $author = $1;
1670 my ($name, $address) = parse_email($author);
1671 $author = format_email($name, $address, 1);
1672 push(@authors, $author);
1673 }
63ab52db
JP
1674 }
1675
683c6f8f
JP
1676 save_commits_by_author(@lines) if ($interactive);
1677 save_commits_by_signer(@lines) if ($interactive);
1678
1679 return @authors;
63ab52db
JP
1680}
1681
60db31ac
JP
1682sub vcs_save_commits {
1683 my ($cmd) = @_;
1684 my @lines = ();
1685 my @commits = ();
1686
1687 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1688
1689 foreach my $line (@lines) {
1690 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1691 push(@commits, $1);
1692 }
1693 }
1694
1695 return @commits;
1696}
1697
1698sub vcs_blame {
1699 my ($file) = @_;
1700 my $cmd;
1701 my @commits = ();
1702
1703 return @commits if (!(-f $file));
1704
1705 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1706 my @all_commits = ();
1707
1708 $cmd = $VCS_cmds{"blame_file_cmd"};
1709 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1710 @all_commits = vcs_save_commits($cmd);
1711
1712 foreach my $file_range_diff (@range) {
1713 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1714 my $diff_file = $1;
1715 my $diff_start = $2;
1716 my $diff_length = $3;
1717 next if ("$file" ne "$diff_file");
1718 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1719 push(@commits, $all_commits[$i]);
1720 }
1721 }
1722 } elsif (@range) {
1723 foreach my $file_range_diff (@range) {
1724 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1725 my $diff_file = $1;
1726 my $diff_start = $2;
1727 my $diff_length = $3;
1728 next if ("$file" ne "$diff_file");
1729 $cmd = $VCS_cmds{"blame_range_cmd"};
1730 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1731 push(@commits, vcs_save_commits($cmd));
1732 }
1733 } else {
1734 $cmd = $VCS_cmds{"blame_file_cmd"};
1735 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1736 @commits = vcs_save_commits($cmd);
1737 }
1738
63ab52db
JP
1739 foreach my $commit (@commits) {
1740 $commit =~ s/^\^//g;
1741 }
1742
60db31ac
JP
1743 return @commits;
1744}
1745
1746my $printed_novcs = 0;
1747sub vcs_exists {
1748 %VCS_cmds = %VCS_cmds_git;
1749 return 1 if eval $VCS_cmds{"available"};
1750 %VCS_cmds = %VCS_cmds_hg;
683c6f8f 1751 return 2 if eval $VCS_cmds{"available"};
60db31ac 1752 %VCS_cmds = ();
26d98e9f 1753 if (!$printed_novcs && $email_git) {
60db31ac
JP
1754 warn("$P: No supported VCS found. Add --nogit to options?\n");
1755 warn("Using a git repository produces better results.\n");
1756 warn("Try Linus Torvalds' latest git repository using:\n");
3d1c2f72 1757 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git\n");
60db31ac
JP
1758 $printed_novcs = 1;
1759 }
1760 return 0;
1761}
1762
683c6f8f 1763sub vcs_is_git {
b9e2331d 1764 vcs_exists();
683c6f8f
JP
1765 return $vcs_used == 1;
1766}
1767
1768sub vcs_is_hg {
1769 return $vcs_used == 2;
1770}
1771
2f5bd343
JP
1772sub vcs_add_commit_signers {
1773 return if (!vcs_exists());
1774
1775 my ($commit, $desc) = @_;
1776 my $commit_count = 0;
1777 my $commit_authors_ref;
1778 my $commit_signers_ref;
1779 my $stats_ref;
1780 my @commit_authors = ();
1781 my @commit_signers = ();
1782 my $cmd;
1783
1784 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1785 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1786
1787 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, "");
1788 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
1789 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
1790
1791 foreach my $signer (@commit_signers) {
1792 $signer = deduplicate_email($signer);
1793 }
1794
1795 vcs_assign($desc, 1, @commit_signers);
1796}
1797
6ef1c52e 1798sub interactive_get_maintainers {
683c6f8f 1799 my ($list_ref) = @_;
dace8e30
FM
1800 my @list = @$list_ref;
1801
683c6f8f 1802 vcs_exists();
dace8e30
FM
1803
1804 my %selected;
683c6f8f
JP
1805 my %authored;
1806 my %signed;
dace8e30 1807 my $count = 0;
6ef1c52e 1808 my $maintained = 0;
6ef1c52e 1809 foreach my $entry (@list) {
b9e2331d
JP
1810 $maintained = 1 if ($entry->[1] =~ /^(maintainer|supporter)/i);
1811 $selected{$count} = 1;
683c6f8f
JP
1812 $authored{$count} = 0;
1813 $signed{$count} = 0;
1814 $count++;
dace8e30
FM
1815 }
1816
1817 #menu loop
683c6f8f
JP
1818 my $done = 0;
1819 my $print_options = 0;
1820 my $redraw = 1;
1821 while (!$done) {
1822 $count = 0;
1823 if ($redraw) {
6ef1c52e
JP
1824 printf STDERR "\n%1s %2s %-65s",
1825 "*", "#", "email/list and role:stats";
1826 if ($email_git ||
1827 ($email_git_fallback && !$maintained) ||
1828 $email_git_blame) {
1829 print STDERR "auth sign";
1830 }
1831 print STDERR "\n";
683c6f8f
JP
1832 foreach my $entry (@list) {
1833 my $email = $entry->[0];
1834 my $role = $entry->[1];
1835 my $sel = "";
1836 $sel = "*" if ($selected{$count});
1837 my $commit_author = $commit_author_hash{$email};
1838 my $commit_signer = $commit_signer_hash{$email};
1839 my $authored = 0;
1840 my $signed = 0;
1841 $authored++ for (@{$commit_author});
1842 $signed++ for (@{$commit_signer});
1843 printf STDERR "%1s %2d %-65s", $sel, $count + 1, $email;
1844 printf STDERR "%4d %4d", $authored, $signed
1845 if ($authored > 0 || $signed > 0);
1846 printf STDERR "\n %s\n", $role;
1847 if ($authored{$count}) {
1848 my $commit_author = $commit_author_hash{$email};
1849 foreach my $ref (@{$commit_author}) {
1850 print STDERR " Author: @{$ref}[1]\n";
dace8e30 1851 }
dace8e30 1852 }
683c6f8f
JP
1853 if ($signed{$count}) {
1854 my $commit_signer = $commit_signer_hash{$email};
1855 foreach my $ref (@{$commit_signer}) {
1856 print STDERR " @{$ref}[2]: @{$ref}[1]\n";
1857 }
1858 }
1859
1860 $count++;
1861 }
1862 }
1863 my $date_ref = \$email_git_since;
1864 $date_ref = \$email_hg_since if (vcs_is_hg());
1865 if ($print_options) {
1866 $print_options = 0;
1867 if (vcs_exists()) {
b9e2331d
JP
1868 print STDERR <<EOT
1869
1870Version Control options:
1871g use git history [$email_git]
1872gf use git-fallback [$email_git_fallback]
1873b use git blame [$email_git_blame]
1874bs use blame signatures [$email_git_blame_signatures]
1875c# minimum commits [$email_git_min_signatures]
1876%# min percent [$email_git_min_percent]
1877d# history to use [$$date_ref]
1878x# max maintainers [$email_git_max_maintainers]
1879t all signature types [$email_git_all_signature_types]
1880m use .mailmap [$email_use_mailmap]
1881EOT
dace8e30 1882 }
b9e2331d
JP
1883 print STDERR <<EOT
1884
1885Additional options:
18860 toggle all
1887tm toggle maintainers
1888tg toggle git entries
1889tl toggle open list entries
1890ts toggle subscriber list entries
0c78c013 1891f emails in file [$email_file_emails]
b9e2331d
JP
1892k keywords in file [$keywords]
1893r remove duplicates [$email_remove_duplicates]
1894p# pattern match depth [$pattern_depth]
1895EOT
dace8e30 1896 }
683c6f8f
JP
1897 print STDERR
1898"\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1899
1900 my $input = <STDIN>;
dace8e30
FM
1901 chomp($input);
1902
683c6f8f
JP
1903 $redraw = 1;
1904 my $rerun = 0;
1905 my @wish = split(/[, ]+/, $input);
1906 foreach my $nr (@wish) {
1907 $nr = lc($nr);
1908 my $sel = substr($nr, 0, 1);
1909 my $str = substr($nr, 1);
1910 my $val = 0;
1911 $val = $1 if $str =~ /^(\d+)$/;
1912
1913 if ($sel eq "y") {
1914 $interactive = 0;
1915 $done = 1;
1916 $output_rolestats = 0;
1917 $output_roles = 0;
9ad18c85 1918 $output_substatus = 0;
683c6f8f
JP
1919 last;
1920 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1921 $selected{$nr - 1} = !$selected{$nr - 1};
1922 } elsif ($sel eq "*" || $sel eq '^') {
1923 my $toggle = 0;
1924 $toggle = 1 if ($sel eq '*');
1925 for (my $i = 0; $i < $count; $i++) {
1926 $selected{$i} = $toggle;
dace8e30 1927 }
683c6f8f
JP
1928 } elsif ($sel eq "0") {
1929 for (my $i = 0; $i < $count; $i++) {
1930 $selected{$i} = !$selected{$i};
1931 }
b9e2331d
JP
1932 } elsif ($sel eq "t") {
1933 if (lc($str) eq "m") {
1934 for (my $i = 0; $i < $count; $i++) {
1935 $selected{$i} = !$selected{$i}
1936 if ($list[$i]->[1] =~ /^(maintainer|supporter)/i);
1937 }
1938 } elsif (lc($str) eq "g") {
1939 for (my $i = 0; $i < $count; $i++) {
1940 $selected{$i} = !$selected{$i}
1941 if ($list[$i]->[1] =~ /^(author|commit|signer)/i);
1942 }
1943 } elsif (lc($str) eq "l") {
1944 for (my $i = 0; $i < $count; $i++) {
1945 $selected{$i} = !$selected{$i}
1946 if ($list[$i]->[1] =~ /^(open list)/i);
1947 }
1948 } elsif (lc($str) eq "s") {
1949 for (my $i = 0; $i < $count; $i++) {
1950 $selected{$i} = !$selected{$i}
1951 if ($list[$i]->[1] =~ /^(subscriber list)/i);
1952 }
1953 }
683c6f8f
JP
1954 } elsif ($sel eq "a") {
1955 if ($val > 0 && $val <= $count) {
1956 $authored{$val - 1} = !$authored{$val - 1};
1957 } elsif ($str eq '*' || $str eq '^') {
1958 my $toggle = 0;
1959 $toggle = 1 if ($str eq '*');
1960 for (my $i = 0; $i < $count; $i++) {
1961 $authored{$i} = $toggle;
1962 }
1963 }
1964 } elsif ($sel eq "s") {
1965 if ($val > 0 && $val <= $count) {
1966 $signed{$val - 1} = !$signed{$val - 1};
1967 } elsif ($str eq '*' || $str eq '^') {
1968 my $toggle = 0;
1969 $toggle = 1 if ($str eq '*');
1970 for (my $i = 0; $i < $count; $i++) {
1971 $signed{$i} = $toggle;
1972 }
1973 }
1974 } elsif ($sel eq "o") {
1975 $print_options = 1;
1976 $redraw = 1;
1977 } elsif ($sel eq "g") {
1978 if ($str eq "f") {
1979 bool_invert(\$email_git_fallback);
dace8e30 1980 } else {
683c6f8f
JP
1981 bool_invert(\$email_git);
1982 }
1983 $rerun = 1;
1984 } elsif ($sel eq "b") {
1985 if ($str eq "s") {
1986 bool_invert(\$email_git_blame_signatures);
1987 } else {
1988 bool_invert(\$email_git_blame);
1989 }
1990 $rerun = 1;
1991 } elsif ($sel eq "c") {
1992 if ($val > 0) {
1993 $email_git_min_signatures = $val;
1994 $rerun = 1;
1995 }
1996 } elsif ($sel eq "x") {
1997 if ($val > 0) {
1998 $email_git_max_maintainers = $val;
1999 $rerun = 1;
2000 }
2001 } elsif ($sel eq "%") {
2002 if ($str ne "" && $val >= 0) {
2003 $email_git_min_percent = $val;
2004 $rerun = 1;
dace8e30 2005 }
683c6f8f
JP
2006 } elsif ($sel eq "d") {
2007 if (vcs_is_git()) {
2008 $email_git_since = $str;
2009 } elsif (vcs_is_hg()) {
2010 $email_hg_since = $str;
2011 }
2012 $rerun = 1;
2013 } elsif ($sel eq "t") {
2014 bool_invert(\$email_git_all_signature_types);
2015 $rerun = 1;
2016 } elsif ($sel eq "f") {
0c78c013 2017 bool_invert(\$email_file_emails);
683c6f8f
JP
2018 $rerun = 1;
2019 } elsif ($sel eq "r") {
2020 bool_invert(\$email_remove_duplicates);
2021 $rerun = 1;
b9e2331d
JP
2022 } elsif ($sel eq "m") {
2023 bool_invert(\$email_use_mailmap);
2024 read_mailmap();
2025 $rerun = 1;
683c6f8f
JP
2026 } elsif ($sel eq "k") {
2027 bool_invert(\$keywords);
2028 $rerun = 1;
2029 } elsif ($sel eq "p") {
2030 if ($str ne "" && $val >= 0) {
2031 $pattern_depth = $val;
2032 $rerun = 1;
2033 }
6ef1c52e
JP
2034 } elsif ($sel eq "h" || $sel eq "?") {
2035 print STDERR <<EOT
2036
2037Interactive mode allows you to select the various maintainers, submitters,
2038commit signers and mailing lists that could be CC'd on a patch.
2039
2040Any *'d entry is selected.
2041
47abc722 2042If you have git or hg installed, you can choose to summarize the commit
6ef1c52e
JP
2043history of files in the patch. Also, each line of the current file can
2044be matched to its commit author and that commits signers with blame.
2045
2046Various knobs exist to control the length of time for active commit
2047tracking, the maximum number of commit authors and signers to add,
2048and such.
2049
2050Enter selections at the prompt until you are satisfied that the selected
2051maintainers are appropriate. You may enter multiple selections separated
2052by either commas or spaces.
2053
2054EOT
683c6f8f
JP
2055 } else {
2056 print STDERR "invalid option: '$nr'\n";
2057 $redraw = 0;
2058 }
2059 }
2060 if ($rerun) {
2061 print STDERR "git-blame can be very slow, please have patience..."
2062 if ($email_git_blame);
6ef1c52e 2063 goto &get_maintainers;
683c6f8f
JP
2064 }
2065 }
dace8e30
FM
2066
2067 #drop not selected entries
2068 $count = 0;
683c6f8f
JP
2069 my @new_emailto = ();
2070 foreach my $entry (@list) {
2071 if ($selected{$count}) {
2072 push(@new_emailto, $list[$count]);
dace8e30
FM
2073 }
2074 $count++;
2075 }
683c6f8f 2076 return @new_emailto;
dace8e30
FM
2077}
2078
683c6f8f
JP
2079sub bool_invert {
2080 my ($bool_ref) = @_;
2081
2082 if ($$bool_ref) {
2083 $$bool_ref = 0;
2084 } else {
2085 $$bool_ref = 1;
2086 }
dace8e30
FM
2087}
2088
b9e2331d
JP
2089sub deduplicate_email {
2090 my ($email) = @_;
2091
2092 my $matched = 0;
2093 my ($name, $address) = parse_email($email);
2094 $email = format_email($name, $address, 1);
2095 $email = mailmap_email($email);
2096
2097 return $email if (!$email_remove_duplicates);
2098
2099 ($name, $address) = parse_email($email);
2100
fae99206 2101 if ($name ne "" && $deduplicate_name_hash{lc($name)}) {
b9e2331d
JP
2102 $name = $deduplicate_name_hash{lc($name)}->[0];
2103 $address = $deduplicate_name_hash{lc($name)}->[1];
2104 $matched = 1;
2105 } elsif ($deduplicate_address_hash{lc($address)}) {
2106 $name = $deduplicate_address_hash{lc($address)}->[0];
2107 $address = $deduplicate_address_hash{lc($address)}->[1];
2108 $matched = 1;
2109 }
2110 if (!$matched) {
2111 $deduplicate_name_hash{lc($name)} = [ $name, $address ];
2112 $deduplicate_address_hash{lc($address)} = [ $name, $address ];
2113 }
2114 $email = format_email($name, $address, 1);
2115 $email = mailmap_email($email);
2116 return $email;
2117}
2118
683c6f8f
JP
2119sub save_commits_by_author {
2120 my (@lines) = @_;
2121
2122 my @authors = ();
2123 my @commits = ();
2124 my @subjects = ();
2125
2126 foreach my $line (@lines) {
2127 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2128 my $author = $1;
b9e2331d 2129 $author = deduplicate_email($author);
683c6f8f
JP
2130 push(@authors, $author);
2131 }
2132 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2133 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2134 }
2135
2136 for (my $i = 0; $i < @authors; $i++) {
2137 my $exists = 0;
2138 foreach my $ref(@{$commit_author_hash{$authors[$i]}}) {
2139 if (@{$ref}[0] eq $commits[$i] &&
2140 @{$ref}[1] eq $subjects[$i]) {
2141 $exists = 1;
2142 last;
2143 }
2144 }
2145 if (!$exists) {
2146 push(@{$commit_author_hash{$authors[$i]}},
2147 [ ($commits[$i], $subjects[$i]) ]);
2148 }
dace8e30 2149 }
dace8e30
FM
2150}
2151
683c6f8f
JP
2152sub save_commits_by_signer {
2153 my (@lines) = @_;
2154
2155 my $commit = "";
2156 my $subject = "";
dace8e30 2157
683c6f8f
JP
2158 foreach my $line (@lines) {
2159 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
2160 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
2161 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
2162 my @signatures = ($line);
2163 my ($types_ref, $signers_ref) = extract_formatted_signatures(@signatures);
2164 my @types = @$types_ref;
2165 my @signers = @$signers_ref;
2166
2167 my $type = $types[0];
2168 my $signer = $signers[0];
2169
b9e2331d 2170 $signer = deduplicate_email($signer);
6ef1c52e 2171
683c6f8f
JP
2172 my $exists = 0;
2173 foreach my $ref(@{$commit_signer_hash{$signer}}) {
2174 if (@{$ref}[0] eq $commit &&
2175 @{$ref}[1] eq $subject &&
2176 @{$ref}[2] eq $type) {
2177 $exists = 1;
2178 last;
2179 }
2180 }
2181 if (!$exists) {
2182 push(@{$commit_signer_hash{$signer}},
2183 [ ($commit, $subject, $type) ]);
2184 }
2185 }
2186 }
dace8e30
FM
2187}
2188
60db31ac 2189sub vcs_assign {
a8af2430
JP
2190 my ($role, $divisor, @lines) = @_;
2191
2192 my %hash;
2193 my $count = 0;
2194
a8af2430
JP
2195 return if (@lines <= 0);
2196
2197 if ($divisor <= 0) {
60db31ac 2198 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
a8af2430 2199 $divisor = 1;
3c7385b8 2200 }
8cbb3a77 2201
7fa8ff2e 2202 @lines = mailmap(@lines);
0e70e83d 2203
63ab52db
JP
2204 return if (@lines <= 0);
2205
0e70e83d 2206 @lines = sort(@lines);
11ecf53c 2207
0e70e83d 2208 # uniq -c
11ecf53c
JP
2209 $hash{$_}++ for @lines;
2210
0e70e83d 2211 # sort -rn
0e70e83d 2212 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
11ecf53c 2213 my $sign_offs = $hash{$line};
a8af2430 2214 my $percent = $sign_offs * 100 / $divisor;
3c7385b8 2215
a8af2430 2216 $percent = 100 if ($percent > 100);
435de078 2217 next if (ignore_email_address($line));
11ecf53c
JP
2218 $count++;
2219 last if ($sign_offs < $email_git_min_signatures ||
2220 $count > $email_git_max_maintainers ||
a8af2430 2221 $percent < $email_git_min_percent);
3c7385b8 2222 push_email_address($line, '');
3c7385b8 2223 if ($output_rolestats) {
a8af2430
JP
2224 my $fmt_percent = sprintf("%.0f", $percent);
2225 add_role($line, "$role:$sign_offs/$divisor=$fmt_percent%");
2226 } else {
2227 add_role($line, $role);
3c7385b8 2228 }
f5492666
JP
2229 }
2230}
2231
60db31ac 2232sub vcs_file_signoffs {
a8af2430
JP
2233 my ($file) = @_;
2234
c9ecefea
JP
2235 my $authors_ref;
2236 my $signers_ref;
2237 my $stats_ref;
2238 my @authors = ();
a8af2430 2239 my @signers = ();
c9ecefea 2240 my @stats = ();
60db31ac 2241 my $commits;
f5492666 2242
683c6f8f
JP
2243 $vcs_used = vcs_exists();
2244 return if (!$vcs_used);
a8af2430 2245
60db31ac
JP
2246 my $cmd = $VCS_cmds{"find_signers_cmd"};
2247 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
f5492666 2248
c9ecefea
JP
2249 ($commits, $signers_ref, $authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2250
2251 @signers = @{$signers_ref} if defined $signers_ref;
2252 @authors = @{$authors_ref} if defined $authors_ref;
2253 @stats = @{$stats_ref} if defined $stats_ref;
2254
2255# print("commits: <$commits>\nsigners:<@signers>\nauthors: <@authors>\nstats: <@stats>\n");
b9e2331d
JP
2256
2257 foreach my $signer (@signers) {
2258 $signer = deduplicate_email($signer);
2259 }
2260
60db31ac 2261 vcs_assign("commit_signer", $commits, @signers);
c9ecefea
JP
2262 vcs_assign("authored", $commits, @authors);
2263 if ($#authors == $#stats) {
2264 my $stat_pattern = $VCS_cmds{"stat_pattern"};
2265 $stat_pattern =~ s/(\$\w+)/$1/eeg; #interpolate $stat_pattern
2266
2267 my $added = 0;
2268 my $deleted = 0;
2269 for (my $i = 0; $i <= $#stats; $i++) {
2270 if ($stats[$i] =~ /$stat_pattern/) {
2271 $added += $1;
2272 $deleted += $2;
2273 }
2274 }
2275 my @tmp_authors = uniq(@authors);
2276 foreach my $author (@tmp_authors) {
2277 $author = deduplicate_email($author);
2278 }
2279 @tmp_authors = uniq(@tmp_authors);
2280 my @list_added = ();
2281 my @list_deleted = ();
2282 foreach my $author (@tmp_authors) {
2283 my $auth_added = 0;
2284 my $auth_deleted = 0;
2285 for (my $i = 0; $i <= $#stats; $i++) {
2286 if ($author eq deduplicate_email($authors[$i]) &&
2287 $stats[$i] =~ /$stat_pattern/) {
2288 $auth_added += $1;
2289 $auth_deleted += $2;
2290 }
2291 }
2292 for (my $i = 0; $i < $auth_added; $i++) {
2293 push(@list_added, $author);
2294 }
2295 for (my $i = 0; $i < $auth_deleted; $i++) {
2296 push(@list_deleted, $author);
2297 }
2298 }
2299 vcs_assign("added_lines", $added, @list_added);
2300 vcs_assign("removed_lines", $deleted, @list_deleted);
2301 }
f5492666
JP
2302}
2303
60db31ac 2304sub vcs_file_blame {
f5492666
JP
2305 my ($file) = @_;
2306
a8af2430 2307 my @signers = ();
63ab52db 2308 my @all_commits = ();
60db31ac 2309 my @commits = ();
a8af2430 2310 my $total_commits;
63ab52db 2311 my $total_lines;
f5492666 2312
683c6f8f
JP
2313 $vcs_used = vcs_exists();
2314 return if (!$vcs_used);
f5492666 2315
63ab52db
JP
2316 @all_commits = vcs_blame($file);
2317 @commits = uniq(@all_commits);
a8af2430 2318 $total_commits = @commits;
63ab52db 2319 $total_lines = @all_commits;
8cbb3a77 2320
683c6f8f
JP
2321 if ($email_git_blame_signatures) {
2322 if (vcs_is_hg()) {
2323 my $commit_count;
c9ecefea
JP
2324 my $commit_authors_ref;
2325 my $commit_signers_ref;
2326 my $stats_ref;
2327 my @commit_authors = ();
683c6f8f
JP
2328 my @commit_signers = ();
2329 my $commit = join(" -r ", @commits);
2330 my $cmd;
8cbb3a77 2331
683c6f8f
JP
2332 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2333 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
60db31ac 2334
c9ecefea
JP
2335 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2336 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2337 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
63ab52db 2338
683c6f8f
JP
2339 push(@signers, @commit_signers);
2340 } else {
2341 foreach my $commit (@commits) {
2342 my $commit_count;
c9ecefea
JP
2343 my $commit_authors_ref;
2344 my $commit_signers_ref;
2345 my $stats_ref;
2346 my @commit_authors = ();
683c6f8f
JP
2347 my @commit_signers = ();
2348 my $cmd;
2349
2350 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
2351 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2352
c9ecefea
JP
2353 ($commit_count, $commit_signers_ref, $commit_authors_ref, $stats_ref) = vcs_find_signers($cmd, $file);
2354 @commit_authors = @{$commit_authors_ref} if defined $commit_authors_ref;
2355 @commit_signers = @{$commit_signers_ref} if defined $commit_signers_ref;
683c6f8f
JP
2356
2357 push(@signers, @commit_signers);
2358 }
2359 }
f5492666
JP
2360 }
2361
a8af2430 2362 if ($from_filename) {
63ab52db
JP
2363 if ($output_rolestats) {
2364 my @blame_signers;
683c6f8f
JP
2365 if (vcs_is_hg()) {{ # Double brace for last exit
2366 my $commit_count;
2367 my @commit_signers = ();
2368 @commits = uniq(@commits);
2369 @commits = sort(@commits);
2370 my $commit = join(" -r ", @commits);
2371 my $cmd;
2372
2373 $cmd = $VCS_cmds{"find_commit_author_cmd"};
2374 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
2375
2376 my @lines = ();
2377
2378 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
2379
2380 if (!$email_git_penguin_chiefs) {
2381 @lines = grep(!/${penguin_chiefs}/i, @lines);
2382 }
2383
2384 last if !@lines;
2385
2386 my @authors = ();
2387 foreach my $line (@lines) {
2388 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
2389 my $author = $1;
b9e2331d
JP
2390 $author = deduplicate_email($author);
2391 push(@authors, $author);
683c6f8f
JP
2392 }
2393 }
2394
2395 save_commits_by_author(@lines) if ($interactive);
2396 save_commits_by_signer(@lines) if ($interactive);
2397
2398 push(@signers, @authors);
2399 }}
2400 else {
2401 foreach my $commit (@commits) {
2402 my $i;
2403 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
2404 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
2405 my @author = vcs_find_author($cmd);
2406 next if !@author;
b9e2331d
JP
2407
2408 my $formatted_author = deduplicate_email($author[0]);
2409
683c6f8f
JP
2410 my $count = grep(/$commit/, @all_commits);
2411 for ($i = 0; $i < $count ; $i++) {
b9e2331d 2412 push(@blame_signers, $formatted_author);
683c6f8f 2413 }
63ab52db
JP
2414 }
2415 }
2416 if (@blame_signers) {
2417 vcs_assign("authored lines", $total_lines, @blame_signers);
2418 }
2419 }
b9e2331d
JP
2420 foreach my $signer (@signers) {
2421 $signer = deduplicate_email($signer);
2422 }
60db31ac 2423 vcs_assign("commits", $total_commits, @signers);
a8af2430 2424 } else {
b9e2331d
JP
2425 foreach my $signer (@signers) {
2426 $signer = deduplicate_email($signer);
2427 }
60db31ac 2428 vcs_assign("modified commits", $total_commits, @signers);
cb7301c7 2429 }
cb7301c7
JP
2430}
2431
4cad35a7
JP
2432sub vcs_file_exists {
2433 my ($file) = @_;
2434
2435 my $exists;
2436
2437 my $vcs_used = vcs_exists();
2438 return 0 if (!$vcs_used);
2439
2440 my $cmd = $VCS_cmds{"file_exists_cmd"};
2441 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
8582fb59 2442 $cmd .= " 2>&1";
4cad35a7
JP
2443 $exists = &{$VCS_cmds{"execute_cmd"}}($cmd);
2444
8582fb59
JP
2445 return 0 if ($? != 0);
2446
4cad35a7
JP
2447 return $exists;
2448}
2449
e1f75904
TS
2450sub vcs_list_files {
2451 my ($file) = @_;
2452
2453 my @lsfiles = ();
2454
2455 my $vcs_used = vcs_exists();
2456 return 0 if (!$vcs_used);
2457
2458 my $cmd = $VCS_cmds{"list_files_cmd"};
2459 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
2460 @lsfiles = &{$VCS_cmds{"execute_cmd"}}($cmd);
2461
2462 return () if ($? != 0);
2463
2464 return @lsfiles;
2465}
2466
cb7301c7 2467sub uniq {
a8af2430 2468 my (@parms) = @_;
cb7301c7
JP
2469
2470 my %saw;
2471 @parms = grep(!$saw{$_}++, @parms);
2472 return @parms;
2473}
2474
2475sub sort_and_uniq {
a8af2430 2476 my (@parms) = @_;
cb7301c7
JP
2477
2478 my %saw;
2479 @parms = sort @parms;
2480 @parms = grep(!$saw{$_}++, @parms);
2481 return @parms;
2482}
2483
03372dbb
JP
2484sub clean_file_emails {
2485 my (@file_emails) = @_;
2486 my @fmt_emails = ();
2487
2488 foreach my $email (@file_emails) {
2489 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
2490 my ($name, $address) = parse_email($email);
03372dbb 2491
2639772a
2492 # Strip quotes for easier processing, format_email will add them back
2493 $name =~ s/^"(.*)"$/$1/;
2494
2495 # Split into name-like parts and remove stray punctuation particles
9c334eb9 2496 my @nw = split(/[^\p{L}\'\,\.\+-]/, $name);
2639772a
2497 @nw = grep(!/^[\'\,\.\+-]$/, @nw);
2498
2499 # Make a best effort to extract the name, and only the name, by taking
2500 # only the last two names, or in the case of obvious initials, the last
2501 # three names.
03372dbb
JP
2502 if (@nw > 2) {
2503 my $first = $nw[@nw - 3];
2504 my $middle = $nw[@nw - 2];
2505 my $last = $nw[@nw - 1];
2506
9c334eb9 2507 if (((length($first) == 1 && $first =~ m/\p{L}/) ||
03372dbb
JP
2508 (length($first) == 2 && substr($first, -1) eq ".")) ||
2509 (length($middle) == 1 ||
2510 (length($middle) == 2 && substr($middle, -1) eq "."))) {
2511 $name = "$first $middle $last";
2512 } else {
2513 $name = "$middle $last";
2514 }
2639772a
2515 } else {
2516 $name = "@nw";
03372dbb
JP
2517 }
2518
2519 if (substr($name, -1) =~ /[,\.]/) {
2520 $name = substr($name, 0, length($name) - 1);
03372dbb
JP
2521 }
2522
2523 if (substr($name, 0, 1) =~ /[,\.]/) {
2524 $name = substr($name, 1, length($name) - 1);
03372dbb
JP
2525 }
2526
2527 my $fmt_email = format_email($name, $address, $email_usename);
2528 push(@fmt_emails, $fmt_email);
2529 }
2530 return @fmt_emails;
2531}
2532
3c7385b8
JP
2533sub merge_email {
2534 my @lines;
2535 my %saw;
2536
2537 for (@_) {
2538 my ($address, $role) = @$_;
2539 if (!$saw{$address}) {
2540 if ($output_roles) {
60db31ac 2541 push(@lines, "$address ($role)");
3c7385b8 2542 } else {
60db31ac 2543 push(@lines, $address);
3c7385b8
JP
2544 }
2545 $saw{$address} = 1;
2546 }
2547 }
2548
2549 return @lines;
2550}
2551
cb7301c7 2552sub output {
a8af2430 2553 my (@parms) = @_;
cb7301c7
JP
2554
2555 if ($output_multiline) {
2556 foreach my $line (@parms) {
2557 print("${line}\n");
2558 }
2559 } else {
2560 print(join($output_separator, @parms));
2561 print("\n");
2562 }
2563}
1b5e1cf6
JP
2564
2565my $rfc822re;
2566
2567sub make_rfc822re {
2568# Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
2569# comment. We must allow for rfc822_lwsp (or comments) after each of these.
2570# This regexp will only work on addresses which have had comments stripped
2571# and replaced with rfc822_lwsp.
2572
2573 my $specials = '()<>@,;:\\\\".\\[\\]';
2574 my $controls = '\\000-\\037\\177';
2575
2576 my $dtext = "[^\\[\\]\\r\\\\]";
2577 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
2578
2579 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
2580
2581# Use zero-width assertion to spot the limit of an atom. A simple
2582# $rfc822_lwsp* causes the regexp engine to hang occasionally.
2583 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
2584 my $word = "(?:$atom|$quoted_string)";
2585 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
2586
2587 my $sub_domain = "(?:$atom|$domain_literal)";
2588 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
2589
2590 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
2591
2592 my $phrase = "$word*";
2593 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
2594 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
2595 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
2596
2597 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
2598 my $address = "(?:$mailbox|$group)";
2599
2600 return "$rfc822_lwsp*$address";
2601}
2602
2603sub rfc822_strip_comments {
2604 my $s = shift;
2605# Recursively remove comments, and replace with a single space. The simpler
2606# regexps in the Email Addressing FAQ are imperfect - they will miss escaped
2607# chars in atoms, for example.
2608
2609 while ($s =~ s/^((?:[^"\\]|\\.)*
2610 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
2611 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
2612 return $s;
2613}
2614
2615# valid: returns true if the parameter is an RFC822 valid address
2616#
22dd5b0c 2617sub rfc822_valid {
1b5e1cf6
JP
2618 my $s = rfc822_strip_comments(shift);
2619
2620 if (!$rfc822re) {
2621 $rfc822re = make_rfc822re();
2622 }
2623
2624 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
2625}
2626
2627# validlist: In scalar context, returns true if the parameter is an RFC822
2628# valid list of addresses.
2629#
2630# In list context, returns an empty list on failure (an invalid
2631# address was found); otherwise a list whose first element is the
2632# number of addresses found and whose remaining elements are the
2633# addresses. This is needed to disambiguate failure (invalid)
2634# from success with no addresses found, because an empty string is
2635# a valid list.
2636
22dd5b0c 2637sub rfc822_validlist {
1b5e1cf6
JP
2638 my $s = rfc822_strip_comments(shift);
2639
2640 if (!$rfc822re) {
2641 $rfc822re = make_rfc822re();
2642 }
2643 # * null list items are valid according to the RFC
2644 # * the '1' business is to aid in distinguishing failure from no results
2645
2646 my @r;
2647 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
2648 $s =~ m/^$rfc822_char*$/) {
5f2441e9 2649 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
60db31ac 2650 push(@r, $1);
1b5e1cf6
JP
2651 }
2652 return wantarray ? (scalar(@r), @r) : 1;
2653 }
60db31ac 2654 return wantarray ? () : 0;
1b5e1cf6 2655}