File Coverage

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


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