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