1#!/usr/bin/perl -w 2# (c) 2007, Joe Perches <joe@perches.com> 3# created from checkpatch.pl 4# 5# Print selected MAINTAINERS information for 6# the files modified in a patch or for a file 7# 8# usage: perl scripts/get_maintainers.pl [OPTIONS] <patch> 9# perl scripts/get_maintainers.pl [OPTIONS] -f <file> 10# 11# Licensed under the terms of the GNU GPL License version 2 12 13use strict; 14 15my $P = $0; 16my $V = '0.15'; 17 18use Getopt::Long qw(:config no_auto_abbrev); 19 20my $lk_path = "./"; 21my $email = 1; 22my $email_usename = 1; 23my $email_maintainer = 1; 24my $email_list = 1; 25my $email_subscriber_list = 0; 26my $email_git = 1; 27my $email_git_penguin_chiefs = 0; 28my $email_git_min_signatures = 1; 29my $email_git_max_maintainers = 5; 30my $email_git_since = "1-year-ago"; 31my $output_multiline = 1; 32my $output_separator = ", "; 33my $scm = 0; 34my $web = 0; 35my $subsystem = 0; 36my $status = 0; 37my $from_filename = 0; 38my $version = 0; 39my $help = 0; 40 41my $exit = 0; 42 43my @penguin_chief = (); 44push(@penguin_chief,"Linus Torvalds:torvalds\@linux-foundation.org"); 45#Andrew wants in on most everything - 2009/01/14 46#push(@penguin_chief,"Andrew Morton:akpm\@linux-foundation.org"); 47 48my @penguin_chief_names = (); 49foreach my $chief (@penguin_chief) { 50 if ($chief =~ m/^(.*):(.*)/) { 51 my $chief_name = $1; 52 my $chief_addr = $2; 53 push(@penguin_chief_names, $chief_name); 54 } 55} 56my $penguin_chiefs = "\(" . join("|",@penguin_chief_names) . "\)"; 57 58if (!GetOptions( 59 'email!' => \$email, 60 'git!' => \$email_git, 61 'git-chief-penguins!' => \$email_git_penguin_chiefs, 62 'git-min-signatures=i' => \$email_git_min_signatures, 63 'git-max-maintainers=i' => \$email_git_max_maintainers, 64 'git-since=s' => \$email_git_since, 65 'm!' => \$email_maintainer, 66 'n!' => \$email_usename, 67 'l!' => \$email_list, 68 's!' => \$email_subscriber_list, 69 'multiline!' => \$output_multiline, 70 'separator=s' => \$output_separator, 71 'subsystem!' => \$subsystem, 72 'status!' => \$status, 73 'scm!' => \$scm, 74 'web!' => \$web, 75 'f|file' => \$from_filename, 76 'v|version' => \$version, 77 'h|help' => \$help, 78 )) { 79 usage(); 80 die "$P: invalid argument\n"; 81} 82 83if ($help != 0) { 84 usage(); 85 exit 0; 86} 87 88if ($version != 0) { 89 print("${P} ${V}\n"); 90 exit 0; 91} 92 93if ($#ARGV < 0) { 94 usage(); 95 die "$P: argument missing: patchfile or -f file please\n"; 96} 97 98my $selections = $email + $scm + $status + $subsystem + $web; 99if ($selections == 0) { 100 usage(); 101 die "$P: Missing required option: email, scm, status, subsystem or web\n"; 102} 103 104if ($email && ($email_maintainer + $email_list + $email_subscriber_list 105 + $email_git + $email_git_penguin_chiefs) == 0) { 106 usage(); 107 die "$P: Please select at least 1 email option\n"; 108} 109 110if (!top_of_kernel_tree($lk_path)) { 111 die "$P: The current directory does not appear to be " 112 . "a linux kernel source tree.\n"; 113} 114 115## Read MAINTAINERS for type/value pairs 116 117my @typevalue = (); 118open(MAINT, "<${lk_path}MAINTAINERS") || die "$P: Can't open MAINTAINERS\n"; 119while (<MAINT>) { 120 my $line = $_; 121 122 if ($line =~ m/^(\C):\s*(.*)/) { 123 my $type = $1; 124 my $value = $2; 125 126 ##Filename pattern matching 127 if ($type eq "F" || $type eq "X") { 128 $value =~ s@\.@\\\.@g; ##Convert . to \. 129 $value =~ s/\*/\.\*/g; ##Convert * to .* 130 $value =~ s/\?/\./g; ##Convert ? to . 131 } 132 push(@typevalue, "$type:$value"); 133 } elsif (!/^(\s)*$/) { 134 $line =~ s/\n$//g; 135 push(@typevalue, $line); 136 } 137} 138close(MAINT); 139 140## use the filenames on the command line or find the filenames in the patchfiles 141 142my @files = (); 143 144foreach my $file (@ARGV) { 145 next if ((-d $file)); 146 if (!(-f $file)) { 147 die "$P: file '${file}' not found\n"; 148 } 149 if ($from_filename) { 150 push(@files, $file); 151 } else { 152 my $file_cnt = @files; 153 open(PATCH, "<$file") or die "$P: Can't open ${file}\n"; 154 while (<PATCH>) { 155 if (m/^\+\+\+\s+(\S+)/) { 156 my $filename = $1; 157 $filename =~ s@^[^/]*/@@; 158 $filename =~ s@\n@@; 159 push(@files, $filename); 160 } 161 } 162 close(PATCH); 163 if ($file_cnt == @files) { 164 die "$P: file '${file}' doesn't appear to be a patch. " 165 . "Add -f to options?\n"; 166 } 167 @files = sort_and_uniq(@files); 168 } 169} 170 171my @email_to = (); 172my @scm = (); 173my @web = (); 174my @subsystem = (); 175my @status = (); 176 177# Find responsible parties 178 179foreach my $file (@files) { 180 181#Do not match excluded file patterns 182 183 my $exclude = 0; 184 foreach my $line (@typevalue) { 185 if ($line =~ m/^(\C):(.*)/) { 186 my $type = $1; 187 my $value = $2; 188 if ($type eq 'X') { 189 if (file_match_pattern($file, $value)) { 190 $exclude = 1; 191 } 192 } 193 } 194 } 195 196 if (!$exclude) { 197 my $tvi = 0; 198 foreach my $line (@typevalue) { 199 if ($line =~ m/^(\C):(.*)/) { 200 my $type = $1; 201 my $value = $2; 202 if ($type eq 'F') { 203 if (file_match_pattern($file, $value)) { 204 add_categories($tvi); 205 } 206 } 207 } 208 $tvi++; 209 } 210 } 211 212 if ($email && $email_git) { 213 recent_git_signoffs($file); 214 } 215 216} 217 218if ($email_git_penguin_chiefs) { 219 foreach my $chief (@penguin_chief) { 220 if ($chief =~ m/^(.*):(.*)/) { 221 my $chief_name = $1; 222 my $chief_addr = $2; 223 if ($email_usename) { 224 push(@email_to, format_email($chief_name, $chief_addr)); 225 } else { 226 push(@email_to, $chief_addr); 227 } 228 } 229 } 230} 231 232if ($email) { 233 my $address_cnt = @email_to; 234 if ($address_cnt == 0 && $email_list) { 235 push(@email_to, "linux-kernel\@vger.kernel.org"); 236 } 237 238#Don't sort email address list, but do remove duplicates 239 @email_to = uniq(@email_to); 240 output(@email_to); 241} 242 243if ($scm) { 244 @scm = sort_and_uniq(@scm); 245 output(@scm); 246} 247 248if ($status) { 249 @status = sort_and_uniq(@status); 250 output(@status); 251} 252 253if ($subsystem) { 254 @subsystem = sort_and_uniq(@subsystem); 255 output(@subsystem); 256} 257 258if ($web) { 259 @web = sort_and_uniq(@web); 260 output(@web); 261} 262 263exit($exit); 264 265sub file_match_pattern { 266 my ($file, $pattern) = @_; 267 if (substr($pattern, -1) eq "/") { 268 if ($file =~ m@^$pattern@) { 269 return 1; 270 } 271 } else { 272 if ($file =~ m@^$pattern@) { 273 my $s1 = ($file =~ tr@/@@); 274 my $s2 = ($pattern =~ tr@/@@); 275 if ($s1 == $s2) { 276 return 1; 277 } 278 } 279 } 280 return 0; 281} 282 283sub usage { 284 print <<EOT; 285usage: $P [options] patchfile 286 $P [options] -f file 287version: $V 288 289MAINTAINER field selection options: 290 --email => print email address(es) if any 291 --git => include recent git \*-by: signers 292 --git-chief-penguins => include ${penguin_chiefs} 293 --git-min-signatures => number of signatures required (default: 1) 294 --git-max-maintainers => maximum maintainers to add (default: 5) 295 --git-since => git history to use (default: 1-year-ago) 296 --m => include maintainer(s) if any 297 --n => include name 'Full Name <addr\@domain.tld>' 298 --l => include list(s) if any 299 --s => include subscriber only list(s) if any 300 --scm => print SCM tree(s) if any 301 --status => print status if any 302 --subsystem => print subsystem name if any 303 --web => print website(s) if any 304 305Output type options: 306 --separator [, ] => separator for multiple entries on 1 line 307 --multiline => print 1 entry per line 308 309Default options: 310 [--email --git --m --l --multiline] 311 312Other options: 313 --version -> show version 314 --help => show this help information 315 316EOT 317} 318 319sub top_of_kernel_tree { 320 my ($lk_path) = @_; 321 322 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") { 323 $lk_path .= "/"; 324 } 325 if ( (-f "${lk_path}COPYING") 326 && (-f "${lk_path}CREDITS") 327 && (-f "${lk_path}Kbuild") 328 && (-f "${lk_path}MAINTAINERS") 329 && (-f "${lk_path}Makefile") 330 && (-f "${lk_path}README") 331 && (-d "${lk_path}Documentation") 332 && (-d "${lk_path}arch") 333 && (-d "${lk_path}include") 334 && (-d "${lk_path}drivers") 335 && (-d "${lk_path}fs") 336 && (-d "${lk_path}init") 337 && (-d "${lk_path}ipc") 338 && (-d "${lk_path}kernel") 339 && (-d "${lk_path}lib") 340 && (-d "${lk_path}scripts")) { 341 return 1; 342 } 343 return 0; 344} 345 346sub format_email { 347 my ($name, $email) = @_; 348 349 $name =~ s/^\s+|\s+$//g; 350 $email =~ s/^\s+|\s+$//g; 351 352 my $formatted_email = ""; 353 354 if ($name =~ /[^a-z0-9 \.\-]/i) { ##has "must quote" chars 355 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes 356 $formatted_email = "\"${name}\"\ \<${email}\>"; 357 } else { 358 $formatted_email = "${name} \<${email}\>"; 359 } 360 return $formatted_email; 361} 362 363sub add_categories { 364 my ($index) = @_; 365 366 $index = $index - 1; 367 while ($index >= 0) { 368 my $tv = $typevalue[$index]; 369 if ($tv =~ m/^(\C):(.*)/) { 370 my $ptype = $1; 371 my $pvalue = $2; 372 if ($ptype eq "L") { 373 my $subscr = $pvalue; 374 if ($subscr =~ m/\s*\(subscribers-only\)/) { 375 if ($email_subscriber_list) { 376 $subscr =~ s/\s*\(subscribers-only\)//g; 377 push(@email_to, $subscr); 378 } 379 } else { 380 if ($email_list) { 381 push(@email_to, $pvalue); 382 } 383 } 384 } elsif ($ptype eq "M") { 385 if ($email_maintainer) { 386 if ($index >= 0) { 387 my $tv = $typevalue[$index - 1]; 388 if ($tv =~ m/^(\C):(.*)/) { 389 if ($1 eq "P" && $email_usename) { 390 push(@email_to, format_email($2, $pvalue)); 391 } else { 392 push(@email_to, $pvalue); 393 } 394 } 395 } else { 396 push(@email_to, $pvalue); 397 } 398 } 399 } elsif ($ptype eq "T") { 400 push(@scm, $pvalue); 401 } elsif ($ptype eq "W") { 402 push(@web, $pvalue); 403 } elsif ($ptype eq "S") { 404 push(@status, $pvalue); 405 } 406 407 $index--; 408 } else { 409 push(@subsystem,$tv); 410 $index = -1; 411 } 412 } 413} 414 415sub which { 416 my ($bin) = @_; 417 418 foreach my $path (split /:/, $ENV{PATH}) { 419 if (-e "$path/$bin") { 420 return "$path/$bin"; 421 } 422 } 423 424 return ""; 425} 426 427sub recent_git_signoffs { 428 my ($file) = @_; 429 430 my $sign_offs = ""; 431 my $cmd = ""; 432 my $output = ""; 433 my $count = 0; 434 my @lines = (); 435 436 if (which("git") eq "") { 437 die("$P: git not found. Add --nogit to options?\n"); 438 } 439 440 $cmd = "git log --since=${email_git_since} -- ${file}"; 441 $cmd .= " | grep -Pi \"^[-_ a-z]+by:.*\\\@\""; 442 if (!$email_git_penguin_chiefs) { 443 $cmd .= " | grep -Pv \"${penguin_chiefs}\""; 444 } 445 $cmd .= " | cut -f2- -d\":\""; 446 $cmd .= " | sed -e \"s/^\\s+//g\""; 447 $cmd .= " | sort | uniq -c | sort -rn"; 448 449 $output = `${cmd}`; 450 $output =~ s/^\s*//gm; 451 452 @lines = split("\n", $output); 453 foreach my $line (@lines) { 454 if ($line =~ m/([0-9]+)\s+(.*)/) { 455 my $sign_offs = $1; 456 $line = $2; 457 $count++; 458 if ($sign_offs < $email_git_min_signatures || 459 $count > $email_git_max_maintainers) { 460 last; 461 } 462 } else { 463 die("$P: Unexpected git output: ${line}\n"); 464 } 465 if ($line =~ m/(.+)<(.+)>/) { 466 my $git_name = $1; 467 my $git_addr = $2; 468 $git_name =~ tr/^\"//; 469 $git_name =~ tr/^\\s*//; 470 $git_name =~ tr/\"$//; 471 $git_name =~ tr/\\s*$//; 472 if ($email_usename) { 473 push(@email_to, format_email($git_name, $git_addr)); 474 } else { 475 push(@email_to, $git_addr); 476 } 477 } elsif ($line =~ m/<(.+)>/) { 478 my $git_addr = $1; 479 push(@email_to, $git_addr); 480 } else { 481 push(@email_to, $line); 482 } 483 } 484 return $output; 485} 486 487sub uniq { 488 my @parms = @_; 489 490 my %saw; 491 @parms = grep(!$saw{$_}++, @parms); 492 return @parms; 493} 494 495sub sort_and_uniq { 496 my @parms = @_; 497 498 my %saw; 499 @parms = sort @parms; 500 @parms = grep(!$saw{$_}++, @parms); 501 return @parms; 502} 503 504sub output { 505 my @parms = @_; 506 507 if ($output_multiline) { 508 foreach my $line (@parms) { 509 print("${line}\n"); 510 } 511 } else { 512 print(join($output_separator, @parms)); 513 print("\n"); 514 } 515} 516