vcp #80

  • //
  • guest/
  • perforce_software/
  • revml/
  • bin/
  • vcp
  • View
  • Commits
  • Open Download .zip Download (26 KB)
#!/usr/local/bin/perl -w

=head1 NAME

vcp - Copy versions of files between repositories and/or RevML

=head1 SYNOPSIS

   # interactive mode:

   vcp [vcp_opts]

   # scriptable command line mode:

   vcp [vcp_opts] <source> <dest>

   # getting options from a file:

   vcp vcp:config.vcp

   # help output:

   vcp help
   vcp help [topic]

=head1 DESCRIPTION

C<vcp> ('version copy') copies versions of files from one repository to another,
translating as much metadata as possible along the way.  This allows you to
copy and translate files and their histories between revision storage systems.

Supported source and destination types are C<cvs:>, C<p4:>, and C<revml:>.

=head2 Copying Versions

The general syntax of the vcp command line is:

   vcp [<options>] <source> [<filters>] <dest>

The portions of the command line are:

=over

=item C<E<lt>optionsE<gt>>

Command line options that control the operation of the C<vcp> command, like
C<--debug> for debugging or C<-h> for help.  There are very few global options,
these are covered below. Note that they must come before the
C<E<lt>sourceE<gt>> specification.

=item C<E<lt>sourceE<gt>>

Were to extract versions from, including any command line options needed to
control what is extracted and how.  See the next section.

=item C<E<lt>filtersE<gt>>

How to alter the revisions before putting them in the destination.  Use
the special string "default_filters:" to use the filters that VCP thinks
should be present, like so:

    vcp cvs:<...> default_filters: -- p4:<...>

will copy from a CVS repository to a p4 repository with a default Map
filter. 

To specifify filters manually, use a lowercase version of the filter name
as a "scheme" and follow it with options and a trailing "--", like so:

   vcp: cvs:<...> map: <map arguments> -- p4:<...>

You can add your own filters before or afcter the "default_filters: --"
but do not add any that conflict with the default filters.

=item C<E<lt>destE<gt>>

Where to insert versions, including any command line options needed to control
how files are stored.  See the next section.

=back

=head2 Specifying Repositories

The C<E<lt>sourceE<gt>> and C<E<lt>destE<gt>> specifications specify a
repository and provide any options needed for accessing that repository.

These spefications may be a simple filename for reading or writing RevML
files (if the requisite XML handling modules are installed). or a full
repository specification like C<cvs:/home/cvs/root:module> or
C<p4:user:password@server:port://depot/dir>.

When using the long form to access a repository, C<E<lt>sourceE<gt>> and
C<E<lt>destE<gt>> specification have several fields delimited by C<:>
and C<@>, and may have trailing command line options.  The full (rarely
used) syntax is:

   scheme:user(view):password@repository:filespec [<options>]

where

=over

=item C<scheme:>

The repository type (C<p4:>, C<cvs:>, C<revml:>).

=item C<user>, C<view>, and C<password>

Optional values for authenticating with the repository and identifying which
view to use.  C<cvs> does not use C<view>.  For C<p4>, C<view> is the client
setting (equibalent to setting C<P4CLIENT> or using C<p4>'s C<-c> option).

=item C<repository>

The repository spec, CVSROOT for CVS or P4PORT for p4.

=item C<filespec>

Which versions of what files to move.  As much as possible, this spec is
similar to the native filespecs used by the repository indicated by the scheme.

=item C<E<lt>optionsE<gt>>

Command line options that usually mimic the options provided by the underlying
repositories' command line tools (C<cvs>, C<p4>, etc).

=back

Most of these fields are omitted in practice, only the C<scheme> field is
required, though (in most cases) the C<repository> field is also needed unless
you set the appropriate environment variables (C<CVSROOT>, C<P4PORT>, etc).

The a bit confusing, here are some examples specs:

   cvs:server:/foo
   p4:user@server://depot/foo/...
   p4:user:[email protected]:1666://depot/foo/...

Options and formats for of individual schemes can be found in the relevant
help topics, for instance:

   vcp help source::cvs

Run C<vcp help> for a list of topics.

When reading and writing RevML files, a simple filename will do
(although the long form may also be used).  The special value "-" means
to read/write stdin and stdout when used as a source or destination
name, respectively.  "-" is assumed if a specification is not provided,
so these invocations all accomplish the same thing, reading and writing
RevML:

   vcp
   vcp -
   vcp revml:-
   vcp revml:
   vcp - -
   vcp - revml:-
   vcp - revml:
   vcp revml:- revml:-
   vcp revml: revml:

=head2 C<vcp> Options

All general options to vcp must precede the C<E<lt>sourceE<gt>>.
Scheme-specific options must be placed immediately after the
C<E<lt>sourceE<gt>> or C<E<lt>destE<gt>> spec and before the next one.

=over

=item --debug

Enables logging of debugging information.

=item --help, -h, -?

These are all equivalent to C<vcp help>.

=item --output-config-file=$filename

Write the settings (parsed from the UI, the command line, or a config
file to a file.  Useful for capturing settings or user interface output.
Does not affect running.  Use "-" to emit to STDOUT.

B<NOTE 1>: This does I<not> emit an "Options:" section containing global
options (those listed here).  Almost all of these options are not useful
to emit; we can add an option to force their emission if need be.

B<NOTE 2>: When using the interactive user interface, this option takes
effect after the last interactive portion and, if vcp goes on to run a
conversion, before any conversion is run.  This occurs in addition to
any configuration files the user may ask the interactive interface to
write.  This may change in the future (for instance, if the interactive
dialog includes an option to extract and analyze metadata).  

=item --dont-convert

Do not run a conversion.  Useful when you just want to emit a .vcp file.

=item --versions

Emits the version numbers of bundled files.

=item --terse, -t

Suppress verbose explanations when running the interactive UI.  Has
no effect on operation if all settings are read from the command line
or a .vcp file.

=item --quiet, -q

Suppresses the banner and progress bars.

=back

=head2 Getting help

(See also L<Generating HTML Documentation|/Generating HTML Documentation>,
below).

There is a slightly different command line format for requesting help:

   vcp help [<topic>]

where C<E<lt>topicE<gt>> is the optional name of a topic.  C<vcp help> without
a C<E<lt>>topicC<E<gt>> prints out a list of topics, and C<vcp help vcp>
emits this page.

All help documents are also available as Unix C<man> pages and using the
C<perldoc> command, although the names are slightly different:

   with vcp               via perldoc        
   ================       ===========
   vcp help vcp           perldoc vcp
   vcp help source::cvs   perldoc VCP::Source::cvs
   vcp help source::cvs   perldoc VCP::Dest::p4

C<vcp help> is case insensitive, C<perldoc> and C<man> may or may not be
depending on your filesystem.  The C<man> commands look just like the example
C<perldoc> commands except for the command name.  Both have the advantage that
they use your system's configured pager if possible.

=head2 Environment Variables

The environment is often used to set context for the source and
destination by way of variables like P4USER, P4CLIENT, CVSROOT, etc.

=over

=item VCPDEBUG

Setting the VCPDEBUG environment variable acts just like C<--debug> was
present on the command line:

   VCPDEBUG=1

(see L<--debug> for more info).  This is useful when VCP is embedded in
another application, like a makefile or a test suite.

=back

=for test_scripts t/10vcp.t t/50revml.t

=cut

## THIS ONLY WORKS WHILE IN THE DEVELOPMENT DIRECTORY, IT ASSUMES ALL
## .pm AND .pod FILES ARE IN lib/ AND PAR <= 0.79 DOES NOT ALLOW US TO
## GET AT THESE EASILY
## 
## =head2 Generating HTML Documentation
## 
## All of the help pages in C<vcp> can be built in to an HTML tree with the
## command:
## 
##    vcp html <dest_dir>
## 
## The index file will be C<E<lt>dest_dirE<gt>/index.html>.
## 
## t/50revml.t is used too so that we actually make sure that filenames get
## passed through to the source (at least) properly.
## 

use strict ;
use File::Spec;

BEGIN {
   ## Massage @INC because we do a lot of lazy loading and relative
   ## dirs in @INC give us fits
   for ( @INC ) {
      next if ref;
      next if File::Spec->file_name_is_absolute( $_ );
      $_ = File::Spec->rel2abs( $_ );
   }

   ## Check for options that must take effect ASAP
   $ENV{VCPDEBUG}   ||= grep /\A(--debug)\z/, @ARGV;
   $ENV{VCPPROFILE} ||= grep /\A(--profile)\z/, @ARGV;
   if ( grep /\A(--diestack)\z/, @ARGV ) {
      $SIG{__DIE__} = sub {
         local $SIG{__DIE__} = 'DEFAULT';
         require Carp;
         Carp::confess( @_ );
      };
      @ARGV = grep ! /\A(--diestack)\z/, @ARGV;
   }
}

use VCP::Logger qw( lg pr set_quiet_mode BUG );
use VCP::Debug qw( :debug ) ;
use VCP::Utils qw( empty shell_quote program_name );
use Getopt::Long ;
use VCP ;

my $program_name = program_name;

my $quiet_mode;
my $terse_ui_prompts;   ## set from command line
my $output_config_file; ## Set from a command line.
my $dont_convert;

eval {
   my $dtd_spec ;
   my $arg = "";

   lg shell_quote( $program_name, @ARGV );

   my @plugins;

   ## Parse up to the first non-option, then let sources & dests parse
   ## from there.
   Getopt::Long::Configure( qw( no_auto_abbrev no_bundling no_permute ) ) ;
   parse_cli_options( \@ARGV );

   my $interactive_ui = !@ARGV;  ## will also be set later if 'edit' arg
   my $vcp_filename;  ## Set if we're to edit an existing file

   if ( !$interactive_ui ) {
      ## command line mode

      $arg = $ARGV[0];
#      build_html_tree_and_exit( $program_name, @ARGV[1..$#ARGV] )
      die "HTML is now generated by developers at build time with\nbin/genhtml, sorry\n"
         if $arg eq "html";
      help_and_exit( @ARGV[1..$#ARGV] )
         if $arg eq 'help' ;

      my $vcp_spec;

      if ( $arg eq "scan" || $arg eq "filter" || $arg eq "transfer" ) {
         shift;
      }

      my @errors;

      if ( $arg eq "edit" ) {
         shift;
         $interactive_ui = 1;
         my $spec = shift;
         if ( empty $spec ) {
            push @errors, "edit requires a vcp configuration file name"
         }
         else {
            require VCP::ConfigFileUtils;
            ( $vcp_filename = $spec ) =~ s/^vcp://i;
            $vcp_spec = VCP::ConfigFileUtils::parse_config_file(
               $vcp_filename
            );
         }
      }
      else {
         ## See if it's a config file: look for vcp: or .vcp or even
         ## try sniffing it to see if it looks like one
         my $source_spec = $ARGV[0];

         require VCP::ConfigFileUtils;
         my $is_vcp_file =
            $source_spec =~ s/^vcp://i || $source_spec =~ /\.vcp\z/i;

         my $cant_be_vcp_file = ! $is_vcp_file && (
            $source_spec eq "-"   ## must be a revml file
            || $source_spec =~ /\.revml\z/i
            || $source_spec =~ /^\w{2,}:/ # ignore foo:, sniff C:
            || ! -e $source_spec
            || -S _ < 1_000_000       # That would be toooo big
         );

         $vcp_spec = $is_vcp_file
            ? VCP::ConfigFileUtils::parse_config_file( $source_spec )
            : ! $cant_be_vcp_file
               ? VCP::ConfigFileUtils::parse_config_file(
                  $source_spec,
                  "may not be a config file"
               )   # sniff the file, return FALSE if !vcp file
               : 0;

         if ( $vcp_spec ) {
            $vcp_filename = $source_spec;
            shift @ARGV;
         }

      };

      if ( $vcp_spec ) {
         ## It's a .vcp file parsed in to @$vcp_spec.
         if ( @$vcp_spec && $vcp_spec->[0] eq "options" ) {
            shift @$vcp_spec;
            parse_cli_options( shift @$vcp_spec );
         }

         push @errors, "$vcp_filename does not specify a Source\n"
            unless $arg eq "filter"
               || ( $vcp_spec && @$vcp_spec && $vcp_spec->[0] eq "source" );

         push @errors, "$vcp_filename does not specify a Destination\n"
            unless $arg eq "scan" || $arg eq "filter"
               || ( $vcp_spec && @$vcp_spec && $vcp_spec->[-2] eq "dest" );

         push @errors,
            "command line parameters not allowed when using config file: "
            . join( " ", @ARGV )
            . "\n"
            if @ARGV;

         while ( @$vcp_spec ) {
            my ( $tag, $value ) = ( shift @$vcp_spec, shift @$vcp_spec );

            my $default_scheme;
            my $type;
            my $spec;
            my $parms;
            # Unlike the command line, we know there *must* be a source
            # and a dest in @$vcp_spec.  Anything in between is filters.
            if ( $tag eq "source" || $tag eq "dest" ) {
               $default_scheme = "revml";
               $type = $tag;
               $spec = shift @$value;
            }
            else {
               $default_scheme = $tag;
               $type = "Filter";
               $spec = "";
            }

            print banner()
               if !$quiet_mode
                   && $type eq "dest"
                   && length $spec
                   && $spec ne "-";
               ## Don't emit the banner if we're emitting to STDOUT.

            push @plugins, load_module( $spec, $type, $default_scheme, $value );

            die "extra parameters for $tag: ", shell_quote( @$value ), "\n" 
               if @$value;
         }
      }
      else {
         ## Parse the command line.
         my $type = "Source";
         while ( @ARGV ) {
            my $spec = shift;

            if ( $spec eq "default_filters:" ) {
               if ( shift( @ARGV ) ne "--" ) {
                  die "\"default_filters:\" must be followed only by \"--\"\n";
               }
               push @plugins, $spec;
               next;
            }

            my $default_scheme;
            if ( $type eq "Source" ) {
               $default_scheme = "revml";
            }
            elsif ( $type ne "Dest" && find_filter( $spec ) ) {
               ## It's a filter.
               $type = "Filter";
            }
            else {
               $type           = "Dest";
               $default_scheme = "revml";
            }

           print banner()
               if !$quiet_mode
                   && $type eq "Dest"
                   && length $spec
                   && $spec ne "-";
               ## Don't emit the banner if we're emitting to STDOUT.

            ## We pass \@ARGV to the constructors for source and dest so
            ## that they may parse some of @ARGV and leave the rest.
            ## Actually, that's only important for sources, since the
            ## dests should consume it all anyway.  But, for
            ## consistency's sake, I do the same to both.
            push @plugins, load_module( $spec, $type, $default_scheme, \@ARGV );
            $type = "";

            ## Fake up a destination if none was passed.
            push( @ARGV, "revml:-" ), $type = "Dest"
               if ! @ARGV && ! $plugins[-1]->isa( "VCP::Dest" ) ;
         }

         push @errors, "extra parameters: " . join( ' ', @ARGV ) . "\n"
            if @ARGV;
      }

      if ( @errors ) {
         my $errors = join( '', @errors ) ;
         $errors =~ s/^/$program_name ERROR: /mg ;
         chomp $errors;
         die $errors, "\n" ;
      }

      @plugins = map {
          $_ eq "default_filters:"
              ? load_default_filters( $plugins[0], $plugins[-1] )
              : $_;
      } @plugins;
   }

   if ( $interactive_ui ) {
      ### interactive mode
      print banner() if !$quiet_mode;
      
      require VCP::UI;
      my $ui = VCP::UI->new( 
         defined $terse_ui_prompts ? ( TersePrompts => 1 ) : (),
      );

      print <<END_INTRO unless @plugins || defined $terse_ui_prompts;

VCP: Generate a configuration file

When you call VCP with no arguments, VCP asks a series of questions
and generates a configuration file based on your answers.  You use
the generated file with VCP to convert file revisions and history
from one SCM system (the source repository) to another system (the
destination repository).

To accept the default value, press "Enter".  For yes/no questions
or menu options, enter "y", "n", the menu number, or the text beside
the menu option.  For questions requiring free-form input, an example
value is shown in parentheses, and a default value is shown in square
brackets.

When you have answered the questions, VCP creates a configuration
file.  To convert your repository based on a configuration file,
call VCP with this configuration file, run VCP as follows:

  vcp name-of-configuration-file.vcp

Advanced users can supply information to VCP on the command line,
or use other configuration options.  For more information about
these options, run "vcp help" from the command line after exiting
the interactive interface.

END_INTRO

      my ( $source, $dest, $do_convert );
      ( $source, $dest, $output_config_file, $do_convert ) = $ui->run(
         @plugins
            ? (
               Source   => $plugins[ 0],
               Dest     => $plugins[-1],
               Filename => $vcp_filename,
            )
            : ()
      );

      $dont_convert = 1 unless $do_convert;

      if ( ! @plugins ) {  ## None scanned from existing .vcp file
         push @plugins, $source;

	 push @plugins, load_default_filters( $source, $dest );


         push @plugins, $dest;
      }
      else {
         $plugins[ 0] = $source;
         $plugins[-1] = $dest;
      }

   }

   if ( $arg eq "scan" ) {
      @plugins = (
         $plugins[0],
         load_module( "metadb:source_metadb:", "Dest", undef, [] ),
      );
   }
   elsif ( $arg eq "filter" ) {
      shift @plugins; ## filtering requires no source and no dest
      pop @plugins;
      pr "vcp: no filters to apply\n" unless @plugins;
      @plugins = (
         load_module( "metadb:source_metadb:", "Source", undef, [] ),
         @plugins,
         load_module( "metadb:filtered_metadb:", "Dest", undef, [] ),
      );
   }
   elsif ( $arg eq "transfer" ) {
      @plugins = (
         load_module( "metadb:filtered_metadb:", "Source", undef, [
            $plugins[0],
         ] ),
         $plugins[-1],
      );
   }

   if ( defined $output_config_file ) {
      require VCP::ConfigFileUtils;
      VCP::ConfigFileUtils::write_config_file( $output_config_file, @plugins );
   }

   unless ( $dont_convert ) {
      # init should:
      #   set default values don't make sense in the constructor
      #   do initialization that doesn't make sense in constructor
      #   do cross-checking between fields
      for my $plugin ( @plugins ) {
         $plugin->init;
      }

      my $cp = VCP->new( @plugins );
      $cp->insert_required_sort_filter
         unless $arg eq "scan" || $arg eq "transfer";
      my $header = {} ;
      my $footer = {} ;
      $cp->copy_all( $header, $footer ) ;
   }

   1;

} or do {
   my $x = $@;
   lg $x;
   die $x;
};

###############################################################################
###############################################################################

sub parse_cli_options { 
   local *ARGV = shift @_;
   GetOptions(
      'debug'                => \my $unused_1_see_BEGIN_above,
      'dont-convert'         => \$dont_convert,
      'output-config-file=s' => \$output_config_file,
      'help|h|?:s'  => sub {
         help_and_exit( length $_[1] ? $_[1] : () );
      },
      'profile'              => \my $unused_2_see_BEGIN_above,
      'quiet|q'              => \$quiet_mode,
      'terse|t'              => \$terse_ui_prompts,
      'versions'             => \&versions_and_exit,
   ) or options_and_exit() ;

   set_quiet_mode( $quiet_mode );
   die "--output-config-file requires a filename\n"
      if defined $output_config_file && ! length $output_config_file;
}


sub load_module {
   my ( $spec, $type, $default_scheme, @args ) = @_;

   $type = ucfirst $type;

   my $class = "VCP::$type";

   my ( $scheme, $s ) = $spec =~ /^(\w{2,}):/
      ? ( $1, $spec )
      : defined $default_scheme
         ? ( $default_scheme, "$default_scheme:$spec" )
         : die "vcp: '$spec' has no scheme, try ",
            list_modules( $class ),
            "\n";
   $scheme = lc($scheme);

   my $name = "${class}::$scheme";

   my $filename = $name ;
   $filename =~ s{::}{/}g ;

   my $v = eval "require '$filename.pm';" ;
   die "unknown \L$type\E scheme '$scheme:', try ",
      list_modules( $class ),
      "\n"
      if ! $v && $@ =~ /^Can't locate $filename.pm/ ;
   die $@ unless $v;

   lg "loaded '$name' from '", $INC{"$filename.pm"}, "'";

   # New should: 
   #   construct the object
   #   parse options, if preent     
   #   set some default values
   #   do some initialization       
   my $module = $name->new( $s, @args ) ;

   return $module;
}


{
   my $filters;
   sub find_filter {
      my $spec = shift;

      ## This sequence of code rearranged to not have "lc $1"
      ## produce extremely flaky results on RH8, perl5.8.3

      return '' unless $spec =~ /\A(\w{2,}):/; ## filters *must* have a scheme
      my $scheme = $1;
      $scheme = lc $scheme;
      $filters ||= {
         map { ( $_ => undef ) } scan_modules( "VCP::Filter" )
      };
      return exists $filters->{$scheme};
   }
}


sub load_default_filters {
   my ( $source, $dest ) = @_;

   require VCP::DefaultFilters;
   my $df = VCP::DefaultFilters->new;
   my @filter_args = $df->create_default_filters( $source, $dest );
   my @filters;

   # call load_module for each filter in @filter_args.
   while ( @filter_args ) {
      my $spec = shift @filter_args;

      if ( find_filter( $spec ) ) {
	 push @filters, load_module(
	    $spec, "Filter", undef, \@filter_args
	 );
      }
      else {
	 BUG "attempted to load a non-existent default filter: $spec";
      }
   }
   return @filters;
}


sub inc_dirs {
   ## PAR plays coderef-in-@INC games with @INC.
   ## And even if it didn't, we don't want to scan the same dir multiple
   ## times.
   my %seen_dirs;
   return
   grep
       ref ne "CODE" && !$seen_dirs{$_}++ && -d,
       @INC;
}

## PAR <= 0.79 does not allow us to walk @INC directory trees, so we hard code
## the list of modules in this hash in bin\build_vcp_executable.pl

BEGIN {
   my @bundled_files = grep length, ( "",
       ## The grep is to work around a bug in perl5.8.4 that
       ## causes a "Bizarre copy of ARRAY in leavesub.  This is untested
       ## for now (2004-09-08), but should work.
       ## INSERT BUNDLED FILES LIST HERE ##
   );

   sub bundled_files {
      unless ( @bundled_files ) {
         require File::Spec;
         require File::Find;
         my %seen;
         for ( inc_dirs ) {
            next if ref eq "CODE";
            for my $dir (
                File::Spec->catdir( $_, "VCP" ),
                File::Spec->catdir( $_, "RevML" )
            ) {
               next if ! -d $dir;

               File::Find::find(
                  sub {
                     return unless -f $_ && $_ =~ /\.pm\z/i;
                     no warnings 'once';
                     my $fn = $File::Find::name;
                     $fn =~ s{[\\\/]+}{/}g;
                     $fn =~ s{^.*/(RevML|VCP)/}{$1/}i
                        if File::Spec->file_name_is_absolute( $fn );
                     $fn =~ s{lib/+}{};
                     push @bundled_files, $fn unless $seen{$fn}++;
                  },
                  $dir
               );
            }
         }
      }

      return @bundled_files;
   }
}

sub scan_modules {
   my ( $prefix ) = @_ ;

   my $dirname = $prefix . '::' ;
   $dirname =~ s{(::)+}{/}g ;

   my $l = length $dirname;

   return
      map {
         my $module_name = substr( $_, $l );
         $module_name =~ s/\.pm//i
             ? $module_name
             : ();
      }
      grep substr( $_, 0, $l ) eq $dirname, bundled_files;
}


sub list_modules {
   my ( $prefix ) = @_ ;

   my $list = join ', ', map "$_:", scan_modules( @_ );
   $list =~ s/,([^,]*)$/ or$1/ ;
   return $list ;
}


sub options_and_exit {
   lg @_;
   require VCP::Help;
   print STDERR "\n";
   VCP::Help->error( "vcp usage" );
   exit 1;
}


sub help_and_exit {
   my ( $topic ) = @_;
   require VCP::Help;
   VCP::Help->print( $topic );
   exit;
}

sub banner {
   "vcp v$VCP::VERSION, change number $VCP::CHANGE_ID ($VCP::DATE)\n";
}


sub versions_and_exit {
   for ( bundled_files ) {
      next unless m/\.pm$/i ;
      my $module = $_;
      $module =~ s/\..*//;
      $module =~ s{[\\/]+}{::}g;
      ## Avoid "name used only once" warning
      eval "require $module" or warn $@;
   } ;

   my %vers ;
   my %no_vers ;

   my $recur ;
   $recur = sub {
      my ( $pkg_namespace ) = @_ ;

      no strict "refs" ;

      my $pkg_name = substr( $pkg_namespace, 0, -2 ) ;

      ## The grep means "only bother with namespaces that contain somthing
      ## other than child namespaces.
      if ( ! grep /::/, keys %{$pkg_namespace} ) {
         if ( exists ${$pkg_namespace}{VERSION} ) {
	    $vers{$pkg_name} = ${"${pkg_namespace}VERSION"}
	 }
	 else {
	    $no_vers{$pkg_name} = undef ;
	 }
      }

      my $prefix = $pkg_namespace eq "main::" ? "" : $pkg_namespace ;
      for ( keys %{$pkg_namespace} ) {
	 next unless /::$/ ;
	 next if /^main::/ ;
	 $recur->( "$prefix$_" ) ;
      }
   } ;

   $recur->( "main::" ) ;

   my $max_len = 0 ;
   $max_len = length > $max_len ? length : $max_len for keys %vers ;
      
   print "Package \$VERSIONs:\n" ;
   for ( sort keys %vers ) {
      printf(
         "   %-${max_len}s: %s\n",
	 $_,
	 defined $vers{$_} ? $vers{$_} : "undef"
      ) ;
   }

   print "No \$VERSION found for: ", join( ", ", sort keys %no_vers ), "\n" ;

   $max_len = 0 ;
   $max_len = length > $max_len ? length : $max_len for values %INC ;
   print "\nFile sizes:\n" ;
   for ( sort values %INC ) {
      printf( "   %-${max_len}s: %7d\n", $_, -s $_ ) ;
   }

   print "\nperl -V:\n" ;

   my $v = `$^X -V` ;
   $v =~ s/^/   /gm ;
   print $v ;

   exit ;
}

=head1 SEE ALSO

L<VCP::Process>, L<VCP::Newlines>, L<VCP::Source::p4>, L<VCP::Dest::p4>,
L<VCP::Source::cvs>, L<VCP::Dest::cvs>, L<VCP::Source::revml>,
L<VCP::Dest::revml>, L<VCP::Newlines>.  All are also available using C<vcp
help>.

=head1 AUTHOR

Barrie Slaymaker <[email protected]>

=head1 COPYRIGHT

Copyright (c) 2000, 2001, 2002 Perforce Software, Inc.
All rights reserved.

See L<VCP::License|VCP::License> (C<vcp help license>) for the terms of use.

=cut
# Change User Description Committed
#80 4969 Barrie Slaymaker - Added a special command line macro "default_filters:" added to
  allow easy tests of, for instance, cvs to p4.
#79 4967 Barrie Slaymaker - Removed "-d", leaving only "--debug" so that "-d" is properly passed through
  to plugins (esp. VCP::Source::cvs).
#78 4581 Barrie Slaymaker - user prompts have been improved, but not tested
#77 4498 Barrie Slaymaker Work around odd bug on RH8, perl5.8.3
#76 4493 Barrie Slaymaker - Attempted to work around perl5.8.4 Bizarre copy of ARRAY bug
#75 4482 Barrie Slaymaker - spurious banner quieted
#74 4404 Barrie Slaymaker - Added -q option (mostly to suppress banner & progress bars for test suite)
#73 4236 Barrie Slaymaker - banner not emitted when writing revml to STDOUT
#72 4232 Barrie Slaymaker - bin/vcp now prints a version info banner
#71 4154 Barrie Slaymaker - dist/vcp.exe passes almost all tests
#70 4150 Barrie Slaymaker - bin/vcp can now be tested away from its lib/ dir
#69 4148 Barrie Slaymaker - scan_modules() returns a list, dummy
#68 4143 Barrie Slaymaker - Further adaptation to vcp.exe packaging format
#67 4141 Barrie Slaymaker - Adapt online help and html generation to vcp.exe environment
#66 4065 Barrie Slaymaker - bin/vcp no longer whinges about undefined values when running the
  interactive UI.
#65 3855 Barrie Slaymaker - vcp scan, filter, transfer basically functional
    - Need more work in re: storage format, etc, but functional
#64 3779 Barrie Slaymaker - bin/vcp has nascent support for 3 stage transfers:
    - scan the source
    - filter the metadata (modify or prune)
    - transfer the filtered results
    - PRELIMINARY
#63 3725 Barrie Slaymaker - Minor spelling correction
#62 3697 Barrie Slaymaker - Write out new source if editing .vcp file changed source
#61 3676 Barrie Slaymaker - The user interface should no longer append a second Dest:
  section.
#60 3672 Barrie Slaymaker - vcp emits more meaningful errors when fed a 0 length .vcp file
#59 3666 Barrie Slaymaker - vcp can now edit existing .vcp files, for VSS sources and revml dests
#58 3663 Barrie Slaymaker - Yes/no questions may be answered 'y' or 'n' (case insensitive)
- Multiple choice questions may be answered by typing in full
  text of an answer (case insensitive)
#57 3649 Barrie Slaymaker - Stray DB::single = 1s removed
#56 3644 Barrie Slaymaker - Add Save & Run options to end of UI
#55 3640 Barrie Slaymaker - xmllint no longer require to build UI
- UI now offers multiple choices where appropriate
#54 3636 Barrie Slaymaker - --terse option description improved
#53 3567 John Fetkovich - added the field UIManager in VCP::UI::Text.pm
- added the fields UIImplementation and TersePrompts in UI.pm
- removed Source and Dest fields in VCP::UI.pm
- UI.pm now returns the result of running the UI implementation.
- VCP::UI::Text->run return a list of (source, dest)
  all future UI implementations must do the same.
- bin/vcp gets (source, dest) list from VCP::UI->run.
- added --terse (or -t) command line option to vcp
  to remove verbose help from interactive UI.
#52 3548 John Fetkovich Load default filters when vcp interactive ui is run.
#51 3495 John Fetkovich improved help message at start of interactive vcp
#50 3488 John Fetkovich move init of each plugin after writing config file
#49 3487 Barrie Slaymaker - Add --dont-convert option to bin/vcp
#48 3486 John Fetkovich moved (source or dest)->init calls to bin/vcp
#47 3481 John Fetkovich intro text moved out of state machine to bin/vcp.
       no longer requires user interaction to move on.
#46 3479 John Fetkovich doc fix
#45 3468 Barrie Slaymaker - get --output-config-file working with ui (if ui completes,
  which may not happen if the user enters invalid input).
- Note various UI issue in TODO.
#44 3466 Barrie Slaymaker - --output-config-file now usable with vcp:- specification
- --output-config-file now tested
- VCP::{Source,Dest}::null now sets a repo_scheme so they
  may be emitted to config files.
- Dest: is now emitted right after Source: when there are filters
  to dump.
#43 3464 Barrie Slaymaker - Create VCP::ConfigFileUtils and move bin/vcp::parse_config_file
  in to it.
- Add VCP::ConfigFileUtils::write_config_file()
- Add --output-vcp-file to bin/vcp.
- Add VCP::Driver::repo_spec_as_string()
- Add VCP::Driver::config_file_section_as_string()
- VCP::Driver::parse_repo_spec() now clears any settings that
  are not set by a given spec string (so old values don't
  remain after a call to it).
#42 3441 Barrie Slaymaker - Add some debugging code
#41 3406 Barrie Slaymaker - bin/vcp tries harder to keep backslashes for Win32 compat
#40 3364 John Fetkovich clean up code flow in options parsing/interactive-ui calling section
#39 3363 John Fetkovich cleanup code flow, remove some debugging statements
#38 3362 John Fetkovich revml source and dest now works through interactive UI
#37 3271 John Fetkovich Added stub init() in VCP/Plugin.pm,
       and call to it in load_module in bin/vcp.
       This is in preparation for Sources and Dests
       to have parts of their current constructors split
       out into init() functions which will facilitate
       setting of fields from the interative ui code.
#36 3244 Barrie Slaymaker Integrate VCP::UI with bin/vcp.
       Type 'vcp' to run the UI.
#35 3167 Barrie Slaymaker Add profiling report that details various chunks of time
       taken.
#34 3155 Barrie Slaymaker Convert to logging using VCP::Logger to reduce stdout/err spew.
       Simplify & speed up debugging quite a bit.
       Provide more verbose information in logs.
       Print to STDERR progress reports to keep users from wondering
       what's going on.
       Breaks test; halfway through upgrading run3() to an inline
       function for speed and for VCP specific features.
#33 3018 Barrie Slaymaker fix command line parsing: options were not being cleansed
       from @ARGV properly.
#32 2844 John Fetkovich Made VCP help aware of PAGER environment variable.
#31 2711 Barrie Slaymaker remove debugging statement, fix spurios error message
#30 2708 Barrie Slaymaker Add support for Options: in .vcp files, improve testing of CLI
#29 2705 Barrie Slaymaker Clean up comments
#28 2688 Barrie Slaymaker Add back $ENV{VCPDEBUG} and augment with turning on
       debugging for IPC::Run3.
#27 2639 John Fetkovich Added profiling to be made active when VCPPROFILE environment
       variable turned on.  writes profile info to filename defined
       in VCPPROFILE.  Put some profiling statements (activated at
       compile time) in vcp and p4.pm.
#26 2453 John Fetkovich removed compilation of revml.
 will be making that a separate executable.
#25 2354 Barrie Slaymaker favor .pod files over .pm files in the vcp help system.
#24 2307 Barrie Slaymaker get VCP::Filter::map working, update docs
#23 2306 Barrie Slaymaker tweak error text
#22 2304 Barrie Slaymaker VCP::Filter::map docs and infrastructure
#21 2302 Barrie Slaymaker test, debug filter chain CLI parsing
#20 2301 Barrie Slaymaker A chain of plugins instead of source & dest
#19 2297 Barrie Slaymaker Adapt cvs, revml to a more realistic branching structure
#18 2293 Barrie Slaymaker Update CHANGES, TODO, improve .vcp files, add --init-cvs
#17 2282 Barrie Slaymaker Add in support for *.vcp files
#16 2014 Barrie Slaymaker Give helpful error messages if the vcp command can't read/write
       RevML due to a missing required module.
#15 2013 Barrie Slaymaker Reenable vcp as a unixy command line filter processing revml
#14 1759 Barrie Slaymaker Don't try to keep parsing cmd line after unknown scheme
#13 1506 Barrie Slaymaker tweaks
#12 1367 Barrie Slaymaker lots of docco updates
#11 1359 Barrie Slaymaker Revamp the help system, clean up and add POD
#10 703 Barrie Slaymaker VCP::Source::p4 now uses VCP::Utils::p4::parse_p4_repo_spec()
#9 692 Barrie Slaymaker Add VCP::Utils::p4 and use it to get VCP::Dest::p4 to create it's
own client view as needed.
#8 688 Barrie Slaymaker Fixed docos for --debug.
#7 628 Barrie Slaymaker Cleaned up POD in bin/vcp, added BSD-style license.
#6 627 Barrie Slaymaker Beef up CVS log file parsing.
#5 624 Barrie Slaymaker Add a space to bin/vcp SYNOPSIS after the cvs -r option.
#4 613 Barrie Slaymaker Tweak README and documentation.
#3 480 Barrie Slaymaker 0.06 Wed Dec 20 23:19:15 EST 2000
   - bin/vcp: Added --versions, which loads all modules and checks them
     for a $VERSION and print the results out.  This should help with
     diagnosing out-of-sync modules.
   - Added $VERSION vars to a few modules :-).  Forgot to increment any
     $VERSION strings.
   - VCP::Dest::cvs: The directory "deeply" was not being `cvs add`ed on
     paths like "a/deeply/nested/file", assuming "deeply" had no files
     in it.
   - VCP::Dest::revml: fixed a bug that was causing files with a lot of
     linefeeds to be emitted in base64 instead of deltaed.  This means
     most text files.
   - Various minor cleanups of diagnostics and error messages, including
     exposing "Can't locate Foo.pm" when a VCP::Source or VCP::Dest
     module depends on a module that's not installed, as reported by
     Jeff Anton.
#2 468 Barrie Slaymaker - VCP::Dest::p4 now does change number aggregation based on the
  comment field changing or whenever a new revision of a file with
  unsubmitted changes shows up on the input stream.  Since revisions of
  files are normally sorted in time order, this should work in a number
  of cases.  I'm sure we'll need to generalize it, perhaps with a time
  thresholding function.
- t/90cvs.t now tests cvs->p4 replication.
- VCP::Dest::p4 now doesn't try to `p4 submit` when no changes are
  pending.
- VCP::Rev now prevents the same label from being applied twice to
  a revision.  This was occuring because the "r_1"-style label that
  gets added to a target revision by VCP::Dest::p4 could duplicate
  a label "r_1" that happened to already be on a revision.
- Added t/00rev.t, the beginnings of a test suite for VCP::Rev.
- Tweaked bin/gentrevml to comment revisions with their change number
  instead of using a unique comment for every revision for non-p4
  t/test-*-in-0.revml files.  This was necessary to test cvs->p4
  functionality.
#1 467 Barrie Slaymaker Version 0.01, initial checkin in perforce public depot.