File Coverage

blib/lib/App/Sqitch/Command/bundle.pm
Criterion Covered Total %
statement 107 107 100.0
branch 23 24 95.8
condition 23 31 74.1
subroutine 20 20 100.0
pod 6 6 100.0
total 179 188 95.2


line stmt bran cond sub pod time code
1              
2             use 5.010;
3 3     3   1045 use strict;
  3         11  
4 3     3   14 use warnings;
  3         8  
  3         66  
5 3     3   13 use utf8;
  3         6  
  3         65  
6 3     3   13 use Moo;
  3         6  
  3         17  
7 3     3   64 use App::Sqitch::Types qw(Str Dir Maybe Bool);
  3         16  
  3         18  
8 3     3   1078 use File::Path qw(make_path);
  3         13  
  3         50  
9 3     3   3405 use Path::Class;
  3         7  
  3         178  
10 3     3   18 use Locale::TextDomain qw(App-Sqitch);
  3         5  
  3         154  
11 3     3   17 use App::Sqitch::X qw(hurl);
  3         7  
  3         24  
12 3     3   618 use File::Copy ();
  3         6  
  3         28  
13 3     3   1853 use List::Util qw(first);
  3         4046  
  3         69  
14 3     3   17 use namespace::autoclean;
  3         5  
  3         178  
15 3     3   17  
  3         4  
  3         23  
16             extends 'App::Sqitch::Command';
17             with 'App::Sqitch::Role::ContextCommand';
18              
19             our $VERSION = 'v1.3.0'; # VERSION
20              
21             has from => (
22             is => 'ro',
23             isa => Maybe[Str],
24             );
25              
26             has to => (
27             is => 'ro',
28             isa => Maybe[Str],
29             );
30              
31             has dest_dir => (
32             is => 'ro',
33             isa => Dir,
34             lazy => 1,
35             default => sub { dir 'bundle' },
36             );
37              
38             has all => (
39             is => 'ro',
40             isa => Bool,
41             default => 0
42             );
43              
44             my $self = shift;
45             dir $self->dest_dir, shift->top_dir->relative;
46 21     21 1 5914 }
47 21         400  
48             my ($self, $target) = @_;
49             my $dest = $self->dest_dir;
50             return {
51 19     19 1 2227 deploy => dir($dest, $target->deploy_dir->relative),
52 19         301 revert => dir($dest, $target->revert_dir->relative),
53             verify => dir($dest, $target->verify_dir->relative),
54 19         429 reworked_deploy => dir($dest, $target->reworked_deploy_dir->relative),
55             reworked_revert => dir($dest, $target->reworked_revert_dir->relative),
56             reworked_verify => dir($dest, $target->reworked_verify_dir->relative),
57             };
58             }
59              
60             return qw(
61             dest-dir|dir=s
62             all|a!
63             from=s
64             to=s
65             );
66             }
67              
68             my ( $class, $config, $opt ) = @_;
69              
70             my %params;
71              
72             if (my $dir = $opt->{dest_dir} || $config->get(key => 'bundle.dest_dir') ) {
73             $params{dest_dir} = dir $dir;
74             }
75              
76             # Make sure we get the --all, --from and --to options passed through.
77             for my $key (qw(all from to)) {
78             $params{$key} = $opt->{$key} if exists $opt->{$key};
79             }
80              
81             return \%params;
82             }
83              
84             my $self = shift;
85             my ($targets, $changes) = $self->parse_args(
86             all => $self->all,
87             args => \@_,
88             );
89              
90 14     14 1 51610 # Warn if --to or --from is specified for more thane one target.
91 14         107 if ( @{ $targets } > 1 && ($self->from || $self->to) ) {
92             $self->sqitch->warn(__(
93             "Use of --to or --from to bundle multiple targets is not recommended.\nPass them as arguments after each target argument, instead."
94             ));
95             }
96              
97 11 100 66     70 # Die if --to or --from and changes are specified.
  11   66     59  
98 2         33 if ( @{ $changes } && ($self->from || $self->to) ) {
99             hurl bundle => __(
100             'Cannot specify both --from or --to and change arguments'
101             );
102             }
103              
104 11 100 66     182 # Time to get started!
  11   66     39  
105 1         4 $self->info(__x 'Bundling into {dir}', dir => $self->dest_dir );
106             $self->bundle_config;
107              
108             if (my @fromto = grep { $_ } $self->from, $self->to) {
109             # One set of from/to options for all targets.
110             for my $target (@{ $targets }) {
111 10         188 $self->bundle_plan($target, @fromto);
112 10         1527 $self->bundle_scripts($target, @fromto);
113             }
114 10 100       78 } else {
  20         57  
115             # Separate from/to options for all targets.
116 2         4 for my $target (@{ $ targets }) {
  2         5  
117 4         19 my @fromto = splice @{ $changes }, 0, 2;
118 4         27 $self->bundle_plan($target, @fromto);
119             $self->bundle_scripts($target, @fromto);
120             }
121             }
122 8         14  
  8         25  
123 10         18 return $self;
  10         37  
124 10         42 }
125 10         116  
126             my ( $self, $src, $dst ) = @_;
127              
128             hurl bundle => __x(
129 10         428 'Cannot copy {file}: does not exist',
130             file => $src,
131             ) unless -e $src;
132              
133 131     131   31028 if (-e $dst) {
134             # Skip the file if it is up-to-date.
135 131 100       434 return $self if -M $dst <= -M $src;
136             } else {
137             # Create the directory.
138             $self->_mkpath( $dst->dir );
139             }
140 130 100       4979  
141             $self->debug(' ', __x(
142 19 100       704 "Copying {source} -> {dest}",
143             source => $src,
144             dest => $dst
145 111         4752 ));
146              
147             # Stringify to work around bug in File::Copy warning on 5.10.0.
148 112         452 File::Copy::copy "$src", "$dst" or hurl bundle => __x(
149             'Cannot copy "{source}" to "{dest}": {error}',
150             source => $src,
151             dest => $dst,
152             error => $!,
153             );
154             return $self;
155 112 100       18524 }
156              
157             my $self = shift;
158             $self->info(__ 'Writing config');
159             my $file = $self->sqitch->config->local_file;
160             $self->_copy_if_modified( $file, $self->dest_dir->file( $file->basename ) );
161 111         37390 }
162              
163             my ($self, $target, $from, $to) = @_;
164              
165 11     11 1 3506 my $dir = $self->dest_top_dir($target);
166 11         48  
167 11         950 if (!defined $from && !defined $to) {
168 11         657 $self->info(__ 'Writing plan');
169             my $file = $target->plan_file;
170             return $self->_copy_if_modified(
171             $file,
172 17     17 1 1035 $dir->file( $file->basename ),
173             );
174 17         52 }
175              
176 17 100 100     6298 $self->info(__x(
177 10         35 'Writing plan from {from} to {to}',
178 10         902 from => $from // '@ROOT',
179 10         1657 to => $to // '@HEAD',
180             ));
181              
182             $self->_mkpath( $dir );
183             $target->plan->write_to(
184             $dir->file( $target->plan_file->basename ),
185 7   100     49 $from,
      100        
186             $to,
187             );
188             }
189              
190             my ($self, $target, $from, $to) = @_;
191 7         890 my $plan = $target->plan;
192 7         131  
193             my $from_index = $plan->index_of(
194             $from // '@ROOT'
195             ) // hurl bundle => __x(
196             'Cannot find change {change}',
197             change => $from,
198             );
199              
200 19     19 1 595 my $to_index = $plan->index_of(
201 19         381 $to // '@HEAD'
202             ) // hurl bundle => __x(
203 19   100     1660 'Cannot find change {change}',
      66        
204             change => $to,
205             );
206              
207             $self->info(__ 'Writing scripts');
208             $plan->position( $from_index );
209             my $dir_for = $self->dest_dirs_for($target);
210 17   100     96  
      33        
211             while ( $plan->position <= $to_index ) {
212             my $change = $plan->current // last;
213             $self->info(' + ', $change->format_name_with_tags);
214             my $prefix = $change->is_reworked ? 'reworked_' : '';
215             my @path = $change->path_segments;
216             if (-e ( my $file = $change->deploy_file )) {
217 17         55 $self->_copy_if_modified(
218 17         1573 $file,
219 17         484 $dir_for->{"${prefix}deploy"}->file(@path)
220             );
221 17         5144 }
222 56   50     396 if (-e ( my $file = $change->revert_file )) {
223 56         161 $self->_copy_if_modified(
224 56 100       2165 $file,
225 56         1036 $dir_for->{"${prefix}revert"}->file(@path)
226 56 50       1197 );
227             }
228             if (-e ( my $file = $change->verify_file )) {
229 56         6230 $self->_copy_if_modified(
230             $file,
231             $dir_for->{"${prefix}verify"}->file(@path)
232 56 100       910 );
233             }
234             $plan->next;
235 44         5176 }
236              
237             return $self;
238 56 100       2139 }
239              
240             1;
241 4         420  
242              
243             =head1 Name
244 56         5763  
245             App::Sqitch::Command::bundle - Bundle Sqitch changes for distribution
246              
247 17         208 =head1 Synopsis
248              
249             my $cmd = App::Sqitch::Command::bundle->new(%params);
250             $cmd->execute;
251              
252             =head1 Description
253              
254             Bundles a Sqitch project for distribution. Done by creating a new directory
255             and copying the configuration file, plan file, and change files into it.
256              
257             =head1 Interface
258              
259             =head2 Attributes
260              
261             =head3 C<from>
262              
263             Change from which to build the bundled plan.
264              
265             =head3 C<to>
266              
267             Change up to which to build the bundled plan.
268              
269             =head3 C<all>
270              
271             Boolean indicating whether or not to run the command against all plans in the
272             project.
273              
274             =head2 Instance Methods
275              
276             =head3 C<execute>
277              
278             $bundle->execute($command);
279              
280             Executes the C<bundle> command.
281              
282             =head3 C<bundle_config>
283              
284             $bundle->bundle_config;
285              
286             Copies the configuration file to the bundle directory.
287              
288             =head3 C<bundle_plan>
289              
290             $bundle->bundle_plan($target);
291              
292             Copies the plan file for the specified target to the bundle directory.
293              
294             =head3 C<bundle_scripts>
295              
296             $bundle->bundle_scripts($target);
297              
298             Copies the deploy, revert, and verify scripts for each step in the plan for
299             the specified target to the bundle directory. Files in the script directories
300             that do not correspond to changes in the plan will not be copied.
301              
302             =head3 C<dest_top_dir>
303              
304             my $top_dir = $bundle->top_dir($target);
305              
306             Returns the destination top directory for the specified target.
307              
308             =head3 C<dest_dirs_for>
309              
310             my $dirs = $bundle->dest__dirs_for($target);
311              
312             Returns a hash of change script destination directories for the specified
313             target. The keys are the types of scripts, and include:
314              
315             =over
316              
317             =item C<deploy>
318              
319             =item C<revert>
320              
321             =item C<verfiy>
322              
323             =item C<reworked_deploy>
324              
325             =item C<reworked_revert>
326              
327             =item C<reworked_verfiy>
328              
329             =back
330              
331             =head1 See Also
332              
333             =over
334              
335             =item L<sqitch-bundle>
336              
337             Documentation for the C<bundle> command to the Sqitch command-line client.
338              
339             =item L<sqitch>
340              
341             The Sqitch command-line client.
342              
343             =back
344              
345             =head1 Author
346              
347             David E. Wheeler <david@justatheory.com>
348              
349             =head1 License
350              
351             Copyright (c) 2012-2022 iovation Inc., David E. Wheeler
352              
353             Permission is hereby granted, free of charge, to any person obtaining a copy
354             of this software and associated documentation files (the "Software"), to deal
355             in the Software without restriction, including without limitation the rights
356             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
357             copies of the Software, and to permit persons to whom the Software is
358             furnished to do so, subject to the following conditions:
359              
360             The above copyright notice and this permission notice shall be included in all
361             copies or substantial portions of the Software.
362              
363             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
364             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
365             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
366             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
367             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
368             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
369             SOFTWARE.
370              
371             =cut