File Coverage

blib/lib/VCS/Lite/Element.pm
Criterion Covered Total %
statement 222 238 93.2
branch 78 106 73.5
condition 30 40 75.0
subroutine 22 23 95.6
pod 7 7 100.0
total 359 414 86.7


line stmt bran cond sub pod time code
1             package VCS::Lite::Element;
2              
3 11     11   67 use strict;
  11         22  
  11         425  
4 11     11   62 use warnings;
  11         33  
  11         616  
5              
6             our $VERSION = '0.11';
7              
8             #----------------------------------------------------------------------------
9              
10 11     11   108 use File::Spec::Functions qw(splitpath catfile catdir catpath rel2abs);
  11         106  
  11         1224  
11 11     11   1084 use Time::Piece;
  11         25859  
  11         81  
12 11     11   714 use Carp;
  11         19  
  11         1150  
13 11     11   12700 use VCS::Lite;
  11         205354  
  11         813  
14 11     11   12550 use Params::Validate qw(:all);
  11         145100  
  11         3031  
15 11     11   247 use Cwd qw(abs_path);
  11         25  
  11         633  
16              
17 11     11   65 use base qw(VCS::Lite::Common);
  11         24  
  11         7686  
18              
19             #----------------------------------------------------------------------------
20              
21             sub new {
22 120     120 1 2091 my $pkg = shift;
23 120         229 my $file = shift;
24 120         1121 my %args = validate ( @_,
25             {
26             store => {
27             type => SCALAR | OBJECT,
28             default => $pkg->default_store,
29             },
30             verbose => 0,
31             recordsize => 0, #ignored unless VCS::Lite::Element::Binary
32             } );
33 120         839 my $lite = $file;
34 120         225 my $verbose = $args{verbose};
35              
36 120         671 $file = rel2abs($file);
37 120         2244 my $store_pkg;
38 120 50       368 if (ref $args{store}) {
39 0         0 $store_pkg = $args{store};
40             } else {
41 120 100       503 $store_pkg = ($args{store} =~ /\:\:/) ? $args{store} : "VCS::Lite::Store::$args{store}";
42 120         10858 eval "require $store_pkg";
43 120 50       599 warn "Failed to require $store_pkg\n$@" if $@;
44             }
45              
46 120         809 my $ele = $store_pkg->retrieve($file);
47 120 100       431 if ($ele) {
48 94         327 $ele->path($file);
49 94         773 return $ele;
50             }
51              
52 26         162 my $proto = bless {
53             %args,
54             path => $file,
55             }, $pkg;
56              
57 26         278 $ele = $store_pkg->retrieve_or_create($proto);
58              
59 26         71 $ele->{path} = $file;
60              
61 26 50       84 if (!ref $lite) {
62 26 100       666 unless (-f $file) {
63 5 50       388 open FIL, '>', $file or croak("Failed to create $file, $!");
64 5         55 close FIL;
65             }
66 26         109 $lite = $ele->_slurp_lite($file);
67             } else {
68 0         0 $file = $lite->id; # Not handled at present
69             }
70              
71 26         4063 $ele->_assimilate($lite);
72 26         85 $ele->save;
73              
74 26         7200 $ele->{verbose} = $verbose;
75 26         6479 $ele;
76             }
77              
78             sub check_in {
79 27     27 1 626 my $self = shift;
80 27         656 my %args = validate ( @_,
81             {
82             check_in_anyway => 0,
83             description => { type => SCALAR },
84             } );
85 27         170 my $file = $self->{path};
86              
87 27         101 my $lite = $self->_slurp_lite($file);
88              
89 27         3954 my $newgen = $self->_assimilate($lite);
90 27 50 66     277 return if !$newgen && !$args{check_in_anyway};
91              
92 14         93 $self->_mumble("Check in $file");
93 14   100     80 $self->{generation} ||= {};
94 14         27 my %gen = %{$self->{generation}};
  14         58  
95 14         102 $gen{$newgen} = {
96             author => $self->user,
97             description => $args{description},
98             updated => localtime->datetime,
99             };
100              
101 14   100     1930 $self->{latest} ||= {};
102 14         23 my %lat = %{$self->{latest}};
  14         48  
103 14         85 $newgen =~ /(\d+\.)*\d+$/;
104 14   50     100 my $base = $1 || '';
105 14         39 $lat{$base}=$newgen;
106              
107 14         74 $self->_update_ctrl( generation => \%gen, latest => \%lat);
108 14         4743 $newgen;
109             }
110              
111             sub repository {
112 0     0 1 0 my $self = shift;
113              
114 0         0 my ($vol,$dir,$fil) = splitpath($self->{path});
115 0 0       0 my $repos_path = $vol ? catdir($vol,$dir) : $dir;
116              
117 0         0 VCS::Lite::Repository->new($repos_path, verbose => $self->{verbose});
118             }
119              
120             sub traverse {
121 16     16 1 35 undef;
122             }
123              
124             sub fetch {
125 58     58 1 4469 my $self = shift;
126 58         950 my %args = validate ( @_,
127             {
128             time => 0,
129             generation => 0,
130             } );
131              
132 58   100     459 my $gen = $args{generation} || $self->latest;
133              
134 58 50       159 if ($args{time}) {
135 0         0 my $latest_time = '';
136 0   0     0 my $branch = $args{generation} || '';
137 0 0       0 $branch .= '.' if $branch;
138 0         0 for (keys %{$self->{generation}}) {
  0         0  
139 0 0       0 next unless /^$branch\d+$/;
140 0 0       0 next if $self->{generation}{$_}{updated} > $args{time};
141 0 0       0 ($latest_time,$gen) = ($self->{generation}{$_}{updated}, $_)
142             if $self->{generation}{$_}{updated} > $latest_time;
143             }
144 0 0       0 return unless $latest_time;
145             }
146 58 50 66     309 return if $self->{generation} && !$self->{generation}{$gen};
147              
148 58         74 my $skip_to;
149             my @out;
150 58         83 for (@{$self->_contents}) {
  58         212  
151 2853 100       6109 if ($skip_to) {
152 121 100       599 if (/^=$skip_to$/) {
153 25         40 undef $skip_to;
154             }
155 121         313 next;
156             }
157 2732 100       7714 if (my ($type,$gensel) = /^([+-])(.+)/) {
158 60 100       139 if (_is_parent_of($gensel,$gen) ^ ($type eq '+')) {
159 25         49 $skip_to = $gensel;
160             }
161 60         237 next;
162             }
163 2672 100       12016 next if /^=/;
164              
165 2637 50       8175 if (/^ /) {
166 2637         6610 push @out,substr($_,1);
167             }
168             }
169              
170 58         168 my $file = $self->{path};
171 58         653 VCS::Lite->new("$file\@\@$gen",undef,\@out);
172             }
173              
174             sub commit {
175 4     4 1 9 my ($self,$parent) = @_;
176              
177 4         16 my ($vol,$dir,$file) = splitpath($self->path);
178 4         75 my $updfile = catfile($parent,$file);
179 4         16 my $chg = $self->fetch;
180 4         264 my $before = VCS::Lite->new($updfile);
181 4 100       593 return unless $before->delta($chg);
182              
183 3         4843 $self->_mumble("Committing $file to $parent");
184              
185 3         3 my $out;
186 3 50       377 open $out,'>',$updfile or croak "Failed to open $file for committing, $!";
187 3         16 print $out $chg->text;
188             }
189              
190             sub update {
191 9     9 1 21 my ($self,$parent) = @_;
192              
193 9         41 my $file = $self->path;
194 9         58 $self->_mumble("Updating $file from $parent");
195              
196 9         33 my ($vol,$dir,$fil) = splitpath($file);
197 9         308 my $fromfile = catfile($parent,$fil);
198 9   50     56 my $baseline = $self->{baseline} || 0;
199 9         18 my $parbas = $self->{parent_baseline};
200              
201 9         34 my $orig = $self->fetch( generation => $baseline);
202 9         25157 my $parele = VCS::Lite::Element->new($fromfile, verbose => $self->{verbose});
203 9         37 my $parfrom = $parele->fetch( generation => $parbas);
204 9         668 my $parlat = $parele->latest($parbas);
205 9         28 my $parto = $parele->fetch( generation => $parlat);
206 9         437 my $origplus = $parfrom->merge($parto,$orig);
207              
208 9         18478 my $chg = VCS::Lite->new($file);
209 9         1727 my $merged = $orig->merge($origplus,$chg);
210 9         18388 my $out;
211 9 50       1478 open $out,'>',$file or croak "Failed to write back merge of $fil, $!";
212 9         43 print $out $merged->text;
213 9         292 $self->_update_ctrl(baseline => $self->latest, parent_baseline => $parlat);
214             }
215              
216             sub _check_out_member {
217 17     17   37 my $self = shift;
218 17         32 my $newpath = shift;
219 17         350 my %args = validate(@_,
220             {
221             store => { type => SCALAR|OBJECT, optional => 1 },
222             } );
223              
224 17         208 my $repos = VCS::Lite::Repository->new(
225             $newpath,
226             verbose => $self->{verbose},
227             %args);
228              
229 17         72 my ($vol,$dir,$fil) = splitpath($self->path);
230 17         260 my $newfil = catfile($newpath,$fil);
231 17         34 my $out;
232 17 50       1876 open $out,'>',$newfil or croak "Failed to check_out $fil, $!";
233 17         148 print $out $self->fetch->text;
234 17         2611 close $out;
235              
236 17         38 my $pkg = ref $self;
237 17         198 $pkg->new($newfil,%args);
238             }
239              
240             sub _assimilate {
241 53     53   132 my ($self,$lite,%args) = @_;
242              
243 53         845 my @newgen = map { [' '.$_] } $lite->text;
  2462         6031  
244 53         419 my (@oldgen,@openers,@closers,$skip_to);
245 53   66     352 my $genbase = $args{generation} || $self->latest;
246              
247 53 100       178 if (my $cont = $self->_contents) {
248 28         73 for (@$cont) {
249 1118 100       2004 if ($skip_to) {
250 4         7 push @openers, $_;
251 4 100       37 if (/^=$skip_to$/) {
252 2         4 undef $skip_to;
253             }
254 4         9 next;
255             }
256 1114 100       2967 if (my ($type,$gen) = /^([+-])(.+)/) {
257 7 50       23 $oldgen[-1][2] = [@closers] if @closers;
258 7         13 @closers = ();
259 7         18 push @openers, $_;
260 7 100       25 if (_is_parent_of($gen,$genbase) ^ ($type eq '+')) {
261 2         3 $skip_to = $gen;
262             }
263 7         19 next;
264             }
265 1107 100       2478 if (my ($gen) = /^=(.+)/) {
266 5         13 push @closers, $_;
267 5         15 next;
268             }
269 1102 50       2761 if (/^ /) {
270 1102 100       2033 $oldgen[-1][2] = [@closers] if @closers;
271 1102         2894 push @oldgen,[$_, [@openers]];
272 1102         1643 @openers = @closers = ();
273 1102         1687 next;
274             }
275 0         0 croak "Invalid format in element contents";
276             }
277 28 100       105 $oldgen[-1][2] = [@closers] if @closers;
278             } else {
279 25         574 $self->_contents([map $_->[0], @newgen]);
280 25         2444 return 1;
281             }
282              
283 28         184 $genbase =~ s/(\d+)$/$1+1/e;
  28         135  
284 28     2500   257 my @sd = Algorithm::Diff::sdiff( \@oldgen, \@newgen, sub { $_[0][0] });
  2500         18589  
285 28         12346 my (@newcont,@pending);
286 28         56 my $prev = 'u';
287 28         47 my $changed = 0;
288              
289 28         87 for (@sd) {
290 1362         2361 my ($ind,$c1,$c2) = @$_;
291 1362         1514 my @res1;
292 1362 100       2441 if ($c1) {
293 1102         1068 @res1 = (@{$c1->[1]},$c1->[0]);
  1102         2473  
294 1102 100       2359 push @res1,@{$c1->[2]} if defined $c1->[2];
  5         15  
295             }
296 1362 50       2917 my $res2 = $c2->[0] if $c2;
297              
298 1362 100 100     3596 push @newcont,"=$genbase\n" if ($prev ne 'u') && ($ind ne $prev);
299 1362 100 100     3335 if (@pending && ($ind ne 'c')) {
300 9         38 push @newcont, @pending, "=$genbase\n";
301 9         23 @pending=();
302             }
303 1362 100 100     7549 if (($prev =~ /[u+]/) && ($ind =~ /[c-]/)) {
304 9         31 push @newcont,"-$genbase\n";
305 9         17 $changed++;
306             }
307 1362 100       2369 if ($ind eq '+') {
308 260 100       493 push @newcont,"+$genbase\n" if ($prev ne $ind);
309 260         420 push @newcont, $res2;
310 260         311 $changed++;
311             } else {
312 1102         1617 push @newcont, @res1;
313             }
314 1362 100       2575 if ($ind eq 'c') {
315 38 100       87 push @pending,"+$genbase\n" if ($prev ne $ind);
316 38         60 push @pending, $res2;
317             }
318 1362         2805 $prev = $ind;
319             }
320              
321 28 100       107 push @newcont,"=$genbase\n" if ($prev ne 'u');
322 28 100       624 return unless $changed;
323 14         63 $self->_contents(\@newcont);
324 14         404 $genbase;
325             }
326              
327             sub _is_parent_of {
328 67     67   111 my ($gen1,$gen2) = @_;
329              
330 67         200 my @g1v = split /\./,$gen1;
331 67         145 my @g2v = split /\./,$gen2;
332 67   66     807 (shift @g1v,shift @g2v) while @g1v && @g2v && ($g1v[0] eq $g2v[0]);
      100        
333              
334 67 100       273 return 1 unless @g2v;
335 18 50       43 return 0 unless @g1v;
336 18 50       45 return 0 if @g1v > 1;
337              
338 18         95 $g1v[0] < $g2v[0];
339             }
340              
341             sub _update_ctrl {
342 23     23   90 my ($self,%args) = @_;
343              
344 23   33     135 my $path = $args{path} || $self->{path};
345 23         117 my ($vol,$dir,$fil) = splitpath($path);
346 23         564 $self->{$_} = $args{$_} for keys %args;
347 23         112 $self->{updated} = localtime->datetime;
348 23         2666 $self->save;
349             }
350              
351             sub _contents {
352 143     143   198 my $self = shift;
353              
354 143 100       751 $self->{contents} = shift if @_;
355 143 100       451 return unless exists $self->{contents};
356              
357 118         317 $self->{contents};
358             }
359              
360             sub _slurp_lite {
361 50     50   100 my ($self,$name) = @_;
362              
363 50         323 VCS::Lite->new($name);
364             }
365              
366             1;
367              
368             __END__