File Coverage

blib/lib/VCfs.pm
Criterion Covered Total %
statement 24 161 14.9
branch 0 70 0.0
condition 0 7 0.0
subroutine 8 27 29.6
pod 17 17 100.0
total 49 282 17.3


line stmt bran cond sub pod time code
1             package VCfs;
2             $VERSION = v0.0.1;
3              
4 1     1   1161 use warnings;
  1         3  
  1         52  
5 1     1   6 use strict;
  1         2  
  1         36  
6 1     1   17 use Carp;
  1         2  
  1         86  
7              
8 1         65 use File::Basename qw(
9             dirname
10 1     1   6 );
  1         2  
11              
12 1     1   1645 use IPC::Run ();
  1         56358  
  1         30  
13              
14 1     1   993 use Class::Accessor::Classy;
  1         4999  
  1         14  
15             ro qw(
16             dir
17             vcs
18             vcs_cmd
19             );
20 1     1   225 no Class::Accessor::Classy;
  1         2  
  1         6  
21              
22             =head1 NAME
23              
24             VCfs - Version Control agnostic interface on the local system
25              
26             =head1 Synopsis
27              
28             my $vc = VCfs->new(".");
29             my %status = $vc->status;
30             my @tags = $vc->taglist;
31              
32             =head1 About
33              
34             I need somewhere to put all of this repeated code. There are probably
35             other modules on the CPAN which do this sort of thing differently. The
36             basic idea is to just capture output from shelling-out to the
37             appropriate frontend command for a given version control tool. Examples
38             of usage can be found in the 'bin/' directory of this distribution.
39              
40             Where necessary, assumes a typical "trunk,branches,tags" layout.
41              
42             This currently supports svn and svk. Your help and input is welcome.
43              
44             =cut
45              
46             =head1 Constructor
47              
48             =head2 new
49              
50             $vc = VCfs->new($dir|$file, \%options);
51              
52             =cut
53              
54             sub new {
55 0     0 1   my $caller = shift;
56 0           my ($dir, $opts) = @_;
57 0   0       my $class = ref($caller) || $caller;
58 0 0         my $self = {$opts ? %$opts : ()};
59              
60 0 0         $dir or croak("constructor must have a directory or file");
61 0 0         unless(-d $dir) {
62 0           $dir = dirname($dir);
63             }
64 0 0         (-d $dir) or croak("eek");
65 0           $self->{dir} = $dir;
66              
67 0           bless($self, $class);
68 0           $self->detect;
69 0           return($self);
70             } # end subroutine new definition
71             ########################################################################
72              
73             =head1 Methods
74              
75             =head2 detect
76              
77             Tries to guess at what sort of VCS by examining the directory.
78              
79             $vc->detect;
80              
81             =cut
82              
83             sub detect {
84 0     0 1   my $self = shift;
85 0           my $dir = $self->{dir};
86 0 0         (-d $dir) or croak("eek");
87 0           my %dmatches = (
88             svn => "$dir/.svn",
89             darcs => "$dir/_darcs",
90             cvs => "$dir/CVS",
91             );
92 0           foreach my $k (keys(%dmatches)) {
93 0 0         (-d $dmatches{$k}) and ($self->{vcs} = $k);
94             }
95 0   0       $self->{vcs} ||= 'svk'; # the oddball
96 0           $self->{vcs_cmd} = $self->{vcs}; # XXX for now;
97             } # end subroutine detect definition
98             ########################################################################
99              
100             =head2 _do_run
101              
102             %res = $vc->_do_run(@command);
103              
104             =cut
105              
106             sub _do_run {
107 0     0     my $self = shift;
108 0           my @command = @_;
109 0           my ($in, $out, $err);
110 0           0 and warn "run $self->{vcs_cmd} @command\n";
111 0           my $ret = IPC::Run::run([$self->vcs_cmd, @command], \$in, \$out, \$err);
112 0 0         $ret or die "command died $err";
113 0           return(out => $out, err => $err, status => ($? >> 8), ret => $ret);
114             } # end subroutine _do_run definition
115             ########################################################################
116              
117             =head2 is_
118              
119             Returns true if the underlying VCS is .
120              
121             These are mostly used internally to handle special cases.
122              
123             =over
124              
125             =item is_svn
126              
127             =item is_svk
128              
129             =item is_cvs
130              
131             =item is_darcs
132              
133             =back
134              
135             =cut
136              
137             foreach my $type (qw(svn svk cvs darcs)) {
138 1     1   1175 no strict 'refs';
  1         3  
  1         2005  
139             *{__PACKAGE__ . "::is_$type"} = sub {
140 0     0     my $self = shift;
141 0           return($self->{vcs} eq $type);
142             }; # end sub
143             }
144              
145             =head2 get_log
146              
147             $vc->get_log($target);
148              
149             =cut
150              
151             sub get_log {
152 0     0 1   my $self = shift;
153 0           my ($target, %opts) = @_;
154              
155 0 0         my @args = $opts{args} ? @{$opts{args}} : ();
  0            
156              
157 0           my ($in, $out, $err);
158 0 0         IPC::Run::run(
159             [$self->vcs_cmd, 'log', ($self->is_svk ? '-x' : ()), @args, $target],
160             \$in, \$out, \$err
161             );
162 0 0         $err and warn "eek! $err ";
163             # warn "see: $out";
164             # XXX error checking?
165             # XXX wantarray?
166 0           return(split(/\n/, $out));
167             } # end subroutine get_log definition
168             ########################################################################
169              
170             =head2 get_log_times
171              
172             $vc->get_log_times($target);
173              
174             =cut
175              
176             sub get_log_times {
177 0     0 1   my $self = shift;
178             # XXX maybe want this regex for other things? - get_summary_lines ?
179 0           my @l = grep(/^r\d+:?.*\|\s/,
180             $self->get_log(@_)
181             );
182 0           my @times;
183 0           foreach my $s (@l) { # XXX also, usable in other areas
184 0 0         if($self->is_svk) {
185 0           $s =~ s/^(r\d+):\s*/$1 | /;
186             }
187 0           my ($r, $u, $d, $else) = split(/\s\|\s/, $s, 4);
188 0   0       $else ||= '';
189             #warn "split into ", join("#", $r, $u, $d, $else), "\n";
190 0           push(@times, $d);
191             }
192 0           return(@times);
193             } # end subroutine get_log_times definition
194             ########################################################################
195              
196             =head2 get_info
197              
198             my %vals = $vc->get_info;
199              
200             =cut
201              
202             sub get_info {
203 0     0 1   my $self = shift;
204              
205 0           my %ans = $self->_do_run('info', $self->dir);
206 0           my %info;
207 0           foreach my $line (split(/\n/, $ans{out})) {
208 0           my ($key, $val) = split(/ *: */, $line, 2);
209 0           $key = lc($key);
210 0           $key =~ s/ +/_/g;
211 0           $key =~ s/__+/_/g;
212 0           $key =~ s/[^a-z0-9_]+//g;
213 0 0         exists($info{$key}) and die "oops $key twice in $ans{out}";
214 0           $info{$key} = $val;
215             }
216 0           return(%info);
217             } # end subroutine get_info definition
218             ########################################################################
219              
220             =head2 taglist
221              
222             my @tags = $vc->taglist;
223              
224             =cut
225              
226             sub taglist {
227 0     0 1   my $self = shift;
228 0           return(map({s#/$##;$_} $self->list($self->tag_dir)));
  0            
  0            
229             } # end subroutine taglist definition
230             ########################################################################
231              
232             =head2 tag_dir
233              
234             my $dir = $vc->tag_dir;
235              
236             =cut
237              
238             sub tag_dir {
239 0     0 1   my $self = shift;
240              
241 0           my %info = $self->get_info;
242 0           my $url = $info{url};
243 0           my $tagdir = $url;
244 0 0         $tagdir =~ s/trunk$/tags\// or die "eek, $url not trunk?";
245 0           return($tagdir);
246             } # end subroutine tag_dir definition
247             ########################################################################
248              
249             =head2 taggit
250              
251             (Currently) assumes a proj/trunk, proj/tags layout and that we're
252             looking at trunk. I guess you could tag a branch, but, uh...
253              
254             $vc->taggit($tagname, message => $message);
255              
256             Big issue: There is no syntax of copy that prevents writing into an
257             existing tag directory. The subversion developers seem to think this
258             should be handled via pre-commit hooks (see
259             http://svn.haxx.se/users/archive-2005-11/0056.shtml for details.)
260              
261             =cut
262              
263             sub taggit {
264 0     0 1   my $self = shift;
265 0           my ($name, %opts) = @_;
266 0 0         ($name =~ m#/#) and die "improper tagname $name";
267              
268 0           my %info = $self->get_info;
269 0           my $url = $info{url};
270 0 0         die "I can't taggit() on type ", $self->vcs_command, " yet"
271             unless($url);
272              
273             # TODO svk support
274             # TODO config-file and/or propval layout?
275              
276 0           my $trunk = $url; # could also be a branch I guess
277 0           my $tagdir = $url;
278 0 0         $tagdir =~ s{(?:trunk|branches/[^/]+)/?$}{tags/} or
279             croak("eek, $url not trunk|branches?");
280 0           my $tagdest = $tagdir . $name;
281              
282             # Bah! svn doesn't prevent copying into an existing tag directory (at
283             # least not in any form that I can see.)
284             #warn $self->list($tagdir);
285 0           my @has = grep(/^\Q$name\E\/$/, $self->list($tagdir));
286 0 0         @has and die "tag '$name' already exists in $tagdir";
287              
288 0           my $message = $opts{message};
289 0 0         $message = "tagging $name" unless(defined($message));
290              
291 0           $self->_do_run('copy', $trunk, $tagdest, '--message', $message);
292             } # end subroutine taggit definition
293             ########################################################################
294              
295             =head1 normal methods
296              
297             Just abstraction for standard commands.
298              
299              
300             =head2 add
301              
302             $vc->add(@files);
303              
304             =cut
305              
306             sub add {
307 0     0 1   my $self = shift;
308 0           my @files = @_;
309 0           my %r = $self->_do_run('add', @files);
310 0 0         $r{err} and warn "eek! $r{err} ($r{status})";
311 0 0         $r{ret} or warn "eek";
312             # XXX or should parse output and return number of added files?
313 0           return($r{ret});
314             } # end subroutine add definition
315             ########################################################################
316              
317             =head2 remove
318              
319             $vc->remove(@files);
320              
321             =cut
322              
323             sub remove {
324 0     0 1   my $self = shift;
325 0           my @files = @_;
326 0           my %r = $self->_do_run('remove', @files);
327 0 0         $r{err} and warn "eek! $r{err} ($r{status})";
328 0 0         $r{ret} or warn "eek";
329             # XXX or should parse output and return number of added files?
330 0           return($r{ret});
331             } # end subroutine remove definition
332             ########################################################################
333              
334             =head2 commit
335              
336             $vc->commit($message, @files);
337              
338             =cut
339              
340             sub commit {
341 0     0 1   my $self = shift;
342 0           my ($message, @files) = @_;
343 0 0         @files or die;
344 0           my %r = $self->_do_run('commit', @files, '-m', $message);
345 0 0         $r{err} and warn "eek! $r{err} ($r{status})";
346 0 0         $r{ret} or warn "eek";
347             # XXX or should return what?
348 0           return($r{ret});
349             } # end subroutine commit definition
350             ########################################################################
351              
352             =head2 update
353              
354             $vc->update;
355              
356             =cut
357              
358             sub update {
359 0     0 1   my $self = shift;
360              
361 0           my %r = $self->_do_run('update');
362             } # end subroutine update definition
363             ########################################################################
364              
365             =head2 list
366              
367             my @list = $vc->list($path);
368              
369             =cut
370              
371             sub list {
372 0     0 1   my $self = shift;
373 0           my ($path) = @_;
374 0 0         $path or die; # XXX ?
375 0           my %r = $self->_do_run('list', $path);
376             #$r{err} and warn "eek! $r{err} ($r{status})";
377             #$r{ret} or warn "eek";
378             #$r{out} or warn "that's a problem";
379 0           return(split(/\n/, $r{out}));
380             } # end subroutine list definition
381             ########################################################################
382              
383             =head2 revert
384              
385             $vc->revert(@files);
386              
387             =cut
388              
389             sub revert {
390 0     0 1   my $self = shift;
391 0           my (@files) = @_;
392 0 0         @files or die "need files";
393 0           my %r = $self->_do_run('revert', @files);
394             # TODO read the qr/Reverted '([^']+)'/ lines?
395 0           warn $r{out};
396             } # end subroutine revert definition
397             ########################################################################
398              
399             =head2 status
400              
401             Returns a hash of files and their status codes.
402              
403             %status = $vc->status(@files);
404              
405             =cut
406              
407             sub status {
408 0     0 1   my $self = shift;
409 0           my @files = @_;
410 0           my %r = $self->_do_run('status', @files);
411 0 0         $r{err} and warn "eek! $r{err} ($r{status})";
412 0 0         $r{ret} or warn "eek";
413 0 0         $r{out} or return();
414 0           return(map({reverse(split(/\s+/, $_, 2))}
  0            
415             split(/\n/, $r{out})
416             ));
417             } # end subroutine status definition
418             ########################################################################
419              
420             =head2 propget
421              
422             $vc->propget($propname, $url||$file);
423              
424             =cut
425              
426             sub propget {
427 0     0 1   my $self = shift;
428 0           my ($prop, $file) = @_;
429              
430 0           my %r = $self->_do_run('propget', $prop, $file);
431 0 0         defined(my $string = $r{out}) or croak("nothing there");
432              
433 0           die "this is unfinished";
434              
435              
436             } # end subroutine propget definition
437             ########################################################################
438              
439             =head2 propset
440              
441             Takes an array reference or string for propvals.
442              
443             $vc->propset($propname, \@vals, @files);
444              
445             $vc->propset($propname, $valstring, @files);
446              
447             =cut
448              
449             sub propset {
450 0     0 1   my $self = shift;
451 0           my ($prop, $val, @files) = @_;
452 0 0         if(ref($val)) {
453 0 0         UNIVERSAL::isa($val, 'ARRAY') or die;
454 0           $val = join("\n", @$val);
455             }
456 0           my %r = $self->_do_run('propset', $prop, $val, @files);
457 0 0         $r{err} and warn "eek! $r{err} ($r{status})";
458 0 0         $r{ret} or warn "eek";
459 0           return($r{ret});
460             } # end subroutine propset definition
461             ########################################################################
462              
463             =head1 AUTHOR
464              
465             Eric Wilhelm @
466              
467             http://scratchcomputing.com/
468              
469             =head1 BUGS
470              
471             If you found this module on CPAN, please report any bugs or feature
472             requests through the web interface at L. I will be
473             notified, and then you'll automatically be notified of progress on your
474             bug as I make changes.
475              
476             If you pulled this development version from my /svn/, please contact me
477             directly.
478              
479             =head1 COPYRIGHT
480              
481             Copyright (C) 2004-2009 Eric L. Wilhelm, All Rights Reserved.
482              
483             =head1 NO WARRANTY
484              
485             Absolutely, positively NO WARRANTY, neither express or implied, is
486             offered with this software. You use this software at your own risk. In
487             case of loss, no person or entity owes you anything whatsoever. You
488             have been warned.
489              
490             =head1 LICENSE
491              
492             This program is free software; you can redistribute it and/or modify it
493             under the same terms as Perl itself.
494              
495             =cut
496              
497             # vi:sw=2:ts=2:et:sta
498             1;