File Coverage

blib/lib/LCFG/Build/VCS/SVN.pm
Criterion Covered Total %
statement 30 188 15.9
branch 0 60 0.0
condition 0 6 0.0
subroutine 10 25 40.0
pod 9 13 69.2
total 49 292 16.7


line stmt bran cond sub pod time code
1             package LCFG::Build::VCS::SVN; # -*-perl-*-
2 1     1   1077 use strict;
  1         4  
  1         35  
3 1     1   4 use warnings;
  1         1  
  1         40  
4              
5             # $Id: SVN.pm.in 31796 2017-01-19 13:31:51Z squinney@INF.ED.AC.UK $
6             # $Source: /var/cvs/dice/LCFG-Build-VCS/lib/LCFG/Build/VCS/SVN.pm.in,v $
7             # $Revision: 31796 $
8             # $HeadURL: https://svn.lcfg.org/svn/source/tags/LCFG-Build-VCS/LCFG_Build_VCS_0_2_3/lib/LCFG/Build/VCS/SVN.pm.in $
9             # $Date: 2017-01-19 13:31:51 +0000 (Thu, 19 Jan 2017) $
10              
11             our $VERSION = '0.2.3';
12              
13 1     1   4 use File::Path ();
  1         0  
  1         10  
14 1     1   3 use File::Spec ();
  1         1  
  1         16  
15 1     1   3 use IPC::Run qw(run);
  1         1  
  1         63  
16 1     1   4 use IO::File qw(O_WRONLY O_CREAT O_NONBLOCK O_NOCTTY);
  1         1  
  1         43  
17 1     1   3 use Try::Tiny;
  1         1  
  1         42  
18 1     1   527 use URI ();
  1         3373  
  1         18  
19              
20 1     1   5 use Moose;
  1         2  
  1         6  
21              
22             with 'LCFG::Build::VCS';
23              
24             has '+binpath' => ( default => 'svn' );
25              
26             has '+id' => ( default => 'SVN' );
27              
28 1     1   4457 no Moose;
  1         1  
  1         4  
29             __PACKAGE__->meta->make_immutable;
30              
31             sub auto_detect {
32 0     0 1   my ( $class, $dir ) = @_;
33              
34 0           my $is_svn = 0;
35             try {
36 0     0     my $vcs = $class->new( module => 'test',
37             workdir => $dir );
38              
39 0           $vcs->run_cmd( 'info', $dir );
40 0           $is_svn = 1;
41 0           };
42              
43 0           return $is_svn;
44             }
45              
46             sub get_info {
47 0     0 1   my ( $self, $infokey, $directory ) = @_;
48              
49 0 0         if ( !defined $directory ) {
50 0           $directory = $self->workdir;
51             }
52              
53 0           my @info = $self->run_infocmd( 'info', $directory );
54              
55 0           for my $line (@info) {
56 0 0         if ( $line =~ m/^
57             ([^:]+) # key
58             :
59             \s+
60             (.+) # value
61             $/x ) {
62 0           my ( $key, $value ) = ( $1, $2 );
63 0           $key =~ s/\s+//g;
64              
65 0 0         if ( $key eq $infokey ) {
66 0           return $value;
67             }
68             }
69             }
70              
71 0           return;
72             }
73              
74             sub _build_url {
75 0     0     my ( $self, $dir, $section, @extra ) = @_;
76              
77 0           my $url = $self->get_info( 'URL', $dir );
78              
79 0           my $u = URI->new($url);
80 0           my @path = split '/', $u->path;
81              
82 0           while ( defined( my $part = pop @path ) ) {
83 0 0 0       if ( $part eq 'trunk' || $part eq 'tags' || $part eq 'branches' ) {
      0        
84 0           last;
85             }
86             }
87              
88 0           my $module = $self->module;
89              
90 0           my $tags_path = join q{/}, @path, $section, $module, @extra;
91              
92 0           $u->path($tags_path);
93              
94 0           return $u->as_string;
95             }
96              
97             sub tag_base {
98 0     0 0   my ( $self, $dir ) = @_;
99              
100 0           return $self->_build_url( $dir, 'tags' );
101             }
102              
103             sub tag_url {
104 0     0 0   my ( $self, $tag, $dir ) = @_;
105              
106 0           return $self->_build_url( $dir, 'tags', $tag );
107             }
108              
109             sub trunk_url {
110 0     0 0   my ( $self, $dir ) = @_;
111              
112 0           return $self->_build_url( $dir, 'trunk' );
113             }
114              
115             sub run_infocmd {
116 0     0 1   my ( $self, @args ) = @_;
117              
118 0           my @cmd = $self->build_cmd(@args);
119              
120 0           my ( $in, $out, $err );
121              
122 0           my $success = run \@cmd, \$in, \$out, \$err;
123 0 0         if ( !$success ) {
124 0           die "Error whilst running @cmd: $err\n";
125             }
126              
127 0           my @out = split /[\r\n]+/, $out;
128              
129 0           return @out;
130             }
131              
132             sub checkcommitted {
133 0     0 1   my ( $self, $dir ) = @_;
134              
135 0           warn "Checking that all file changes have been committed.\n";
136              
137 0 0         if ( !defined $dir ) {
138 0           $dir = $self->workdir;
139             }
140              
141 0           my @notcommitted = $self->run_infocmd( 'status', '--quiet', $dir );
142              
143 0           my $allcommitted;
144 0 0         if ( scalar @notcommitted > 0 ) {
145 0           $allcommitted = 0;
146             }
147             else {
148 0           $allcommitted = 1;
149             }
150              
151 0 0         if (wantarray) {
152             # This makes the output the same as for the CVS module
153              
154 0           my @shortnames;
155 0           for my $file (@notcommitted) {
156 0 0         if ( $file =~ m/^M\s+(.+)$/ ) {
157 0           my $short = File::Spec->abs2rel( $1, $dir );
158 0           push @shortnames, $short;
159             }
160             }
161              
162 0           return ( $allcommitted, @shortnames );
163             }
164             else {
165 0           return $allcommitted;
166             }
167              
168             }
169              
170             sub checkout_project {
171 0     0 0   my ( $self, $version, $outdir ) = @_;
172              
173 0           my $url;
174 0 0         if ( defined $version ) {
175 0           my $tag = $self->gen_tag($version);
176 0           $url = $self->tag_url($tag);
177             }
178             else {
179 0           $url = $self->trunk_url();
180             }
181              
182 0           my @cmd = ( 'checkout', $url );
183 0 0         if ( defined $outdir ) {
184 0           push @cmd, $outdir;
185             }
186              
187 0           $self->run_cmd(@cmd);
188              
189 0           return;
190             }
191              
192             sub export {
193 0     0 1   my ( $self, $version, $dir ) = @_;
194              
195 0           my $tag = $self->gen_tag($version);
196              
197 0           my $target = join q{-}, $self->module, $version;
198 0           my $exportdir = File::Spec->catdir( $dir, $target );
199              
200 0 0         if ( !$self->dryrun ) {
201 0 0         if ( !-d $dir ) {
202 0           eval { File::Path::mkpath($dir) };
  0            
203 0 0         if ($@) {
204 0           die "Could not create $dir: $@\n";
205             }
206             }
207              
208 0 0         if ( -d $exportdir ) {
209 0           File::Path::rmtree($exportdir);
210             }
211             }
212              
213 0           my $tag_url = $self->tag_url($tag);
214              
215             # It appears that svn will pretend that you can export anything
216             # whether or not it actually exists. So we have to check for
217             # existence first with a 'list' command.
218              
219 0           eval { $self->run_infocmd( 'list', $tag_url ) };
  0            
220 0 0         if ( $@ ) {
221 0           die "Could not find tag $tag_url\n";
222             }
223              
224 0           $self->run_cmd( 'export', $tag_url, $exportdir );
225              
226 0           return $exportdir;
227             }
228              
229             sub export_devel {
230 0     0 1   my ( $self, $version, $dir ) = @_;
231              
232 0           my $target = join q{-}, $self->module, $version;
233              
234 0           my $exportdir = File::Spec->catdir( $dir, $target );
235              
236 0 0         if ( !$self->dryrun ) {
237 0 0         if ( !-d $dir ) {
238 0           eval { File::Path::mkpath($dir) };
  0            
239 0 0         if ($@) {
240 0           die "Could not create $dir: $@\n";
241             }
242             }
243              
244 0 0         if ( -d $exportdir ) {
245 0           File::Path::rmtree($exportdir);
246             }
247             }
248              
249 0           $self->run_cmd( 'export', $self->workdir, $exportdir );
250              
251 0           return $exportdir;
252              
253             }
254              
255             sub genchangelog {
256 0     0 1   my ($self) = @_;
257              
258 0           warn "Generating Changelog from subversion log\n";
259              
260 0           my $dir = $self->workdir;
261 0           my $logfile = $self->logfile;
262              
263 0 0         if ( !-e $logfile ) {
264              
265             # This bit borrowed from File::Touch
266 0 0         sysopen my $fh, $logfile, O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY
267             or die "Cannot create $logfile : $!\n";
268 0 0         $fh->close or die "Cannot close $logfile : $!\n";
269              
270             # Assume it is not already part of the repository
271 0           $self->run_cmd( 'add', $logfile );
272             }
273              
274             # MUST do an update beforehand otherwise some logs get missed.
275 0           $self->run_cmd( 'update', $dir );
276              
277 0           my @cmd = ( 'svn2cl', '--output', $logfile, $dir );
278 0 0         if ( $self->dryrun ) {
279 0           print "Dry-run: @cmd\n";
280             }
281             else {
282 0           my ( $in, $out, $err );
283 0           my $success = run \@cmd, \$in, \$out, \$err;
284 0 0         if ( !$success ) {
285 0           die "Could not run svn2cl: $err\n";
286             }
287             }
288              
289 0           return;
290             }
291              
292             sub import_project {
293 0     0 1   my ( $self, $dir, $version, $message ) = @_;
294              
295 0 0         if ( !defined $message ) {
296 0           $message = 'Imported with LCFG build tools';
297             }
298              
299 0           my $module = $self->module;
300 0           my $trunk_url = $self->trunk_url();
301              
302             # we want this to fail...
303 0           eval { $self->run_infocmd( 'list', $trunk_url ) };
  0            
304 0 0         if ( !$@ ) {
305 0           die "There is already a project named \"$module\" at $trunk_url\n";
306             }
307              
308 0           $self->run_cmd( 'import',
309             '--message', $message,
310             $dir, $trunk_url );
311              
312 0           my $tag_base = $self->tag_base();
313              
314             # Ensure the tags directory for this project actually exists
315 0           eval { $self->run_infocmd( 'list', $tag_base ) };
  0            
316 0 0         if ( $@ ) {
317 0           warn "Creating tag base directory for $module\n";
318 0           $self->run_cmd( 'mkdir', '--message', "Creating tag base directory for $module", $tag_base );
319             }
320              
321 0           my $tag = $self->gen_tag($version);
322              
323 0           my $tagurl = $self->tag_url($tag);
324              
325 0           $self->run_cmd( 'copy', '--message', "Tagging $module at $tag", $trunk_url, $tagurl );
326              
327 0           return;
328             }
329              
330             sub tagversion {
331 0     0 1   my ( $self, $version ) = @_;
332              
333 0           my $module = $self->module;
334              
335 0           warn "Tagging $module at version $version\n";
336              
337 0           $self->update_changelog($version);
338              
339 0           my $dir = $self->workdir;
340              
341 0 0         $self->run_cmd( 'commit', '--message', "$module release: $version", $dir )
342             or die "Could not mark release for $dir at $version\n";
343              
344 0           my $tag_base = $self->tag_base();
345              
346             # Ensure the tags directory for this project actually exists
347 0           eval { $self->run_infocmd( 'list', $tag_base ) };
  0            
348 0 0         if ( $@ ) {
349 0           warn "Creating tag base directory for $module\n";
350 0           $self->run_cmd( 'mkdir', '--message', "Creating tag base directory for $module", $tag_base );
351             }
352              
353 0           my $tag = $self->gen_tag($version);
354              
355 0           my $tagurl = $self->tag_url($tag);
356              
357             # It would appear that it is occasionally necessary to do an
358             # "update" to get things into a sane state so that a copy will
359             # complete successfully. See https://bugs.lcfg.org/show_bug.cgi?id=302
360              
361 0           $self->run_cmd( 'update', $dir );
362              
363 0           $self->run_cmd( 'copy', '--message', "Tagging $module at $tag", $dir, $tagurl );
364              
365 0           $self->run_cmd( 'update', $dir );
366              
367 0           return;
368             }
369              
370             __END__
371              
372             =head1 NAME
373              
374             LCFG::Build::VCS::SVN - LCFG build tools for subversion version-control
375              
376             =head1 VERSION
377              
378             This documentation refers to LCFG::Build::VCS::SVN version 0.2.3
379              
380             =head1 SYNOPSIS
381              
382             my $dir = ".";
383              
384             my $spec = LCFG::Build::PkgSpec->new_from_metafile("$dir/lcfg.yml");
385              
386             my $vcs = LCFG::Build::VCS::SVN->new( module => $spec->fullname,
387             workdir => $dir );
388              
389             $vcs->genchangelog();
390              
391             if ( $vcs->checkcommitted() ) {
392             $vcs->tagversion();
393             }
394              
395             =head1 DESCRIPTION
396              
397             This is part of a suite of tools designed to provide a standardised
398             interface to version-control systems so that the LCFG build tools can
399             deal with project version-control in a high-level abstract fashion.
400              
401             This module implements the interface specified by
402             L<LCFG::Build::VCS>. It provides support for LCFG projects which use
403             the subversion version-control system. Facilities are available for
404             procedures such as importing and exporting projects, doing tagged
405             releases, generating the project changelog from the version-control
406             log and checking all changes are committed.
407              
408             More information on the LCFG build tools is available from the website
409             http://www.lcfg.org/doc/buildtools/
410              
411             =head1 ATTRIBUTES
412              
413             =over
414              
415             =item module
416              
417             The name of the software package in this repository. This is required
418             and there is no default value.
419              
420             =item workdir
421              
422             The directory in which the svn commands should be carried out. This is
423             required and if none is specified then it will default to '.', the
424             current working directory. This must be an absolute path but if you
425             pass in a relative path coercion will automatically occur based on the
426             current working directory.
427              
428             =item binpath
429              
430             The name of the svn executable, by default this is C<svn>.
431              
432             =item quiet
433              
434             This is a boolean value which controls the quietness of the subversion
435             commands. By default it is false and commands, such as svn, will print
436             some extra stuff to the screen.
437              
438             =item dryrun
439              
440             This is a boolean value which controls whether the commands will
441             actually have a real effect or just print out what would be done. By
442             default it is false.
443              
444             =item logname
445              
446             The name of the logfile to which information should be directed when
447             doing version updates. This is also the name of the logfile to be used
448             if you utilise the automatic changelog generation option. The default
449             file name is 'ChangeLog'.
450              
451             =back
452              
453             =head1 SUBROUTINES/METHODS
454              
455             The following class methods are available:
456              
457             =over
458              
459             =item new
460              
461             Creates a new instance of the class.
462              
463             =item auto_detect($dir)
464              
465             This method returns a boolean value which indicates whether or not the
466             specified directory is part of a checked out working copy of a
467             subversion repository.
468              
469             =back
470              
471             The following instance methods are available:
472              
473             =over
474              
475             =item get_info($key)
476              
477             This method can be used to query any information (for example URL or
478             Repository Root) which is available in the output of the subversion
479             C<info> command. The key has had the whitespace stripped, for example,
480             "Last Changed Author" becomes "LastChangedAuthor". If you request
481             information for a key which is not present this method will die.
482              
483             =item checkcommitted()
484              
485             Test to see if there are any uncommitted files in the project
486             directory. Note this test does not spot files which have not been
487             added to the version-control system. In scalar context the subroutine
488             returns 1 if all files are committed and 0 (zero) otherwise. In list
489             context the subroutine will return this code along with a list of any
490             files which require committing.
491              
492             =item genchangelog()
493              
494             This method will generate a changelog (the name of which is controlled
495             by the logname attribute) from the log kept within the version-control
496             system. For subversion the svn2cl(1) command is used.
497              
498             =item tagversion($version)
499              
500             This method is used to tag a set of files for a project at a
501             particular version. It will also update the changelog
502             appropriately. Tags are generated using the I<gen_tag()> method, see
503             below for details.
504              
505             =item gen_tag($version)
506              
507             Tags are generated from the name and version details passed in by
508             replacing any hyphens or dots with underscores and joining the two
509             fields with an underscore. For example, lcfg-foo and 1.0.1 would
510             become lcfg_foo_1_0_1.
511              
512             =item run_cmd(@args)
513              
514             A method used to handle the running of commands for the particular
515             version-control system. Although we could have used the proper perl
516             API for subversion it was a lot quicker to just wrap the command line
517             tools. This method honours the dry-run setting and when a dry-run has
518             been requested will print out the command and not execute.
519              
520             For example:
521              
522             $vcs->run_cmd( 'update', $workingcopydir );
523              
524             =item run_infocmd(@args)
525              
526             This is similar to run_cmd( ) except that it will B<always> run the
527             command. This is for executing commands which just gather information
528             and do not modify the repository or working copy.
529              
530             $vcs->run_infocmd( 'ls', '-R', $repos_url );
531              
532             =item export( $version, $dir )
533              
534             This will export a particular tagged version of the module. You need
535             to specify the target "build" directory into which the exported tree
536             will be put. The exported tree will be named like
537             "modulename-version". For example:
538              
539             my $vcs = LCFG::Build::VCS::SVN->new(module => "lcfg-foo");
540             $vcs->export( "1.2.3", "/tmp" );
541              
542             Would give you an exported tree of code for the lcfg-foo module tagged
543             as lcfg_foo_1_2_3 and it would be put into /tmp/lcfg-foo-1.2.3/
544              
545             Returns the name of the directory into which the tree was exported.
546              
547             =item export_devel( $version, $dir )
548              
549             This is similar to the export method. It takes the current working
550             copy tree for a module and exports it directly to another tree based
551             in the specified target "build" directory.
552              
553             my $vcs = LCFG::Build::VCS::SVN->new(module => "lcfg-foo");
554             $vcs->export_devel( "1.2.3_dev", "/tmp" );
555              
556             Would give you an exported tree of code for the lcfg-foo module
557             directory and it would be put into /tmp/lcfg-foo-1.2.3_dev/
558              
559             Returns the name of the directory into which the tree was exported.
560              
561             =item import_project( $dir, $version, $message )
562              
563             Imports a project source tree into the version-control system. You
564             need to specify the version for the initial tag. Optionally you can
565             specify a message which will be used.
566              
567             =item logfile()
568              
569             This is a convenience method which returns the full path to the
570             logfile based on the workdir and logname attributes.
571              
572             =back
573              
574             =head1 DEPENDENCIES
575              
576             This module is L<Moose> powered and it depends on
577             L<LCFG::Build::VCS>. You will need a working C<svn> executable
578             somewhere on your system and a subversion repository for this module
579             to be in anyway useful.
580              
581             =head1 SEE ALSO
582              
583             L<LCFG::Build::PkgSpec>, L<LCFG::Build::VCS::None>, L<LCFG::Build::Tools>
584              
585             =head1 PLATFORMS
586              
587             This is the list of platforms on which we have tested this
588             software. We expect this software to work on any Unix-like platform
589             which is supported by Perl.
590              
591             FedoraCore5, FedoraCore6, ScientificLinux5
592              
593             =head1 BUGS AND LIMITATIONS
594              
595             There are no known bugs in this application. Please report any
596             problems to bugs@lcfg.org, feedback and patches are also always very
597             welcome.
598              
599             =head1 AUTHOR
600              
601             Stephen Quinney <squinney@inf.ed.ac.uk>
602              
603             =head1 LICENSE AND COPYRIGHT
604              
605             Copyright (C) 2008-2013 University of Edinburgh. All rights reserved.
606              
607             This library is free software; you can redistribute it and/or modify
608             it under the terms of the GPL, version 2 or later.
609              
610             =cut