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   974 use utf8;
  50         218  
4 50     50   248 use namespace::autoclean;
  50         103  
  50         255  
5 50     50   1016 use Encode;
  50         101  
  50         288  
6 50     50   3417 use Moo;
  50         108  
  50         3918  
7 50     50   360 use App::Sqitch::Types qw(Str Bool Maybe Change Tag Depend UserEmail DateTime ArrayRef);
  50         114  
  50         285  
8 50     50   17424 use App::Sqitch::Plan::Depend;
  50         99  
  50         468  
9 50     50   93343 use Locale::TextDomain qw(App-Sqitch);
  50         147  
  50         1648  
10 50     50   347 extends 'App::Sqitch::Plan::Line';
  50         101  
  50         339  
11              
12             our $VERSION = 'v1.3.1'; # 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 7542 has _conflicts => (
  1881         5855  
23             is => 'ro',
24             isa => ArrayRef[Depend],
25             init_arg => 'conflicts',
26             default => sub { [] },
27             );
28              
29              
30             has pspace => (
31 1111     1111 1 5106 is => 'ro',
  1111         3637  
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 522 # Need to reset the file name if a new value is passed.
  154         2174  
58             $self->_clear_path_segments(undef);
59 1     1 1 553 };
  1         22  
60 685     685 1 2636  
  685         9185  
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 13056 my $self = shift;
  1578         23331  
76 575     575 1 10478 my @path = split m{/} => $self->name;
  575         9404  
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 35908 return join "\n", (
  632         8754  
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 931 my $self = shift;
173 1         5 $self->deploy_dir->file( $self->path_segments );
174             }
175              
176             my $path = shift->deploy_file;
177 255     255 1 2759 return undef unless -f $path;
178 255         3881 require Digest::SHA;
179 255 100       5929 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 20943 my $target = $self->target;
186 221         570 return $self->is_reworked
187             ? $target->reworked_revert_dir
188             : $target->revert_dir;
189             }
190 3     3   890  
191 3 100       277 my $self = shift;
192 2         113 $self->revert_dir->file( $self->path_segments );
193 2         16 }
194 2         39  
195 2         528 my $self = shift;
196             my $target = $self->target;
197             return $self->is_reworked
198             ? $target->reworked_verify_dir
199 207     207 1 1935 : $target->verify_dir;
200 207         3774 }
201 207 100       4774  
202             my $self = shift;
203             $self->verify_dir->file( $self->path_segments );
204             }
205              
206             my ($self, $name) = @_;
207 205     205 1 10873 if ( my $meth = $self->can("$name\_file") ) {
208 205         406 return $self->$meth;
209             }
210             return $self->target->top_dir->subdir($name)->cleanup->file(
211             $self->path_segments
212 145     145 1 1794 );
213 145         2797 }
214 145 100       3509  
215             shift->operator eq '-';
216             }
217              
218             shift->operator ne '-';
219             }
220 143     143 1 34721  
221 143         277 shift->is_deploy ? 'deploy' : 'revert';
222             }
223              
224             my $self = shift;
225 42     42 1 263 return join ' ', $self->format_name, map { $_->format_name } $self->tags;
226 42 100       248 }
227 40         94  
228             my $self = shift;
229 2         37 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 39  
236             my $self = shift;
237             my $deps = join(
238             ' ',
239 32     32 1 8075 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 64057 }
248 353         850  
  168         1232  
249             my $self = shift;
250             return $self->format_operator . $self->format_name_with_dependencies;
251             }
252 12     12 1 1229  
253 12         39 my $self = shift;
254 12 100       131 return join ' ', $self->planner_name, '<' . $self->planner_email . '>';
255 6 100       21 }
256              
257 7         118 my $self = shift;
258             $self->plan->open_script($self->deploy_file);
259             }
260              
261 179     179 1 260 my $self = shift;
262             $self->plan->open_script($self->revert_file);
263             }
264 179 100       426  
  102         303  
265             my $self = shift;
266 63         431 $self->plan->open_script($self->verify_file);
267             }
268              
269             my $self = shift;
270 33     33 1 65 return $self->SUPER::format_content . $self->pspace . join (
271 33 100       78 ' ',
272 15         56 ($self->format_dependencies || ()),
273             $self->timestamp->as_string,
274             $self->format_planner
275             );
276 31     31 1 876 }
277 31         124  
278             my $self = shift;
279             my $plan = $self->plan;
280             return map { $plan->find( $_->key_name ) } $self->requires;
281 1014     1014 1 9798 }
282 1014         6878  
283             my $self = shift;
284             my $plan = $self->plan;
285             return map { $plan->find( $_->key_name ) } $self->conflicts;
286 1     1 1 572 }
287 1         13  
288             my ( $self, %p ) = @_;
289              
290             return join(
291 1     1 1 352 '',
292 1         136 __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 363 ),
297 1         7 "\n",
298             __x('Change to {command}:', command => $p{for}),
299             "\n\n",
300             ' ', $self->format_op_name_dependencies,
301 144     144 1 252 join "\n ", '', @{ $p{scripts} },
302 144   66     316 "\n",
303             );
304             }
305              
306             1;
307              
308              
309             =head1 Name
310              
311 1     1 1 1724 App::Sqitch::Plan::Change - Sqitch deployment plan tag
312 1         5  
313 1         5 =head1 Synopsis
  2         18  
314              
315             my $plan = App::Sqitch::Plan->new( sqitch => $sqitch );
316             for my $line ($plan->lines) {
317 1     1 1 4 say $line->as_string;
318 1         5 }
319 1         4  
  1         4  
320             =head1 Description
321              
322             A App::Sqitch::Plan::Change represents a change as parsed from a plan file. In
323 2     2 1 138 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         13 plan B<before> the change. May be C<undef>.
  2         12  
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