package VCI::VCS::Cvs; use Moose; use MooseX::Method; extends 'VCI'; use Cwd; use IPC::Cmd; use Scalar::Util qw(tainted); use VCI::Util qw(taint_fail detaint); use VCI::VCS::Cvs::Repository; our $VERSION = '0.5.2'; has 'x_cvsps' => (is => 'ro', isa => 'Str', lazy_build => 1); has 'x_cvs' => (is => 'ro', isa => 'Str', lazy_build => 1); sub BUILD { my $self = shift; taint_fail("The x_cvs argument '$self->{x_cvs}' is tainted") if tainted($self->{x_cvs}); taint_fail("The x_cvsps argument '$self->{x_cvsps}' is tainted") if tainted($self->{x_cvsps}); } sub _build_x_cvsps { my $cmd = IPC::Cmd::can_run('cvsps') || confess('Could not find "cvsps" in your path'); taint_fail("We found '$cmd' for cvsps, but that string is tainted." . ' This probably means $ENV{PATH} is tainted') if tainted($cmd); return $cmd; } sub _build_x_cvs { my $cmd = IPC::Cmd::can_run('cvs') || confess('Could not find "cvs" in your path'); taint_fail("We found '$cmd' for cvs, but that string is tainted." . ' This probably means $ENV{PATH} is tainted') if tainted($cmd); return $cmd; } method 'x_do' => named ( args => { isa => 'ArrayRef', required => 1 }, fromdir => { isa => 'Str', default => '.' }, ) => sub { my ($self, $params) = @_; my $fromdir = $params->{fromdir}; my $args = $params->{args}; my $full_command = $self->x_cvs . ' -f ' . join(' ', @$args); if ($self->debug) { print STDERR "Command: $full_command\n", " From: $fromdir\n"; } my $old_cwd = cwd(); chdir $fromdir || confess("Failed to chdir to $fromdir: $!"); # See http://rt.cpan.org/Ticket/Display.html?id=31738 local $IPC::Cmd::USE_IPC_RUN = 1; my ($success, $errorcode, $all, $stdout, $stderr) = IPC::Cmd::run(command => [$self->x_cvs, '-f', @$args]); # We are forced to trust this directory, and we don't do # anything dangerous with it, only chdir (which we can't do while # it's tainted). detaint($old_cwd); chdir $old_cwd || confess("Failed to chdir back to $old_cwd: $!"); # "cvs diff" returns 256 always, it seems. if (!$success && !(grep($_ eq 'diff', @$args) && $errorcode == 256)) { my $err_string = join('', @$stderr); chomp($err_string); confess("$full_command failed: $err_string"); } my $output = join('', @$all); if ($self->debug) { print STDERR "Exit Code: $errorcode\n"; (print STDERR "Results: $output\n") if $self->debug > 1; } return $output; }; 1; __END__ =head1 NAME VCI::VCS::Cvs - The CVS implementation of VCI =head1 DESCRIPTION This is a "driver" for L for the CVS (Concurrent Versioning System) version-control system. You can find out more about CVS at L. For information on how to use VCI::VCS::Cvs, see L. =head1 CONNECTING TO A CVS REPOSITORY For the L argument to L, choose what you would put in the C environment variable. The constructor also takes two additional, optional parameters: =over =item C The path to the "cvs" binary on your system. If not specified, we will search your C and throw an error if C isn't found. B: VCI will throw an error if this argument is tainted, because VCI just runs this command blindly, and we wouldn't want to run something like C. =item C The path to the "cvsps" binary on your system. If not specified, we will search your C and throw an error if C isn't found. B: VCI will throw an error if this argument is tainted, because VCI just runs this command blindly, and we wouldn't want to run something like C. =back =head2 Local Repositories Though CVS itself doesn't allow relative paths in C<:local:> roots, VCI::VCS::Cvs does. So C<:local:path/to/repo> (or just C) will be interpreted as meaning that you want the CVS repository in the directory C. In actuality, VCI::VCS::Cvs converts the relative path to an absolute path when creating the Repository object, so using relative paths will fail if you are in an environment where L fails. =head1 REQUIREMENTS In addition to the Perl modules listed for CVS Support when you install L, VCI::VCS::Cvs requires that the following things be installed on your system: =over =item cvs The C client program, at least version 1.11. You can get this at L for *nix systems and L for Windows systems. =item cvsps This is a program that interacts with CVS to figure out what files were committed together, since CVS doesn't normally track that information, and VCI needs that information. You can get it from L. (Windows users have to use Cygwin to run cvsps, which you can get from L.) =back =head1 REVISION IDENTIFIERS cvsps groups file commits that are close together in time and have the same message into "PatchSets". Each of these PatchSets is given a unique, integer identifier. Since VCI::VCS::Cvs uses cvsps, the revision identifiers on Commit objects will be these PatchSet ids. For File objects, the revision identifiers will be the actual revision identifier as returned by CVS for that file. For example C<1.1>, etc. For Directory objects, the revision identifier is currently always C. =head1 LIMITATIONS AND EXTENSIONS =over =item * Currently VCI doesn't understand the concept of "branches", so you are always dealing with the C branch of a project. This will change in the future so that VCI can access branches of projects. =item * cvsps needs to write to the C directory of the current user, you must have write access to that directory in order to interact with the History of a Project. =item * VCI::VCS::Cvs has to write files to your system's temporary directory (F on *nix systems), and many operations will fail if it cannot. It uses the temporary directory returned by L. =item * If your program dies during execution, there is a chance that directories named like F will be left in your temporary directory. As long as no instance of VCI is currently running, it should be safe to delete these directories. =back In addition, here are the limitations of specific modules compared to the general API specified in the C modules: =head2 VCI::VCS::Cvs::Repository C doesn't support modules yet, only directory names in the repository. Using a module name won't throw an error, but operations on that Project are likely to then fail. =head2 VCI::VCS::Cvs::Project CVS supports L<"root_project"|VCI::Abstract::Project/root_project>. =head2 VCI::VCS::Cvs::Commit CVS doesn't track the history of a Directory, so Directory objects will never show up in the added, removed, modified, or contents of a Commit. =head2 VCI::VCS::Cvs::Directory =over =item * For the C