use
5.008;
require
"Maintainers.pl"
;
our
(
%Modules
,
%Maintainers
);
our
@ISA
=
qw(Exporter)
;
our
@EXPORT_OK
=
qw(%Modules %Maintainers
get_module_files get_module_pat
show_results process_options files_to_modules
finish_tap_output
reload_manifest)
;
our
$VERSION
= 0.14;
my
%MANIFEST
;
sub
reload_manifest {
%MANIFEST
= ();
my
$manifest_path
=
'MANIFEST'
;
if
(! -e
$manifest_path
) {
$manifest_path
=
"../MANIFEST"
;
}
if
(
open
(
my
$manfh
,
'<'
,
$manifest_path
)) {
while
(<
$manfh
>) {
if
(/^(\S+)/) {
$MANIFEST
{$1}++;
}
else
{
warn
"MANIFEST:$.: malformed line: $_\n"
;
}
}
close
$manfh
;
}
else
{
die
"$0: Failed to open MANIFEST for reading: $!\n"
;
}
}
reload_manifest;
sub
get_module_pat {
my
$m
=
shift
;
split
' '
,
$Modules
{
$m
}{FILES};
}
sub
expand_glob {
sort
{
lc
$a
cmp
lc
$b
}
map
{
-f
$_
&&
$_
!~ /[*?]/ ?
$_
:
-d _ &&
$_
!~ /[*?]/ ?
do
{
my
@files
;
find(
sub
{
push
@files
,
$File::Find::name
if
-f
$_
&&
exists
$MANIFEST
{
$File::Find::name
};
},
$_
);
@files
;
}
:
$_
!~ /[*?{]/ ?
$_
: expand_glob(
glob
(
$_
))
}
@_
;
}
sub
filter_excluded {
my
(
$m
,
@files
) =
@_
;
my
$excluded
=
$Modules
{
$m
}{EXCLUDED};
return
@files
unless
$excluded
and
@$excluded
;
my
(
$pat
) =
map
{
qr/$_/
}
join
'|'
=>
map
{
ref
$_
?
$_
:
qr/\b\Q$_\E$/
} @{
$excluded
};
return
grep
{
$_
!~
$pat
}
@files
;
}
sub
get_module_files {
my
$m
=
shift
;
return
filter_excluded
$m
=>
map
{ expand_glob(
$_
) } get_module_pat(
$m
);
}
sub
get_maintainer_modules {
my
$m
=
shift
;
sort
{
lc
$a
cmp
lc
$b
}
grep
{
$Modules
{
$_
}{MAINTAINER} eq
$m
}
keys
%Modules
;
}
sub
usage {
warn
<<__EOF__;
$0: Usage:
--maintainer M | --module M [--files]
List modules or maintainers matching the pattern M.
With --files, list all the files associated with them
or
--check | --checkmani [commit | file ... | dir ... ]
Check consistency of Maintainers.pl
with a file checks if it has a maintainer
with a dir checks all files have a maintainer
with a commit checks files modified by that commit
no arg checks for multiple maintainers
--checkmani is like --check, but only reports on unclaimed
files if they are in MANIFEST
or
--opened | file ....
List the module ownership of modified or the listed files
Matching is case-ignoring regexp, author matching is both by
the short id and by the full name and email. A "module" may
not be just a module, it may be a file or files or a subdirectory.
The options may be abbreviated to their unique prefixes
__EOF__
exit
(0);
}
my
$Maintainer
;
my
$Module
;
my
$Files
;
my
$Check
;
my
$Checkmani
;
my
$Opened
;
my
$TestCounter
= 0;
sub
process_options {
usage()
unless
GetOptions(
'maintainer=s'
=> \
$Maintainer
,
'module=s'
=> \
$Module
,
'files'
=> \
$Files
,
'check'
=> \
$Check
,
'checkmani'
=> \
$Checkmani
,
'opened'
=> \
$Opened
,
);
my
@Files
;
if
(
$Opened
) {
usage
if
@ARGV
;
chomp
(
@Files
= `git ls-files -m --full-name`);
die
if
$?;
}
elsif
(
@ARGV
== 1 &&
$ARGV
[0] =~ /^(?:HEAD|[0-9a-f]{4,40})(?:~\d+)?\^*$/) {
my
$command
=
"git diff --name-only $ARGV[0]^ $ARGV[0]"
;
chomp
(
@Files
= `
$command
`);
die
"'$command' failed: $?"
if
$?;
}
else
{
@Files
=
@ARGV
;
}
usage()
if
@Files
&& (
$Maintainer
||
$Module
||
$Files
);
for
my
$mean
(
$Maintainer
,
$Module
) {
warn
"$0: Did you mean '$0 $mean'?\n"
if
$mean
&& -e
$mean
&&
$mean
ne
'.'
&& !
$Files
;
}
warn
"$0: Did you mean '$0 -mo $Maintainer'?\n"
if
defined
$Maintainer
&&
exists
$Modules
{
$Maintainer
};
warn
"$0: Did you mean '$0 -ma $Module'?\n"
if
defined
$Module
&&
exists
$Maintainers
{
$Module
};
return
(
$Maintainer
,
$Module
,
$Files
,
@Files
);
}
sub
files_to_modules {
my
@Files
=
@_
;
my
%ModuleByFile
;
for
(
@Files
) { s:^\./:: }
@ModuleByFile
{
@Files
} = ();
my
%ModuleByPat
;
for
my
$module
(
keys
%Modules
) {
for
my
$pat
(get_module_pat(
$module
)) {
$ModuleByPat
{
$pat
} =
$module
;
}
}
my
%ExpModuleByPat
;
for
my
$pat
(
keys
%ModuleByPat
) {
if
(-e
$pat
) {
$ExpModuleByPat
{
$pat
} =
$ModuleByPat
{
$pat
};
}
else
{
for
my
$exp
(
glob
(
$pat
)) {
$ExpModuleByPat
{
$exp
} =
$ModuleByPat
{
$pat
};
}
}
}
%ModuleByPat
=
%ExpModuleByPat
;
for
my
$file
(
@Files
) {
$ModuleByFile
{
$file
} =
$ModuleByPat
{
$file
}
if
exists
$ModuleByPat
{
$file
};
}
if
(
my
@ToDo
=
grep
{ !
defined
$ModuleByFile
{
$_
} }
keys
%ModuleByFile
) {
@ToDo
=
grep
{ -e
$_
}
@ToDo
;
if
(
@ToDo
) {
my
@OrderedModuleByPat
=
sort
{
length
$b
<=>
length
$a
}
keys
%ModuleByPat
;
for
(
@ToDo
) { s|/$|| }
my
%ToDo
;
@ToDo
{
@ToDo
} = ();
for
my
$pat
(
@OrderedModuleByPat
) {
last
unless
keys
%ToDo
;
if
(-d
$pat
) {
my
@Done
;
for
my
$file
(
keys
%ToDo
) {
if
(
$file
=~ m|^
$pat
|i) {
$ModuleByFile
{
$file
} =
$ModuleByPat
{
$pat
};
push
@Done
,
$file
;
}
}
delete
@ToDo
{
@Done
};
}
}
}
}
\
%ModuleByFile
;
}
sub
show_results {
my
(
$Maintainer
,
$Module
,
$Files
,
@Files
) =
@_
;
if
(
$Maintainer
) {
for
my
$m
(
sort
keys
%Maintainers
) {
if
(
$m
=~ /
$Maintainer
/io ||
$Maintainers
{
$m
} =~ /
$Maintainer
/io) {
my
@modules
= get_maintainer_modules(
$m
);
if
(
$Module
) {
@modules
=
grep
{ /
$Module
/io }
@modules
;
}
if
(
$Files
) {
my
@files
;
for
my
$module
(
@modules
) {
push
@files
, get_module_files(
$module
);
}
printf
"%-15s @files\n"
,
$m
;
}
else
{
if
(
$Module
) {
printf
"%-15s @modules\n"
,
$m
;
}
else
{
printf
"%-15s $Maintainers{$m}\n"
,
$m
;
}
}
}
}
}
elsif
(
$Module
) {
for
my
$m
(
sort
{
lc
$a
cmp
lc
$b
}
keys
%Modules
) {
if
(
$m
=~ /
$Module
/io) {
if
(
$Files
) {
my
@files
= get_module_files(
$m
);
printf
"%-15s @files\n"
,
$m
;
}
else
{
printf
"%-15s %-12s %s\n"
,
$m
,
$Modules
{
$m
}{MAINTAINER},
$Modules
{
$m
}{UPSTREAM}||
'unknown'
;
}
}
}
}
elsif
(
$Check
or
$Checkmani
) {
Test::More->
import
;
if
(
@Files
) {
missing_maintainers(
$Checkmani
?
sub
{ -f
$_
and
exists
$MANIFEST
{
$File::Find::name
} }
:
sub
{ /\.(?:[chty]|p[lm]|xs)\z/msx },
@Files
);
}
else
{
duplicated_maintainers();
superfluous_maintainers();
}
}
elsif
(
@Files
) {
my
$ModuleByFile
= files_to_modules(
@Files
);
for
my
$file
(
@Files
) {
if
(
defined
$ModuleByFile
->{
$file
}) {
my
$module
=
$ModuleByFile
->{
$file
};
my
$maintainer
=
$Modules
{
$ModuleByFile
->{
$file
}}{MAINTAINER};
my
$upstream
=
$Modules
{
$module
}{UPSTREAM}||
'unknown'
;
printf
"%-15s [%-7s] $module $maintainer $Maintainers{$maintainer}\n"
,
$file
,
$upstream
;
}
else
{
printf
"%-15s ?\n"
,
$file
;
}
}
}
elsif
(
$Opened
) {
print
STDERR
"(No files are modified)\n"
;
}
else
{
usage();
}
}
my
%files
;
sub
maintainers_files {
%files
= ();
for
my
$k
(
keys
%Modules
) {
for
my
$f
(get_module_files(
$k
)) {
++
$files
{
$f
};
}
}
}
sub
duplicated_maintainers {
maintainers_files();
for
my
$f
(
sort
keys
%files
) {
cmp_ok(
$files
{
$f
},
'<='
, 1,
"File $f appears $files{$f} times in Maintainers.pl"
);
}
}
sub
warn_maintainer {
my
$name
=
shift
;
ok(
$files
{
$name
},
"$name has a maintainer (see Porting/Maintainers.pl)"
);
}
sub
missing_maintainers {
my
(
$check
,
@path
) =
@_
;
maintainers_files();
my
@dir
;
for
my
$d
(
@path
) {
if
( -d
$d
) {
push
@dir
,
$d
}
else
{ warn_maintainer(
$d
) }
}
find
sub
{ warn_maintainer(
$File::Find::name
)
if
$check
->() },
@dir
if
@dir
;
}
sub
superfluous_maintainers {
maintainers_files();
for
my
$f
(
sort
keys
%files
) {
ok(
$MANIFEST
{
$f
},
"File $f has a maintainer and is in MANIFEST"
);
}
}
sub
finish_tap_output {
done_testing();
}
1;