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. | ||