File Coverage

blib/lib/LCFG/Build/VCS/SVN.pm
Criterion Covered Total %
statement 32 190 16.8
branch 0 60 0.0
condition 0 6 0.0
subroutine 11 26 42.3
pod 9 13 69.2
total 52 295 17.6


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