File Coverage

lib/Slaughter/Transport/revisionControl.pm
Criterion Covered Total %
statement 19 56 33.9
branch 2 18 11.1
condition 1 3 33.3
subroutine 4 9 44.4
pod 5 6 83.3
total 31 92 33.7


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3            
4             Slaughter::Transport::revisionControl - Transport base-class.
5            
6             =head1 SYNOPSIS
7            
8             This is a base-class for a generic revision control based transport.
9            
10             =cut
11              
12             =head1 DESCRIPTION
13            
14             This module implements the primitives which our transport API demands, but
15             it does so in an abstract fashion with the intention that sub-classes
16             will provide the missing configuration to allow it to be used.
17            
18             This module may be used by any revision-control system, or other tool,
19             that allows a fetch of a remote repository to be carried out by a simple
20             command such as:
21            
22             =for example begin
23            
24             $cmd repository-location destination-path
25            
26             =for example end
27            
28             In our derived Mercurical class we set the command to "C<hg clone>", similarly
29             in the GIT class we use "C<git clone>". Finally although it isn't a revision
30             control system our rsync implementation works via a subclass precisely
31             because it is possible to fetch a remote tree using a simple command,
32             in that case it is:
33            
34             =for example begin
35            
36             rsync -qazr repository-location destination-path
37            
38             =for example end
39            
40             B<NOTE>: A full checkout of the remote repository is always inititated by
41             this module.
42            
43             It is possible that a future extension to this module will allow an existing
44             repository to be uploaded in-place.
45            
46             =cut
47              
48             =head1 SUBCLASSING
49            
50             If you wish to write your own transport for a revision control tool,
51             or similar command that will fetch a remote repository, you must
52             subclass this class and implement the C<_init> method.
53            
54             The following parameters should be populated in your derived class:
55            
56             =over 8
57            
58             =item C<cmd_clone>
59            
60             The command to clone the repository. This will have the repository location, as specified by "C<--prefix>", and the destination directory appended to it.
61            
62             The command will have with the strings "C<#SRC#>" and "C<#DST#>" replaced with the source of the fetch and the destination into which to fetch it repectively.
63            
64             The following, taken from C<Slaughter::Transport::hg>, demonstrates this:
65            
66             =for example begin
67            
68             $self->{ 'cmd_clone' } = "hg clone #SRC# #DST#";
69            
70             =for example end
71            
72            
73             =item C<cmd_update>
74            
75             A command to call to update an I<existing> repository. Currently each time slaughter runs it will pull the remote repository from scratch to a brand new temporary directory, it is possible in the future we will work with a local directory that persists - at that point having the ability to both checkout and update a remote repository will be useful.
76            
77             =item C<cmd_version>
78            
79             A command to call which will output the version of the revision control system. This may be any command which outputs text, as the output is discarded. The purposes is to ensure that the binary required for cloning is present on the system.
80            
81             =item C<name>
82            
83             The name of the transport.
84            
85             =back
86            
87             For a sample implementation please consult C<Slaughter::Transport::hg>.
88            
89             =cut
90              
91              
92             =head1 METHODS
93            
94             Now follows documentation on the available methods.
95            
96             =cut
97              
98              
99 1     1   250 use strict;
  1         2  
  1         23  
100 1     1   2 use warnings;
  1         1  
  1         535  
101              
102              
103              
104             package Slaughter::Transport::revisionControl;
105              
106             #
107             # The version of our release.
108             #
109             our $VERSION = "3.0.5";
110              
111              
112              
113              
114             =head2 new
115            
116             Create a new instance of this object.
117            
118             This constructor calls the "C<_init>" method of any derived class, if present,
119             which is where we'll expect the setup mentioned in L</SUBCLASSING> to take place.
120            
121             =cut
122              
123             sub new
124             {
125 5     5 1 1028     my ( $proto, %supplied ) = (@_);
126 5   33     20     my $class = ref($proto) || $proto;
127              
128 5         6     my $self = {};
129              
130             #
131             # Allow user supplied values to override our defaults
132             #
133 5         10     foreach my $key ( keys %supplied )
134                 {
135 0         0         $self->{ lc $key } = $supplied{ $key };
136                 }
137              
138              
139             #
140             # Explicitly ensure we have no error.
141             #
142 5         9     $self->{ 'error' } = undef;
143              
144             #
145             # This will get replaced by sub-classes.
146             #
147 5         6     $self->{ 'name' } = "revisionControl";
148              
149 5         7     bless( $self, $class );
150              
151             #
152             # We expect a derived class will implement an "_init" method,
153             # which will populate the variables we expect.
154             #
155             # We don't call this method unconditionally for the simple reason
156             # that we wish this class to be constructable by our test suite.
157             #
158 5 100       17     if ( UNIVERSAL::can( $self, '_init' ) )
159                 {
160 4         5         $self->_init();
161 4         6         $self->{ 'setup' } = 1;
162                 }
163              
164 5         8     return $self;
165              
166             }
167              
168              
169             =head2 isAvailable
170            
171             Is this module available? This uses the details from the derived class
172             to determine whether I<that> transport is available.
173            
174             We regard the transport as available if the execution of the command
175             stored in L</cmd_version> succeeds.
176            
177             =cut
178              
179             sub isAvailable
180             {
181 0     0 1 0     my ($self) = (@_);
182              
183 0         0     $self->{ 'error' } = "";
184              
185             #
186             # If the _init method didn't get called we've not been subclassed,
187             # and that means we don't have the commands we should run setup.
188             #
189 0 0       0     if ( !$self->{ 'setup' } )
190                 {
191 0         0         $self->{ 'error' } =
192                       "This is a base-class, and should not be used directly\n";
193 0         0         return 0;
194                 }
195              
196 0 0       0     if ( !-d $self->{ 'transportdir' } )
197                 {
198 0         0         $self->{ 'error' } =
199                       "Transport directory went away: $self->{'transportdir'}\n";
200 0         0         return 0;
201                 }
202              
203 0 0       0     if ( system("$self->{'cmd_version'} >/dev/null 2>/dev/null") != 0 )
204                 {
205 0         0         $self->{ 'error' } =
206                       "Failed to execute '$self->{'cmd_version'}', is $self->{'name'} installed?\n";
207 0         0         return 0;
208                 }
209              
210 0         0     return 1;
211             }
212              
213              
214              
215             =head2 error
216            
217             Return the last error from the transport, this is set in L</isAvailable>.
218            
219             =cut
220              
221             sub error
222             {
223 0     0 1 0     my ($self) = (@_);
224 0         0     return ( $self->{ 'error' } );
225             }
226              
227              
228              
229             =head2 name
230            
231             Return the name of this transport. This will be setup in the derived class,
232             via the L</name> parameter.
233            
234             =cut
235              
236             sub name
237             {
238 5     5 1 5902     my ($self) = (@_);
239 5         8     return ( $self->{ 'name' } );
240             }
241              
242              
243              
244             =begin doc
245            
246             Clone the repository.
247            
248             This is called only once, when the object is constructed. It will be
249             called by slaughter, for example, to clone the repository prior to
250             processing policies.
251            
252             =end doc
253            
254             =cut
255              
256             sub setup
257             {
258 0     0 0       my ($self) = (@_);
259              
260             #
261             # The repository, and the destination to which we clone it.
262             #
263 0               my $repo = $self->{ 'prefix' };
264 0               my $dst = $self->{ 'transportdir' };
265              
266 0 0             $self->{ 'verbose' } && print "Fetching $repo into $dst\n";
267              
268             #
269             # Convert "#SRC#" and "#DST#" into the appropriate args from our
270             # cloning command, and then execute it.
271             #
272 0               my $cmd = $self->{ 'cmd_clone' };
273 0               $cmd =~ s/#SRC#/$repo/g;
274 0               $cmd =~ s/#DST#/$dst/g;
275              
276             #
277             # Hide output unless we're being verbose.
278             #
279 0 0             $cmd .= " >/dev/null 2>/dev/null" unless ( $self->{ 'verbose' } );
280              
281 0 0             if ( system("$cmd") != 0 )
282                 {
283 0 0                 $self->{ 'verbose' } &&
284                       print "WARNING: Failed to clone repository, command failed: $cmd";
285                 }
286             }
287              
288              
289              
290             =begin doc
291            
292             This is an internal/private method that merely returns the contents of the
293             named file - or undef on error.
294            
295             =end doc
296            
297             =cut
298              
299             sub _readFile
300             {
301 0     0         my ( $self, $file ) = (@_);
302              
303 0               my $txt = undef;
304              
305 0 0             open( my $handle, "<", $file ) or return ($txt);
306              
307 0               while ( my $line = <$handle> )
308                 {
309 0                   $txt .= $line;
310                 }
311 0               close($handle);
312              
313 0               return $txt;
314             }
315              
316              
317              
318             =head2 fetchContents
319            
320             Fetch a file from within the checked-out repository.
321            
322             Given a root repository of /path/to/repo/ the file is looked for beneath
323             /path/to/repo/files.
324            
325             =cut
326              
327             sub fetchContents
328             {
329 0     0 1       my ( $self, %args ) = (@_);
330              
331             #
332             # The prefix to fetch from: /files/, /modules/, or /policies/.
333             #
334 0               my $prefix = $args{ 'prefix' };
335              
336             #
337             # The file to retrieve.
338             #
339 0               my $file = $args{ 'file' };
340              
341             #
342             # The complete path.
343             #
344 0               my $complete = $self->{ 'transportdir' } . $prefix . $file;
345              
346             #
347             # Read the file.
348             #
349 0               return ( $self->_readFile($complete) );
350             }
351              
352              
353             1;
354              
355              
356              
357             =head1 AUTHOR
358            
359             Steve Kemp <steve@steve.org.uk>
360            
361             =cut
362              
363             =head1 LICENSE
364            
365             Copyright (c) 2010-2015 by Steve Kemp. All rights reserved.
366            
367             This module is free software;
368             you can redistribute it and/or modify it under
369             the same terms as Perl itself.
370             The LICENSE file contains the full text of the license.
371            
372             =cut
373