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 | |
---|---|---|---|---|---|
#1 | 6118 | Dimitry Andric | Integ from //public/revml to //guest/dimitry_andric/revml/main. | ||
//guest/perforce_software/revml/lib/VCP/Utils/svn.pm | |||||
#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. |