File Coverage

blib/lib/App/Sqitch/Command/checkout.pm
Criterion Covered Total %
statement 70 70 100.0
branch 12 12 100.0
condition 6 6 100.0
subroutine 15 15 100.0
pod 1 1 100.0
total 104 104 100.0


line stmt bran cond sub pod time code
1             package App::Sqitch::Command::checkout;
2              
3 2     2   1866 use 5.010;
  2         9  
4 2     2   16 use strict;
  2         15  
  2         64  
5 2     2   11 use warnings;
  2         5  
  2         66  
6 2     2   37 use utf8;
  2         5  
  2         12  
7 2     2   47 use Moo;
  2         12  
  2         11  
8 2     2   737 use App::Sqitch::Types qw(Str);
  2         4  
  2         22  
9 2     2   1486 use Locale::TextDomain qw(App-Sqitch);
  2         4  
  2         16  
10 2     2   431 use App::Sqitch::X qw(hurl);
  2         7  
  2         19  
11 2     2   612 use App::Sqitch::Plan;
  2         4  
  2         81  
12 2     2   15 use Path::Class qw(dir);
  2         4  
  2         94  
13 2     2   12 use Try::Tiny;
  2         7  
  2         159  
14 2     2   14 use namespace::autoclean;
  2         9  
  2         27  
15              
16             extends 'App::Sqitch::Command';
17             with 'App::Sqitch::Role::RevertDeployCommand';
18              
19             our $VERSION = 'v1.4.0'; # VERSION
20              
21             has client => (
22             is => 'ro',
23             isa => Str,
24             lazy => 1,
25             default => sub {
26             my $sqitch = shift->sqitch;
27             return $sqitch->config->get( key => 'core.vcs.client' )
28             || 'git' . ( App::Sqitch::ISWIN ? '.exe' : '' );
29             },
30             );
31              
32             sub configure { {} }
33              
34             sub execute {
35 13     13 1 40719 my $self = shift;
36 13         81 my ($branch, $targets) = $self->parse_args(
37             target => $self->target,
38             names => [undef],
39             args => \@_,
40             no_changes => 1,
41             );
42              
43             # Branch required.
44 11 100       560 $self->usage unless length $branch;
45              
46             # Warn on multiple targets.
47 9         25 my $target = shift @{ $targets };
  9         20  
48             $self->warn(__x(
49             'Too many targets specified; connecting to {target}',
50             target => $target->name,
51 9 100       25 )) if @{ $targets };
  9         30  
52              
53             # Now get to work.
54 9         193 my $sqitch = $self->sqitch;
55 9         200 my $git = $self->client;
56 9         614 my $engine = $target->engine;
57 9         1775 $engine->with_verify( $self->verify );
58 9         405 $engine->log_only( $self->log_only );
59 9         380 $engine->lock_timeout( $self->lock_timeout );
60              
61             # What branch are we on?
62 9         539 my $current_branch = $sqitch->probe($git, qw(rev-parse --abbrev-ref HEAD));
63 9 100       81 hurl {
64             ident => 'checkout',
65             message => __x('Already on branch {branch}', branch => $branch),
66             exitval => 1,
67             } if $current_branch eq $branch;
68              
69             # Instantitate a plan without calling $target->plan.
70 8         169 my $from_plan = App::Sqitch::Plan->new(
71             sqitch => $sqitch,
72             target => $target,
73             );
74              
75             # Load the branch plan from Git, assuming the same path.
76 8         6285 my $to_plan = App::Sqitch::Plan->new(
77             sqitch => $sqitch,
78             target => $target,
79             )->parse(
80             # Git assumes a relative file name is relative to the repo root, even
81             # when you're in a subdirectory. So we have to prepend the currrent
82             # directory path ./ to convince it to read the file relative to the
83             # current directory. See #560 and
84             # https://git-scm.com/docs/gitrevisions#Documentation/gitrevisions.txt-emltrevgtltpathgtemegemHEADREADMEememmasterREADMEem
85             # for details.
86             # XXX Handle missing file/no contents.
87             scalar $sqitch->capture(
88             $git, 'show', "$branch:"
89             . File::Spec->catfile(File::Spec->curdir, $target->plan_file)
90             )
91             );
92              
93             # Find the last change the plans have in common.
94 8         33 my $last_common_change;
95 8         22 for my $change ($to_plan->changes){
96 22 100       399 last unless $from_plan->get( $change->id );
97 14         29 $last_common_change = $change;
98             }
99              
100 8 100       31 hurl checkout => __x(
101             'Branch {branch} has no changes in common with current branch {current}',
102             branch => $branch,
103             current => $current_branch,
104             ) unless $last_common_change;
105              
106 7         44 $sqitch->info(__x(
107             'Last change before the branches diverged: {last_change}',
108             last_change => $last_common_change->format_name_with_tags,
109             ));
110              
111             # Revert to the last common change.
112 7         729 $engine->set_variables( $self->_collect_revert_vars($target) );
113 7         242 $engine->plan( $from_plan );
114             try {
115 7     7   475 $engine->revert( $last_common_change->id, ! $self->no_prompt, $self->prompt_accept );
116             } catch {
117             # Rethrow unknown errors or errors with exitval > 1.
118 5 100 100 5   3233 die $_ if ! eval { $_->isa('App::Sqitch::X') }
  5   100     172  
119             || $_->exitval > 1
120             || $_->ident eq 'revert:confirm';
121             # Emite notice of non-fatal errors (e.g., nothing to revert).
122 2         64 $self->info($_->message)
123 7         254 };
124              
125              
126             # Check out the new branch.
127 4         201 $sqitch->run($git, 'checkout', $branch);
128              
129             # Deploy!
130 4         31 $engine->set_variables( $self->_collect_deploy_vars($target) );
131 4         140 $engine->plan( $to_plan );
132 4         117 $engine->deploy( undef, $self->mode);
133 4         233 return $self;
134             }
135              
136             1;
137              
138             __END__
139              
140             =head1 Name
141              
142             App::Sqitch::Command::checkout - Revert, change checkout a VCS branch, and redeploy
143              
144             =head1 Synopsis
145              
146             my $cmd = App::Sqitch::Command::checkout->new(%params);
147             $cmd->execute;
148              
149             =head1 Description
150              
151             If you want to know how to use the C<checkout> command, you probably want to
152             be reading C<sqitch-checkout>. But if you really want to know how the
153             C<checkout> command works, read on.
154              
155             =head1 Interface
156              
157             =head2 Class Methods
158              
159             =head3 C<options>
160              
161             my @opts = App::Sqitch::Command::checkout->options;
162              
163             Returns a list of L<Getopt::Long> option specifications for the command-line
164             options for the C<checkout> command.
165              
166             =head2 Instance Methods
167              
168             =head3 C<execute>
169              
170             $checkout->execute;
171              
172             Executes the checkout command.
173              
174             =head1 See Also
175              
176             =over
177              
178             =item L<sqitch-checkout>
179              
180             Documentation for the C<checkout> command to the Sqitch command-line client.
181              
182             =item L<sqitch>
183              
184             The Sqitch command-line client.
185              
186             =back
187              
188             =head1 Authors
189              
190             =over
191              
192             =item * Ronan Dunklau <ronan@dunklau.fr>
193              
194             =item * David E. Wheeler <david@justatheory.com>
195              
196             =back
197              
198             =head1 License
199              
200             Copyright (c) 2012-2023 iovation Inc., David E. Wheeler
201              
202             Copyright (c) 2012-2013 Ronan Dunklau
203              
204             Permission is hereby granted, free of charge, to any person obtaining a copy
205             of this software and associated documentation files (the "Software"), to deal
206             in the Software without restriction, including without limitation the rights
207             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
208             copies of the Software, and to permit persons to whom the Software is
209             furnished to do so, subject to the following conditions:
210              
211             The above copyright notice and this permission notice shall be included in all
212             copies or substantial portions of the Software.
213              
214             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
215             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
216             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
217             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
218             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
219             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
220             SOFTWARE.
221              
222             =cut