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