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   259137 use 5.010;
  2         10  
4 2     2   12 use strict;
  2         4  
  2         88  
5 2     2   10 use warnings;
  2         4  
  2         134  
6 2     2   12 use utf8;
  2         2  
  2         17  
7 2     2   68 use Moo;
  2         3  
  2         14  
8 2     2   821 use App::Sqitch::Types qw(Str);
  2         4  
  2         43  
9 2     2   4556 use Locale::TextDomain qw(App-Sqitch);
  2         5  
  2         20  
10 2     2   469 use App::Sqitch::X qw(hurl);
  2         5  
  2         20  
11 2     2   680 use App::Sqitch::Plan;
  2         4  
  2         64  
12 2     2   11 use Path::Class qw(dir);
  2         5  
  2         177  
13 2     2   14 use Try::Tiny;
  2         4  
  2         147  
14 2     2   13 use namespace::autoclean;
  2         14  
  2         19  
15              
16             extends 'App::Sqitch::Command';
17             with 'App::Sqitch::Role::RevertDeployCommand';
18              
19             our $VERSION = 'v1.6.1'; # 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 43986 my $self = shift;
36 13         122 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       860 $self->usage unless length $branch;
45              
46             # Warn on multiple targets.
47 9         23 my $target = shift @{ $targets };
  9         25  
48             $self->warn(__x(
49             'Too many targets specified; connecting to {target}',
50             target => $target->name,
51 9 100       19 )) if @{ $targets };
  9         34  
52              
53             # Now get to work.
54 9         165 my $sqitch = $self->sqitch;
55 9         273 my $git = $self->client;
56 9         766 my $engine = $target->engine;
57 9         2063 $engine->with_verify( $self->verify );
58 9         437 $engine->log_only( $self->log_only );
59 9         491 $engine->lock_timeout( $self->lock_timeout );
60              
61             # What branch are we on?
62 9         608 my $current_branch = $sqitch->probe($git, qw(rev-parse --abbrev-ref HEAD));
63 9 100       124 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         193 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         9093 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         45 my $last_common_change;
95 8         41 for my $change ($to_plan->changes){
96 22 100       459 last unless $from_plan->get( $change->id );
97 14         30 $last_common_change = $change;
98             }
99              
100 8 100       32 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         56 $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         857 $engine->set_variables( $self->_collect_revert_vars($target) );
113 7         342 $engine->plan( $from_plan );
114             try {
115 7     7   705 $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   3571 die $_ if ! eval { $_->isa('App::Sqitch::X') }
  5   100     273  
119             || $_->exitval > 1
120             || $_->ident eq 'revert:confirm';
121             # Emite notice of non-fatal errors (e.g., nothing to revert).
122 2         61 $self->info($_->message)
123 7         380 };
124              
125              
126             # Check out the new branch.
127 4         239 $sqitch->run($git, 'checkout', $branch);
128              
129             # Deploy!
130 4         47 $engine->set_variables( $self->_collect_deploy_vars($target) );
131 4         187 $engine->plan( $to_plan );
132 4         162 $engine->deploy( undef, $self->mode);
133 4         282 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-2026 David E. Wheeler, 2012-2021 iovation Inc.
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