2 # Maintainers.pm - show information about maintainers
11 # Please don't use post 5.008 features as this module is used by
12 # Porting/makemeta, and that in turn has to be run by the perl just built.
15 require "Maintainers.pl";
16 use vars qw(%Modules %Maintainers);
18 use vars qw(@ISA @EXPORT_OK $VERSION);
20 @EXPORT_OK = qw(%Modules %Maintainers
21 get_module_files get_module_pat
22 show_results process_options files_to_modules
34 # (re)read the MANIFEST file, blowing away any previous effort
39 my $manifest_path = 'MANIFEST';
40 if (! -e $manifest_path) {
41 $manifest_path = "../MANIFEST";
44 if (open(my $manfh, $manifest_path )) {
50 warn "MANIFEST:$.: malformed line: $_\n";
55 die "$0: Failed to open MANIFEST for reading: $!\n";
64 split ' ', $Modules{$m}{FILES};
67 # exand dir/ or foo* into a full list of files
70 sort { lc $a cmp lc $b }
72 -f $_ && $_ !~ /[*?]/ ? # File as-is.
74 -d _ && $_ !~ /[*?]/ ? # Recurse into directories.
79 push @files, $File::Find::name
80 if -f $_ && exists $MANIFEST{$File::Find::name};
84 # The rest are globbable patterns; expand the glob, then
85 # recursively perform directory expansion on any results
86 : expand_glob(grep -e $_,glob($_))
94 unless my $excluded = $Modules{$m}{EXCLUDED};
96 my ($pat) = map { qr/$_/ } join '|' => map {
97 ref $_ ? qr/\Q$_\E/ : $_
100 return grep { $_ !~ $pat } @files;
103 sub get_module_files {
105 return filter_excluded $m => map { expand_glob($_) } get_module_pat($m);
109 sub get_maintainer_modules {
111 sort { lc $a cmp lc $b }
112 grep { $Modules{$_}{MAINTAINER} eq $m }
119 --maintainer M | --module M [--files]
120 List modules or maintainers matching the pattern M.
121 With --files, list all the files associated with them
123 --check | --checkmani [commit | file ... | dir ... ]
124 Check consistency of Maintainers.pl
125 with a file checks if it has a maintainer
126 with a dir checks all files have a maintainer
127 with a commit checks files modified by that commit
128 no arg checks for multiple maintainers
129 --checkmani is like --check, but only reports on unclaimed
130 files if they are in MANIFEST
133 List the module ownership of modified or the listed files
136 Show results as valid TAP output. Currently only compatible
137 with --check, --checkmani
139 Matching is case-ignoring regexp, author matching is both by
140 the short id and by the full name and email. A "module" may
141 not be just a module, it may be a file or files or a subdirectory.
142 The options may be abbreviated to their unique prefixes
156 sub process_options {
160 'maintainer=s' => \$Maintainer,
161 'module=s' => \$Module,
164 'checkmani' => \$Checkmani,
165 'opened' => \$Opened,
166 'tap-output' => \$TapOutput,
173 chomp (@Files = `git ls-files -m --full-name`);
175 } elsif (@ARGV == 1 &&
176 $ARGV[0] =~ /^(?:HEAD|[0-9a-f]{4,40})(?:~\d+)?\^*$/) {
177 my $command = "git diff --name-only $ARGV[0]^ $ARGV[0]";
178 chomp (@Files = `$command`);
179 die "'$command' failed: $?" if $?;
184 usage() if @Files && ($Maintainer || $Module || $Files);
186 for my $mean ($Maintainer, $Module) {
187 warn "$0: Did you mean '$0 $mean'?\n"
188 if $mean && -e $mean && $mean ne '.' && !$Files;
191 warn "$0: Did you mean '$0 -mo $Maintainer'?\n"
192 if defined $Maintainer && exists $Modules{$Maintainer};
194 warn "$0: Did you mean '$0 -ma $Module'?\n"
195 if defined $Module && exists $Maintainers{$Module};
197 return ($Maintainer, $Module, $Files, @Files);
200 sub files_to_modules {
204 for (@Files) { s:^\./:: }
206 @ModuleByFile{@Files} = ();
208 # First try fast match.
211 for my $module (keys %Modules) {
212 for my $pat (get_module_pat($module)) {
213 $ModuleByPat{$pat} = $module;
218 for my $pat (keys %ModuleByPat) {
220 $ExpModuleByPat{$pat} = $ModuleByPat{$pat};
222 for my $exp (glob($pat)) {
223 $ExpModuleByPat{$exp} = $ModuleByPat{$pat};
227 %ModuleByPat = %ExpModuleByPat;
228 for my $file (@Files) {
229 $ModuleByFile{$file} = $ModuleByPat{$file}
230 if exists $ModuleByPat{$file};
233 # If still unresolved files...
234 if (my @ToDo = grep { !defined $ModuleByFile{$_} } keys %ModuleByFile) {
236 # Cannot match what isn't there.
237 @ToDo = grep { -e $_ } @ToDo;
240 # Try prefix matching.
242 # Need to try longst prefixes first, else lib/CPAN may match
243 # lib/CPANPLUS/... and similar
245 my @OrderedModuleByPat
246 = sort {length $b <=> length $a} keys %ModuleByPat;
248 # Remove trailing slashes.
249 for (@ToDo) { s|/$|| }
254 for my $pat (@OrderedModuleByPat) {
255 last unless keys %ToDo;
258 for my $file (keys %ToDo) {
259 if ($file =~ m|^$pat|i) {
260 $ModuleByFile{$file} = $ModuleByPat{$pat};
272 my ($Maintainer, $Module, $Files, @Files) = @_;
275 for my $m (sort keys %Maintainers) {
276 if ($m =~ /$Maintainer/io || $Maintainers{$m} =~ /$Maintainer/io) {
277 my @modules = get_maintainer_modules($m);
279 @modules = grep { /$Module/io } @modules;
283 for my $module (@modules) {
284 push @files, get_module_files($module);
286 printf "%-15s @files\n", $m;
289 printf "%-15s @modules\n", $m;
291 printf "%-15s $Maintainers{$m}\n", $m;
297 for my $m (sort { lc $a cmp lc $b } keys %Modules) {
298 if ($m =~ /$Module/io) {
300 my @files = get_module_files($m);
301 printf "%-15s @files\n", $m;
303 printf "%-15s %-12s %s\n", $m, $Modules{$m}{MAINTAINER}, $Modules{$m}{UPSTREAM}||'unknown';
307 } elsif ($Check or $Checkmani) {
311 ? sub { -f $_ and exists $MANIFEST{$File::Find::name} }
312 : sub { /\.(?:[chty]|p[lm]|xs)\z/msx },
316 duplicated_maintainers();
319 my $ModuleByFile = files_to_modules(@Files);
320 for my $file (@Files) {
321 if (defined $ModuleByFile->{$file}) {
322 my $module = $ModuleByFile->{$file};
323 my $maintainer = $Modules{$ModuleByFile->{$file}}{MAINTAINER};
324 my $upstream = $Modules{$module}{UPSTREAM}||'unknown';
325 printf "%-15s [%-7s] $module $maintainer $Maintainers{$maintainer}\n", $file, $upstream;
327 printf "%-15s ?\n", $file;
332 print STDERR "(No files are modified)\n";
341 sub maintainers_files {
343 for my $k (keys %Modules) {
344 for my $f (get_module_files($k)) {
350 sub duplicated_maintainers {
352 for my $f (keys %files) {
354 if ($files{$f} > 1) {
355 print "not ok ".++$TestCounter." - File $f appears $files{$f} times in Maintainers.pl\n";
357 print "ok ".++$TestCounter." - File $f appears $files{$f} times in Maintainers.pl\n";
360 if ($files{$f} > 1) {
361 warn "File $f appears $files{$f} times in Maintainers.pl\n";
367 sub warn_maintainer {
371 print "ok ".++$TestCounter." - $name has a maintainer\n";
373 print "not ok ".++$TestCounter." - $name has NO maintainer\n";
378 warn "File $name has no maintainer\n" if not $files{$name};
382 sub missing_maintainers {
383 my($check, @path) = @_;
387 if( -d $d ) { push @dir, $d } else { warn_maintainer($d) }
389 find sub { warn_maintainer($File::Find::name) if $check->() }, @dir if @dir;
392 sub finish_tap_output {
393 print "1..".$TestCounter."\n";