VCI::VCS::Cvs - Object-oriented interface to CVS


VCI documentation Contained in the VCI distribution.

Index


Code Index:

NAME

Top

VCI::VCS::Cvs - Object-oriented interface to CVS

SYNOPSIS

Top

 use VCI;
 my $repository = VCI->connect(
    type => 'Cvs',
    repo => ':pserver:anonymous@cvs.example.com:/cvsroot'
 );

DESCRIPTION

Top

This is a "driver" for VCI for the CVS (Concurrent Versioning System) version-control system. You can find out more about CVS at http://www.nongnu.org/cvs/.

For information on how to use VCI::VCS::Cvs, see VCI.

CONNECTING TO A CVS REPOSITORY

Top

For CVS, the format of the repo argument to connect in VCI is the same as what you would put in the CVSROOT environment variable when using the cvs program.

The constructor also takes two additional, optional parameters:

x_cvs

The path to the "cvs" binary on your system. If not specified, we will search your PATH and throw an error if cvs isn't found.

Taint Mode: 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 delete_everything_on_this_computer.sh.

x_cvsps

The path to the "cvsps" binary on your system. If not specified, we will search your PATH and throw an error if cvsps isn't found.

Taint Mode: 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 delete_everything_on_this_computer.sh.

Local Repositories

Though CVS itself doesn't allow relative paths in :local: roots, VCI::VCS::Cvs does. So :local:path/to/repo (or just path/to/repo) will be interpreted as meaning that you want the CVS repository in the directory path/to/repo.

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 abs_path in Cwd fails.

REQUIREMENTS

Top

In addition to the Perl modules listed for CVS Support when you install VCI, VCI::VCS::Cvs requires that the following things be installed on your system:

cvs

The cvs client program, at least version 1.11. You can get this at http://www.nongnu.org/cvs for *nix systems and http://www.cvsnt.org/ for Windows systems.

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 http://www.cobite.com/cvsps/. (Windows users have to use Cygwin to run cvsps, which you can get from http://www.cygwin.com/.)

REVISION IDENTIFIERS

Top

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.

These patchset ids are cached by cvsps in your home directory, so as long as you keep using VCI on the same system, the revision identifiers should stay stable. However, if you move VCI to a different system and don't copy the cvsps cache (usually in $HOME/.cvsps/) then the revision identifiers for Commits might change.

For File objects, the revision identifiers will be the actual revision identifier as returned by CVS for that file. For example 1.1, etc.

For Directory objects, the revision identifier is currently always HEAD.

LIMITATIONS AND EXTENSIONS

Top

In addition, here are the limitations of specific modules compared to the general API specified in the VCI::Abstract modules:

VCI::VCS::Cvs::Repository

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

VCI::VCS::Cvs::Project

CVS supports "root_project".

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.

VCI::VCS::Cvs::Directory

PERFORMANCE

Top

VCI::VCS::Cvs performs fairly well, although it may be slower on projects that have lots of files in one directory, or very long histories.

Working with a local repository will always be faster than working with a remote repository. For most operations, the latency between you and the repository is far more important than the bandwidth between you and the repository.

SEE ALSO

Top

VCI

AUTHOR

Top

Max Kanat-Alexander <mkanat@cpan.org>

COPYRIGHT AND LICENSE

Top


VCI documentation Contained in the VCI distribution.

package VCI::VCS::Cvs;
use Moose;
our $VERSION = '0.7.1';

use MooseX::Method;
extends 'VCI';

use Cwd;
use IPC::Cmd;
use Scalar::Util qw(tainted);

use VCI::Util qw(taint_fail detaint);

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});
}

use constant revisions_are_global => 0;
use constant revisions_are_universal => 0;

sub missing_requirements {
    my @need;
    foreach my $bin qw(cvs cvsps) {
        push(@need, $bin) if !IPC::Cmd::can_run($bin);
    }
    return @need;
}

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 or confess("Failed to chdir to $fromdir: $!");

    my ($success, $error_msg, $all, $stdout, $stderr) =
        IPC::Cmd::run(command => [$self->x_cvs, '-f', @$args]);

    my $error_code = 0;
    if (defined $error_msg) {
        if ($error_msg =~ /exited with value (\d+)/) {
            $error_code = $1;
        }
        else {
            $error_code = -1;
        }
    }
    
    # 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 or confess("Failed to chdir back to $old_cwd: $!");

    # "cvs diff" returns 256 always, it seems.
    if (!$success && !(grep($_ eq 'diff', @$args) && $error_code == 256)) {
        my $err_string = join('', @$stderr);
        chomp($err_string);
        confess("$full_command failed: $err_string");
    }
    
    my $output = join('', @$all);
    if ($self->debug) {
        print STDERR "Error Message: $error_msg\n" if $error_msg;
        (print STDERR "Results: $output\n") if $self->debug > 1;
    }
    return $output;
};

__PACKAGE__->meta->make_immutable;

1;

__END__