0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013 use warnings;
0014 use strict;
0015
0016 my $P = $0;
0017 my $V = '0.26';
0018
0019 use Getopt::Long ;
0020 use Cwd;
0021 use File::Find;
0022 use File::Spec::Functions;
0023
0024 my $cur_path = fastgetcwd() . '/';
0025 my $lk_path = "./";
0026 my $email = 1;
0027 my $email_usename = 1;
0028 my $email_maintainer = 1;
0029 my $email_reviewer = 1;
0030 my $email_fixes = 1;
0031 my $email_list = 1;
0032 my $email_moderated_list = 1;
0033 my $email_subscriber_list = 0;
0034 my $email_git_penguin_chiefs = 0;
0035 my $email_git = 0;
0036 my $email_git_all_signature_types = 0;
0037 my $email_git_blame = 0;
0038 my $email_git_blame_signatures = 1;
0039 my $email_git_fallback = 1;
0040 my $email_git_min_signatures = 1;
0041 my $email_git_max_maintainers = 5;
0042 my $email_git_min_percent = 5;
0043 my $email_git_since = "1-year-ago";
0044 my $email_hg_since = "-365";
0045 my $interactive = 0;
0046 my $email_remove_duplicates = 1;
0047 my $email_use_mailmap = 1;
0048 my $output_multiline = 1;
0049 my $output_separator = ", ";
0050 my $output_roles = 0;
0051 my $output_rolestats = 1;
0052 my $output_section_maxlen = 50;
0053 my $scm = 0;
0054 my $tree = 1;
0055 my $web = 0;
0056 my $subsystem = 0;
0057 my $status = 0;
0058 my $letters = "";
0059 my $keywords = 1;
0060 my $sections = 0;
0061 my $email_file_emails = 0;
0062 my $from_filename = 0;
0063 my $pattern_depth = 0;
0064 my $self_test = undef;
0065 my $version = 0;
0066 my $help = 0;
0067 my $find_maintainer_files = 0;
0068 my $maintainer_path;
0069 my $vcs_used = 0;
0070
0071 my $exit = 0;
0072
0073 my @files = ();
0074 my @fixes = ();
0075 my @range = ();
0076 my @keyword_tvi = ();
0077 my @file_emails = ();
0078
0079 my %commit_author_hash;
0080 my %commit_signer_hash;
0081
0082 my @penguin_chief = ();
0083 push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
0084
0085
0086
0087 my @penguin_chief_names = ();
0088 foreach my $chief (@penguin_chief) {
0089 if ($chief =~ ) {
0090 my $chief_name = $1;
0091 my $chief_addr = $2;
0092 push(@penguin_chief_names, $chief_name);
0093 }
0094 }
0095 my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
0096
0097
0098
0099
0100 my @signature_tags = ();
0101 push(@signature_tags, "Signed-off-by:");
0102 push(@signature_tags, "Reviewed-by:");
0103 push(@signature_tags, "Acked-by:");
0104
0105 my $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
0106
0107
0108 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
0109 my $rfc822_char = '[\\000-\\377]';
0110
0111
0112
0113 my %VCS_cmds;
0114
0115 my %VCS_cmds_git = (
0116 "execute_cmd" => \&git_execute_cmd,
0117 "available" => '(which("git") ne "") && (-e ".git")',
0118 "find_signers_cmd" =>
0119 "git log --no-color --follow --since=\$email_git_since " .
0120 '--numstat --no-merges ' .
0121 '--format="GitCommit: %H%n' .
0122 'GitAuthor: %an <%ae>%n' .
0123 'GitDate: %aD%n' .
0124 'GitSubject: %s%n' .
0125 '%b%n"' .
0126 " -- \$file",
0127 "find_commit_signers_cmd" =>
0128 "git log --no-color " .
0129 '--numstat ' .
0130 '--format="GitCommit: %H%n' .
0131 'GitAuthor: %an <%ae>%n' .
0132 'GitDate: %aD%n' .
0133 'GitSubject: %s%n' .
0134 '%b%n"' .
0135 " -1 \$commit",
0136 "find_commit_author_cmd" =>
0137 "git log --no-color " .
0138 '--numstat ' .
0139 '--format="GitCommit: %H%n' .
0140 'GitAuthor: %an <%ae>%n' .
0141 'GitDate: %aD%n' .
0142 'GitSubject: %s%n"' .
0143 " -1 \$commit",
0144 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
0145 "blame_file_cmd" => "git blame -l \$file",
0146 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
0147 "blame_commit_pattern" => "^([0-9a-f]+) ",
0148 "author_pattern" => "^GitAuthor: (.*)",
0149 "subject_pattern" => "^GitSubject: (.*)",
0150 "stat_pattern" => "^(\\d+)\\t(\\d+)\\t\$file\$",
0151 "file_exists_cmd" => "git ls-files \$file",
0152 "list_files_cmd" => "git ls-files \$file",
0153 );
0154
0155 my %VCS_cmds_hg = (
0156 "execute_cmd" => \&hg_execute_cmd,
0157 "available" => '(which("hg") ne "") && (-d ".hg")',
0158 "find_signers_cmd" =>
0159 "hg log --date=\$email_hg_since " .
0160 "--template='HgCommit: {node}\\n" .
0161 "HgAuthor: {author}\\n" .
0162 "HgSubject: {desc}\\n'" .
0163 " -- \$file",
0164 "find_commit_signers_cmd" =>
0165 "hg log " .
0166 "--template='HgSubject: {desc}\\n'" .
0167 " -r \$commit",
0168 "find_commit_author_cmd" =>
0169 "hg log " .
0170 "--template='HgCommit: {node}\\n" .
0171 "HgAuthor: {author}\\n" .
0172 "HgSubject: {desc|firstline}\\n'" .
0173 " -r \$commit",
0174 "blame_range_cmd" => "",
0175 "blame_file_cmd" => "hg blame -n \$file",
0176 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
0177 "blame_commit_pattern" => "^([ 0-9a-f]+):",
0178 "author_pattern" => "^HgAuthor: (.*)",
0179 "subject_pattern" => "^HgSubject: (.*)",
0180 "stat_pattern" => "^(\\d+)\t(\\d+)\t\$file\$",
0181 "file_exists_cmd" => "hg files \$file",
0182 "list_files_cmd" => "hg manifest -R \$file",
0183 );
0184
0185 my $conf = which_conf(".get_maintainer.conf");
0186 if (-f $conf) {
0187 my @conf_args;
0188 open(my $conffile, '<', "$conf")
0189 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
0190
0191 while (<$conffile>) {
0192 my $line = $_;
0193
0194 $line =~ s/\s*\n?$//g;
0195 $line =~ s/^\s*//g;
0196 $line =~ s/\s+/ /g;
0197
0198 next if ($line =~ );
0199 next if ($line =~ );
0200
0201 my @words = split(" ", $line);
0202 foreach my $word (@words) {
0203 last if ($word =~ );
0204 push (@conf_args, $word);
0205 }
0206 }
0207 close($conffile);
0208 unshift(@ARGV, @conf_args) if @conf_args;
0209 }
0210
0211 my @ignore_emails = ();
0212 my $ignore_file = which_conf(".get_maintainer.ignore");
0213 if (-f $ignore_file) {
0214 open(my $ignore, '<', "$ignore_file")
0215 or warn "$P: Can't find a readable .get_maintainer.ignore file $!\n";
0216 while (<$ignore>) {
0217 my $line = $_;
0218
0219 $line =~ s/\s*\n?$//;
0220 $line =~ s/^\s*//;
0221 $line =~ s/\s+$//;
0222 $line =~ s/#.*$//;
0223
0224 next if ($line =~ );
0225 if (rfc822_valid($line)) {
0226 push(@ignore_emails, $line);
0227 }
0228 }
0229 close($ignore);
0230 }
0231
0232 if ($#ARGV > 0) {
0233 foreach (@ARGV) {
0234 if ($_ =~ /^-{1,2}self-test(?:=|$)/) {
0235 die "$P: using --self-test does not allow any other option or argument\n";
0236 }
0237 }
0238 }
0239
0240 if (!GetOptions(
0241 'email!' => \$email,
0242 'git!' => \$email_git,
0243 'git-all-signature-types!' => \$email_git_all_signature_types,
0244 'git-blame!' => \$email_git_blame,
0245 'git-blame-signatures!' => \$email_git_blame_signatures,
0246 'git-fallback!' => \$email_git_fallback,
0247 'git-chief-penguins!' => \$email_git_penguin_chiefs,
0248 'git-min-signatures=i' => \$email_git_min_signatures,
0249 'git-max-maintainers=i' => \$email_git_max_maintainers,
0250 'git-min-percent=i' => \$email_git_min_percent,
0251 'git-since=s' => \$email_git_since,
0252 'hg-since=s' => \$email_hg_since,
0253 'i|interactive!' => \$interactive,
0254 'remove-duplicates!' => \$email_remove_duplicates,
0255 'mailmap!' => \$email_use_mailmap,
0256 'm!' => \$email_maintainer,
0257 'r!' => \$email_reviewer,
0258 'n!' => \$email_usename,
0259 'l!' => \$email_list,
0260 'fixes!' => \$email_fixes,
0261 'moderated!' => \$email_moderated_list,
0262 's!' => \$email_subscriber_list,
0263 'multiline!' => \$output_multiline,
0264 'roles!' => \$output_roles,
0265 'rolestats!' => \$output_rolestats,
0266 'separator=s' => \$output_separator,
0267 'subsystem!' => \$subsystem,
0268 'status!' => \$status,
0269 'scm!' => \$scm,
0270 'tree!' => \$tree,
0271 'web!' => \$web,
0272 'letters=s' => \$letters,
0273 'pattern-depth=i' => \$pattern_depth,
0274 'k|keywords!' => \$keywords,
0275 'sections!' => \$sections,
0276 'fe|file-emails!' => \$email_file_emails,
0277 'f|file' => \$from_filename,
0278 'find-maintainer-files' => \$find_maintainer_files,
0279 'mpath|maintainer-path=s' => \$maintainer_path,
0280 'self-test:s' => \$self_test,
0281 'v|version' => \$version,
0282 'h|help|usage' => \$help,
0283 )) {
0284 die "$P: invalid argument - use --help if necessary\n";
0285 }
0286
0287 if ($help != 0) {
0288 usage();
0289 exit 0;
0290 }
0291
0292 if ($version != 0) {
0293 print("${P} ${V}\n");
0294 exit 0;
0295 }
0296
0297 if (defined $self_test) {
0298 read_all_maintainer_files();
0299 self_test();
0300 exit 0;
0301 }
0302
0303 if (-t STDIN && !@ARGV) {
0304
0305 die "$P: missing patchfile or -f file - use --help if necessary\n";
0306 }
0307
0308 $output_multiline = 0 if ($output_separator ne ", ");
0309 $output_rolestats = 1 if ($interactive);
0310 $output_roles = 1 if ($output_rolestats);
0311
0312 if ($sections || $letters ne "") {
0313 $sections = 1;
0314 $email = 0;
0315 $email_list = 0;
0316 $scm = 0;
0317 $status = 0;
0318 $subsystem = 0;
0319 $web = 0;
0320 $keywords = 0;
0321 $interactive = 0;
0322 } else {
0323 my $selections = $email + $scm + $status + $subsystem + $web;
0324 if ($selections == 0) {
0325 die "$P: Missing required option: email, scm, status, subsystem or web\n";
0326 }
0327 }
0328
0329 if ($email &&
0330 ($email_maintainer + $email_reviewer +
0331 $email_list + $email_subscriber_list +
0332 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
0333 die "$P: Please select at least 1 email option\n";
0334 }
0335
0336 if ($tree && !top_of_kernel_tree($lk_path)) {
0337 die "$P: The current directory does not appear to be "
0338 . "a linux kernel source tree.\n";
0339 }
0340
0341
0342
0343 my @typevalue = ();
0344 my %keyword_hash;
0345 my @mfiles = ();
0346 my @self_test_info = ();
0347
0348 sub read_maintainer_file {
0349 my ($file) = @_;
0350
0351 open (my $maint, '<', "$file")
0352 or die "$P: Can't open MAINTAINERS file '$file': $!\n";
0353 my $i = 1;
0354 while (<$maint>) {
0355 my $line = $_;
0356 chomp $line;
0357
0358 if ($line =~ ) {
0359 my $type = $1;
0360 my $value = $2;
0361
0362
0363 if ($type eq "F" || $type eq "X") {
0364 $value =~ s@\.@\\\.@g;
0365 $value =~ s/\*/\.\*/g;
0366 $value =~ s/\?/\./g;
0367
0368 if ((-d $value)) {
0369 $value =~ s@([^/])$@$1/@;
0370 }
0371 } elsif ($type eq "K") {
0372 $keyword_hash{@typevalue} = $value;
0373 }
0374 push(@typevalue, "$type:$value");
0375 } elsif (!(/^\s*$/ || /^\s*\
0376 push(@typevalue, $line);
0377 }
0378 if (defined $self_test) {
0379 push(@self_test_info, {file=>$file, linenr=>$i, line=>$line});
0380 }
0381 $i++;
0382 }
0383 close($maint);
0384 }
0385
0386 sub find_is_maintainer_file {
0387 my ($file) = $_;
0388 return if ($file !~ );
0389 $file = $File::Find::name;
0390 return if (! -f $file);
0391 push(@mfiles, $file);
0392 }
0393
0394 sub find_ignore_git {
0395 return grep { $_ !~ /^\.git$/; } @_;
0396 }
0397
0398 read_all_maintainer_files();
0399
0400 sub read_all_maintainer_files {
0401 my $path = "${lk_path}MAINTAINERS";
0402 if (defined $maintainer_path) {
0403 $path = $maintainer_path;
0404
0405 $path =~ s@^~([^/]*)@ $1 ? (getpwnam($1))[7] : ( $ENV{HOME} || $ENV{LOGDIR} || (getpwuid($<))[7])@ex;
0406 }
0407
0408 if (-d $path) {
0409 $path .= '/' if ($path !~ );
0410 if ($find_maintainer_files) {
0411 find( { wanted => \&find_is_maintainer_file,
0412 preprocess => \&find_ignore_git,
0413 no_chdir => 1,
0414 }, "$path");
0415 } else {
0416 opendir(DIR, "$path") or die $!;
0417 my @files = readdir(DIR);
0418 closedir(DIR);
0419 foreach my $file (@files) {
0420 push(@mfiles, "$path$file") if ($file !~ /^\./);
0421 }
0422 }
0423 } elsif (-f "$path") {
0424 push(@mfiles, "$path");
0425 } else {
0426 die "$P: MAINTAINER file not found '$path'\n";
0427 }
0428 die "$P: No MAINTAINER files found in '$path'\n" if (scalar(@mfiles) == 0);
0429 foreach my $file (@mfiles) {
0430 read_maintainer_file("$file");
0431 }
0432 }
0433
0434 sub maintainers_in_file {
0435 my ($file) = @_;
0436
0437 return if ($file =~ );
0438
0439 if (-f $file && ($email_file_emails || $file =~ /\.yaml$/)) {
0440 open(my $f, '<', $file)
0441 or die "$P: Can't open $file: $!\n";
0442 my $text = do { local($/) ; <$f> };
0443 close($f);
0444
0445 my @poss_addr = $text =~ ;
0446 push(@file_emails, clean_file_emails(@poss_addr));
0447 }
0448 }
0449
0450
0451
0452
0453
0454 my $mailmap;
0455
0456 read_mailmap();
0457
0458 sub read_mailmap {
0459 $mailmap = {
0460 names => {},
0461 addresses => {}
0462 };
0463
0464 return if (!$email_use_mailmap || !(-f "${lk_path}.mailmap"));
0465
0466 open(my $mailmap_file, '<', "${lk_path}.mailmap")
0467 or warn "$P: Can't open .mailmap: $!\n";
0468
0469 while (<$mailmap_file>) {
0470 s/#.*$//;
0471 s/^\s+|\s+$//g;
0472
0473 next if (/^\s*$/);
0474
0475
0476
0477
0478
0479
0480
0481 if (/^([^<]+)<([^>]+)>$/) {
0482 my $real_name = $1;
0483 my $address = $2;
0484
0485 $real_name =~ s/\s+$//;
0486 ($real_name, $address) = parse_email("$real_name <$address>");
0487 $mailmap->{names}->{$address} = $real_name;
0488
0489 } elsif (/^<([^>]+)>\s*<([^>]+)>$/) {
0490 my $real_address = $1;
0491 my $wrong_address = $2;
0492
0493 $mailmap->{addresses}->{$wrong_address} = $real_address;
0494
0495 } elsif (/^(.+)<([^>]+)>\s*<([^>]+)>$/) {
0496 my $real_name = $1;
0497 my $real_address = $2;
0498 my $wrong_address = $3;
0499
0500 $real_name =~ s/\s+$//;
0501 ($real_name, $real_address) =
0502 parse_email("$real_name <$real_address>");
0503 $mailmap->{names}->{$wrong_address} = $real_name;
0504 $mailmap->{addresses}->{$wrong_address} = $real_address;
0505
0506 } elsif (/^(.+)<([^>]+)>\s*(.+)\s*<([^>]+)>$/) {
0507 my $real_name = $1;
0508 my $real_address = $2;
0509 my $wrong_name = $3;
0510 my $wrong_address = $4;
0511
0512 $real_name =~ s/\s+$//;
0513 ($real_name, $real_address) =
0514 parse_email("$real_name <$real_address>");
0515
0516 $wrong_name =~ s/\s+$//;
0517 ($wrong_name, $wrong_address) =
0518 parse_email("$wrong_name <$wrong_address>");
0519
0520 my $wrong_email = format_email($wrong_name, $wrong_address, 1);
0521 $mailmap->{names}->{$wrong_email} = $real_name;
0522 $mailmap->{addresses}->{$wrong_email} = $real_address;
0523 }
0524 }
0525 close($mailmap_file);
0526 }
0527
0528
0529
0530 if (!@ARGV) {
0531 push(@ARGV, "&STDIN");
0532 }
0533
0534 foreach my $file (@ARGV) {
0535 if ($file ne "&STDIN") {
0536 $file = canonpath($file);
0537
0538 if ((-d $file)) {
0539 $file =~ s@([^/])$@$1/@;
0540 } elsif (!(-f $file)) {
0541 die "$P: file '${file}' not found\n";
0542 }
0543 }
0544 if ($from_filename && (vcs_exists() && !vcs_file_exists($file))) {
0545 warn "$P: file '$file' not found in version control $!\n";
0546 }
0547 if ($from_filename || ($file ne "&STDIN" && vcs_file_exists($file))) {
0548 $file =~ s/^\Q${cur_path}\E//;
0549 $file =~ s/^\Q${lk_path}\E//;
0550 push(@files, $file);
0551 if ($file ne "MAINTAINERS" && -f $file && $keywords) {
0552 open(my $f, '<', $file)
0553 or die "$P: Can't open $file: $!\n";
0554 my $text = do { local($/) ; <$f> };
0555 close($f);
0556 if ($keywords) {
0557 foreach my $line (keys %keyword_hash) {
0558 if ($text =~ ) {
0559 push(@keyword_tvi, $line);
0560 }
0561 }
0562 }
0563 }
0564 } else {
0565 my $file_cnt = @files;
0566 my $lastfile;
0567
0568 open(my $patch, "< $file")
0569 or die "$P: Can't open $file: $!\n";
0570
0571
0572
0573
0574
0575
0576 my $patch_prefix = "";
0577
0578 while (<$patch>) {
0579 my $patch_line = $_;
0580 if () {
0581 my $filename = $1;
0582 push(@files, $filename);
0583 } elsif () {
0584 my $filename = $1;
0585 push(@files, $filename);
0586 } elsif () {
0587 my $filename1 = $1;
0588 my $filename2 = $2;
0589 push(@files, $filename1);
0590 push(@files, $filename2);
0591 } elsif () {
0592 push(@fixes, $1) if ($email_fixes);
0593 } elsif ( or ) {
0594 my $filename = $1;
0595 $filename =~ s@^[^/]*/@@;
0596 $filename =~ s@\n@@;
0597 $lastfile = $filename;
0598 push(@files, $filename);
0599 $patch_prefix = "^[+-].*";
0600 } elsif () {
0601 if ($email_git_blame) {
0602 push(@range, "$lastfile:$1:$2");
0603 }
0604 } elsif ($keywords) {
0605 foreach my $line (keys %keyword_hash) {
0606 if ($patch_line =~ ) {
0607 push(@keyword_tvi, $line);
0608 }
0609 }
0610 }
0611 }
0612 close($patch);
0613
0614 if ($file_cnt == @files) {
0615 warn "$P: file '${file}' doesn't appear to be a patch. "
0616 . "Add -f to options?\n";
0617 }
0618 @files = sort_and_uniq(@files);
0619 }
0620 }
0621
0622 @file_emails = uniq(@file_emails);
0623 @fixes = uniq(@fixes);
0624
0625 my %email_hash_name;
0626 my %email_hash_address;
0627 my @email_to = ();
0628 my %hash_list_to;
0629 my @list_to = ();
0630 my @scm = ();
0631 my @web = ();
0632 my @subsystem = ();
0633 my @status = ();
0634 my %deduplicate_name_hash = ();
0635 my %deduplicate_address_hash = ();
0636
0637 my @maintainers = get_maintainers();
0638 if (@maintainers) {
0639 @maintainers = merge_email(@maintainers);
0640 output(@maintainers);
0641 }
0642
0643 if ($scm) {
0644 @scm = uniq(@scm);
0645 output(@scm);
0646 }
0647
0648 if ($status) {
0649 @status = uniq(@status);
0650 output(@status);
0651 }
0652
0653 if ($subsystem) {
0654 @subsystem = uniq(@subsystem);
0655 output(@subsystem);
0656 }
0657
0658 if ($web) {
0659 @web = uniq(@web);
0660 output(@web);
0661 }
0662
0663 exit($exit);
0664
0665 sub self_test {
0666 my @lsfiles = ();
0667 my @good_links = ();
0668 my @bad_links = ();
0669 my @section_headers = ();
0670 my $index = 0;
0671
0672 @lsfiles = vcs_list_files($lk_path);
0673
0674 for my $x (@self_test_info) {
0675 $index++;
0676
0677
0678 if (($self_test eq "" || $self_test =~ /\bsections\b/) &&
0679 $x->{line} =~ /^\S[^:]/ &&
0680 defined $self_test_info[$index] &&
0681 $self_test_info[$index]->{line} =~ /^([A-Z]):\s*\S/) {
0682 my $has_S = 0;
0683 my $has_F = 0;
0684 my $has_ML = 0;
0685 my $status = "";
0686 if (grep(, @section_headers)) {
0687 print("$x->{file}:$x->{linenr}: warning: duplicate section header\t$x->{line}\n");
0688 } else {
0689 push(@section_headers, $x->{line});
0690 }
0691 my $nextline = $index;
0692 while (defined $self_test_info[$nextline] &&
0693 $self_test_info[$nextline]->{line} =~ /^([A-Z]):\s*(\S.*)/) {
0694 my $type = $1;
0695 my $value = $2;
0696 if ($type eq "S") {
0697 $has_S = 1;
0698 $status = $value;
0699 } elsif ($type eq "F" || $type eq "N") {
0700 $has_F = 1;
0701 } elsif ($type eq "M" || $type eq "R" || $type eq "L") {
0702 $has_ML = 1;
0703 }
0704 $nextline++;
0705 }
0706 if (!$has_ML && $status !~ /orphan|obsolete/i) {
0707 print("$x->{file}:$x->{linenr}: warning: section without email address\t$x->{line}\n");
0708 }
0709 if (!$has_S) {
0710 print("$x->{file}:$x->{linenr}: warning: section without status \t$x->{line}\n");
0711 }
0712 if (!$has_F) {
0713 print("$x->{file}:$x->{linenr}: warning: section without file pattern\t$x->{line}\n");
0714 }
0715 }
0716
0717 next if ($x->{line} !~ /^([A-Z]):\s*(.*)/);
0718
0719 my $type = $1;
0720 my $value = $2;
0721
0722
0723 if (($type eq "F" || $type eq "X") &&
0724 ($self_test eq "" || $self_test =~ /\bpatterns\b/)) {
0725 $value =~ s@\.@\\\.@g;
0726 $value =~ s/\*/\.\*/g;
0727 $value =~ s/\?/\./g;
0728
0729 if ((-d $value)) {
0730 $value =~ s@([^/])$@$1/@;
0731 }
0732 if (!grep(, @lsfiles)) {
0733 print("$x->{file}:$x->{linenr}: warning: no file matches\t$x->{line}\n");
0734 }
0735
0736
0737 } elsif (($type eq "W" || $type eq "Q" || $type eq "B") &&
0738 $value =~ /^https?:/ &&
0739 ($self_test eq "" || $self_test =~ /\blinks\b/)) {
0740 next if (grep(, @good_links));
0741 my $isbad = 0;
0742 if (grep(, @bad_links)) {
0743 $isbad = 1;
0744 } else {
0745 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $value`;
0746 if ($? == 0) {
0747 push(@good_links, $value);
0748 } else {
0749 push(@bad_links, $value);
0750 $isbad = 1;
0751 }
0752 }
0753 if ($isbad) {
0754 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
0755 }
0756
0757
0758 } elsif ($type eq "T" &&
0759 ($self_test eq "" || $self_test =~ /\bscm\b/)) {
0760 next if (grep(, @good_links));
0761 my $isbad = 0;
0762 if (grep(, @bad_links)) {
0763 $isbad = 1;
0764 } elsif ($value !~ /^(?:git|quilt|hg)\s+\S/) {
0765 print("$x->{file}:$x->{linenr}: warning: malformed entry\t$x->{line}\n");
0766 } elsif ($value =~ /^git\s+(\S+)(\s+([^\(]+\S+))?/) {
0767 my $url = $1;
0768 my $branch = "";
0769 $branch = $3 if $3;
0770 my $output = `git ls-remote --exit-code -h "$url" $branch > /dev/null 2>&1`;
0771 if ($? == 0) {
0772 push(@good_links, $value);
0773 } else {
0774 push(@bad_links, $value);
0775 $isbad = 1;
0776 }
0777 } elsif ($value =~ /^(?:quilt|hg)\s+(https?:\S+)/) {
0778 my $url = $1;
0779 my $output = `wget --spider -q --no-check-certificate --timeout 10 --tries 1 $url`;
0780 if ($? == 0) {
0781 push(@good_links, $value);
0782 } else {
0783 push(@bad_links, $value);
0784 $isbad = 1;
0785 }
0786 }
0787 if ($isbad) {
0788 print("$x->{file}:$x->{linenr}: warning: possible bad link\t$x->{line}\n");
0789 }
0790 }
0791 }
0792 }
0793
0794 sub ignore_email_address {
0795 my ($address) = @_;
0796
0797 foreach my $ignore (@ignore_emails) {
0798 return 1 if ($ignore eq $address);
0799 }
0800
0801 return 0;
0802 }
0803
0804 sub range_is_maintained {
0805 my ($start, $end) = @_;
0806
0807 for (my $i = $start; $i < $end; $i++) {
0808 my $line = $typevalue[$i];
0809 if ($line =~ ) {
0810 my $type = $1;
0811 my $value = $2;
0812 if ($type eq 'S') {
0813 if ($value =~ /(maintain|support)/i) {
0814 return 1;
0815 }
0816 }
0817 }
0818 }
0819 return 0;
0820 }
0821
0822 sub range_has_maintainer {
0823 my ($start, $end) = @_;
0824
0825 for (my $i = $start; $i < $end; $i++) {
0826 my $line = $typevalue[$i];
0827 if ($line =~ ) {
0828 my $type = $1;
0829 my $value = $2;
0830 if ($type eq 'M') {
0831 return 1;
0832 }
0833 }
0834 }
0835 return 0;
0836 }
0837
0838 sub get_maintainers {
0839 %email_hash_name = ();
0840 %email_hash_address = ();
0841 %commit_author_hash = ();
0842 %commit_signer_hash = ();
0843 @email_to = ();
0844 %hash_list_to = ();
0845 @list_to = ();
0846 @scm = ();
0847 @web = ();
0848 @subsystem = ();
0849 @status = ();
0850 %deduplicate_name_hash = ();
0851 %deduplicate_address_hash = ();
0852 if ($email_git_all_signature_types) {
0853 $signature_pattern = "(.+?)[Bb][Yy]:";
0854 } else {
0855 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
0856 }
0857
0858
0859
0860 my %exact_pattern_match_hash = ();
0861
0862 foreach my $file (@files) {
0863
0864 my %hash;
0865 my $tvi = find_first_section();
0866 while ($tvi < @typevalue) {
0867 my $start = find_starting_index($tvi);
0868 my $end = find_ending_index($tvi);
0869 my $exclude = 0;
0870 my $i;
0871
0872
0873
0874 for ($i = $start; $i < $end; $i++) {
0875 my $line = $typevalue[$i];
0876 if ($line =~ ) {
0877 my $type = $1;
0878 my $value = $2;
0879 if ($type eq 'X') {
0880 if (file_match_pattern($file, $value)) {
0881 $exclude = 1;
0882 last;
0883 }
0884 }
0885 }
0886 }
0887
0888 if (!$exclude) {
0889 for ($i = $start; $i < $end; $i++) {
0890 my $line = $typevalue[$i];
0891 if ($line =~ ) {
0892 my $type = $1;
0893 my $value = $2;
0894 if ($type eq 'F') {
0895 if (file_match_pattern($file, $value)) {
0896 my $value_pd = ($value =~ tr@/@@);
0897 my $file_pd = ($file =~ tr@/@@);
0898 $value_pd++ if (substr($value,-1,1) ne "/");
0899 $value_pd = -1 if ($value =~ /^\.\*/);
0900 if ($value_pd >= $file_pd &&
0901 range_is_maintained($start, $end) &&
0902 range_has_maintainer($start, $end)) {
0903 $exact_pattern_match_hash{$file} = 1;
0904 }
0905 if ($pattern_depth == 0 ||
0906 (($file_pd - $value_pd) < $pattern_depth)) {
0907 $hash{$tvi} = $value_pd;
0908 }
0909 }
0910 } elsif ($type eq 'N') {
0911 if ($file =~ ) {
0912 $hash{$tvi} = 0;
0913 }
0914 }
0915 }
0916 }
0917 }
0918 $tvi = $end + 1;
0919 }
0920
0921 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
0922 add_categories($line);
0923 if ($sections) {
0924 my $i;
0925 my $start = find_starting_index($line);
0926 my $end = find_ending_index($line);
0927 for ($i = $start; $i < $end; $i++) {
0928 my $line = $typevalue[$i];
0929 if ($line =~ /^[FX]:/) {
0930 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
0931 $line =~ s/([^\\])\.$/$1\?/g;
0932 $line =~ s/\\\./\./g;
0933 $line =~ s/\.\*/\*/g;
0934 }
0935 my $count = $line =~ s/^([A-Z]):/$1:\t/g;
0936 if ($letters eq "" || (!$count || $letters =~ /$1/i)) {
0937 print("$line\n");
0938 }
0939 }
0940 print("\n");
0941 }
0942 }
0943
0944 maintainers_in_file($file);
0945 }
0946
0947 if ($keywords) {
0948 @keyword_tvi = sort_and_uniq(@keyword_tvi);
0949 foreach my $line (@keyword_tvi) {
0950 add_categories($line);
0951 }
0952 }
0953
0954 foreach my $email (@email_to, @list_to) {
0955 $email->[0] = deduplicate_email($email->[0]);
0956 }
0957
0958 foreach my $file (@files) {
0959 if ($email &&
0960 ($email_git ||
0961 ($email_git_fallback &&
0962 $file !~ /MAINTAINERS$/ &&
0963 !$exact_pattern_match_hash{$file}))) {
0964 vcs_file_signoffs($file);
0965 }
0966 if ($email && $email_git_blame) {
0967 vcs_file_blame($file);
0968 }
0969 }
0970
0971 if ($email) {
0972 foreach my $chief (@penguin_chief) {
0973 if ($chief =~ ) {
0974 my $email_address;
0975
0976 $email_address = format_email($1, $2, $email_usename);
0977 if ($email_git_penguin_chiefs) {
0978 push(@email_to, [$email_address, 'chief penguin']);
0979 } else {
0980 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
0981 }
0982 }
0983 }
0984
0985 foreach my $email (@file_emails) {
0986 $email = mailmap_email($email);
0987 my ($name, $address) = parse_email($email);
0988
0989 my $tmp_email = format_email($name, $address, $email_usename);
0990 push_email_address($tmp_email, '');
0991 add_role($tmp_email, 'in file');
0992 }
0993 }
0994
0995 foreach my $fix (@fixes) {
0996 vcs_add_commit_signers($fix, "blamed_fixes");
0997 }
0998
0999 my @to = ();
1000 if ($email || $email_list) {
1001 if ($email) {
1002 @to = (@to, @email_to);
1003 }
1004 if ($email_list) {
1005 @to = (@to, @list_to);
1006 }
1007 }
1008
1009 if ($interactive) {
1010 @to = interactive_get_maintainers(\@to);
1011 }
1012
1013 return @to;
1014 }
1015
1016 sub file_match_pattern {
1017 my ($file, $pattern) = @_;
1018 if (substr($pattern, -1) eq "/") {
1019 if ($file =~ ) {
1020 return 1;
1021 }
1022 } else {
1023 if ($file =~ ) {
1024 my $s1 = ($file =~ tr@/@@);
1025 my $s2 = ($pattern =~ tr@/@@);
1026 if ($s1 == $s2) {
1027 return 1;
1028 }
1029 }
1030 }
1031 return 0;
1032 }
1033
1034 sub usage {
1035 print <<EOT;
1036 usage: $P [options] patchfile
1037 $P [options] -f file|directory
1038 version: $V
1039
1040 MAINTAINER field selection options:
1041 --email => print email address(es) if any
1042 --git => include recent git \*-by: signers
1043 --git-all-signature-types => include signers regardless of signature type
1044 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
1045 --git-fallback = > use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
1046 --git-chief-penguins = > include ${penguin_chiefs}
1047 --git-min-signatures = > number of signatures required (default: $email_git_min_signatures)
1048 --git-max-maintainers = > maximum maintainers to add (default: $email_git_max_maintainers)
1049 --git-min-percent = > minimum percentage of commits required (default: $email_git_min_percent)
1050 --git-blame = > use git blame to find modified commits for patch or file
1051 --git-blame-signatures = > when used with --git-blame, also include all commit signers
1052 --git-since = > git history to use (default: $email_git_since)
1053 --hg-since = > hg history to use (default: $email_hg_since)
1054 --interactive = > display a menu (mostly useful if used with the --git option)
1055 --m = > include maintainer(s) if any
1056 --r = > include reviewer(s) if any
1057 --n = > include name 'Full Name <addr\@domain.tld >'
1058 --l = > include list(s) if any
1059 --moderated = > include moderated lists(s) if any (default: true)
1060 --s = > include subscriber only list(s) if any (default: false)
1061 --remove-duplicates = > minimize duplicate email names/addresses
1062 --roles = > show roles (status:subsystem, git-signer, list, etc...)
1063 --rolestats = > show roles and statistics (commits/total_commits, %)
1064 --file-emails = > add email addresses found in -f file (default: 0 (off))
1065 --fixes = > for patches, add signatures of commits with 'Fixes: <commit