1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
|
package VCP::Utils::cvs ;
=head1 NAME
VCP::Utils::cvs - utilities for dealing with the cvs command
=head1 SYNOPSIS
use VCP::Utils::cvs ;
=head1 DESCRIPTION
A mix-in class providing methods shared by VCP::Source::cvs and VCP::Dest::cvs,
mostly wrappers for calling the cvs command.
=for test_scripts t/90revml2cvs.t t/91cvs2revml.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 );
use VCP::Logger qw( pr );
use File::Spec ;
use File::Temp qw( mktemp ) ;
use POSIX ":sys_wait_h" ;
=head1 METHODS
=over
=item cvs
Calls the cvs command with the appropriate cvsroot option.
=cut
sub cvs {
my $self = shift ;
my $cvs_command = "";
if ( profiling ) {
profile_group ref( $self ) . " cvs ";
for( @{$_[0]} ) {
unless ( /^-/ ) {
$cvs_command = $_;
last;
}
}
}
local $VCP::Debug::profile_category = ref( $self ) . " cvs $cvs_command"
if profiling;
my @args = @{shift()} ;
unshift @args, "-d" . $self->cvsroot
if defined $self->repo_server;
return $self->run_safely( [ qw( cvs -Q -z9 ), @args ], @_ ) ;
}
=item parse_cvs_repo_spec
This handles ":pserver:"-like type repository specs specially, defaulting to
normal processing if the scheme is not followed by something like "foo". The
username and password are parsed out of the spec
If the first colon is followed by a colon, like
cvs::pserver:user@server/foo:bar
, then the special processing kicks in and the spec is parsed accordingly.
Everything up to and including the first colon and starting with the last colon
are stripped, just like with L<normal specs|VCP::Plugin/parse_repo_spec>, and
the remainder becomes the CVSROOT. This does have the side effect of
plaintexting the password in various CVS places (like the local CVS directories
and the command lines that VCP forks to launch CVS). Let me know if you need
this changed.
=cut
sub parse_cvs_repo_spec {
my $self = shift;
my ( $spec ) = @_;
unless ( $spec =~ /\A\w+::/ ) {
$self->parse_repo_spec( @_ ) unless $spec =~ /\A\w+::/;
}
else {
my ( $scheme, $cvs_root, $filespec ) = ( $spec =~ /\A([^:]*):(.*):([^:]*)\z/ )
or die "Can't parse CVS remote file spec '$spec'\n";
$self->repo_scheme( $scheme );
$self->repo_server( $cvs_root );
$self->repo_filespec( $filespec );
}
my $filespec = $self->repo_filespec;
$filespec =~ s(/{2,})(/)g;
$filespec =~ s(\\{2,})(\\)g;
$self->repo_filespec( $filespec );
debug "parsed '$spec' as",
" scheme=", $self->repo_scheme,
" server=", $self->repo_server,
" filespec=", $self->repo_filespec
if debugging;
die "parse_cvs_repo_spec does not return a result" if defined wantarray;
}
=item cvsroot
Returns the specced cvsroot if set, or $ENV{CVSROOT} if not.
While $ENV{CVSROOT} must be an absolute path if it's local (to be
completely consistent with the cvs command), the path repo_server value,
if set, may be relative (unless it begins with a ':', which indicates a
non-local path).
=cut
sub cvsroot {
my $self = shift;
my $root = $self->repo_server;
( ! empty $root )
? substr( $root, 0, 1 ) eq ":"
? $root ## Remote repo
: do { ## local repo.
$root = start_dir_rel2abs $root;
## The slashes need to be left-leaning or CVS misinterprets
## "-d C:/foo/bar" as an rsh CVSROOT spec
$root =~ s{/}{\\}g if is_win32;
$root;
}
: $ENV{CVSROOT};
}
=item create_cvs_workspace
$self->create_cvs_workspace;
$self->create_cvs_workspace( create_in_repository => 1 );
Creates a temp dir named "co" for C<cvs> to work in, checks out the module
there, and sets the work root and cvs working dir to that directory.
=cut
sub create_cvs_workspace {
my $self = shift ;
my %options = @_;
## establish_workspace in a directory named "co" for "checkout". This is
## so that VCP::Source::cvs 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.
$self->command_chdir( $self->tmp_dir( "co" ) ) ;
my $module = $self->repo_filespec;
die "vcp: empty cvs module spec\n"
if empty $module ;
$module =~ s{[\\/]+[^\\/]*(?:\.\.\.|[*\\?[].*)}{};
## if the server contains a user name we must log in
if ( ( $self->repo_server || "" ) =~ /^:([^:]+):([^:]*)(?::([^:]*))?\@/ &&
$1 ne 'ext' ) {
my ( $username, $password ) = $2;
pr "\$ cvs login # for '$username'";
pr "ignoring provided cvs password; cvs' password prompt is unscriptable"
unless empty( $password );
$self->cvs( ["login"], undef, undef, \*STDERR );
}
my @expect_cannot_find_module = (
stderr_filter => qr/cvs checkout: cannot find module .*\n/,
ok_result_codes => [0,1],
);
pr "\$ cvs ... checkout $module # establish local CVS workspace";
$self->cvs(
[ "checkout", $module ],
{
$options{create_in_repository}
? @expect_cannot_find_module
: ()
}
) ;
if ( $self->command_result_code == 1 ) {
pr "cvs checkout failed. Missing module?";
pr "\$ cvs import ... $module # create module in repository";
my $empty_dir = $self->tmp_dir( "empty_dir" );
$self->mkdir( $empty_dir );
$self->cvs(
[
"import",
"-m",
"VCP destination module creation",
$module,
"vcp",
"start"
]
);
pr "\$ cvs ... checkout $module # establish local CVS workspace";
$self->cvs( [ "checkout", $module ] ) ;
}
$self->work_root( $self->tmp_dir( "co" ) ) ;
}
=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 CVS'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];
}
=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 ;
|