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($_))
90 sub get_module_files {
92 return map { expand_glob($_) } get_module_pat($m);
96 sub get_maintainer_modules {
98 sort { lc $a cmp lc $b }
99 grep { $Modules{$_}{MAINTAINER} eq $m }
106 --maintainer M | --module M [--files]
107 List modules or maintainers matching the pattern M.
108 With --files, list all the files associated with them
110 --check | --checkmani [commit | file ... | dir ... ]
111 Check consistency of Maintainers.pl
112 with a file checks if it has a maintainer
113 with a dir checks all files have a maintainer
114 with a commit checks files modified by that commit
115 no arg checks for multiple maintainers
116 --checkmani is like --check, but only reports on unclaimed
117 files if they are in MANIFEST
120 List the module ownership of modified or the listed files
123 Show results as valid TAP output. Currently only compatible
124 with --check, --checkmani
126 Matching is case-ignoring regexp, author matching is both by
127 the short id and by the full name and email. A "module" may
128 not be just a module, it may be a file or files or a subdirectory.
129 The options may be abbreviated to their unique prefixes
143 sub process_options {
147 'maintainer=s' => \$Maintainer,
148 'module=s' => \$Module,
151 'checkmani' => \$Checkmani,
152 'opened' => \$Opened,
153 'tap-output' => \$TapOutput,
160 chomp (@Files = `git ls-files -m --full-name`);
162 } elsif (@ARGV == 1 &&
163 $ARGV[0] =~ /^(?:HEAD|[0-9a-f]{4,40})(?:~\d+)?\^*$/) {
164 my $command = "git diff --name-only $ARGV[0]^ $ARGV[0]";
165 chomp (@Files = `$command`);
166 die "'$command' failed: $?" if $?;
171 usage() if @Files && ($Maintainer || $Module || $Files);
173 for my $mean ($Maintainer, $Module) {
174 warn "$0: Did you mean '$0 $mean'?\n"
175 if $mean && -e $mean && $mean ne '.' && !$Files;
178 warn "$0: Did you mean '$0 -mo $Maintainer'?\n"
179 if defined $Maintainer && exists $Modules{$Maintainer};
181 warn "$0: Did you mean '$0 -ma $Module'?\n"
182 if defined $Module && exists $Maintainers{$Module};
184 return ($Maintainer, $Module, $Files, @Files);
187 sub files_to_modules {
191 for (@Files) { s:^\./:: }
193 @ModuleByFile{@Files} = ();
195 # First try fast match.
198 for my $module (keys %Modules) {
199 for my $pat (get_module_pat($module)) {
200 $ModuleByPat{$pat} = $module;
205 for my $pat (keys %ModuleByPat) {
207 $ExpModuleByPat{$pat} = $ModuleByPat{$pat};
209 for my $exp (glob($pat)) {
210 $ExpModuleByPat{$exp} = $ModuleByPat{$pat};
214 %ModuleByPat = %ExpModuleByPat;
215 for my $file (@Files) {
216 $ModuleByFile{$file} = $ModuleByPat{$file}
217 if exists $ModuleByPat{$file};
220 # If still unresolved files...
221 if (my @ToDo = grep { !defined $ModuleByFile{$_} } keys %ModuleByFile) {
223 # Cannot match what isn't there.
224 @ToDo = grep { -e $_ } @ToDo;
227 # Try prefix matching.
229 # Need to try longst prefixes first, else lib/CPAN may match
230 # lib/CPANPLUS/... and similar
232 my @OrderedModuleByPat
233 = sort {length $b <=> length $a} keys %ModuleByPat;
235 # Remove trailing slashes.
236 for (@ToDo) { s|/$|| }
241 for my $pat (@OrderedModuleByPat) {
242 last unless keys %ToDo;
245 for my $file (keys %ToDo) {
246 if ($file =~ m|^$pat|i) {
247 $ModuleByFile{$file} = $ModuleByPat{$pat};
259 my ($Maintainer, $Module, $Files, @Files) = @_;
262 for my $m (sort keys %Maintainers) {
263 if ($m =~ /$Maintainer/io || $Maintainers{$m} =~ /$Maintainer/io) {
264 my @modules = get_maintainer_modules($m);
266 @modules = grep { /$Module/io } @modules;
270 for my $module (@modules) {
271 push @files, get_module_files($module);
273 printf "%-15s @files\n", $m;
276 printf "%-15s @modules\n", $m;
278 printf "%-15s $Maintainers{$m}\n", $m;
284 for my $m (sort { lc $a cmp lc $b } keys %Modules) {
285 if ($m =~ /$Module/io) {
287 my @files = get_module_files($m);
288 printf "%-15s @files\n", $m;
290 printf "%-15s %-12s %s\n", $m, $Modules{$m}{MAINTAINER}, $Modules{$m}{UPSTREAM}||'unknown';
294 } elsif ($Check or $Checkmani) {
298 ? sub { -f $_ and exists $MANIFEST{$File::Find::name} }
299 : sub { /\.(?:[chty]|p[lm]|xs)\z/msx },
303 duplicated_maintainers();
306 my $ModuleByFile = files_to_modules(@Files);
307 for my $file (@Files) {
308 if (defined $ModuleByFile->{$file}) {
309 my $module = $ModuleByFile->{$file};
310 my $maintainer = $Modules{$ModuleByFile->{$file}}{MAINTAINER};
311 my $upstream = $Modules{$module}{UPSTREAM}||'unknown';
312 printf "%-15s [%-7s] $module $maintainer $Maintainers{$maintainer}\n", $file, $upstream;
314 printf "%-15s ?\n", $file;
319 print STDERR "(No files are modified)\n";
328 sub maintainers_files {
330 for my $k (keys %Modules) {
331 for my $f (get_module_files($k)) {
337 sub duplicated_maintainers {
339 for my $f (keys %files) {
341 if ($files{$f} > 1) {
342 print "not ok ".++$TestCounter." - File $f appears $files{$f} times in Maintainers.pl\n";
344 print "ok ".++$TestCounter." - File $f appears $files{$f} times in Maintainers.pl\n";
347 if ($files{$f} > 1) {
348 warn "File $f appears $files{$f} times in Maintainers.pl\n";
354 sub warn_maintainer {
358 print "ok ".++$TestCounter." - $name has a maintainer\n";
360 print "not ok ".++$TestCounter." - $name has NO maintainer\n";
365 warn "File $name has no maintainer\n" if not $files{$name};
369 sub missing_maintainers {
370 my($check, @path) = @_;
374 if( -d $d ) { push @dir, $d } else { warn_maintainer($d) }
376 find sub { warn_maintainer($File::Find::name) if $check->() }, @dir if @dir;
379 sub finish_tap_output {
380 print "1..".$TestCounter."\n";