SVN::Class - manipulate Subversion workspaces with Perl objects


SVN-Class documentation Contained in the SVN-Class distribution.

Index


Code Index:

NAME

Top

SVN::Class - manipulate Subversion workspaces with Perl objects

SYNOPSIS

Top

 use SVN::Class;

 my $file = svn_file( 'path/to/file' );
 my $fh = $file->open('>>');
 print {$fh} "hello world\n";
 $fh->close;
 $file->add;
 if ($file->modified) {
    my $rev = $file->commit('the file changed');
    print "$file was committed with revision $rev\n";
 }
 else {
    croak "$file was not committed: " . $file->errstr;
 }

 my $dir = svn_dir( 'path/to/dir' );
 $dir->mkpath unless -d $dir;
 $dir->add;  # recurses by default
 $dir->commit('added directory') if $dir->modified;

DESCRIPTION

Top

SVN::Class extends Path::Class to allow for basic Subversion workspace management. SVN::Class::File and SVN::Class::Dir are subclasses of Path::Class::File::Stat and Path::Class::Dir respectively.

SVN::Class does not use the SVN::Core Subversion SWIG bindings. Instead, the svn binary tool is used for all interactions, using IPC::Cmd. This design decision was made for maximum portability and to eliminate non-CPAN dependencies.

EXPORT

Top

SVN::Class exports two functions by default: svn_file() and svn_dir(). These work just like the dir() and file() functions in Path::Class. If you do not want to export them, just invoke SVN::Class like:

 use SVN::Class ();

svn_file( file )

Works just like Path::Class::file().

svn_dir( dir )

Works just like Path::Class::dir().

METHODS

Top

SVN::Class inherits from Path::Class. Only new or overridden methods are documented here.

svn

Path to the svn binary. Defaults to svn and thus relies on environment's PATH to find and execute the correct command.

stdout

Get the stdout from the last svn_run().

stderr

Get the stderr from the last svn_run().

error

If the last svn_run() exited with non-zero, error() will return same as stderr(). If svn_run() was successful, returns the empty string.

error_code

Returns the last exit value of svn_run().

verbose

Get/set a true value to enable IPC output in svn_run().

debug

Get/set a true value to see debugging output printed on stderr.

svn_run( cmd, opts, file )

Execute cmd given opts and file as arguments. This is a wrapper around the IPC::Run run() function.

opts should be an array ref of options to pass to cmd.

file defaults to $self->stringify().

Returns the success code from IPC::Run run(). Sets the stdout, stderr, err, errstr, and error_code values in the SVN::Class object.

This method is used internally by all the Subversion commands.

NOTE: In order to standardize the output of Subversion commands into a locale that is easily parse-able by other methods that call svn_run() internally, all commands are run with LC_ALL=C to make sure output is ASCII only.

log

Returns svn log of the file or 0 on error. The log is returned as an arrayref (same as accessing stdout()).

add

Schedule the object for addition to the repository.

delete

Schedule the object for removal from the repository.

update

Get the latest version of the object from the repository.

up

Alias for update().

revert

Undo the last Subversion action on the object.

commit( message )

Commit the object to the repository with the log message.

Returns the revision number of the commit on success, 0 on failure.

status

Returns the workspace status of the object.

modified

Returns true if the status() of the object is Add or Modified.

conflicted

Returns true if the status() of the object is Conflicted.

diff

Diff the workspace version of the object against either the repository or the current working baseline version.

blame

Annotated accounting of who modified what lines of the object.

info

Returns SVN::Class::Info instance with information about the current object or 0 on failure.

dump

Returns a Data::Dump serialization of the object. Useful for debugging.

errstr

Returns the contents of error() as a newline-joined string.

outstr

Returns the contents of stdout() as a newline-joined string.

AUTHOR

Top

Peter Karman, <karman at cpan.org>

BUGS

Top

Please report any bugs or feature requests to bug-svn-class at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=SVN-Class. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

Top

You can find documentation for this module with the perldoc command.

    perldoc SVN::Class

You can also look for information at:

* AnnoCPAN: Annotated CPAN documentation

http://annocpan.org/dist/SVN-Class

* CPAN Ratings

http://cpanratings.perl.org/d/SVN-Class

* RT: CPAN's request tracker

http://rt.cpan.org/NoAuth/Bugs.html?Dist=SVN-Class

* Search CPAN

http://search.cpan.org/dist/SVN-Class

ACKNOWLEDGEMENTS

Top

I looked at SVN::Agent before starting this project. It has a different API, more like SVN::Client in the SVN::Core, but I cribbed some of the ideas.

The Minnesota Supercomputing Institute http://www.msi.umn.edu/ sponsored the development of this software.

COPYRIGHT

Top

SEE ALSO

Top

Path::Class, Class::Accessor::Fast, SVN::Agent, IPC::Cmd


SVN-Class documentation Contained in the SVN-Class distribution.
package SVN::Class;
use strict;
use warnings;
use base qw( Path::Class Rose::Object );
use Rose::Object::MakeMethods::Generic (
    scalar => [qw( svn stdout stderr error error_code verbose debug )] );
use Carp;
use Data::Dump;
use IPC::Cmd qw( can_run run );
use SVN::Class::File;
use SVN::Class::Dir;
use SVN::Class::Info;
use Text::ParseWords;
use File::Temp;

$ENV{LC_ALL} = 'C';    # we expect our responses in ASCII

#$IPC::Cmd::DEBUG   = 1;
#$IPC::Cmd::VERBOSE = 1;

unless ( IPC::Cmd->can_capture_buffer ) {
    croak "IPC::Cmd is not configured to capture buffers. "
        . "Do you have IPC::Run installed?";
}

# IPC::Run fails tests because we use built-in shell commands
# not found in PATH
$IPC::Cmd::USE_IPC_RUN = 1;

# this trick cribbed from mst's Catalyst::Controller::WrapCGI
# we alias STDIN and STDOUT since Catalyst (and presumaly other code)
# might be messing with STDOUT or STDIN
my $REAL_STDIN  = *STDIN;
my $REAL_STDOUT = *STDOUT;
my $REAL_STDERR = *STDERR;
if ( $ENV{SVN_CLASS_ALIAS_STDOUT} ) {
    open $REAL_STDIN,  "<&=" . CORE::fileno(*STDIN);
    open $REAL_STDOUT, ">>&=" . CORE::fileno(*STDOUT);
    open $REAL_STDERR, ">>&=" . CORE::fileno(*STDERR);
}

sub _debug_stdin_fh {

    #warn "     stdin fileno = " . CORE::fileno(*STDIN);
    #warn "real_stdin fileno = " . CORE::fileno($REAL_STDIN);
}

sub _debug_stdout_fh {

    #warn "     stdout fileno = " . CORE::fileno(*STDOUT);
    #warn "real_stdout fileno = " . CORE::fileno($REAL_STDOUT);
}

our @EXPORT    = qw( svn_file svn_dir );
our @EXPORT_OK = qw( svn_file svn_dir );

our $VERSION = '0.16';

sub svn_file {
    SVN::Class::File->new(@_);
}

sub svn_dir {
    SVN::Class::Dir->new(@_);
}

sub svn_run {
    my $self = shift;
    my $cmd  = shift or croak "svn command required";
    my $opts = shift || [];
    my $file = shift || "$self";

    # since $opts may contain whitespace, must pass command as array ref
    # to IPC::Run
    my $command
        = [ $self->svn, $cmd, shellwords( join( ' ', @$opts ) ), $file ];

    my @out;

    $self->_debug_stdin_fh;
    $self->_debug_stdout_fh;

    {
        local *STDIN  = $REAL_STDIN;    # restore the real ones so the filenos
        local *STDOUT = $REAL_STDOUT;   # are 0 and 1 for the env setup
        local *STDERR = $REAL_STDERR;

        my $old = select($REAL_STDOUT);  # in case somebody just calls 'print'

        # Use local signal handler so global handler
        # does not result in bad values in $? and $!
        # http://www.perlmonks.org/?node_id=197500
        # useful for running under Catalyst (e.g.)
        local $SIG{CHLD} = '';

        $self->_debug_stdin_fh;
        $self->_debug_stdout_fh;

        (@out) = run( command => $command, verbose => $self->verbose );

        select($old);
    }

    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) = @out;

    # buffers do not always split on \n so force them to.
    my @stdout = split( m/\n/, join( '', @$stdout_buf ) );
    my @stderr = split( m/\n/, join( '', @$stderr_buf ) );

    # return code is a little murky as $error_code is often -1
    # which sometimes signals success, while $success is undef.
    if ( !defined($success) ) {
        if ( $error_code eq '-1' && !@stderr ) {
            $success = 1;
        }
        else {
            $success = 0;
        }
    }

    $self->stdout( \@stdout );
    $self->stderr( \@stderr );
    $self->error( $success ? "" : \@stderr );
    $self->error_code($error_code);

    if ( $self->debug || $ENV{PERL_DEBUG} ) {
        carp "command: " . Data::Dump::dump($command);
        carp Data::Dump::dump \@out;
        $self->dump;
        carp "success = $success";
    }

    return $success;
}

sub log {
    my $self = shift;
    my $ret = $self->svn_run( 'log', @_ );
    return 0 unless $ret > 0;
    return $self->stdout;
}

sub add {
    shift->svn_run( 'add', @_ );
}

sub delete {
    shift->svn_run( 'rm', @_ );
}

sub update {
    shift->svn_run( 'update', @_ );
}

*up = \&update;

sub revert {
    shift->svn_run( 'revert', @_ );
}

sub commit {

    # croak if failure but set error() and error_code()
    # first in case wrapped in eval().
    my $self    = shift;
    my $message = shift or croak "commit message required";
    my $opts    = shift || [];

    # create temp file to print message to. see RT #48748
    my $message_fh = File::Temp->new();
    print $message_fh $message;
    my $message_file = $message_fh->filename;
    $message_file =~ s!\\!/!g;  # escape literal \ for Windows users. see RT#54969

    my $ret = $self->svn_run( 'commit', [ '--file', $message_file, @$opts ] );

    # confirm temp file is removed
    undef $message_fh;
    if ( -s $message_file ) {
        warn "temp file not removed: $message_file";
    }

    # $ret is empty string on success. that's odd.
    if ( defined( $self->{stdout}->[0] )
        && $self->{stdout}->[-1] =~ m/Committed revision (\d+)/ )
    {
        return $1;
    }
    return 0;
}

sub status {
    my $self = shift;
    $self->svn_run('status');

    if ( $self->is_dir ) {

        # find the arg that matches $self
        if ( defined $self->stdout->[0] ) {
            for my $line ( @{ $self->stdout } ) {
                if ( $line =~ m/^(\S)\s+\Q$self\E$/ ) {
                    return $1;
                }
            }
            return 0;
        }
    }

    if ( defined $self->stdout->[0] ) {
        my ($stat) = ( $self->stdout->[0] =~ m/^([A-Z\?])/ );
        return $stat;
    }
    return 0;
}

sub modified {
    return $_[0]->status =~ m/^[MA]$/ ? 1 : 0;
}

sub conflicted {
    return $_[0]->status eq 'C';
}

sub diff {
    shift->svn_run( 'diff', @_ );
}

sub blame {
    shift->svn_run( 'blame', @_ );
}

sub info {
    my $self = shift;
    return 0 unless $self->svn_run( 'info', @_ );
    return SVN::Class::Info->new( $self->stdout );
}

sub dump {
    Data::Dump::dump(shift);
}

sub errstr {
    my $self = shift;
    my $err  = $self->error;
    return ref($err) ? join( "\n", @$err ) : $err;
}

sub outstr {
    my $self = shift;
    my $out  = $self->stdout;
    return ( ref($out) ? join( "\n", @$out ) : $out ) . "\n";
}

1;

__END__