2009-04-08 07:40:12 +04:00
#!/usr/bin/perl -w
# (c) 2007, Joe Perches <joe@perches.com>
# created from checkpatch.pl
#
# Print selected MAINTAINERS information for
# the files modified in a patch or for a file
#
# usage: perl scripts/get_maintainers.pl [OPTIONS] <patch>
# perl scripts/get_maintainers.pl [OPTIONS] -f <file>
#
# Licensed under the terms of the GNU GPL License version 2
use strict ;
my $ P = $ 0 ;
2009-06-17 02:33:58 +04:00
my $ V = '0.16' ;
2009-04-08 07:40:12 +04:00
use Getopt::Long qw( :config no_auto_abbrev ) ;
my $ lk_path = "./" ;
my $ email = 1 ;
my $ email_usename = 1 ;
my $ email_maintainer = 1 ;
my $ email_list = 1 ;
my $ email_subscriber_list = 0 ;
my $ email_git = 1 ;
my $ email_git_penguin_chiefs = 0 ;
my $ email_git_min_signatures = 1 ;
my $ email_git_max_maintainers = 5 ;
my $ email_git_since = "1-year-ago" ;
my $ output_multiline = 1 ;
my $ output_separator = ", " ;
my $ scm = 0 ;
my $ web = 0 ;
my $ subsystem = 0 ;
my $ status = 0 ;
2009-04-10 23:28:57 +04:00
my $ from_filename = 0 ;
2009-04-08 07:40:12 +04:00
my $ version = 0 ;
my $ help = 0 ;
my $ exit = 0 ;
my @ penguin_chief = ( ) ;
push ( @ penguin_chief , "Linus Torvalds:torvalds\@linux-foundation.org" ) ;
#Andrew wants in on most everything - 2009/01/14
#push(@penguin_chief,"Andrew Morton:akpm\@linux-foundation.org");
my @ penguin_chief_names = ( ) ;
foreach my $ chief ( @ penguin_chief ) {
if ( $ chief =~ m/^(.*):(.*)/ ) {
my $ chief_name = $ 1 ;
my $ chief_addr = $ 2 ;
push ( @ penguin_chief_names , $ chief_name ) ;
}
}
my $ penguin_chiefs = "\(" . join ( "|" , @ penguin_chief_names ) . "\)" ;
if ( ! GetOptions (
'email!' = > \ $ email ,
'git!' = > \ $ email_git ,
'git-chief-penguins!' = > \ $ email_git_penguin_chiefs ,
'git-min-signatures=i' = > \ $ email_git_min_signatures ,
'git-max-maintainers=i' = > \ $ email_git_max_maintainers ,
'git-since=s' = > \ $ email_git_since ,
'm!' = > \ $ email_maintainer ,
'n!' = > \ $ email_usename ,
'l!' = > \ $ email_list ,
's!' = > \ $ email_subscriber_list ,
'multiline!' = > \ $ output_multiline ,
'separator=s' = > \ $ output_separator ,
'subsystem!' = > \ $ subsystem ,
'status!' = > \ $ status ,
'scm!' = > \ $ scm ,
'web!' = > \ $ web ,
2009-04-10 23:28:57 +04:00
'f|file' = > \ $ from_filename ,
2009-04-08 07:40:12 +04:00
'v|version' = > \ $ version ,
'h|help' = > \ $ help ,
) ) {
usage ( ) ;
die "$P: invalid argument\n" ;
}
if ( $ help != 0 ) {
usage ( ) ;
exit 0 ;
}
if ( $ version != 0 ) {
print ( "${P} ${V}\n" ) ;
exit 0 ;
}
if ( $# ARGV < 0 ) {
usage ( ) ;
die "$P: argument missing: patchfile or -f file please\n" ;
}
my $ selections = $ email + $ scm + $ status + $ subsystem + $ web ;
if ( $ selections == 0 ) {
usage ( ) ;
die "$P: Missing required option: email, scm, status, subsystem or web\n" ;
}
if ( $ email && ( $ email_maintainer + $ email_list + $ email_subscriber_list
+ $ email_git + $ email_git_penguin_chiefs ) == 0 ) {
usage ( ) ;
die "$P: Please select at least 1 email option\n" ;
}
if ( ! top_of_kernel_tree ( $ lk_path ) ) {
die "$P: The current directory does not appear to be "
. "a linux kernel source tree.\n" ;
}
## Read MAINTAINERS for type/value pairs
my @ typevalue = ( ) ;
open ( MAINT , "<${lk_path}MAINTAINERS" ) || die "$P: Can't open MAINTAINERS\n" ;
while ( <MAINT> ) {
my $ line = $ _ ;
if ( $ line =~ m/^(\C):\s*(.*)/ ) {
my $ type = $ 1 ;
my $ value = $ 2 ;
##Filename pattern matching
if ( $ type eq "F" || $ type eq "X" ) {
$ value =~ s@\.@\\\.@g ; ##Convert . to \.
$ value =~ s/\*/\.\*/g ; ##Convert * to .*
$ value =~ s/\?/\./g ; ##Convert ? to .
}
push ( @ typevalue , "$type:$value" ) ;
} elsif ( ! /^(\s)*$/ ) {
$ line =~ s/\n$//g ;
push ( @ typevalue , $ line ) ;
}
}
close ( MAINT ) ;
2009-04-10 23:28:57 +04:00
## use the filenames on the command line or find the filenames in the patchfiles
2009-04-08 07:40:12 +04:00
my @ files = ( ) ;
2009-04-10 23:28:57 +04:00
foreach my $ file ( @ ARGV ) {
next if ( ( - d $ file ) ) ;
if ( ! ( - f $ file ) ) {
die "$P: file '${file}' not found\n" ;
2009-04-08 07:40:12 +04:00
}
2009-04-10 23:28:57 +04:00
if ( $ from_filename ) {
push ( @ files , $ file ) ;
} else {
my $ file_cnt = @ files ;
open ( PATCH , "<$file" ) or die "$P: Can't open ${file}\n" ;
while ( <PATCH> ) {
if ( m/^\+\+\+\s+(\S+)/ ) {
my $ filename = $ 1 ;
$ filename =~ s@^[^/]*/@@ ;
$ filename =~ s@\n@@ ;
push ( @ files , $ filename ) ;
}
2009-04-08 07:40:12 +04:00
}
2009-04-10 23:28:57 +04:00
close ( PATCH ) ;
if ( $ file_cnt == @ files ) {
die "$P: file '${file}' doesn't appear to be a patch. "
. "Add -f to options?\n" ;
}
@ files = sort_and_uniq ( @ files ) ;
2009-04-08 07:40:12 +04:00
}
}
my @ email_to = ( ) ;
2009-06-17 02:33:58 +04:00
my @ list_to = ( ) ;
2009-04-08 07:40:12 +04:00
my @ scm = ( ) ;
my @ web = ( ) ;
my @ subsystem = ( ) ;
my @ status = ( ) ;
# Find responsible parties
foreach my $ file ( @ files ) {
#Do not match excluded file patterns
my $ exclude = 0 ;
foreach my $ line ( @ typevalue ) {
2009-06-17 02:33:58 +04:00
if ( $ line =~ m/^(\C):\s*(.*)/ ) {
2009-04-08 07:40:12 +04:00
my $ type = $ 1 ;
my $ value = $ 2 ;
if ( $ type eq 'X' ) {
if ( file_match_pattern ( $ file , $ value ) ) {
$ exclude = 1 ;
}
}
}
}
if ( ! $ exclude ) {
my $ tvi = 0 ;
foreach my $ line ( @ typevalue ) {
2009-06-17 02:33:58 +04:00
if ( $ line =~ m/^(\C):\s*(.*)/ ) {
2009-04-08 07:40:12 +04:00
my $ type = $ 1 ;
my $ value = $ 2 ;
if ( $ type eq 'F' ) {
if ( file_match_pattern ( $ file , $ value ) ) {
add_categories ( $ tvi ) ;
}
}
}
$ tvi + + ;
}
}
2009-04-10 23:28:57 +04:00
if ( $ email && $ email_git ) {
2009-04-08 07:40:12 +04:00
recent_git_signoffs ( $ file ) ;
}
}
if ( $ email_git_penguin_chiefs ) {
foreach my $ chief ( @ penguin_chief ) {
if ( $ chief =~ m/^(.*):(.*)/ ) {
my $ chief_name = $ 1 ;
my $ chief_addr = $ 2 ;
if ( $ email_usename ) {
push ( @ email_to , format_email ( $ chief_name , $ chief_addr ) ) ;
} else {
push ( @ email_to , $ chief_addr ) ;
}
}
}
}
2009-06-17 02:33:58 +04:00
if ( $ email || $ email_list ) {
my @ to = ( ) ;
if ( $ email ) {
@ to = ( @ to , @ email_to ) ;
2009-04-08 07:40:12 +04:00
}
2009-06-17 02:33:58 +04:00
if ( $ email_list ) {
if ( @ list_to == 0 ) {
push ( @ list_to , "linux-kernel\@vger.kernel.org" ) ;
@ to = ( @ to , @ list_to ) ;
}
}
output ( uniq ( @ to ) ) ;
2009-04-08 07:40:12 +04:00
}
if ( $ scm ) {
2009-04-10 23:28:57 +04:00
@ scm = sort_and_uniq ( @ scm ) ;
2009-04-08 07:40:12 +04:00
output ( @ scm ) ;
}
if ( $ status ) {
2009-04-10 23:28:57 +04:00
@ status = sort_and_uniq ( @ status ) ;
2009-04-08 07:40:12 +04:00
output ( @ status ) ;
}
if ( $ subsystem ) {
2009-04-10 23:28:57 +04:00
@ subsystem = sort_and_uniq ( @ subsystem ) ;
2009-04-08 07:40:12 +04:00
output ( @ subsystem ) ;
}
if ( $ web ) {
2009-04-10 23:28:57 +04:00
@ web = sort_and_uniq ( @ web ) ;
2009-04-08 07:40:12 +04:00
output ( @ web ) ;
}
exit ( $ exit ) ;
sub file_match_pattern {
my ( $ file , $ pattern ) = @ _ ;
if ( substr ( $ pattern , - 1 ) eq "/" ) {
if ( $ file =~ m @^$pattern@ ) {
return 1 ;
}
} else {
if ( $ file =~ m @^$pattern@ ) {
my $ s1 = ( $ file =~ tr @ / @ @ ) ;
my $ s2 = ( $ pattern =~ tr @ / @ @ ) ;
if ( $ s1 == $ s2 ) {
return 1 ;
}
}
}
return 0 ;
}
sub usage {
print << EOT ;
usage: $ P [ options ] patchfile
$ P [ options ] - f file
version: $ V
MAINTAINER field selection options:
- - email = > print email address ( es ) if any
- - git = > include recent git \ * - by: signers
- - git - chief - penguins = > include $ { penguin_chiefs }
- - git - min - signatures = > number of signatures required ( default: 1 )
- - git - max - maintainers = > maximum maintainers to add ( default: 5 )
- - git - since = > git history to use ( default: 1 - year - ago )
- - m = > include maintainer ( s ) if any
- - n = > include name 'Full Name <addr\@domain.tld>'
- - l = > include list ( s ) if any
- - s = > include subscriber only list ( s ) if any
- - scm = > print SCM tree ( s ) if any
- - status = > print status if any
- - subsystem = > print subsystem name if any
- - web = > print website ( s ) if any
Output type options:
- - separator [ , ] = > separator for multiple entries on 1 line
- - multiline = > print 1 entry per line
Default options:
2009-06-17 02:33:58 +04:00
[ - - email - - git - - m - - n - - l - - multiline ]
2009-04-08 07:40:12 +04:00
Other options:
- - version - > show version
- - help = > show this help information
EOT
}
sub top_of_kernel_tree {
my ( $ lk_path ) = @ _ ;
if ( $ lk_path ne "" && substr ( $ lk_path , length ( $ lk_path ) - 1 , 1 ) ne "/" ) {
$ lk_path . = "/" ;
}
if ( ( - f "${lk_path}COPYING" )
&& ( - f "${lk_path}CREDITS" )
&& ( - f "${lk_path}Kbuild" )
&& ( - f "${lk_path}MAINTAINERS" )
&& ( - f "${lk_path}Makefile" )
&& ( - f "${lk_path}README" )
&& ( - d "${lk_path}Documentation" )
&& ( - d "${lk_path}arch" )
&& ( - d "${lk_path}include" )
&& ( - d "${lk_path}drivers" )
&& ( - d "${lk_path}fs" )
&& ( - d "${lk_path}init" )
&& ( - d "${lk_path}ipc" )
&& ( - d "${lk_path}kernel" )
&& ( - d "${lk_path}lib" )
&& ( - d "${lk_path}scripts" ) ) {
return 1 ;
}
return 0 ;
}
sub format_email {
my ( $ name , $ email ) = @ _ ;
$ name =~ s/^\s+|\s+$//g ;
$ email =~ s/^\s+|\s+$//g ;
my $ formatted_email = "" ;
if ( $ name =~ /[^a-z0-9 \.\-]/i ) { ##has "must quote" chars
$ name =~ s/(?<!\\)"/\\"/g ; ##escape quotes
$ formatted_email = "\"${name}\"\ \<${email}\>" ;
} else {
$ formatted_email = "${name} \<${email}\>" ;
}
return $ formatted_email ;
}
sub add_categories {
my ( $ index ) = @ _ ;
$ index = $ index - 1 ;
while ( $ index >= 0 ) {
my $ tv = $ typevalue [ $ index ] ;
2009-06-17 02:33:58 +04:00
if ( $ tv =~ m/^(\C):\s*(.*)/ ) {
2009-04-08 07:40:12 +04:00
my $ ptype = $ 1 ;
my $ pvalue = $ 2 ;
if ( $ ptype eq "L" ) {
2009-06-17 02:33:58 +04:00
my $ list_address = $ pvalue ;
my $ list_additional = "" ;
if ( $ list_address =~ m/([^\s]+)\s+(.*)$/ ) {
$ list_address = $ 1 ;
$ list_additional = $ 2 ;
}
if ( $ list_additional =~ m/\(subscribers-only\)/ ) {
2009-04-08 07:40:12 +04:00
if ( $ email_subscriber_list ) {
2009-06-17 02:33:58 +04:00
push ( @ list_to , $ list_address ) ;
2009-04-08 07:40:12 +04:00
}
} else {
if ( $ email_list ) {
2009-06-17 02:33:58 +04:00
push ( @ list_to , $ list_address ) ;
2009-04-08 07:40:12 +04:00
}
}
} elsif ( $ ptype eq "M" ) {
if ( $ email_maintainer ) {
if ( $ index >= 0 ) {
my $ tv = $ typevalue [ $ index - 1 ] ;
2009-06-17 02:33:58 +04:00
if ( $ tv =~ m/^(\C):\s*(.*)/ ) {
2009-04-08 07:40:12 +04:00
if ( $ 1 eq "P" && $ email_usename ) {
push ( @ email_to , format_email ( $ 2 , $ pvalue ) ) ;
} else {
push ( @ email_to , $ pvalue ) ;
}
}
} else {
push ( @ email_to , $ pvalue ) ;
}
}
} elsif ( $ ptype eq "T" ) {
push ( @ scm , $ pvalue ) ;
} elsif ( $ ptype eq "W" ) {
push ( @ web , $ pvalue ) ;
} elsif ( $ ptype eq "S" ) {
push ( @ status , $ pvalue ) ;
}
$ index - - ;
} else {
push ( @ subsystem , $ tv ) ;
$ index = - 1 ;
}
}
}
sub which {
my ( $ bin ) = @ _ ;
foreach my $ path ( split /:/ , $ ENV { PATH } ) {
if ( - e "$path/$bin" ) {
return "$path/$bin" ;
}
}
return "" ;
}
sub recent_git_signoffs {
my ( $ file ) = @ _ ;
my $ sign_offs = "" ;
my $ cmd = "" ;
my $ output = "" ;
my $ count = 0 ;
my @ lines = ( ) ;
if ( which ( "git" ) eq "" ) {
die ( "$P: git not found. Add --nogit to options?\n" ) ;
}
$ cmd = "git log --since=${email_git_since} -- ${file}" ;
2009-04-10 23:28:57 +04:00
$ cmd . = " | grep -Pi \"^[-_ a-z]+by:.*\\\@\"" ;
2009-04-08 07:40:12 +04:00
if ( ! $ email_git_penguin_chiefs ) {
2009-04-10 23:28:57 +04:00
$ cmd . = " | grep -Pv \"${penguin_chiefs}\"" ;
2009-04-08 07:40:12 +04:00
}
2009-04-10 23:28:57 +04:00
$ cmd . = " | cut -f2- -d\":\"" ;
$ cmd . = " | sed -e \"s/^\\s+//g\"" ;
2009-04-08 07:40:12 +04:00
$ cmd . = " | sort | uniq -c | sort -rn" ;
$ output = `${cmd}` ;
$ output =~ s/^\s*//gm ;
@ lines = split ( "\n" , $ output ) ;
foreach my $ line ( @ lines ) {
2009-04-10 23:28:57 +04:00
if ( $ line =~ m/([0-9]+)\s+(.*)/ ) {
2009-04-08 07:40:12 +04:00
my $ sign_offs = $ 1 ;
2009-04-10 23:28:57 +04:00
$ line = $ 2 ;
2009-04-08 07:40:12 +04:00
$ count + + ;
if ( $ sign_offs < $ email_git_min_signatures ||
$ count > $ email_git_max_maintainers ) {
last ;
}
} else {
die ( "$P: Unexpected git output: ${line}\n" ) ;
}
2009-04-10 23:28:57 +04:00
if ( $ line =~ m/(.+)<(.+)>/ ) {
2009-04-08 07:40:12 +04:00
my $ git_name = $ 1 ;
my $ git_addr = $ 2 ;
$ git_name =~ tr /^\"/ / ;
2009-04-10 23:28:57 +04:00
$ git_name =~ tr /^\\s*/ / ;
2009-04-08 07:40:12 +04:00
$ git_name =~ tr /\"$/ / ;
2009-04-10 23:28:57 +04:00
$ git_name =~ tr /\\s*$/ / ;
2009-04-08 07:40:12 +04:00
if ( $ email_usename ) {
push ( @ email_to , format_email ( $ git_name , $ git_addr ) ) ;
} else {
push ( @ email_to , $ git_addr ) ;
}
2009-04-10 23:28:57 +04:00
} elsif ( $ line =~ m/<(.+)>/ ) {
2009-04-08 07:40:12 +04:00
my $ git_addr = $ 1 ;
push ( @ email_to , $ git_addr ) ;
} else {
push ( @ email_to , $ line ) ;
}
}
return $ output ;
}
sub uniq {
my @ parms = @ _ ;
my % saw ;
@ parms = grep ( ! $ saw { $ _ } + + , @ parms ) ;
return @ parms ;
}
sub sort_and_uniq {
my @ parms = @ _ ;
my % saw ;
@ parms = sort @ parms ;
@ parms = grep ( ! $ saw { $ _ } + + , @ parms ) ;
return @ parms ;
}
sub output {
my @ parms = @ _ ;
if ( $ output_multiline ) {
foreach my $ line ( @ parms ) {
print ( "${line}\n" ) ;
}
} else {
print ( join ( $ output_separator , @ parms ) ) ;
print ( "\n" ) ;
}
}