File Coverage

blib/lib/App/Sqitch/Command/rework.pm
Criterion Covered Total %
statement 81 81 100.0
branch 13 14 92.8
condition 7 14 50.0
subroutine 12 12 100.0
pod 1 1 100.0
total 114 122 93.4


line stmt bran cond sub pod time code
1              
2             use 5.010;
3 2     2   1083 use strict;
  2         7  
4 2     2   12 use warnings;
  2         4  
  2         44  
5 2     2   8 use utf8;
  2         4  
  2         62  
6 2     2   11 use Locale::TextDomain qw(App-Sqitch);
  2         4  
  2         14  
7 2     2   68 use App::Sqitch::X qw(hurl);
  2         5  
  2         25  
8 2     2   424 use File::Copy;
  2         5  
  2         23  
9 2     2   1554 use Moo;
  2         4219  
  2         190  
10 2     2   15 use App::Sqitch::Types qw(Str ArrayRef Bool Maybe);
  2         5  
  2         16  
11 2     2   759 use namespace::autoclean;
  2         4  
  2         32  
12 2     2   1962  
  2         6  
  2         21  
13             extends 'App::Sqitch::Command';
14             with 'App::Sqitch::Role::ContextCommand';
15              
16             our $VERSION = 'v1.3.0'; # VERSION
17              
18             has change_name => (
19             is => 'ro',
20             isa => Maybe[Str],
21             );
22              
23             has requires => (
24             is => 'ro',
25             isa => ArrayRef[Str],
26             default => sub { [] },
27             );
28              
29             has conflicts => (
30             is => 'ro',
31             isa => ArrayRef[Str],
32             default => sub { [] },
33             );
34              
35             has all => (
36             is => 'ro',
37             isa => Bool,
38             default => 0
39             );
40              
41             has note => (
42             is => 'ro',
43             isa => ArrayRef[Str],
44             default => sub { [] },
45             );
46              
47             has open_editor => (
48             is => 'ro',
49             isa => Bool,
50             lazy => 1,
51             default => sub {
52             my $self = shift;
53             return $self->sqitch->config->get(
54             key => 'rework.open_editor',
55             as => 'bool',
56             ) // $self->sqitch->config->get(
57             key => 'add.open_editor',
58             as => 'bool',
59             ) // 0;
60             },
61             );
62              
63             return qw(
64             change-name|change|c=s
65             requires|r=s@
66             conflicts|x=s@
67             all|a!
68             note|n|m=s@
69             open-editor|edit|e!
70             );
71             }
72              
73             my ( $class, $config, $opt ) = @_;
74             # Just keep the options.
75             return $opt;
76             }
77              
78             my $self = shift;
79             my ($name, $targets, $changes) = $self->parse_args(
80             names => [$self->change_name],
81             all => $self->all,
82 11     11 1 42532 args => \@_,
83 11         250 no_changes => 1,
84             );
85              
86             # Check if the name is identified as a change.
87             $name ||= shift @{ $changes } || $self->usage;
88              
89             my $note = join "\n\n", => @{ $self->note };
90             my ($first_change, %reworked, @files, %seen);
91 10   0     69  
      33        
92             for my $target (@{ $targets }) {
93 10         27 my $plan = $target->plan;
  10         72  
94 10         31 my $file = $plan->file;
95             my $spec = $reworked{$file} ||= { scripts => [] };
96 10         16 my ($prev, $reworked);
  10         43  
97 15         647 if ($prev = $spec->{prev}) {
98 15         1917 # Need a dupe for *this* target so script names are right.
99 15   100     4580 $reworked = ref($prev)->new(
100 15         565 plan => $plan,
101 15 100       58 name => $name,
102             );
103 1         28  
104             # Copy the rework tags to the previous instance in this plan.
105             my $new_prev = $spec->{prev} = $plan->get(
106             $name . [$plan->last_tagged_change->tags]->[-1]->format_name
107             );
108             $new_prev->add_rework_tags($prev->rework_tags);
109 1         291 $prev = $new_prev;
110              
111             } else {
112 1         19 # Rework it.
113 1         26 $reworked = $spec->{change} = $plan->rework(
114             name => $name,
115             requires => $self->requires,
116             conflicts => $self->conflicts,
117 14         147 note => $note,
118             );
119             $first_change ||= $reworked;
120              
121             # Get the latest instance of the change.
122             $prev = $spec->{prev} = $plan->get(
123 12   66     104 $name . [$plan->last_tagged_change->tags]->[-1]->format_name
124             );
125             }
126 12         84  
127             # Record the files to be copied to the previous change name.
128             push @{ $spec->{scripts} } => map {
129             push @files => $_->[0] if -e $_->[0];
130             $_;
131             } grep {
132 13         77 !$seen{ $_->[0] }++;
133 36 100       477 } (
134 36         1429 [ $reworked->deploy_file, $prev->deploy_file ],
135             [ $reworked->revert_file, $prev->revert_file ],
136 13         40 [ $reworked->verify_file, $prev->verify_file ],
  39         1661  
137             );
138              
139             # Replace the revert file with the previous deploy file.
140             push @{ $spec->{scripts} } => [
141             $reworked->deploy_file,
142             $reworked->revert_file,
143             $prev->revert_file,
144 12         1265 ] unless $seen{$prev->revert_file}++;
145             }
146              
147             # Make sure we have a note.
148 13 100       118 $note = $first_change->request_note(
149             for => __ 'rework',
150             scripts => \@files,
151             );
152 8         708  
153             # Time to write everything out.
154             for my $target (@{ $targets }) {
155             my $plan = $target->plan;
156             my $file = $plan->file;
157             my $spec = delete $reworked{$file} or next;
158 8         63  
  8         23  
159 13         1194 # Copy the files for this spec.
160 13         242 $self->_copy(@{ $_ }) for @{ $spec->{scripts } };
161 13 100       119  
162             # We good, write the plan file back out.
163             $plan->write_to( $plan->file );
164 12         348  
  12         41  
  48         140  
165             # Let the user know.
166             $self->info(__x(
167 12         183 'Added "{change}" to {file}.',
168             change => $spec->{change}->format_op_name_dependencies,
169             file => $plan->file,
170             ));
171             }
172              
173 12         71 # Now tell them what to do.
174             $self->info(__n(
175             'Modify this file as appropriate:',
176             'Modify these files as appropriate:',
177             scalar @files,
178 8         1344 ));
179             $self->info(" * $_") for @files;
180              
181             # Let 'em at it.
182             if ($self->open_editor) {
183 8         811 my $sqitch = $self->sqitch;
184             $sqitch->shell( $sqitch->editor . ' ' . $sqitch->quote_shell(@files) );
185             }
186 8 100       2201  
187 1         19 return $self;
188 1         26 }
189              
190             my ( $self, $src, $dest, $orig ) = @_;
191 8         2627 $orig ||= $src;
192             if (!-e $orig) {
193             $self->debug(__x(
194             'Skipped {dest}: {src} does not exist',
195 48     48   104 dest => $dest,
196 48   66     246 src => $orig,
197 48 100       282 ));
198 6         208 return;
199             }
200              
201             # Stringify to work around bug in File::Copy warning on 5.10.0.
202             File::Copy::syscopy "$src", "$dest" or hurl rework => __x(
203 6         939 'Cannot copy {src} to {dest}: {error}',
204             src => $src,
205             dest => $dest,
206             error => $!,
207 42 50       1775 );
208              
209             $self->debug(__x(
210             'Copied {src} to {dest}',
211             dest => $dest,
212             src => $src,
213             ));
214 42         15077 return $orig;
215             }
216              
217             1;
218              
219 42         8276  
220             =head1 Name
221              
222             App::Sqitch::Command::rework - Rework a Sqitch change
223              
224             =head1 Synopsis
225              
226             my $cmd = App::Sqitch::Command::rework->new(%params);
227             $cmd->execute;
228              
229             =head1 Description
230              
231             Reworks a change. This will result in the copying of the existing deploy,
232             revert, and verify scripts for the change to preserve the earlier instances of
233             the change.
234              
235             =head1 Interface
236              
237             =head2 Class Methods
238              
239             =head3 C<options>
240              
241             my @opts = App::Sqitch::Command::rework->options;
242              
243             Returns a list of L<Getopt::Long> option specifications for the command-line
244             options for the C<rework> command.
245              
246             =head3 C<configure>
247              
248             my $params = App::Sqitch::Command::rework->configure(
249             $config,
250             $options,
251             );
252              
253             Processes the configuration and command options and returns a hash suitable
254             for the constructor.
255              
256             =head2 Attributes
257              
258             =head3 C<change_name>
259              
260             The name of the change to be reworked.
261              
262             =head3 C<note>
263              
264             Text of the change note.
265              
266             =head3 C<requires>
267              
268             List of required changes.
269              
270             =head3 C<conflicts>
271              
272             List of conflicting changes.
273              
274             =head3 C<all>
275              
276             Boolean indicating whether or not to run the command against all plans in the
277             project.
278              
279             =head2 Instance Methods
280              
281             =head3 C<execute>
282              
283             $rework->execute($command);
284              
285             Executes the C<rework> command.
286              
287             =head1 See Also
288              
289             =over
290              
291             =item L<sqitch-rework>
292              
293             Documentation for the C<rework> command to the Sqitch command-line client.
294              
295             =item L<sqitch>
296              
297             The Sqitch command-line client.
298              
299             =back
300              
301             =head1 Author
302              
303             David E. Wheeler <david@justatheory.com>
304              
305             =head1 License
306              
307             Copyright (c) 2012-2022 iovation Inc., David E. Wheeler
308              
309             Permission is hereby granted, free of charge, to any person obtaining a copy
310             of this software and associated documentation files (the "Software"), to deal
311             in the Software without restriction, including without limitation the rights
312             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
313             copies of the Software, and to permit persons to whom the Software is
314             furnished to do so, subject to the following conditions:
315              
316             The above copyright notice and this permission notice shall be included in all
317             copies or substantial portions of the Software.
318              
319             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
320             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
321             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
322             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
323             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
324             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
325             SOFTWARE.
326              
327             =cut