svn.pm #2

  • //
  • guest/
  • perforce_software/
  • revml/
  • lib/
  • VCP/
  • Utils/
  • svn.pm
  • View
  • Commits
  • Open Download .zip Download (8 KB)
package VCP::Utils::svn ;

=head1 NAME

VCP::Utils::svn - utilities for dealing with the svn command

=head1 SYNOPSIS

   use VCP::Utils::svn ;

=head1 DESCRIPTION

A mix-in class providing methods shared by VCP::Source::svn and VCP::Dest::svn,
mostly wrappers for calling the svn command.

=for test_scripts t/90revml2svn.t t/91svn2revml.t

=cut

@EXPORT_OK = qw( RCS_check_tag RCS_underscorify_tag );
@ISA = qw( Exporter );
use Exporter;

use strict ;

use Carp ;
use VCP::Debug qw( :debug :profile ) ;
use VCP::Utils qw( empty start_dir_rel2abs is_win32 shell_quote );
use VCP::Logger qw( BUG pr lg );
use File::Spec ;
use File::Temp qw( mktemp ) ;
use POSIX ":sys_wait_h" ;

=head1 METHODS

=over

=item svn

Calls the svn command with the appropriate svnroot option.

=cut

sub svn {
   my $self = shift ;

   my $svn_command = "";
   if ( profiling ) {
      profile_group ref( $self ) . " svn ";
      for( @{$_[0]} ) {
         unless ( /^-/ ) {
            $svn_command = $_;
            last;
         }
      }
   }
   local $VCP::Debug::profile_category = ref( $self ) . " svn $svn_command"
      if profiling;


   my @args;

   my @in_args = @{shift()};

   my $is_not_interactive = 
       0+grep 0 <= index( "|add|help|--version|", "|$_|" ), @in_args;

   unless ( $is_not_interactive ) {
      push @args, "--non-interactive"
         unless $is_not_interactive;

      push @args, "--username", $self->repo_user
         unless empty $self->repo_user;

      push @args, "--password", $self->repo_password
         unless empty $self->repo_password;
   }

   return $self->run_safely( [ "svn", @args, @in_args ], @_ ) ;
}

=item svnadmin

Calls the svnadmin command with the appropriate svnroot option.

=cut

sub svnadmin {
   my $self = shift ;

   my @args = @{shift()} ;

   return $self->run_safely( [ qw( svnadmin ), @args ], @_ ) ;
}

=item parse_svn_repo_spec

This handles the SVN repo spec, which always has a second colon
after the URI scheme: svn:file:///foo/bar:/path/to/module

=cut

sub parse_svn_repo_spec {
   my $self = shift;

   my ( $spec ) = @_;

   my $uri_scheme;
   $uri_scheme = $2
      if $spec =~ s{\A(.*?:(?:[^:]*(?::[^:]*)?\@)?)((?:file|svn|http)[^:]*:)([^:]*):?}{$1$3:};
   $self->parse_repo_spec( $spec );

   $self->repo_server( $uri_scheme . $self->repo_server )
       if defined $uri_scheme;

   debug "parsed '$spec' as",
      " scheme=", $self->repo_scheme,
      " server=", $self->repo_server,
      " filespec=", $self->repo_filespec
      if debugging;

   die "parse_svn_repo_spec does not return a result" if defined wantarray;
}


=item create_svn_workspace

    $self->create_svn_workspace;
    $self->create_svn_workspace( create_in_repository => 1 );

Creates a temp dir named "co" for C<svn> to work in, checks out the module
there, and sets the work root and svn working dir to that directory.

=cut

sub create_svn_workspace {
   my $self = shift ;
   my %options = @_;

   ## establish_workspace in a directory named "co" for "checkout". This is
   ## so that VCP::Source::svn can use a different directory to contain
   ## the revs, since all the revs need to be kept around until the VCP::Dest
   ## is through with them.
   my $module = $self->repo_filespec;
   die "vcp: empty svn module spec\n"
      if empty $module ;
   $module =~ s{[\\/]+[^\\/]*(?:\.\.\.|[*\\?[].*)}{};

   my @expect_cannot_find_module = (
      stderr_filter => qr/svn: URL .* doesn't exist.*\n/,
      ok_result_codes => [0,1],
   );

   my $source_uri = $self->repo_server . $module;

   ## TODO: use the oldest revision here?

   my $info = $self->get_svn_path_info( $source_uri, "HEAD" );
   $self->{SVN_URL_IS_FILE} = ( $info->{kind} || "" ) eq "file";

   $source_uri =~ s{[\\/][^\\/]*\z}{}g if $self->{SVN_URL_IS_FILE};

   my $dest_dir = $self->tmp_dir( "co" );

   pr "\$ svn ... checkout $source_uri $dest_dir # establish local svn workspace";
   $self->svn(
      [ "checkout", $source_uri, $dest_dir ],
      {
         $options{create_in_repository}
            ? @expect_cannot_find_module
            : ()
      }
   ) ;

   ## Create the directory and any intermediate directories.  First
   ## figure out which already exist, then create all the remaining
   ## ones in a single commit.

   if ( $self->command_result_code == 1 ) {
      pr "$source_uri not found.";
      my @dirs = File::Spec->splitdir( $module );
      shift @dirs while @dirs && !length $dirs[0];
      pop   @dirs while @dirs && !length $dirs[-1];
      my @paths_to_create;
      my $base_uri = $self->repo_server;
      while ( @dirs ) {
         my $dir = $dirs[0];
         my $abs_dir = "$base_uri/$dir";
         $self->svn( [ "ls", $abs_dir ], \undef, \my $stdout, \my $stderr, {
            ok_result_codes => [0,1],
         } );
         if ( $self->command_result_code != 0 ) {
            last;
         }
         shift @dirs;
      }

      BUG "No directories to create" unless @dirs;
      my $prev = $base_uri;
      $prev = $_ = "$prev/$_" for @dirs;

      pr "\$ svn ... mkdir $source_uri   # create dest dir in repository";
      $self->svn(
         [
            "mkdir",
            "-m",
            "VCP destination directory creation",
            @dirs
         ]
      );

      pr "\$ svn ... checkout $source_uri, $dest_dir # establish local svn workspace";
      $self->svn( [ "checkout", $source_uri, $dest_dir ] ) ;
   }

   $self->work_root( $self->tmp_dir( "co" ) ) ;
   $self->command_chdir( $self->work_root );
}


=item RCS_check_tag

    RCS_check_tag $tag1, ...;

Checks a list of tags for legality, die()s if it's not legal.  Named after the
corresponding routine in svn's rcs.c source file.

No clue how this interacts with your locale.

=cut

sub RCS_check_tag {
   my @errors;
   for ( @_ ) {
      if ( /\A[^a-zA-Z]/ ) {
         push @errors, "RCS tag '$_' must start with a letter\n";
      }
      elsif ( /([[:^graph:]])/ ) {
         push @errors,
            sprintf "RCS tag '%s' must not contain \\0x%02x\n", $_, ord $1;
      }
      elsif ( /(["\$,.:;\@])/ ) {
         push @errors, "RCS tag '$_' must not contain '$1'\n"
      }
   }

   die @errors if @errors;
}


=item RCS_underscorify_tag

    @tags = RCS_check_tag $tag1, ...;

Modifies a list of tags, replacing illegal characters with
underscores.  This may lead to tag collisions, but it should be ok
for most uses.

Converts something like "a@" to "a_AF_".  Not a guaranteed solution,
but good enough for now.

=cut

sub RCS_underscorify_tag {
   my @out = @_;
   for ( @out ) {
      s/(["\$,.:;\@[:^graph:]])/sprintf( "_%02x_", ord $1 )/ge;
      s/\A([^a-zA-Z])/tag_$1/;
   }

   wantarray ? @out : @out > 1 ? Carp::confess "Returning multiple tags in scalar context" : $out[0];
}


=item get_svn_path_info

Runs svn info for a particular revision.

=cut

sub get_svn_path_info {
   my $self = shift ;
   my ( $url, $revision, $recursive ) = @_;

   local $self->{_SVN_INFO_RECURSIVE} = $recursive;

   my @cmd = ( "svn", "info",
      $recursive ? "-R" : (),
      "--xml",
      "$url\@$revision"
   );
   my $cmd = join " ", shell_quote @cmd;

   local $self->{_SNV_INFO};

   my $p = XML::Parser->new(
      Handlers => {
         Start => sub {
	    my $expat = shift ;
	    my $tag = shift ;

            my $meth = "start_info_$tag";
            $self->$meth( @_ ) if $self->can( $meth );
	 },

	 End => sub {
	    my $expat = shift ;
	    my $tag = shift ;

            $self->{SVN_INFO_TEXT} = undef;

            my $meth = "end_info_$tag";
            $self->$meth( @_ ) if $self->can( $meth );
	 },

	 Char => sub {
	    my $expat = shift ;
            ${$self->{SVN_INFO_TEXT}} .= shift if $self->{SVN_INFO_TEXT};
             
         }
      },
   ) ;
   lg "\$ $cmd";
   $p->parsefile( "$cmd |" );

   ## Get the hash out of _SVN_INFO do the local() above doesn't rewrite it
   ## upon return.
   my $s = $self->{_SVN_INFO};
   $self->{_SVN_INFO} = undef;
   return $s;
}


sub start_info_entry {
   my $self = shift;
   my %attrs = @_;
   $self->{SVN_INFO_ENTRY} = \%attrs;
}


sub end_info_entry {
   my $self = shift;
   my %attrs = @_;

   my $e = $self->{SVN_INFO_ENTRY};
   $self->{SVN_INFO_ENTRY} = undef;

   return unless $e->{kind} eq "file";

   $e->{path} =~ s{\\+}{/}g;
   $e->{path} =~ s{^/+}{};
   if ( $self->{_SVN_INFO_RECURSIVE} ) {
      $self->{_SVN_INFO}->{$e->{path}} = $e;
   }
   else {
      $self->{_SVN_INFO} = $e;
   }
}


=back

=head1 COPYRIGHT

Copyright 2000, Perforce Software, Inc.  All Rights Reserved.

This module and the VCP package are licensed according to the terms given in
the file LICENSE accompanying this distribution, a copy of which is included in
L<vcp>.

=cut

1 ;
# Change User Description Committed
#2 5404 Barrie Slaymaker - SVN support added
- Makefile gives clearer notices about missing optional
  prereqs.
- VCP::Filter::labelmap and VCP::Filter::map: <<skip>> replaces
  deprecated <<delete>> to be clearer that no revisions
  are deleted from either repository but some just are
  skipped and not inserted.
- VCP::Filter::map: support added for SVN-like branch labels
- VCP::Source: support added for ISO8601 timestamps
  emitted by SVN.
#1 5343 Barrie Slaymaker - cvs branched to svn (non functional)
//guest/perforce_software/revml/lib/VCP/Utils/cvs.pm
#31 5081 Barrie Slaymaker - VCP::Utils::cvs only tries cvs login if :pserver: and password
  field is missing.
#30 5080 Barrie Slaymaker - Don't try to log in to cvs when using :ext: in CVSROOT
#29 4227 Barrie Slaymaker - VCP::Dest::cvs now handles a module name with no trailing "/..."
  (reported by Alexandros Karypidis karypid inf uth gr).
- VCP::Dest::cvs now handles a missing filespec (module name) if
  the source repository passed along a rev_root
#28 4209 Barrie Slaymaker - CVS workspace creation announces cvs subcommands consistently with
         other parts of VCP
#27 4207 Barrie Slaymaker - The cvs login command is now run with STDERR unredirected to
         allow the user to log in.
       - The cvs workspace setup process prints what it's doing before
         running possibly long-lived cvs commands
#26 4151 Barrie Slaymaker - dist/vcp.exe now works again
#25 4021 Barrie Slaymaker - Remove all phashes and all base & fields pragmas
- Work around SWASHGET error
#24 3970 Barrie Slaymaker - VCP::Source handles rev queing, uses disk to reduce RAM
- Lots of other fixes
#23 3571 Barrie Slaymaker - Get working with cvs-11.5 on Win2k
#22 3532 John Fetkovich changed File::Spec->rel2abs( blah, start_dir )
to      start_dir_rel2abs blah
everywhere.

which
   does the same thing
   and is defined in VCP::Utils
#21 3384 John Fetkovich moved setting of default repo_id
#20 3285 John Fetkovich In 'sub new' constructor, Only call parse_cvs_repo_spec if a $spec is
       provided.  parse_cvs_repo_spec also now sets repo_id.
#19 3167 Barrie Slaymaker Add profiling report that details various chunks of time
       taken.
#18 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.
#17 3129 Barrie Slaymaker Stop calling the slow Cwd::cwd so much, use start_dir
       instead.
#16 2933 John Fetkovich Added calls to empty()
#15 2691 Barrie Slaymaker Generalize underscorification of RCS tags a bit to reduce chances
       of a collision (still possible, but should be much lower probability).
#14 2680 Barrie Slaymaker Implemented real CVS tag testing and underscorification based on
       reading CVS's rcs.c source code.
#13 2667 Barrie Slaymaker Convert more to IPC::Run3
#12 2389 John Fetkovich removed calls to methods:
         command_stderr_filter
         command_ok_result_codes
         command_chdir
       and replaced with named Plugin::run_safely method parameters
         stderr_filter
         ok_result_codes
         in_dir
       respectively, where possible.
#11 2293 Barrie Slaymaker Update CHANGES, TODO, improve .vcp files, add --init-cvs
#10 2267 Barrie Slaymaker factor out cvs2revml, test both --use-cvs and direct modes, with times
#9 2240 Barrie Slaymaker Start on cvs -r option support.
#8 2228 Barrie Slaymaker working checkin
#7 2026 Barrie Slaymaker VCP::8::cvs now supoprt branching
#6 2009 Barrie Slaymaker lots of fixes, improve core support for branches and VCP::Source::cvs
       now supports branches.
#5 2006 Barrie Slaymaker more preparations for branching support,
       handling of cvs :foo:... CVSROOT specs,
       misc fixes, improvements
#4 1742 Barrie Slaymaker document VCP::Utils::cvs::create_cvs_workspace()
#3 813 Barrie Slaymaker Fix path math when checking out a module.
#2 723 Barrie Slaymaker VCP::Dest::cvs tuning and cvs and p4 bugfixes
#1 705 Barrie Slaymaker Release 0.22.