File Coverage

blib/lib/App/Sqitch/Plan/Change.pm
Criterion Covered Total %
statement 107 107 100.0
branch 20 20 100.0
condition 2 3 66.6
subroutine 41 41 100.0
pod 32 32 100.0
total 202 203 99.5


line stmt bran cond sub pod time code
1              
2             use 5.010;
3 50     50   930 use utf8;
  50         172  
4 50     50   251 use namespace::autoclean;
  50         96  
  50         275  
5 50     50   1057 use Encode;
  50         91  
  50         301  
6 50     50   3423 use Moo;
  50         105  
  50         3860  
7 50     50   307 use App::Sqitch::Types qw(Str Bool Maybe Change Tag Depend UserEmail DateTime ArrayRef);
  50         125  
  50         293  
8 50     50   17574 use App::Sqitch::Plan::Depend;
  50         115  
  50         502  
9 50     50   89084 use Locale::TextDomain qw(App-Sqitch);
  50         146  
  50         1680  
10 50     50   354 extends 'App::Sqitch::Plan::Line';
  50         116  
  50         380  
11              
12             our $VERSION = 'v1.3.0'; # VERSION
13              
14             has _requires => (
15             is => 'ro',
16             isa => ArrayRef[Depend],
17             init_arg => 'requires',
18             default => sub { [] },
19             );
20              
21              
22 1881     1881 1 7595 has _conflicts => (
  1881         5554  
23             is => 'ro',
24             isa => ArrayRef[Depend],
25             init_arg => 'conflicts',
26             default => sub { [] },
27             );
28              
29              
30             has pspace => (
31 1111     1111 1 5152 is => 'ro',
  1111         3529  
32             isa => Str,
33             default => ' ',
34             );
35              
36             has since_tag => (
37             is => 'ro',
38             isa => Tag,
39             );
40              
41             has parent => (
42             is => 'ro',
43             isa => Change,
44             );
45              
46             has _rework_tags => (
47             is => 'ro',
48             isa => ArrayRef[Tag],
49             init_arg => 'rework_tags',
50             lazy => 1,
51             default => sub { [] },
52             );
53              
54              
55             after add_rework_tags => sub {
56             my $self = shift;
57 154     154 1 500 # Need to reset the file name if a new value is passed.
  154         2054  
58             $self->_clear_path_segments(undef);
59 1     1 1 548 };
  1         23  
60 685     685 1 2505  
  685         8845  
61             has _tags => (
62             is => 'ro',
63             isa => ArrayRef[Tag],
64             lazy => 1,
65             default => sub { [] },
66             );
67              
68              
69             has _path_segments => (
70             is => 'ro',
71             isa => ArrayRef[Str],
72             lazy => 1,
73             clearer => 1, # Creates _clear_path_segments().
74             default => sub {
75 1578     1578 1 12512 my $self = shift;
  1578         22750  
76 575     575 1 10269 my @path = split m{/} => $self->name;
  575         9405  
77             my $ext = '.' . $self->target->extension;
78             if (my @rework_tags = $self->rework_tags) {
79             # Determine suffix based on the first one found in the deploy dir.
80             my $dir = $self->deploy_dir;
81             my $bn = pop @path;
82             my $first;
83             for my $tag (@rework_tags) {
84             my $fn = join '', $bn, $tag->format_name, $ext;
85             $first //= $fn;
86             if ( -e $dir->file(@path, $fn) ) {
87             push @path => $fn;
88             $first = undef;
89             last;
90             }
91             }
92             push @path => $first if defined $first;
93             } else {
94             $path[-1] .= $ext;
95             }
96             return \@path;
97             },
98             );
99              
100              
101             has info => (
102             is => 'ro',
103             isa => Str,
104             lazy => 1,
105             default => sub {
106             my $self = shift;
107             my $reqs = join "\n + ", map { $_->as_string } $self->requires;
108             my $confs = join "\n - ", map { $_->as_string } $self->conflicts;
109 632     632 1 35642 return join "\n", (
  632         8282  
110             'project ' . $self->project,
111             ( $self->uri ? ( 'uri ' . $self->uri->canonical ) : () ),
112             'change ' . $self->format_name,
113             ( $self->parent ? ( 'parent ' . $self->parent->id ) : () ),
114             'planner ' . $self->format_planner,
115             'date ' . $self->timestamp->as_string,
116             ( $reqs ? "requires\n + $reqs" : ()),
117             ( $confs ? "conflicts\n - $confs" : ()),
118             ( $self->note ? ('', $self->note) : ()),
119             );
120             }
121             );
122              
123             has id => (
124             is => 'ro',
125             isa => Str,
126             lazy => 1,
127             default => sub {
128             my $content = encode_utf8 shift->info;
129             require Digest::SHA;
130             return Digest::SHA->new(1)->add(
131             'change ' . length($content) . "\0" . $content
132             )->hexdigest;
133             }
134             );
135              
136             has timestamp => (
137             is => 'ro',
138             isa => DateTime,
139             default => sub { require App::Sqitch::DateTime && App::Sqitch::DateTime->now },
140             );
141              
142             has planner_name => (
143             is => 'ro',
144             isa => Str,
145             default => sub { shift->sqitch->user_name },
146             );
147              
148             has planner_email => (
149             is => 'ro',
150             isa => UserEmail,
151             default => sub { shift->sqitch->user_email },
152             );
153              
154             has script_hash => (
155             is => 'ro',
156             isa => Maybe[Str],
157             lazy => 1,
158             builder => '_deploy_hash'
159             );
160              
161             my $self = shift;
162             return $self->requires, $self->conflicts;
163             }
164              
165             my $self = shift;
166             my $target = $self->target;
167             return $self->is_reworked
168             ? $target->reworked_deploy_dir
169             : $target->deploy_dir;
170             }
171              
172 1     1 1 906 my $self = shift;
173 1         4 $self->deploy_dir->file( $self->path_segments );
174             }
175              
176             my $path = shift->deploy_file;
177 255     255 1 2508 return undef unless -f $path;
178 255         3935 require Digest::SHA;
179 255 100       5812 my $sha = Digest::SHA->new(1);
180             $sha->add( $path->slurp(iomode => '<:raw') );
181             return $sha->hexdigest;
182             }
183              
184             my $self = shift;
185 221     221 1 20014 my $target = $self->target;
186 221         491 return $self->is_reworked
187             ? $target->reworked_revert_dir
188             : $target->revert_dir;
189             }
190 3     3   822  
191 3 100       260 my $self = shift;
192 2         109 $self->revert_dir->file( $self->path_segments );
193 2         19 }
194 2         45  
195 2         477 my $self = shift;
196             my $target = $self->target;
197             return $self->is_reworked
198             ? $target->reworked_verify_dir
199 207     207 1 1898 : $target->verify_dir;
200 207         3658 }
201 207 100       4856  
202             my $self = shift;
203             $self->verify_dir->file( $self->path_segments );
204             }
205              
206             my ($self, $name) = @_;
207 205     205 1 10224 if ( my $meth = $self->can("$name\_file") ) {
208 205         430 return $self->$meth;
209             }
210             return $self->target->top_dir->subdir($name)->cleanup->file(
211             $self->path_segments
212 145     145 1 1790 );
213 145         2717 }
214 145 100       3414  
215             shift->operator eq '-';
216             }
217              
218             shift->operator ne '-';
219             }
220 143     143 1 34702  
221 143         294 shift->is_deploy ? 'deploy' : 'revert';
222             }
223              
224             my $self = shift;
225 42     42 1 364 return join ' ', $self->format_name, map { $_->format_name } $self->tags;
226 42 100       265 }
227 40         117  
228             my $self = shift;
229 2         40 my ($tag) = $self->tags;
230             unless ($tag) {
231             ($tag) = $self->rework_tags or return $self->format_name . '@HEAD';
232             }
233             return join '', $self->format_name, $tag->format_name;
234             }
235 10     10 1 36  
236             my $self = shift;
237             my $deps = join(
238             ' ',
239 32     32 1 6370 map { $_->as_plan_string } $self->requires, $self->conflicts
240             ) or return '';
241             return "[$deps]";
242             }
243 2 100   2 1 6  
244             my $self = shift;
245             my $dep = $self->format_dependencies or return $self->format_name;
246             return $self->format_name . $self->pspace . $dep;
247 353     353 1 60131 }
248 353         823  
  168         1233  
249             my $self = shift;
250             return $self->format_operator . $self->format_name_with_dependencies;
251             }
252 12     12 1 1225  
253 12         33 my $self = shift;
254 12 100       121 return join ' ', $self->planner_name, '<' . $self->planner_email . '>';
255 6 100       23 }
256              
257 7         86 my $self = shift;
258             $self->plan->open_script($self->deploy_file);
259             }
260              
261 179     179 1 249 my $self = shift;
262             $self->plan->open_script($self->revert_file);
263             }
264 179 100       411  
  102         294  
265             my $self = shift;
266 63         343 $self->plan->open_script($self->verify_file);
267             }
268              
269             my $self = shift;
270 33     33 1 61 return $self->SUPER::format_content . $self->pspace . join (
271 33 100       77 ' ',
272 15         61 ($self->format_dependencies || ()),
273             $self->timestamp->as_string,
274             $self->format_planner
275             );
276 31     31 1 701 }
277 31         102  
278             my $self = shift;
279             my $plan = $self->plan;
280             return map { $plan->find( $_->key_name ) } $self->requires;
281 1014     1014 1 9221 }
282 1014         6772  
283             my $self = shift;
284             my $plan = $self->plan;
285             return map { $plan->find( $_->key_name ) } $self->conflicts;
286 1     1 1 536 }
287 1         9  
288             my ( $self, %p ) = @_;
289              
290             return join(
291 1     1 1 307 '',
292 1         5 __x(
293             "Please enter a note for your change. Lines starting with '#' will\n" .
294             "be ignored, and an empty message aborts the {command}.",
295             command => $p{for},
296 1     1 1 293 ),
297 1         8 "\n",
298             __x('Change to {command}:', command => $p{for}),
299             "\n\n",
300             ' ', $self->format_op_name_dependencies,
301 144     144 1 203 join "\n ", '', @{ $p{scripts} },
302 144   66     337 "\n",
303             );
304             }
305              
306             1;
307              
308              
309             =head1 Name
310              
311 1     1 1 1493 App::Sqitch::Plan::Change - Sqitch deployment plan tag
312 1         4  
313 1         3 =head1 Synopsis
  2         8  
314              
315             my $plan = App::Sqitch::Plan->new( sqitch => $sqitch );
316             for my $line ($plan->lines) {
317 1     1 1 3 say $line->as_string;
318 1         3 }
319 1         4  
  1         3  
320             =head1 Description
321              
322             A App::Sqitch::Plan::Change represents a change as parsed from a plan file. In
323 2     2 1 129 addition to the interface inherited from L<App::Sqitch::Plan::Line>, it offers
324             interfaces for parsing dependencies from the deploy script, as well as for
325             opening the deploy, revert, and verify scripts.
326              
327             =head1 Interface
328              
329             See L<App::Sqitch::Plan::Line> for the basics.
330              
331             =head2 Accessors
332              
333             =head3 C<since_tag>
334              
335             An L<App::Sqitch::Plan::Tag> object representing the last tag to appear in the
336 2         22 plan B<before> the change. May be C<undef>.
  2         8  
337              
338             =head3 C<pspace>
339              
340             Blank space separating the change name from the dependencies, timestamp, and
341             planner in the file.
342              
343             =head3 C<is_reworked>
344              
345             Boolean indicting whether or not the change has been reworked.
346              
347             =head3 C<info>
348              
349             Information about the change, returned as a string. Includes the change ID,
350             the name and email address of the user who added the change to the plan, and
351             the timestamp for when the change was added to the plan.
352              
353             =head3 C<id>
354              
355             A SHA1 hash of the data returned by C<info()>, which can be used as a
356             globally-unique identifier for the change.
357              
358             =head3 C<timestamp>
359              
360             Returns the an L<App::Sqitch::DateTime> object representing the time at which
361             the change was added to the plan.
362              
363             =head3 C<planner_name>
364              
365             Returns the name of the user who added the change to the plan.
366              
367             =head3 C<planner_email>
368              
369             Returns the email address of the user who added the change to the plan.
370              
371             =head3 C<parent>
372              
373             Parent change object.
374              
375             =head3 C<tags>
376              
377             A list of tag objects associated with the change.
378              
379             =head2 Instance Methods
380              
381             =head3 C<path_segments>
382              
383             my @segments = $change->path_segments;
384              
385             Returns the path segment for the change. For example, if the change is named
386             "foo", C<('foo.sql')> is returned. If the change is named "functions/bar>
387             C<('functions', 'bar.sql')> is returned. Internally, this data is used to
388             create the deploy, revert, and verify file names.
389              
390             =head3 C<deploy_dir>
391              
392             my $file = $change->deploy_dir;
393              
394             Returns the path to the deploy directory for the change.
395              
396             =head3 C<deploy_file>
397              
398             my $file = $change->deploy_file;
399              
400             Returns the path to the deploy script file for the change.
401              
402             =head3 C<revert_dir>
403              
404             my $file = $change->revert_dir;
405              
406             Returns the path to the revert directory for the change.
407              
408             =head3 C<revert_file>
409              
410             my $file = $change->revert_file;
411              
412             Returns the path to the revert script file for the change.
413              
414             =head3 C<verify_dir>
415              
416             my $file = $change->verify_dir;
417              
418             Returns the path to the verify directory for the change.
419              
420             =head3 C<verify_file>
421              
422             my $file = $change->verify_file;
423              
424             Returns the path to the verify script file for the change.
425              
426             =head3 C<script_file>
427              
428             my $file = $sqitch->script_file($script_name);
429              
430             Returns the path to a script, for the change.
431              
432             =head3 C<script_hash>
433              
434             my $hash = $change->script_hash;
435              
436             Returns the hex digest of the SHA-1 hash for the deploy script.
437              
438             =head3 C<rework_tags>
439              
440             my @tags = $change->rework_tags;
441              
442             Returns a list of tags that occur between a change and its next reworking.
443             Returns an empty list if the change is not reworked.
444              
445             =head3 C<add_tag>
446              
447             $change->add_tag($tag);
448              
449             Adds a tag object to the change.
450              
451             =head3 C<add_rework_tags>
452              
453             $change->add_rework_tags(@tags);
454              
455             Adds tags to the list of rework tags.
456              
457             =head3 C<clear_rework_tags>
458              
459             $change->clear_rework_tags(@tags);
460              
461             Clears the list of rework tags.
462              
463             =head3 C<requires>
464              
465             my @requires = $change->requires;
466              
467             Returns a list of L<App::Sqitch::Plan::Depend> objects representing changes
468             required by this change.
469              
470             =head3 C<requires_changes>
471              
472             my @requires_changes = $change->requires_changes;
473              
474             Returns a list of the C<App::Sqitch::Plan::Change> objects representing
475             changes required by this change.
476              
477             =head3 C<conflicts>
478              
479             my @conflicts = $change->conflicts;
480              
481             Returns a list of L<App::Sqitch::Plan::Depend> objects representing changes
482             with which this change conflicts.
483              
484             =head3 C<conflicts_changes>
485              
486             my @conflicts_changes = $change->conflicts_changes;
487              
488             Returns a list of the C<App::Sqitch::Plan::Change> objects representing
489             changes with which this change conflicts.
490              
491             =head3 C<dependencies>
492              
493             my @dependencies = $change->dependencies;
494              
495             Returns a list of L<App::Sqitch::Plan::Depend> objects representing all
496             dependencies, required and conflicting.
497              
498             =head3 C<is_deploy>
499              
500             Returns true if the change is intended to be deployed, and false if it should be
501             reverted.
502              
503             =head3 C<is_revert>
504              
505             Returns true if the change is intended to be reverted, and false if it should be
506             deployed.
507              
508             =head3 C<action>
509              
510             Returns "deploy" if the change should be deployed, or "revert" if it should be
511             reverted.
512              
513             =head3 C<format_tag_qualified_name>
514              
515             my $tag_qualified_name = $change->format_tag_qualified_name;
516              
517             Returns a string with the change name followed by the next tag in the plan.
518             Useful for displaying unambiguous change specifications for reworked changes.
519             If there is no tag appearing in the file after the change, the C<@HEAD> will
520             be used.
521              
522             =head3 C<format_name_with_tags>
523              
524             my $name_with_tags = $change->format_name_with_tags;
525              
526             Returns a string formatted with the change name followed by the list of tags, if
527             any, associated with the change. Used to display a change as it is deployed.
528              
529             =head3 C<format_dependencies>
530              
531             my $dependencies = $change->format_dependencies;
532              
533             Returns a string containing a bracketed list of dependencies. If there are no
534             dependencies, an empty string will be returned.
535              
536             =head3 C<format_name_with_dependencies>
537              
538             my $name_with_dependencies = $change->format_name_with_dependencies;
539              
540             Returns a string formatted with the change name followed by a bracketed list
541             of dependencies, if any, associated with the change. Used to display a change
542             when added to a plan.
543              
544             =head3 C<format_op_name_dependencies>
545              
546             my $op_name_dependencies = $change->format_op_name_dependencies;
547              
548             Like C<format_name_with_dependencies>, but includes the operator, if present.
549              
550             =head3 C<format_planner>
551              
552             my $planner = $change->format_planner;
553              
554             Returns a string formatted with the name and email address of the user who
555             added the change to the plan.
556              
557             =head3 C<deploy_handle>
558              
559             my $fh = $change->deploy_handle;
560              
561             Returns an L<IO::File> file handle, opened for reading, for the deploy script
562             for the change.
563              
564             =head3 C<revert_handle>
565              
566             my $fh = $change->revert_handle;
567              
568             Returns an L<IO::File> file handle, opened for reading, for the revert script
569             for the change.
570              
571             =head3 C<verify_handle>
572              
573             my $fh = $change->verify_handle;
574              
575             Returns an L<IO::File> file handle, opened for reading, for the verify script
576             for the change.
577              
578             =head3 C<note_prompt>
579              
580             my $prompt = $change->note_prompt(
581             for => 'rework',
582             scripts => [$change->deploy_file, $change->revert_file],
583             );
584              
585             Overrides the implementation from C<App::Sqitch::Plan::Line> to add the
586             C<files> parameter. This is a list of the files to be created for the command.
587             These will usually be the deploy, revert, and verify files, but the caller
588             might not be creating all of them, so it needs to pass the list.
589              
590             =head1 See Also
591              
592             =over
593              
594             =item L<App::Sqitch::Plan>
595              
596             Class representing a plan.
597              
598             =item L<App::Sqitch::Plan::Line>
599              
600             Base class from which App::Sqitch::Plan::Change inherits.
601              
602             =item L<sqitch>
603              
604             The Sqitch command-line client.
605              
606             =back
607              
608             =head1 Author
609              
610             David E. Wheeler <david@justatheory.com>
611              
612             =head1 License
613              
614             Copyright (c) 2012-2022 iovation Inc., David E. Wheeler
615              
616             Permission is hereby granted, free of charge, to any person obtaining a copy
617             of this software and associated documentation files (the "Software"), to deal
618             in the Software without restriction, including without limitation the rights
619             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
620             copies of the Software, and to permit persons to whom the Software is
621             furnished to do so, subject to the following conditions:
622              
623             The above copyright notice and this permission notice shall be included in all
624             copies or substantial portions of the Software.
625              
626             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
627             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
628             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
629             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
630             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
631             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
632             SOFTWARE.
633              
634             =cut