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