File Coverage

blib/lib/SVN/Class.pm
Criterion Covered Total %
statement 36 124 29.0
branch 0 34 0.0
condition 0 16 0.0
subroutine 12 32 37.5
pod 18 18 100.0
total 66 224 29.4


line stmt bran cond sub pod time code
1             package SVN::Class;
2 2     2   118619 use strict;
  2         6  
  2         81  
3 2     2   10 use warnings;
  2         4  
  2         73  
4 2     2   10 use base qw( Path::Class Rose::Object );
  2         8  
  2         7662  
5             use Rose::Object::MakeMethods::Generic (
6 2     2   202196 scalar => [qw( svn stdout stderr error error_code verbose debug )] );
  2         23340  
  2         53  
7 2     2   3150 use Carp;
  2         6  
  2         137  
8 2     2   2579 use Data::Dump;
  2         15068  
  2         252  
9 2     2   3849 use IPC::Cmd qw( can_run run );
  2         204746  
  2         172  
10 2     2   2359 use SVN::Class::File;
  2         7  
  2         86  
11 2     2   13 use SVN::Class::Dir;
  2         4  
  2         84  
12 2     2   1527 use SVN::Class::Info;
  2         8  
  2         80  
13 2     2   25 use Text::ParseWords;
  2         5  
  2         176  
14 2     2   14 use File::Temp;
  2         21  
  2         3992  
15              
16             $ENV{LC_ALL} = 'C'; # we expect our responses in ASCII
17              
18             #$IPC::Cmd::DEBUG = 1;
19             #$IPC::Cmd::VERBOSE = 1;
20              
21             unless ( IPC::Cmd->can_capture_buffer ) {
22             croak "IPC::Cmd is not configured to capture buffers. "
23             . "Do you have IPC::Run installed?";
24             }
25              
26             # IPC::Run fails tests because we use built-in shell commands
27             # not found in PATH
28             $IPC::Cmd::USE_IPC_RUN = 1;
29              
30             # this trick cribbed from mst's Catalyst::Controller::WrapCGI
31             # we alias STDIN and STDOUT since Catalyst (and presumaly other code)
32             # might be messing with STDOUT or STDIN
33             my $REAL_STDIN = *STDIN;
34             my $REAL_STDOUT = *STDOUT;
35             my $REAL_STDERR = *STDERR;
36             if ( $ENV{SVN_CLASS_ALIAS_STDOUT} ) {
37             open $REAL_STDIN, "<&=" . CORE::fileno(*STDIN);
38             open $REAL_STDOUT, ">>&=" . CORE::fileno(*STDOUT);
39             open $REAL_STDERR, ">>&=" . CORE::fileno(*STDERR);
40             }
41              
42 0     0     sub _debug_stdin_fh {
43              
44             #warn " stdin fileno = " . CORE::fileno(*STDIN);
45             #warn "real_stdin fileno = " . CORE::fileno($REAL_STDIN);
46             }
47              
48 0     0     sub _debug_stdout_fh {
49              
50             #warn " stdout fileno = " . CORE::fileno(*STDOUT);
51             #warn "real_stdout fileno = " . CORE::fileno($REAL_STDOUT);
52             }
53              
54             our @EXPORT = qw( svn_file svn_dir );
55             our @EXPORT_OK = qw( svn_file svn_dir );
56              
57             our $VERSION = '0.17';
58              
59             =head1 NAME
60              
61             SVN::Class - manipulate Subversion workspaces with Perl objects
62              
63             =head1 SYNOPSIS
64              
65             use SVN::Class;
66            
67             my $file = svn_file( 'path/to/file' );
68             my $fh = $file->open('>>');
69             print {$fh} "hello world\n";
70             $fh->close;
71             $file->add;
72             if ($file->modified) {
73             my $rev = $file->commit('the file changed');
74             print "$file was committed with revision $rev\n";
75             }
76             else {
77             croak "$file was not committed: " . $file->errstr;
78             }
79            
80             my $dir = svn_dir( 'path/to/dir' );
81             $dir->mkpath unless -d $dir;
82             $dir->add; # recurses by default
83             $dir->commit('added directory') if $dir->modified;
84            
85             =head1 DESCRIPTION
86              
87             SVN::Class extends Path::Class to allow for basic Subversion workspace
88             management. SVN::Class::File and SVN::Class::Dir are subclasses of
89             Path::Class::File::Stat and Path::Class::Dir respectively.
90              
91             SVN::Class does not use the SVN::Core Subversion SWIG bindings. Instead,
92             the C binary tool is used for all interactions, using IPC::Cmd. This
93             design decision was made for maximum portability and to eliminate
94             non-CPAN dependencies.
95              
96             =head1 EXPORT
97              
98             SVN::Class exports two functions by default: svn_file() and svn_dir().
99             These work just like the dir() and file() functions in Path::Class.
100             If you do not want to export them, just invoke SVN::Class like:
101              
102             use SVN::Class ();
103              
104             =head2 svn_file( I )
105              
106             Works just like Path::Class::file().
107              
108             =head2 svn_dir( I )
109              
110             Works just like Path::Class::dir().
111              
112             =cut
113              
114             sub svn_file {
115 0     0 1   SVN::Class::File->new(@_);
116             }
117              
118             sub svn_dir {
119 0     0 1   SVN::Class::Dir->new(@_);
120             }
121              
122             =head1 METHODS
123              
124             SVN::Class inherits from Path::Class. Only new or overridden methods
125             are documented here.
126              
127             =cut
128              
129             =head2 svn
130              
131             Path to the svn binary. Defaults to C and thus relies on environment's
132             PATH to find and execute the correct command.
133              
134             =head2 stdout
135              
136             Get the stdout from the last svn_run().
137              
138             =head2 stderr
139              
140             Get the stderr from the last svn_run().
141              
142             =head2 error
143              
144             If the last svn_run() exited with non-zero, error() will return same
145             as stderr(). If svn_run() was successful, returns the empty string.
146              
147             =head2 error_code
148              
149             Returns the last exit value of svn_run().
150              
151             =head2 verbose
152              
153             Get/set a true value to enable IPC output in svn_run().
154              
155             =head2 debug
156              
157             Get/set a true value to see debugging output printed on stderr.
158              
159             =cut
160              
161             =head2 svn_run( I, I, I )
162              
163             Execute I given I and I as arguments. This is a wrapper
164             around the IPC::Run run() function.
165              
166             I should be an array ref of options to pass to I.
167              
168             I defaults to $self->stringify().
169              
170             Returns the success code from IPC::Run run(). Sets the stdout,
171             stderr, err, errstr, and error_code values in the SVN::Class object.
172              
173             This method is used internally by all the Subversion commands.
174              
175             B In order to standardize the output of Subversion commands into
176             a locale that is easily parse-able by other methods that call svn_run()
177             internally, all commands are run with C to make sure
178             output is ASCII only.
179              
180             =cut
181              
182             sub svn_run {
183 0     0 1   my $self = shift;
184 0 0         my $cmd = shift or croak "svn command required";
185 0   0       my $opts = shift || [];
186 0   0       my $file = shift || "$self";
187              
188             # since $opts may contain whitespace, must pass command as array ref
189             # to IPC::Run
190 0           my $command
191             = [ $self->svn, $cmd, shellwords( join( ' ', @$opts ) ), $file ];
192              
193 0           my @out;
194              
195 0           $self->_debug_stdin_fh;
196 0           $self->_debug_stdout_fh;
197              
198             {
199 0           local *STDIN = $REAL_STDIN; # restore the real ones so the filenos
  0            
200 0           local *STDOUT = $REAL_STDOUT; # are 0 and 1 for the env setup
201 0           local *STDERR = $REAL_STDERR;
202              
203 0           my $old = select($REAL_STDOUT); # in case somebody just calls 'print'
204              
205             # Use local signal handler so global handler
206             # does not result in bad values in $? and $!
207             # http://www.perlmonks.org/?node_id=197500
208             # useful for running under Catalyst (e.g.)
209 0           local $SIG{CHLD} = '';
210              
211 0           $self->_debug_stdin_fh;
212 0           $self->_debug_stdout_fh;
213              
214 0           (@out) = run( command => $command, verbose => $self->verbose );
215              
216 0           select($old);
217             }
218              
219 0           my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) = @out;
220              
221             # buffers do not always split on \n so force them to.
222 0           my @stdout = split( m/\n/, join( '', @$stdout_buf ) );
223 0           my @stderr = split( m/\n/, join( '', @$stderr_buf ) );
224              
225             # return code is a little murky as $error_code is often -1
226             # which sometimes signals success, while $success is undef.
227 0 0         if ( !defined($success) ) {
228 0 0 0       if ( $error_code eq '-1' && !@stderr ) {
229 0           $success = 1;
230             }
231             else {
232 0           $success = 0;
233             }
234             }
235              
236 0           $self->stdout( \@stdout );
237 0           $self->stderr( \@stderr );
238 0 0         $self->error( $success ? "" : \@stderr );
239 0           $self->error_code($error_code);
240              
241 0 0 0       if ( $self->debug || $ENV{PERL_DEBUG} ) {
242 0           carp "command: " . Data::Dump::dump($command);
243 0           carp Data::Dump::dump \@out;
244 0           $self->dump;
245 0           carp "success = $success";
246             }
247              
248 0           return $success;
249             }
250              
251             =head2 log
252              
253             Returns svn log of the file or 0 on error. The log is returned
254             as an arrayref (same as accessing stdout()).
255              
256             =cut
257              
258             sub log {
259 0     0 1   my $self = shift;
260 0           my $ret = $self->svn_run( 'log', @_ );
261 0 0         return 0 unless $ret > 0;
262 0           return $self->stdout;
263             }
264              
265             =head2 add
266              
267             Schedule the object for addition to the repository.
268              
269             =cut
270              
271             sub add {
272 0     0 1   shift->svn_run( 'add', @_ );
273             }
274              
275             =head2 delete
276              
277             Schedule the object for removal from the repository.
278              
279             =cut
280              
281             sub delete {
282 0     0 1   shift->svn_run( 'rm', @_ );
283             }
284              
285             =head2 update
286              
287             Get the latest version of the object from the repository.
288              
289             =cut
290              
291             sub update {
292 0     0 1   shift->svn_run( 'update', @_ );
293             }
294              
295             =head2 up
296              
297             Alias for update().
298              
299             =cut
300              
301             *up = \&update;
302              
303             =head2 revert
304              
305             Undo the last Subversion action on the object.
306              
307             =cut
308              
309             sub revert {
310 0     0 1   shift->svn_run( 'revert', @_ );
311             }
312              
313             =head2 commit( I )
314              
315             Commit the object to the repository with the log I.
316              
317             Returns the revision number of the commit on success, 0 on failure.
318              
319             =cut
320              
321             sub commit {
322              
323             # croak if failure but set error() and error_code()
324             # first in case wrapped in eval().
325 0     0 1   my $self = shift;
326 0 0         my $message = shift or croak "commit message required";
327 0   0       my $opts = shift || [];
328              
329             # create temp file to print message to. see RT #48748
330 0           my $message_fh = File::Temp->new();
331 0           print $message_fh $message;
332 0           my $message_file = $message_fh->filename;
333 0           $message_file =~ s!\\!/!g; # escape literal \ for Windows users. see RT#54969
334              
335 0           my $ret = $self->svn_run( 'commit', [ '--file', $message_file, @$opts ] );
336              
337             # confirm temp file is removed
338 0           undef $message_fh;
339 0 0         if ( -s $message_file ) {
340 0           warn "temp file not removed: $message_file";
341             }
342              
343             # $ret is empty string on success. that's odd.
344 0 0 0       if ( defined( $self->{stdout}->[0] )
345             && $self->{stdout}->[-1] =~ m/Committed revision (\d+)/ )
346             {
347 0           return $1;
348             }
349 0           return 0;
350             }
351              
352             =head2 status
353              
354             Returns the workspace status of the object.
355              
356             =cut
357              
358             sub status {
359 0     0 1   my $self = shift;
360 0           $self->svn_run('status');
361              
362 0 0         if ( $self->is_dir ) {
363              
364             # find the arg that matches $self
365 0 0         if ( defined $self->stdout->[0] ) {
366 0           for my $line ( @{ $self->stdout } ) {
  0            
367 0 0         if ( $line =~ m/^(\S)\s+\Q$self\E$/ ) {
368 0           return $1;
369             }
370             }
371 0           return 0;
372             }
373             }
374              
375 0 0         if ( defined $self->stdout->[0] ) {
376 0           my ($stat) = ( $self->stdout->[0] =~ m/^([A-Z\?])/ );
377 0           return $stat;
378             }
379 0           return 0;
380             }
381              
382             =head2 modified
383              
384             Returns true if the status() of the object is C or C.
385              
386             =cut
387              
388             sub modified {
389 0 0   0 1   return $_[0]->status =~ m/^[MA]$/ ? 1 : 0;
390             }
391              
392             =head2 conflicted
393              
394             Returns true if the status() of the object is C.
395              
396             =cut
397              
398             sub conflicted {
399 0     0 1   return $_[0]->status eq 'C';
400             }
401              
402             =head2 diff
403              
404             Diff the workspace version of the object against either the repository
405             or the current working baseline version.
406              
407             =cut
408              
409             sub diff {
410 0     0 1   shift->svn_run( 'diff', @_ );
411             }
412              
413             =head2 blame
414              
415             Annotated accounting of who modified what lines of the object.
416              
417             =cut
418              
419             sub blame {
420 0     0 1   shift->svn_run( 'blame', @_ );
421             }
422              
423             =head2 info
424              
425             Returns SVN::Class::Info instance with information about the current
426             object or 0 on failure.
427              
428             =cut
429              
430             sub info {
431 0     0 1   my $self = shift;
432 0 0         return 0 unless $self->svn_run( 'info', @_ );
433 0           return SVN::Class::Info->new( $self->stdout );
434             }
435              
436             =head2 dump
437              
438             Returns a Data::Dump serialization of the object. Useful for debugging.
439              
440             =cut
441              
442             sub dump {
443 0     0 1   Data::Dump::dump(shift);
444             }
445              
446             =head2 errstr
447              
448             Returns the contents of error() as a newline-joined string.
449              
450             =cut
451              
452             sub errstr {
453 0     0 1   my $self = shift;
454 0           my $err = $self->error;
455 0 0         return ref($err) ? join( "\n", @$err ) : $err;
456             }
457              
458             =head2 outstr
459              
460             Returns the contents of stdout() as a newline-joined string.
461              
462             =cut
463              
464             sub outstr {
465 0     0 1   my $self = shift;
466 0           my $out = $self->stdout;
467 0 0         return ( ref($out) ? join( "\n", @$out ) : $out ) . "\n";
468             }
469              
470             1;
471              
472             __END__