line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
|
use 5.010; |
3
|
50
|
|
|
50
|
|
962
|
use utf8; |
|
50
|
|
|
|
|
148
|
|
4
|
50
|
|
|
50
|
|
256
|
use App::Sqitch::Plan::Tag; |
|
50
|
|
|
|
|
97
|
|
|
50
|
|
|
|
|
267
|
|
5
|
50
|
|
|
50
|
|
19532
|
use App::Sqitch::Plan::Change; |
|
50
|
|
|
|
|
154
|
|
|
50
|
|
|
|
|
1659
|
|
6
|
50
|
|
|
50
|
|
23807
|
use App::Sqitch::Plan::Blank; |
|
50
|
|
|
|
|
148
|
|
|
50
|
|
|
|
|
1702
|
|
7
|
50
|
|
|
50
|
|
20918
|
use App::Sqitch::Plan::Pragma; |
|
50
|
|
|
|
|
135
|
|
|
50
|
|
|
|
|
1567
|
|
8
|
50
|
|
|
50
|
|
20254
|
use App::Sqitch::Plan::Depend; |
|
50
|
|
|
|
|
143
|
|
|
50
|
|
|
|
|
1608
|
|
9
|
50
|
|
|
50
|
|
334
|
use Path::Class; |
|
50
|
|
|
|
|
95
|
|
|
50
|
|
|
|
|
1068
|
|
10
|
50
|
|
|
50
|
|
219
|
use App::Sqitch::Plan::ChangeList; |
|
50
|
|
|
|
|
94
|
|
|
50
|
|
|
|
|
2626
|
|
11
|
50
|
|
|
50
|
|
22219
|
use App::Sqitch::Plan::LineList; |
|
50
|
|
|
|
|
115
|
|
|
50
|
|
|
|
|
1561
|
|
12
|
50
|
|
|
50
|
|
19126
|
use Locale::TextDomain qw(App-Sqitch); |
|
50
|
|
|
|
|
113
|
|
|
50
|
|
|
|
|
1569
|
|
13
|
50
|
|
|
50
|
|
302
|
use App::Sqitch::X qw(hurl); |
|
50
|
|
|
|
|
108
|
|
|
50
|
|
|
|
|
283
|
|
14
|
50
|
|
|
50
|
|
7239
|
use List::MoreUtils qw(uniq any); |
|
50
|
|
|
|
|
95
|
|
|
50
|
|
|
|
|
344
|
|
15
|
50
|
|
|
50
|
|
11073
|
use namespace::autoclean; |
|
50
|
|
|
|
|
96
|
|
|
50
|
|
|
|
|
399
|
|
16
|
50
|
|
|
50
|
|
52273
|
use Moo; |
|
50
|
|
|
|
|
124
|
|
|
50
|
|
|
|
|
313
|
|
17
|
50
|
|
|
50
|
|
2979
|
use App::Sqitch::Types qw(Str Int HashRef ChangeList LineList Maybe Sqitch URI File Target); |
|
50
|
|
|
|
|
92
|
|
|
50
|
|
|
|
|
276
|
|
18
|
50
|
|
|
50
|
|
17154
|
use constant SYNTAX_VERSION => '1.0.0'; |
|
50
|
|
|
|
|
111
|
|
|
50
|
|
|
|
|
347
|
|
19
|
50
|
|
|
50
|
|
75766
|
|
|
50
|
|
|
|
|
100
|
|
|
50
|
|
|
|
|
271918
|
|
20
|
|
|
|
|
|
|
our $VERSION = 'v1.3.1'; # VERSION |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
# Like [:punct:], but excluding _. Copied from perlrecharclass. |
23
|
|
|
|
|
|
|
my $punct = q{-!"#$%&'()*+,./:;<=>?@[\\]^`{|}~}; |
24
|
|
|
|
|
|
|
my $name_re = qr{ |
25
|
|
|
|
|
|
|
(?![$punct]) # first character isn't punctuation |
26
|
|
|
|
|
|
|
(?: # start non-capturing group, repeated once or more ... |
27
|
|
|
|
|
|
|
(?! # negative look ahead for... |
28
|
|
|
|
|
|
|
[~/=%^] # symbolic reference punctuation |
29
|
|
|
|
|
|
|
[[:digit:]]+ # digits |
30
|
|
|
|
|
|
|
(?:$|[[:blank:]]) # eol or blank |
31
|
|
|
|
|
|
|
) # ... |
32
|
|
|
|
|
|
|
[^[:blank:]:@#\\] # match a valid character |
33
|
|
|
|
|
|
|
)+ # ... end non-capturing group |
34
|
|
|
|
|
|
|
(?<![$punct])\b # last character isn't punctuation |
35
|
|
|
|
|
|
|
}x; |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
my $dir_sep_re = qr{/}; |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
my %reserved = map { $_ => undef } qw(ROOT HEAD); |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
|
42
|
442
|
|
|
442
|
1
|
4614
|
has sqitch => ( |
43
|
|
|
|
|
|
|
is => 'ro', |
44
|
|
|
|
|
|
|
isa => Sqitch, |
45
|
|
|
|
|
|
|
required => 1, |
46
|
|
|
|
|
|
|
weak_ref => 1, |
47
|
|
|
|
|
|
|
); |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
has target => ( |
50
|
|
|
|
|
|
|
is => 'ro', |
51
|
|
|
|
|
|
|
isa => Target, |
52
|
|
|
|
|
|
|
required => 1, |
53
|
|
|
|
|
|
|
weak_ref => 1, |
54
|
|
|
|
|
|
|
); |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
has file => ( |
57
|
|
|
|
|
|
|
is => 'ro', |
58
|
|
|
|
|
|
|
isa => File, |
59
|
|
|
|
|
|
|
lazy => 1, |
60
|
|
|
|
|
|
|
default => sub { |
61
|
|
|
|
|
|
|
shift->target->plan_file |
62
|
|
|
|
|
|
|
}, |
63
|
|
|
|
|
|
|
); |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
has _plan => ( |
66
|
|
|
|
|
|
|
is => 'rw', |
67
|
|
|
|
|
|
|
isa => HashRef, |
68
|
|
|
|
|
|
|
builder => 'load', |
69
|
|
|
|
|
|
|
init_arg => 'plan', |
70
|
|
|
|
|
|
|
lazy => 1, |
71
|
|
|
|
|
|
|
required => 1, |
72
|
|
|
|
|
|
|
); |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
has _changes => ( |
75
|
|
|
|
|
|
|
is => 'ro', |
76
|
|
|
|
|
|
|
isa => ChangeList, |
77
|
|
|
|
|
|
|
lazy => 1, |
78
|
|
|
|
|
|
|
default => sub { |
79
|
|
|
|
|
|
|
App::Sqitch::Plan::ChangeList->new(@{ shift->_plan->{changes} }), |
80
|
|
|
|
|
|
|
}, |
81
|
|
|
|
|
|
|
); |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
has _lines => ( |
84
|
|
|
|
|
|
|
is => 'ro', |
85
|
|
|
|
|
|
|
isa => LineList, |
86
|
|
|
|
|
|
|
lazy => 1, |
87
|
|
|
|
|
|
|
default => sub { |
88
|
|
|
|
|
|
|
App::Sqitch::Plan::LineList->new(@{ shift->_plan->{lines} }), |
89
|
|
|
|
|
|
|
}, |
90
|
|
|
|
|
|
|
); |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
has position => ( |
93
|
|
|
|
|
|
|
is => 'rw', |
94
|
|
|
|
|
|
|
isa => Int, |
95
|
|
|
|
|
|
|
default => -1, |
96
|
|
|
|
|
|
|
); |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
has project => ( |
99
|
|
|
|
|
|
|
is => 'ro', |
100
|
|
|
|
|
|
|
isa => Str, |
101
|
|
|
|
|
|
|
lazy => 1, |
102
|
|
|
|
|
|
|
default => sub { |
103
|
|
|
|
|
|
|
shift->_plan->{pragmas}{project}; |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
); |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
has uri => ( |
108
|
|
|
|
|
|
|
is => 'ro', |
109
|
|
|
|
|
|
|
isa => Maybe[URI], |
110
|
|
|
|
|
|
|
lazy => 1, |
111
|
|
|
|
|
|
|
default => sub { |
112
|
|
|
|
|
|
|
my $uri = shift->_plan->{pragmas}{uri} || return; |
113
|
|
|
|
|
|
|
require URI; |
114
|
|
|
|
|
|
|
'URI'->new($uri); |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
); |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
my ( $self, $data ) = @_; |
119
|
|
|
|
|
|
|
open my $fh, '<:utf8_strict', \$data; |
120
|
|
|
|
|
|
|
$self->_plan( $self->load($fh) ); |
121
|
8
|
|
|
8
|
1
|
1282
|
return $self; |
122
|
8
|
|
|
1
|
|
119
|
} |
|
1
|
|
|
1
|
|
6
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
9
|
|
|
1
|
|
|
|
|
748
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
5
|
|
123
|
8
|
|
|
|
|
737
|
|
124
|
8
|
|
|
|
|
198
|
my $self = shift; |
125
|
|
|
|
|
|
|
my $file = $self->file; |
126
|
|
|
|
|
|
|
my $fh = shift || do { |
127
|
|
|
|
|
|
|
hurl plan => __x('Plan file {file} does not exist', file => $file) |
128
|
277
|
|
|
277
|
1
|
53791
|
unless -e $file; |
129
|
277
|
|
|
|
|
4152
|
hurl plan => __x('Plan file {file} is not a regular file', file => $file) |
130
|
277
|
|
66
|
|
|
33408
|
unless -f $file; |
131
|
|
|
|
|
|
|
$file->open('<:utf8_strict') or hurl io => __x( |
132
|
|
|
|
|
|
|
'Cannot open {file}: {error}', |
133
|
|
|
|
|
|
|
file => $file, |
134
|
|
|
|
|
|
|
error => $! |
135
|
|
|
|
|
|
|
); |
136
|
|
|
|
|
|
|
}; |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
return $self->_parse($file, $fh); |
139
|
|
|
|
|
|
|
} |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
my ( $self, $file, $fh ) = @_; |
142
|
268
|
|
|
|
|
62290
|
|
143
|
|
|
|
|
|
|
my @lines; # List of lines. |
144
|
|
|
|
|
|
|
my @changes; # List of changes. |
145
|
|
|
|
|
|
|
my @curr_changes; # List of changes since last tag. |
146
|
351
|
|
|
351
|
|
706853
|
my %line_no_for; # Maps tags and changes to line numbers. |
147
|
|
|
|
|
|
|
my %change_named; # Maps change names to change objects. |
148
|
351
|
|
|
|
|
2803
|
my %tag_changes; # Maps changes in current tag section to line numbers. |
149
|
|
|
|
|
|
|
my %pragmas; # Maps pragma names to values. |
150
|
351
|
|
|
|
|
0
|
my $seen_version; # Have we seen a version pragma? |
151
|
351
|
|
|
|
|
0
|
my $prev_tag; # Last seen tag. |
152
|
351
|
|
|
|
|
0
|
my $prev_change; # Last seen change. |
153
|
351
|
|
|
|
|
0
|
|
154
|
351
|
|
|
|
|
0
|
# Regex to match timestamps. |
155
|
351
|
|
|
|
|
0
|
my $ts_re = qr/ |
156
|
351
|
|
|
|
|
0
|
(?<yr>[[:digit:]]{4}) # year |
157
|
351
|
|
|
|
|
0
|
- # dash |
158
|
|
|
|
|
|
|
(?<mo>[[:digit:]]{2}) # month |
159
|
|
|
|
|
|
|
- # dash |
160
|
351
|
|
|
|
|
1820
|
(?<dy>[[:digit:]]{2}) # day |
161
|
|
|
|
|
|
|
T # T |
162
|
|
|
|
|
|
|
(?<hr>[[:digit:]]{2}) # hour |
163
|
|
|
|
|
|
|
: # colon |
164
|
|
|
|
|
|
|
(?<mi>[[:digit:]]{2}) # minute |
165
|
|
|
|
|
|
|
: # colon |
166
|
|
|
|
|
|
|
(?<sc>[[:digit:]]{2}) # second |
167
|
|
|
|
|
|
|
Z # Zulu time |
168
|
|
|
|
|
|
|
/x; |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
my $planner_re = qr/ |
171
|
|
|
|
|
|
|
(?<planner_name>[^<]+) # name |
172
|
|
|
|
|
|
|
[[:blank:]]+ # blanks |
173
|
|
|
|
|
|
|
<(?<planner_email>[^>]+)> # email |
174
|
|
|
|
|
|
|
/x; |
175
|
351
|
|
|
|
|
896
|
|
176
|
|
|
|
|
|
|
# Use for raising syntax error exceptions. |
177
|
|
|
|
|
|
|
my $raise_syntax_error = sub { |
178
|
|
|
|
|
|
|
hurl parse => __x( |
179
|
|
|
|
|
|
|
'Syntax error in {file} at line {lineno}: {error}', |
180
|
|
|
|
|
|
|
file => $file, |
181
|
|
|
|
|
|
|
lineno => $fh->input_line_number, |
182
|
|
|
|
|
|
|
error => shift |
183
|
50
|
|
|
50
|
|
3108
|
); |
184
|
|
|
|
|
|
|
}; |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
# First, find pragmas. |
187
|
|
|
|
|
|
|
HEADER: while ( my $line = $fh->getline ) { |
188
|
|
|
|
|
|
|
$line =~ s/\r?\n\z//; |
189
|
351
|
|
|
|
|
1762
|
|
190
|
|
|
|
|
|
|
# Grab blank lines first. |
191
|
|
|
|
|
|
|
if ($line =~ /\A(?<lspace>[[:blank:]]*)(?:#[[:blank:]]*(?<note>.+)|$)/) { |
192
|
351
|
|
|
|
|
8490
|
my $line = App::Sqitch::Plan::Blank->new( plan => $self, %+ ); |
193
|
828
|
|
|
|
|
31731
|
push @lines => $line; |
194
|
|
|
|
|
|
|
last HEADER if @lines && !$line->note; |
195
|
|
|
|
|
|
|
next HEADER; |
196
|
828
|
100
|
|
|
|
3416
|
} |
197
|
379
|
|
|
|
|
9231
|
|
198
|
379
|
|
|
|
|
51299
|
# Grab inline note. |
199
|
379
|
100
|
66
|
|
|
8512
|
$line =~ s/(?<rspace>[[:blank:]]*)(?:[#][[:blank:]]*(?<note>.*))?$//; |
200
|
42
|
|
|
|
|
890
|
my %params = %+; |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
$raise_syntax_error->( |
203
|
|
|
|
|
|
|
__ 'Invalid pragma; a blank line must come between pragmas and changes' |
204
|
449
|
|
|
|
|
3665
|
) unless $line =~ / |
205
|
449
|
|
|
|
|
5564
|
\A # Beginning of line |
206
|
|
|
|
|
|
|
(?<lspace>[[:blank:]]*)? # Optional leading space |
207
|
449
|
50
|
|
|
|
9328
|
[%] # Required % |
208
|
|
|
|
|
|
|
(?<hspace>[[:blank:]]*)? # Optional space |
209
|
|
|
|
|
|
|
(?<name> # followed by name consisting of... |
210
|
|
|
|
|
|
|
[^$punct] # not punct |
211
|
|
|
|
|
|
|
(?: # followed by... |
212
|
|
|
|
|
|
|
[^[:blank:]=]*? # any number non-blank, non-= |
213
|
|
|
|
|
|
|
[^$punct[:blank:]] # one not blank or punct |
214
|
|
|
|
|
|
|
)? # ... optionally |
215
|
|
|
|
|
|
|
) # ... required |
216
|
|
|
|
|
|
|
(?: # followed by value consisting of... |
217
|
|
|
|
|
|
|
(?<lopspace>[[:blank:]]*) # Optional blanks |
218
|
|
|
|
|
|
|
(?<operator>=) # Required = |
219
|
|
|
|
|
|
|
(?<ropspace>[[:blank:]]*) # Optional blanks |
220
|
|
|
|
|
|
|
(?<value>.+) # String value |
221
|
|
|
|
|
|
|
)? # ... optionally |
222
|
|
|
|
|
|
|
\z # end of line |
223
|
|
|
|
|
|
|
/x; |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
# XXX Die if the pragma is a dupe? |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
if ($+{name} eq 'syntax-version') { |
228
|
|
|
|
|
|
|
# Set explicit version in case we write it out later. In future |
229
|
|
|
|
|
|
|
# releases, may change parsers depending on the version. |
230
|
|
|
|
|
|
|
$pragmas{syntax_version} = $params{value} = SYNTAX_VERSION; |
231
|
|
|
|
|
|
|
} elsif ($+{name} eq 'project') { |
232
|
449
|
100
|
|
|
|
3727
|
my $proj = $+{value}; |
|
|
100
|
|
|
|
|
|
233
|
|
|
|
|
|
|
$raise_syntax_error->(__x( |
234
|
|
|
|
|
|
|
qq{invalid project name "{project}": project names must not } |
235
|
93
|
|
|
|
|
312
|
. 'begin with punctuation, contain "@", ":", "#", "\\", or blanks, ' |
236
|
|
|
|
|
|
|
. 'or end in punctuation or digits following punctuation', |
237
|
349
|
|
|
|
|
1377
|
project => $proj, |
238
|
349
|
100
|
|
|
|
4987
|
)) unless $proj =~ /\A$name_re\z/; |
239
|
|
|
|
|
|
|
$pragmas{project} = $proj; |
240
|
|
|
|
|
|
|
} else { |
241
|
|
|
|
|
|
|
$pragmas{ $+{name} } = $+{value} // 1; |
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
|
244
|
337
|
|
|
|
|
1128
|
push @lines => App::Sqitch::Plan::Pragma->new( |
245
|
|
|
|
|
|
|
plan => $self, |
246
|
7
|
|
100
|
|
|
58
|
%+, |
247
|
|
|
|
|
|
|
%params |
248
|
|
|
|
|
|
|
); |
249
|
437
|
|
|
|
|
13438
|
next HEADER; |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
# We should have a version pragma. |
253
|
|
|
|
|
|
|
unless ( $pragmas{syntax_version} ) { |
254
|
437
|
|
|
|
|
84038
|
unshift @lines => $self->_version_line; |
255
|
|
|
|
|
|
|
$pragmas{syntax_version} = SYNTAX_VERSION; |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
|
258
|
339
|
100
|
|
|
|
3054
|
# Should have valid project pragma. |
259
|
246
|
|
|
|
|
805
|
hurl parse => __x( |
260
|
246
|
|
|
|
|
36438
|
'Missing %project pragma in {file}', |
261
|
|
|
|
|
|
|
file => $file, |
262
|
|
|
|
|
|
|
) unless $pragmas{project}; |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
LINE: while ( my $line = $fh->getline ) { |
265
|
|
|
|
|
|
|
$line =~ s/\r?\n\z//; |
266
|
|
|
|
|
|
|
|
267
|
339
|
100
|
|
|
|
886
|
# Grab blank lines first. |
268
|
|
|
|
|
|
|
if ($line =~ /\A(?<lspace>[[:blank:]]*)(?:#[[:blank:]]*(?<note>.+)|$)/) { |
269
|
337
|
|
|
|
|
6461
|
my $line = App::Sqitch::Plan::Blank->new( plan => $self, %+ ); |
270
|
1612
|
|
|
|
|
40012
|
push @lines => $line; |
271
|
|
|
|
|
|
|
next LINE; |
272
|
|
|
|
|
|
|
} |
273
|
1612
|
100
|
|
|
|
5641
|
|
274
|
266
|
|
|
|
|
5298
|
# Grab inline note. |
275
|
266
|
|
|
|
|
33587
|
$line =~ s/(?<rspace>[[:blank:]]*)(?:[#][[:blank:]]*(?<note>.*))?$//; |
276
|
266
|
|
|
|
|
5036
|
my %params = %+; |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
# Is it a tag or a change? |
279
|
|
|
|
|
|
|
my $type = $line =~ /^[[:blank:]]*[@]/ ? 'tag' : 'change'; |
280
|
1346
|
|
|
|
|
22837
|
$line =~ / |
281
|
1346
|
|
|
|
|
11500
|
^ # Beginning of line |
282
|
|
|
|
|
|
|
(?<lspace>[[:blank:]]*)? # Optional leading space |
283
|
|
|
|
|
|
|
|
284
|
1346
|
100
|
|
|
|
5877
|
(?: # followed by... |
285
|
1346
|
|
|
|
|
26436
|
[@] # @ for tag |
286
|
|
|
|
|
|
|
| # ...or... |
287
|
|
|
|
|
|
|
(?<lopspace>[[:blank:]]*) # Optional blanks |
288
|
|
|
|
|
|
|
(?<operator>[+-]) # Required + or - |
289
|
|
|
|
|
|
|
(?<ropspace>[[:blank:]]*) # Optional blanks |
290
|
|
|
|
|
|
|
)? # ... optionally |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
(?<name>$name_re) # followed by name |
293
|
|
|
|
|
|
|
(?<pspace>[[:blank:]]+)? # blanks |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
(?: # followed by... |
296
|
|
|
|
|
|
|
[[](?<dependencies>[^]]+)[]] # dependencies |
297
|
|
|
|
|
|
|
[[:blank:]]* # blanks |
298
|
|
|
|
|
|
|
)? # ... optionally |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
(?: # followed by... |
301
|
|
|
|
|
|
|
$ts_re # timestamp |
302
|
|
|
|
|
|
|
[[:blank:]]* # blanks |
303
|
|
|
|
|
|
|
)? # ... optionally |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
(?: # followed by |
306
|
|
|
|
|
|
|
$planner_re # planner |
307
|
|
|
|
|
|
|
)? # ... optionally |
308
|
|
|
|
|
|
|
$ # end of line |
309
|
|
|
|
|
|
|
/x; |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
%params = ( %params, %+ ); |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
# Raise errors for missing data. |
314
|
|
|
|
|
|
|
$raise_syntax_error->(__( |
315
|
|
|
|
|
|
|
qq{Invalid name; names must not begin with punctuation, } |
316
|
1346
|
|
|
|
|
34899
|
. 'contain "@", ":", "#", "\\", or blanks, or end in punctuation or digits following punctuation', |
317
|
|
|
|
|
|
|
)) if !$params{name} |
318
|
|
|
|
|
|
|
|| (!$params{yr} && $line =~ $ts_re); |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
$raise_syntax_error->(__ 'Missing timestamp and planner name and email') |
321
|
|
|
|
|
|
|
unless $params{yr} || $params{planner_name}; |
322
|
|
|
|
|
|
|
$raise_syntax_error->(__ 'Missing timestamp') unless $params{yr}; |
323
|
1346
|
100
|
100
|
|
|
9482
|
|
|
|
|
100
|
|
|
|
|
324
|
|
|
|
|
|
|
$raise_syntax_error->(__ 'Missing planner name and email') |
325
|
|
|
|
|
|
|
unless $params{planner_name}; |
326
|
1321
|
100
|
100
|
|
|
2812
|
|
327
|
1320
|
100
|
|
|
|
2326
|
# It must not be a reserved name. |
328
|
|
|
|
|
|
|
$raise_syntax_error->(__x( |
329
|
|
|
|
|
|
|
'"{name}" is a reserved name', |
330
|
1319
|
100
|
|
|
|
2165
|
name => ($type eq 'tag' ? '@' : '') . $params{name}, |
331
|
|
|
|
|
|
|
)) if exists $reserved{ $params{name} }; |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
# It must not look like a SHA1 hash. |
334
|
|
|
|
|
|
|
$raise_syntax_error->(__x( |
335
|
|
|
|
|
|
|
'"{name}" is invalid because it could be confused with a SHA1 ID', |
336
|
1318
|
50
|
|
|
|
3123
|
name => $params{name}, |
|
|
100
|
|
|
|
|
|
337
|
|
|
|
|
|
|
)) if $params{name} =~ /^[0-9a-f]{40}/; |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
# Assemble the timestamp. |
340
|
|
|
|
|
|
|
require App::Sqitch::DateTime; |
341
|
|
|
|
|
|
|
$params{timestamp} = App::Sqitch::DateTime->new( |
342
|
1316
|
100
|
|
|
|
2420
|
year => delete $params{yr}, |
343
|
|
|
|
|
|
|
month => delete $params{mo}, |
344
|
|
|
|
|
|
|
day => delete $params{dy}, |
345
|
1315
|
|
|
|
|
15358
|
hour => delete $params{hr}, |
346
|
|
|
|
|
|
|
minute => delete $params{mi}, |
347
|
|
|
|
|
|
|
second => delete $params{sc}, |
348
|
|
|
|
|
|
|
time_zone => 'UTC', |
349
|
|
|
|
|
|
|
); |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
if ($type eq 'tag') { |
352
|
|
|
|
|
|
|
# Faile if contains directory separators |
353
|
1315
|
|
|
|
|
7101
|
if ($params{name} =~ qr/($dir_sep_re)/) { |
354
|
|
|
|
|
|
|
$raise_syntax_error->(__x( |
355
|
|
|
|
|
|
|
'Tag "{tag}" contains illegal character {sep}', |
356
|
1315
|
100
|
|
|
|
416870
|
tag => $params{name}, |
357
|
|
|
|
|
|
|
sep => $1, |
358
|
450
|
100
|
|
|
|
5000
|
)); |
359
|
|
|
|
|
|
|
} |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
# Fail if no changes. |
362
|
1
|
|
|
|
|
6
|
unless ($prev_change) { |
363
|
|
|
|
|
|
|
$raise_syntax_error->(__x( |
364
|
|
|
|
|
|
|
'Tag "{tag}" declared without a preceding change', |
365
|
|
|
|
|
|
|
tag => $params{name}, |
366
|
|
|
|
|
|
|
)); |
367
|
449
|
100
|
|
|
|
1619
|
} |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
# Fail on duplicate tag. |
370
|
|
|
|
|
|
|
my $key = '@' . $params{name}; |
371
|
1
|
|
|
|
|
4
|
if ( my $at = $line_no_for{$key} ) { |
372
|
|
|
|
|
|
|
$raise_syntax_error->(__x( |
373
|
|
|
|
|
|
|
'Tag "{tag}" duplicates earlier declaration on line {line}', |
374
|
|
|
|
|
|
|
tag => $params{name}, |
375
|
448
|
|
|
|
|
1073
|
line => $at, |
376
|
448
|
100
|
|
|
|
1351
|
)); |
377
|
|
|
|
|
|
|
} |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
# Fail on dependencies. |
380
|
1
|
|
|
|
|
8
|
$raise_syntax_error->(__x( |
381
|
|
|
|
|
|
|
__ 'Tags may not specify dependencies' |
382
|
|
|
|
|
|
|
)) if $params{dependencies}; |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
if (@curr_changes) { |
385
|
|
|
|
|
|
|
# Sort all changes up to this tag by their dependencies. |
386
|
|
|
|
|
|
|
push @changes => $self->check_changes( |
387
|
447
|
100
|
|
|
|
963
|
$pragmas{project}, |
388
|
|
|
|
|
|
|
\%line_no_for, |
389
|
446
|
100
|
|
|
|
981
|
@curr_changes, |
390
|
|
|
|
|
|
|
); |
391
|
|
|
|
|
|
|
@curr_changes = (); |
392
|
|
|
|
|
|
|
} |
393
|
376
|
|
|
|
|
1369
|
|
394
|
|
|
|
|
|
|
# Create the tag and associate it with the previous change. |
395
|
|
|
|
|
|
|
$prev_tag = App::Sqitch::Plan::Tag->new( |
396
|
376
|
|
|
|
|
822
|
plan => $self, |
397
|
|
|
|
|
|
|
change => $prev_change, |
398
|
|
|
|
|
|
|
%params, |
399
|
|
|
|
|
|
|
); |
400
|
446
|
|
|
|
|
10099
|
|
401
|
|
|
|
|
|
|
# Keep track of everything and clean up. |
402
|
|
|
|
|
|
|
$prev_change->add_tag($prev_tag); |
403
|
|
|
|
|
|
|
push @lines => $prev_tag; |
404
|
|
|
|
|
|
|
%line_no_for = (%line_no_for, %tag_changes, $key => $fh->input_line_number); |
405
|
|
|
|
|
|
|
%tag_changes = (); |
406
|
|
|
|
|
|
|
} else { |
407
|
446
|
|
|
|
|
39902
|
# Fail on duplicate change since last tag. |
408
|
446
|
|
|
|
|
8624
|
if ( my $at = $tag_changes{ $params{name} } ) { |
409
|
446
|
|
|
|
|
2095
|
$raise_syntax_error->(__x( |
410
|
446
|
|
|
|
|
16842
|
'Change "{change}" duplicates earlier declaration on line {line}', |
411
|
|
|
|
|
|
|
change => $params{name}, |
412
|
|
|
|
|
|
|
line => $at, |
413
|
865
|
100
|
|
|
|
2680
|
)); |
414
|
|
|
|
|
|
|
} |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
# Got dependencies? |
417
|
1
|
|
|
|
|
5
|
if (my $deps = $params{dependencies}) { |
418
|
|
|
|
|
|
|
my (@req, @con, %seen_dep); |
419
|
|
|
|
|
|
|
for my $depstring (split /[[:blank:]]+/, $deps) { |
420
|
|
|
|
|
|
|
my $dep_params = App::Sqitch::Plan::Depend->parse( |
421
|
|
|
|
|
|
|
$depstring, |
422
|
864
|
100
|
|
|
|
1823
|
) or $raise_syntax_error->(__x( |
423
|
214
|
|
|
|
|
403
|
'"{dep}" is not a valid dependency specification', |
424
|
214
|
|
|
|
|
1123
|
dep => $depstring, |
425
|
307
|
100
|
|
|
|
1469
|
)); |
426
|
|
|
|
|
|
|
my $dep = App::Sqitch::Plan::Depend->new( |
427
|
|
|
|
|
|
|
plan => $self, |
428
|
|
|
|
|
|
|
%{ $dep_params }, |
429
|
|
|
|
|
|
|
); |
430
|
|
|
|
|
|
|
# Prevent dupes. |
431
|
|
|
|
|
|
|
$raise_syntax_error->( |
432
|
|
|
|
|
|
|
__x( 'Duplicate dependency "{dep}"', dep => $depstring ), |
433
|
306
|
|
|
|
|
809
|
) if $seen_dep{$depstring}++; |
|
306
|
|
|
|
|
5782
|
|
434
|
|
|
|
|
|
|
if ($dep->conflicts) { |
435
|
|
|
|
|
|
|
push @con => $dep; |
436
|
|
|
|
|
|
|
} else { |
437
|
|
|
|
|
|
|
push @req => $dep; |
438
|
306
|
100
|
|
|
|
29583
|
} |
439
|
305
|
100
|
|
|
|
987
|
} |
440
|
73
|
|
|
|
|
260
|
$params{requires} = \@req; |
441
|
|
|
|
|
|
|
$params{conflicts} = \@con; |
442
|
232
|
|
|
|
|
790
|
} |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
$tag_changes{ $params{name} } = $fh->input_line_number; |
445
|
212
|
|
|
|
|
557
|
push @curr_changes => $prev_change = App::Sqitch::Plan::Change->new( |
446
|
212
|
|
|
|
|
491
|
plan => $self, |
447
|
|
|
|
|
|
|
( $prev_tag ? ( since_tag => $prev_tag ) : () ), |
448
|
|
|
|
|
|
|
( $prev_change ? ( parent => $prev_change ) : () ), |
449
|
862
|
|
|
|
|
2971
|
%params, |
450
|
862
|
100
|
|
|
|
35637
|
); |
|
|
100
|
|
|
|
|
|
451
|
|
|
|
|
|
|
push @lines => $prev_change; |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
if (my $duped = $change_named{ $params{name} }) { |
454
|
|
|
|
|
|
|
# Get rework tags by change in reverse order to reworked change. |
455
|
|
|
|
|
|
|
my @rework_tags; |
456
|
862
|
|
|
|
|
91607
|
for (my $i = $#changes; $changes[$i] ne $duped; $i--) { |
457
|
|
|
|
|
|
|
push @rework_tags => $changes[$i]->tags; |
458
|
862
|
100
|
|
|
|
2587
|
} |
459
|
|
|
|
|
|
|
# Add list of rework tags to the reworked change. |
460
|
91
|
|
|
|
|
154
|
$duped->add_rework_tags(@rework_tags, $duped->tags); |
461
|
91
|
|
|
|
|
415
|
} |
462
|
39
|
|
|
|
|
233
|
$change_named{ $params{name} } = $prev_change; |
463
|
|
|
|
|
|
|
} |
464
|
|
|
|
|
|
|
} |
465
|
91
|
|
|
|
|
713
|
|
466
|
|
|
|
|
|
|
# Sort and store any remaining changes. |
467
|
862
|
|
|
|
|
19864
|
push @changes => $self->check_changes( |
468
|
|
|
|
|
|
|
$pragmas{project}, |
469
|
|
|
|
|
|
|
\%line_no_for, |
470
|
|
|
|
|
|
|
@curr_changes, |
471
|
|
|
|
|
|
|
) if @curr_changes; |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
return { |
474
|
299
|
100
|
|
|
|
11524
|
changes => \@changes, |
475
|
|
|
|
|
|
|
lines => \@lines, |
476
|
|
|
|
|
|
|
pragmas => \%pragmas, |
477
|
|
|
|
|
|
|
}; |
478
|
|
|
|
|
|
|
} |
479
|
299
|
|
|
|
|
13720
|
|
480
|
|
|
|
|
|
|
App::Sqitch::Plan::Pragma->new( |
481
|
|
|
|
|
|
|
plan => shift, |
482
|
|
|
|
|
|
|
name => 'syntax-version', |
483
|
|
|
|
|
|
|
operator => '=', |
484
|
|
|
|
|
|
|
value => SYNTAX_VERSION, |
485
|
|
|
|
|
|
|
); |
486
|
246
|
|
|
246
|
|
3872
|
} |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
my ( $self, $proj ) = ( shift, shift ); |
489
|
|
|
|
|
|
|
my $seen = ref $_[0] eq 'HASH' ? shift : {}; |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
my %position; |
492
|
|
|
|
|
|
|
my @invalid; |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
my $i = 0; |
495
|
467
|
|
|
467
|
1
|
1486
|
for my $change (@_) { |
496
|
467
|
100
|
|
|
|
1234
|
my @bad; |
497
|
|
|
|
|
|
|
|
498
|
467
|
|
|
|
|
755
|
# XXX Ignoring conflicts for now. |
499
|
|
|
|
|
|
|
for my $dep ( $change->requires ) { |
500
|
|
|
|
|
|
|
# Ignore dependencies on other projects. |
501
|
467
|
|
|
|
|
650
|
if ($dep->got_project) { |
502
|
467
|
|
|
|
|
1120
|
# Skip if parsed project name different from current project. |
503
|
843
|
|
|
|
|
2846
|
next if $dep->project ne $proj; |
504
|
|
|
|
|
|
|
} else { |
505
|
|
|
|
|
|
|
# Skip if an ID was passed, is it could be internal or external. |
506
|
843
|
|
|
|
|
2285
|
next if $dep->got_id; |
507
|
|
|
|
|
|
|
} |
508
|
249
|
100
|
|
|
|
1137
|
my $key = $dep->key_name; |
509
|
|
|
|
|
|
|
|
510
|
21
|
100
|
|
|
|
332
|
# Skip it if it's a change from an earlier tag. |
511
|
|
|
|
|
|
|
if ($key =~ /.@/) { |
512
|
|
|
|
|
|
|
# Need to look it up before the tag. |
513
|
228
|
50
|
|
|
|
670
|
my ( $change, $tag ) = split /@/ => $key, 2; |
514
|
|
|
|
|
|
|
if ( my $tag_at = $seen->{"\@$tag"} ) { |
515
|
248
|
|
|
|
|
734
|
if ( my $change_at = $seen->{$change}) { |
516
|
|
|
|
|
|
|
next if $change_at < $tag_at; |
517
|
|
|
|
|
|
|
} |
518
|
248
|
100
|
|
|
|
892
|
} |
519
|
|
|
|
|
|
|
} else { |
520
|
74
|
|
|
|
|
330
|
# Skip it if we've already seen it in the plan. |
521
|
74
|
50
|
|
|
|
329
|
next if exists $seen->{$key} || $position{$key}; |
522
|
74
|
50
|
|
|
|
222
|
} |
523
|
74
|
100
|
|
|
|
249
|
|
524
|
|
|
|
|
|
|
# Hrm, unknown dependency. |
525
|
|
|
|
|
|
|
push @bad, $key; |
526
|
|
|
|
|
|
|
} |
527
|
|
|
|
|
|
|
$position{$change->name} = ++$i; |
528
|
174
|
100
|
100
|
|
|
738
|
push @invalid, [ $change->name => \@bad ] if @bad; |
529
|
|
|
|
|
|
|
} |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
|
532
|
12
|
|
|
|
|
30
|
# Nothing bad, then go! |
533
|
|
|
|
|
|
|
return @_ unless @invalid; |
534
|
843
|
|
|
|
|
3018
|
|
535
|
843
|
100
|
|
|
|
1874
|
# Build up all of the error messages. |
536
|
|
|
|
|
|
|
my @errors; |
537
|
|
|
|
|
|
|
for my $bad (@invalid) { |
538
|
|
|
|
|
|
|
my $change = $bad->[0]; |
539
|
|
|
|
|
|
|
my $max_delta = 0; |
540
|
467
|
100
|
|
|
|
1672
|
for my $dep (@{ $bad->[1] }) { |
541
|
|
|
|
|
|
|
if ($change eq $dep) { |
542
|
|
|
|
|
|
|
push @errors => __x( |
543
|
9
|
|
|
|
|
11
|
'Change "{change}" cannot require itself', |
544
|
9
|
|
|
|
|
29
|
change => $change, |
545
|
10
|
|
|
|
|
524
|
); |
546
|
10
|
|
|
|
|
15
|
} elsif (my $pos = $position{ $dep }) { |
547
|
10
|
|
|
|
|
13
|
my $delta = $pos - $position{$change}; |
|
10
|
|
|
|
|
20
|
|
548
|
12
|
100
|
|
|
|
230
|
$max_delta = $delta if $delta > $max_delta; |
|
|
100
|
|
|
|
|
|
549
|
1
|
|
|
|
|
4
|
push @errors => __xn( |
550
|
|
|
|
|
|
|
'Change "{change}" planned {num} change before required change "{required}"', |
551
|
|
|
|
|
|
|
'Change "{change}" planned {num} changes before required change "{required}"', |
552
|
|
|
|
|
|
|
$delta, |
553
|
|
|
|
|
|
|
change => $change, |
554
|
8
|
|
|
|
|
11
|
required => $dep, |
555
|
8
|
100
|
|
|
|
18
|
num => $delta, |
556
|
8
|
|
|
|
|
24
|
); |
557
|
|
|
|
|
|
|
} else { |
558
|
|
|
|
|
|
|
push @errors => __x( |
559
|
|
|
|
|
|
|
'Unknown change "{required}" required by change "{change}"', |
560
|
|
|
|
|
|
|
required => $dep, |
561
|
|
|
|
|
|
|
change => $change, |
562
|
|
|
|
|
|
|
); |
563
|
|
|
|
|
|
|
} |
564
|
|
|
|
|
|
|
} |
565
|
3
|
|
|
|
|
10
|
if ($max_delta) { |
566
|
|
|
|
|
|
|
# Suggest that the change be moved. |
567
|
|
|
|
|
|
|
# XXX Potentially offer to move it and rewrite the plan. |
568
|
|
|
|
|
|
|
$errors[-1] .= "\n " . __xn( |
569
|
|
|
|
|
|
|
'HINT: move "{change}" down {num} line in {plan}', |
570
|
|
|
|
|
|
|
'HINT: move "{change}" down {num} lines in {plan}', |
571
|
|
|
|
|
|
|
$max_delta, |
572
|
10
|
100
|
|
|
|
892
|
change => $change, |
573
|
|
|
|
|
|
|
num => $max_delta, |
574
|
|
|
|
|
|
|
plan => $self->file, |
575
|
6
|
|
|
|
|
111
|
); |
576
|
|
|
|
|
|
|
} |
577
|
|
|
|
|
|
|
} |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
# Throw the exception with all of the errors. |
580
|
|
|
|
|
|
|
hurl parse => join( |
581
|
|
|
|
|
|
|
"\n ", |
582
|
|
|
|
|
|
|
__n( |
583
|
|
|
|
|
|
|
'Dependency error detected:', |
584
|
|
|
|
|
|
|
'Dependency errors detected:', |
585
|
|
|
|
|
|
|
@errors |
586
|
|
|
|
|
|
|
), |
587
|
9
|
|
|
|
|
638
|
@errors, |
588
|
|
|
|
|
|
|
); |
589
|
|
|
|
|
|
|
} |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
my ( $self, $file ) = @_; |
592
|
|
|
|
|
|
|
# return has higher precedence than or, so use ||. |
593
|
|
|
|
|
|
|
return $file->open('<:utf8_strict') || hurl io => __x( |
594
|
|
|
|
|
|
|
'Cannot open {file}: {error}', |
595
|
|
|
|
|
|
|
file => $file, |
596
|
|
|
|
|
|
|
error => $!, |
597
|
|
|
|
|
|
|
); |
598
|
|
|
|
|
|
|
} |
599
|
6
|
|
|
6
|
1
|
3002
|
|
600
|
|
|
|
|
|
|
|
601
|
6
|
|
66
|
|
|
16
|
my ( $self, %p ) = @_; |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
my $reverse = 0; |
604
|
|
|
|
|
|
|
if (my $d = delete $p{direction}) { |
605
|
|
|
|
|
|
|
$reverse = $d =~ /^ASC/i ? 0 |
606
|
|
|
|
|
|
|
: $d =~ /^DESC/i ? 1 |
607
|
|
|
|
|
|
|
: hurl 'Search direction must be either "ASC" or "DESC"'; |
608
|
1
|
|
|
1
|
1
|
713
|
} |
609
|
58
|
|
|
58
|
1
|
2398
|
|
610
|
37
|
|
|
37
|
1
|
75255
|
# Limit with regular expressions? |
611
|
9
|
|
|
9
|
1
|
704
|
my @filters; |
612
|
62
|
|
|
62
|
1
|
23006
|
if (my $regex = delete $p{planner}) { |
613
|
123
|
|
|
123
|
1
|
5137
|
$regex = qr/$regex/; |
614
|
134
|
|
|
134
|
1
|
29865
|
push @filters => sub { $_[0]->planner_name =~ $regex }; |
615
|
148
|
|
|
148
|
1
|
82598
|
} |
616
|
62
|
|
|
62
|
1
|
11110
|
if (my $regex = delete $p{name}) { |
617
|
48
|
|
|
48
|
1
|
817
|
$regex = qr/$regex/; |
618
|
167
|
|
|
167
|
1
|
20123
|
push @filters => sub { $_[0]->name =~ $regex }; |
619
|
14
|
|
|
14
|
1
|
772
|
} |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
# Match events? |
622
|
19
|
|
|
19
|
1
|
13661
|
if (my $op = lc(delete $p{operation} || '') ) { |
623
|
|
|
|
|
|
|
push @filters => $op eq 'deploy' ? sub { $_[0]->is_deploy } |
624
|
19
|
|
|
|
|
29
|
: $op eq 'revert' ? sub { $_[0]->is_revert } |
625
|
19
|
100
|
|
|
|
53
|
: hurl qq{Unknown change operation "$op"}; |
626
|
6
|
100
|
|
|
|
33
|
} |
|
|
100
|
|
|
|
|
|
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
my $changes = $self->_changes; |
629
|
|
|
|
|
|
|
my $offset = delete $p{offset} || 0; |
630
|
|
|
|
|
|
|
my $limit = delete $p{limit} || 0; |
631
|
|
|
|
|
|
|
|
632
|
18
|
|
|
|
|
24
|
hurl 'Invalid parameters passed to search_changes(): ' |
633
|
18
|
100
|
|
|
|
33
|
. join ', ', sort keys %p if %p; |
634
|
2
|
|
|
|
|
25
|
|
635
|
2
|
|
|
8
|
|
8
|
# If no filters, we want to return everything. |
|
8
|
|
|
|
|
53
|
|
636
|
|
|
|
|
|
|
push @filters => sub { 1 } unless @filters; |
637
|
18
|
100
|
|
|
|
39
|
|
638
|
3
|
|
|
|
|
36
|
if ($reverse) { |
639
|
3
|
|
|
12
|
|
14
|
# Go backwards. |
|
12
|
|
|
|
|
80
|
|
640
|
|
|
|
|
|
|
my $index = $changes->count - ($offset + 1); |
641
|
|
|
|
|
|
|
my $end_at = $limit ? $index - $limit : -1; |
642
|
|
|
|
|
|
|
return sub { |
643
|
18
|
100
|
100
|
|
|
72
|
while ($index > $end_at) { |
644
|
8
|
|
|
8
|
|
18
|
my $change = $changes->change_at($index--) or return; |
645
|
8
|
|
|
8
|
|
19
|
return $change if any { $_->($change) } @filters; |
646
|
5
|
100
|
|
|
|
31
|
} |
|
|
100
|
|
|
|
|
|
647
|
|
|
|
|
|
|
return; |
648
|
|
|
|
|
|
|
}; |
649
|
17
|
|
|
|
|
396
|
} |
650
|
17
|
|
100
|
|
|
194
|
|
651
|
17
|
|
100
|
|
|
45
|
my $index = $offset - 1; |
652
|
|
|
|
|
|
|
my $end_at = $limit ? $index + $limit : $changes->count - 1; |
653
|
17
|
50
|
|
|
|
28
|
return sub { |
654
|
|
|
|
|
|
|
while ($index < $end_at) { |
655
|
|
|
|
|
|
|
my $change = $changes->change_at(++$index) or return; |
656
|
|
|
|
|
|
|
return $change if any { $_->($change) } @filters; |
657
|
17
|
100
|
|
20
|
|
53
|
} |
|
20
|
|
|
|
|
70
|
|
658
|
|
|
|
|
|
|
return; |
659
|
17
|
100
|
|
|
|
31
|
}; |
660
|
|
|
|
|
|
|
} |
661
|
4
|
|
|
|
|
13
|
|
662
|
4
|
100
|
|
|
|
10
|
my ( $self, $key ) = @_; |
663
|
|
|
|
|
|
|
my $index = $self->index_of($key); |
664
|
13
|
|
|
13
|
|
54
|
hurl plan => __x( |
665
|
9
|
50
|
|
|
|
17
|
'Cannot find change "{change}" in plan', |
666
|
9
|
50
|
|
|
|
29
|
change => $key, |
|
9
|
|
|
|
|
15
|
|
667
|
|
|
|
|
|
|
) unless defined $index; |
668
|
4
|
|
|
|
|
6
|
$self->position($index); |
669
|
4
|
|
|
|
|
24
|
return $self; |
670
|
|
|
|
|
|
|
} |
671
|
|
|
|
|
|
|
|
672
|
13
|
|
|
|
|
18
|
my $self = shift; |
673
|
13
|
100
|
|
|
|
39
|
$self->position(-1); |
674
|
|
|
|
|
|
|
return $self; |
675
|
40
|
|
|
40
|
|
183
|
} |
676
|
47
|
50
|
|
|
|
104
|
|
677
|
47
|
100
|
|
|
|
126
|
my $self = shift; |
|
47
|
|
|
|
|
72
|
|
678
|
|
|
|
|
|
|
if ( my $next = $self->peek ) { |
679
|
13
|
|
|
|
|
23
|
$self->position( $self->position + 1 ); |
680
|
13
|
|
|
|
|
84
|
return $next; |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
$self->position( $self->position + 1 ) if defined $self->current; |
683
|
|
|
|
|
|
|
return undef; |
684
|
7
|
|
|
7
|
1
|
608
|
} |
685
|
7
|
|
|
|
|
14
|
|
686
|
6
|
100
|
|
|
|
21
|
my $self = shift; |
687
|
|
|
|
|
|
|
my $pos = $self->position; |
688
|
|
|
|
|
|
|
return if $pos < 0; |
689
|
|
|
|
|
|
|
$self->_changes->change_at( $pos ); |
690
|
5
|
|
|
|
|
78
|
} |
691
|
5
|
|
|
|
|
136
|
|
692
|
|
|
|
|
|
|
my $self = shift; |
693
|
|
|
|
|
|
|
$self->_changes->change_at( $self->position + 1 ); |
694
|
|
|
|
|
|
|
} |
695
|
30
|
|
|
30
|
1
|
58199
|
|
696
|
30
|
|
|
|
|
547
|
shift->_changes->change_at( -1 ); |
697
|
30
|
|
|
|
|
747
|
} |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
my ( $self, $code ) = @_; |
700
|
|
|
|
|
|
|
while ( local $_ = $self->next ) { |
701
|
151
|
|
|
151
|
1
|
5921
|
return unless $code->($_); |
702
|
151
|
100
|
|
|
|
306
|
} |
703
|
125
|
|
|
|
|
1952
|
} |
704
|
125
|
|
|
|
|
4034
|
|
705
|
|
|
|
|
|
|
my ( $self, %p ) = @_; |
706
|
26
|
100
|
|
|
|
102
|
( my $name = $p{name} ) =~ s/^@//; |
707
|
26
|
|
|
|
|
869
|
$self->_is_valid(tag => $name); |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
my $changes = $self->_changes; |
710
|
|
|
|
|
|
|
my $key = "\@$name"; |
711
|
92
|
|
|
92
|
1
|
60816
|
|
712
|
92
|
|
|
|
|
1230
|
hurl plan => __x( |
713
|
92
|
100
|
|
|
|
467
|
'Tag "{tag}" already exists', |
714
|
90
|
|
|
|
|
1133
|
tag => $key |
715
|
|
|
|
|
|
|
) if defined $changes->index_of($key); |
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
my $change; |
718
|
155
|
|
|
155
|
1
|
717
|
if (my $spec = $p{change}) { |
719
|
155
|
|
|
|
|
2594
|
$change = $changes->get($spec) or hurl plan => __x( |
720
|
|
|
|
|
|
|
'Unknown change: "{change}"', |
721
|
|
|
|
|
|
|
change => $spec, |
722
|
|
|
|
|
|
|
); |
723
|
9
|
|
|
9
|
1
|
1844
|
} else { |
724
|
|
|
|
|
|
|
$change = $changes->last_change or hurl plan => __x( |
725
|
|
|
|
|
|
|
'Cannot apply tag "{tag}" to a plan with no changes', |
726
|
|
|
|
|
|
|
tag => $key |
727
|
2
|
|
|
2
|
1
|
5
|
); |
728
|
2
|
|
|
|
|
5
|
} |
729
|
4
|
50
|
|
|
|
12
|
|
730
|
|
|
|
|
|
|
my $tag = App::Sqitch::Plan::Tag->new( |
731
|
|
|
|
|
|
|
%p, |
732
|
|
|
|
|
|
|
plan => $self, |
733
|
|
|
|
|
|
|
name => $name, |
734
|
35
|
|
|
35
|
1
|
45742
|
change => $change, |
735
|
35
|
|
|
|
|
125
|
); |
736
|
35
|
|
|
|
|
108
|
|
737
|
|
|
|
|
|
|
$change->add_tag($tag); |
738
|
19
|
|
|
|
|
351
|
$changes->index_tag( $changes->index_of( $change->id ), $tag ); |
739
|
19
|
|
|
|
|
276
|
|
740
|
|
|
|
|
|
|
# Add tag to line list, after the change and any preceding tags. |
741
|
19
|
100
|
|
|
|
70
|
my $lines = $self->_lines; |
742
|
|
|
|
|
|
|
$lines->insert_at( $tag, $lines->index_of($change) + $change->tags ); |
743
|
|
|
|
|
|
|
return $tag; |
744
|
|
|
|
|
|
|
} |
745
|
|
|
|
|
|
|
|
746
|
18
|
|
|
|
|
32
|
my ( $self, $p ) = @_; |
747
|
18
|
100
|
|
|
|
47
|
# Dependencies must be parsed into objects. |
748
|
3
|
50
|
|
|
|
11
|
$p->{requires} = [ map { |
749
|
|
|
|
|
|
|
my $p = App::Sqitch::Plan::Depend->parse($_) // hurl plan => __x( |
750
|
|
|
|
|
|
|
'"{dep}" is not a valid dependency specification', |
751
|
|
|
|
|
|
|
dep => $_, |
752
|
|
|
|
|
|
|
); |
753
|
15
|
50
|
|
|
|
49
|
App::Sqitch::Plan::Depend->new( |
754
|
|
|
|
|
|
|
%{ $p }, |
755
|
|
|
|
|
|
|
plan => $self, |
756
|
|
|
|
|
|
|
conflicts => 0, |
757
|
|
|
|
|
|
|
); |
758
|
|
|
|
|
|
|
} uniq @{ $p->{requires} } ] if $p->{requires}; |
759
|
18
|
|
|
|
|
345
|
|
760
|
|
|
|
|
|
|
$p->{conflicts} = [ map { |
761
|
|
|
|
|
|
|
my $p = App::Sqitch::Plan::Depend->parse("!$_") // hurl plan => __x( |
762
|
|
|
|
|
|
|
'"{dep}" is not a valid dependency specification', |
763
|
|
|
|
|
|
|
dep => $_, |
764
|
|
|
|
|
|
|
); |
765
|
|
|
|
|
|
|
App::Sqitch::Plan::Depend->new( |
766
|
18
|
|
|
|
|
5378
|
%{ $p }, |
767
|
18
|
|
|
|
|
371
|
plan => $self, |
768
|
|
|
|
|
|
|
conflicts => 1, |
769
|
|
|
|
|
|
|
); |
770
|
18
|
|
|
|
|
288
|
} uniq @{ $p->{conflicts} } ] if $p->{conflicts}; |
771
|
18
|
|
|
|
|
278
|
} |
772
|
18
|
|
|
|
|
89
|
|
773
|
|
|
|
|
|
|
my ( $self, %p ) = @_; |
774
|
|
|
|
|
|
|
$self->_is_valid(change => $p{name}); |
775
|
|
|
|
|
|
|
my $changes = $self->_changes; |
776
|
46
|
|
|
46
|
|
103
|
|
777
|
|
|
|
|
|
|
if ( defined( my $idx = $changes->index_of( $p{name} . '@HEAD' ) ) ) { |
778
|
|
|
|
|
|
|
my $tag_idx = $changes->index_of_last_tagged; |
779
|
10
|
|
100
|
|
|
60
|
hurl plan => __x( |
780
|
|
|
|
|
|
|
qq{Change "{change}" already exists in plan {file}.\n} |
781
|
|
|
|
|
|
|
. 'Use "sqitch rework" to copy and rework it', |
782
|
|
|
|
|
|
|
change => $p{name}, |
783
|
|
|
|
|
|
|
file => $self->file, |
784
|
8
|
|
|
|
|
26
|
); |
|
8
|
|
|
|
|
148
|
|
785
|
|
|
|
|
|
|
} |
786
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
$self->_parse_deps(\%p); |
788
|
46
|
100
|
|
|
|
118
|
my $change = App::Sqitch::Plan::Change->new( %p, plan => $self ); |
|
33
|
|
|
|
|
196
|
|
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
# Make sure dependencies are valid. |
791
|
7
|
|
66
|
|
|
194
|
$self->_check_dependencies( $change, 'add' ); |
792
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
# We good. Append a blank line if the previous change has a tag. |
794
|
|
|
|
|
|
|
if ( $changes->count ) { |
795
|
|
|
|
|
|
|
my $prev = $changes->change_at( $changes->count - 1 ); |
796
|
5
|
|
|
|
|
16
|
if ( $prev->tags ) { |
|
5
|
|
|
|
|
97
|
|
797
|
|
|
|
|
|
|
$self->_lines->append( |
798
|
|
|
|
|
|
|
App::Sqitch::Plan::Blank->new( plan => $self ) |
799
|
|
|
|
|
|
|
); |
800
|
44
|
100
|
|
|
|
974
|
} |
|
28
|
|
|
|
|
101
|
|
801
|
|
|
|
|
|
|
} |
802
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
# Append the change and return. |
804
|
44
|
|
|
44
|
1
|
68368
|
$changes->append( $change ); |
805
|
44
|
|
|
|
|
157
|
$self->_lines->append( $change ); |
806
|
29
|
|
|
|
|
536
|
return $change; |
807
|
|
|
|
|
|
|
} |
808
|
29
|
100
|
|
|
|
483
|
|
809
|
1
|
|
|
|
|
4
|
my ( $self, %p ) = @_; |
810
|
|
|
|
|
|
|
my $changes = $self->_changes; |
811
|
|
|
|
|
|
|
my $idx = $changes->index_of( $p{name} . '@HEAD') // hurl plan => __x( |
812
|
|
|
|
|
|
|
qq{Change "{change}" does not exist in {file}.\n} |
813
|
|
|
|
|
|
|
. 'Use "sqitch add {change}" to add it to the plan', |
814
|
1
|
|
|
|
|
17
|
change => $p{name}, |
815
|
|
|
|
|
|
|
file => $self->file, |
816
|
|
|
|
|
|
|
); |
817
|
|
|
|
|
|
|
|
818
|
28
|
|
|
|
|
130
|
my $tag_idx = $changes->index_of_last_tagged; |
819
|
26
|
|
|
|
|
690
|
hurl plan => __x( |
820
|
|
|
|
|
|
|
qq{Cannot rework "{change}" without an intervening tag.\n} |
821
|
|
|
|
|
|
|
. 'Use "sqitch tag" to create a tag and try again', |
822
|
26
|
|
|
|
|
7389
|
change => $p{name}, |
823
|
|
|
|
|
|
|
) if !defined $tag_idx || $tag_idx < $idx; |
824
|
|
|
|
|
|
|
|
825
|
24
|
100
|
|
|
|
89
|
$self->_parse_deps(\%p); |
826
|
15
|
|
|
|
|
40
|
|
827
|
15
|
100
|
|
|
|
45
|
my ($tag) = $changes->change_at($tag_idx)->tags; |
828
|
3
|
|
|
|
|
67
|
unshift @{ $p{requires} ||= [] } => App::Sqitch::Plan::Depend->new( |
829
|
|
|
|
|
|
|
plan => $self, |
830
|
|
|
|
|
|
|
change => $p{name}, |
831
|
|
|
|
|
|
|
tag => $tag->name, |
832
|
|
|
|
|
|
|
); |
833
|
|
|
|
|
|
|
|
834
|
|
|
|
|
|
|
my $orig = $changes->change_at($idx); |
835
|
24
|
|
|
|
|
205
|
my $new = App::Sqitch::Plan::Change->new( %p, plan => $self ); |
836
|
24
|
|
|
|
|
434
|
|
837
|
24
|
|
|
|
|
131
|
# Make sure dependencies are valid. |
838
|
|
|
|
|
|
|
$self->_check_dependencies( $new, 'rework' ); |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
# We good. |
841
|
22
|
|
|
22
|
1
|
12094
|
$orig->add_rework_tags($tag); |
842
|
22
|
|
|
|
|
478
|
$changes->append( $new ); |
843
|
|
|
|
|
|
|
$self->_lines->append( $new ); |
844
|
|
|
|
|
|
|
return $new; |
845
|
|
|
|
|
|
|
} |
846
|
|
|
|
|
|
|
|
847
|
22
|
|
100
|
|
|
578
|
my ( $self, $change, $action ) = @_; |
848
|
|
|
|
|
|
|
my $changes = $self->_changes; |
849
|
|
|
|
|
|
|
my $project = $self->project; |
850
|
20
|
|
|
|
|
102
|
for my $req ( $change->requires ) { |
851
|
|
|
|
|
|
|
next if $req->project ne $project; |
852
|
|
|
|
|
|
|
$req = $req->key_name; |
853
|
|
|
|
|
|
|
next if defined $changes->index_of($req =~ /@/ ? $req : $req . '@HEAD'); |
854
|
|
|
|
|
|
|
my $name = $change->name; |
855
|
20
|
100
|
100
|
|
|
186
|
if ($action eq 'add') { |
856
|
|
|
|
|
|
|
hurl plan => __x( |
857
|
18
|
|
|
|
|
81
|
'Cannot add change "{change}": requires unknown change "{req}"', |
858
|
|
|
|
|
|
|
change => $name, |
859
|
16
|
|
|
|
|
152
|
req => $req, |
860
|
16
|
|
100
|
|
|
444
|
); |
861
|
|
|
|
|
|
|
} else { |
862
|
|
|
|
|
|
|
hurl plan => __x( |
863
|
16
|
|
|
|
|
131
|
'Cannot rework change "{change}": requires unknown change "{req}"', |
864
|
|
|
|
|
|
|
change => $name, |
865
|
|
|
|
|
|
|
req => $req, |
866
|
16
|
|
|
|
|
2248
|
); |
867
|
16
|
|
|
|
|
343
|
} |
868
|
|
|
|
|
|
|
} |
869
|
|
|
|
|
|
|
return $self; |
870
|
16
|
|
|
|
|
5510
|
} |
871
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
my ( $self, $type, $name ) = @_; |
873
|
15
|
|
|
|
|
331
|
hurl plan => __x( |
874
|
15
|
|
|
|
|
344
|
'"{name}" is a reserved name', |
875
|
15
|
|
|
|
|
301
|
name => $name |
876
|
15
|
|
|
|
|
95
|
) if exists $reserved{$name}; |
877
|
|
|
|
|
|
|
hurl plan => __x( |
878
|
|
|
|
|
|
|
'"{name}" is invalid because it could be confused with a SHA1 ID', |
879
|
|
|
|
|
|
|
name => $name, |
880
|
52
|
|
|
52
|
|
5719
|
) if $name =~ /^[0-9a-f]{40}/; |
881
|
52
|
|
|
|
|
906
|
|
882
|
52
|
|
|
|
|
1068
|
if ($type eq 'change' && $name !~ /\A$name_re\z/) { |
883
|
52
|
|
|
|
|
796
|
hurl plan => __x( |
884
|
34
|
100
|
|
|
|
549
|
qq{"{name}" is invalid: changes must not begin with punctuation, } |
885
|
31
|
|
|
|
|
1108
|
. 'contain "@", ":", "#", "\\", or blanks, or end in punctuation or digits following punctuation', |
886
|
31
|
100
|
|
|
|
233
|
name => $name, |
|
|
100
|
|
|
|
|
|
887
|
6
|
|
|
|
|
17
|
); |
888
|
6
|
100
|
|
|
|
19
|
} elsif ($type eq 'tag' && ($name !~ /\A$name_re\z/ || $name =~ $dir_sep_re)) { |
889
|
2
|
|
|
|
|
7
|
hurl plan => __x( |
890
|
|
|
|
|
|
|
qq{"{name}" is invalid: tags must not begin with punctuation, } |
891
|
|
|
|
|
|
|
. 'contain "@", ":", "#", "/", "\\", or blanks, or end in punctuation or digits following punctuation', |
892
|
|
|
|
|
|
|
name => $name, |
893
|
|
|
|
|
|
|
); |
894
|
|
|
|
|
|
|
} |
895
|
4
|
|
|
|
|
14
|
return 1; |
896
|
|
|
|
|
|
|
} |
897
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
my ( $self, $file, $from, $to ) = @_; |
899
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
my @lines = $self->lines; |
901
|
|
|
|
|
|
|
|
902
|
46
|
|
|
|
|
130
|
if (defined $from || defined $to) { |
903
|
|
|
|
|
|
|
my $lines = $self->_lines; |
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
# Where are the pragmas? |
906
|
101
|
|
|
101
|
|
10457
|
my $head_ends_at = do { |
907
|
|
|
|
|
|
|
my $i = 0; |
908
|
|
|
|
|
|
|
while ( my $line = $lines[$i] ) { |
909
|
|
|
|
|
|
|
last if $line->isa('App::Sqitch::Plan::Blank') |
910
|
101
|
100
|
|
|
|
282
|
&& !length $line->note; |
911
|
97
|
100
|
|
|
|
232
|
++$i; |
912
|
|
|
|
|
|
|
} |
913
|
|
|
|
|
|
|
$i; |
914
|
|
|
|
|
|
|
}; |
915
|
|
|
|
|
|
|
|
916
|
95
|
100
|
100
|
|
|
2024
|
# Where do we start with the changes? |
|
|
100
|
100
|
|
|
|
|
|
|
|
66
|
|
|
|
|
917
|
12
|
|
|
|
|
44
|
my $from_idx = defined $from ? do { |
918
|
|
|
|
|
|
|
my $change = $self->find($from // '@ROOT') // hurl plan => __x( |
919
|
|
|
|
|
|
|
'Cannot find change {change}', |
920
|
|
|
|
|
|
|
change => $from, |
921
|
|
|
|
|
|
|
); |
922
|
|
|
|
|
|
|
$lines->index_of($change); |
923
|
25
|
|
|
|
|
89
|
} : $head_ends_at + 1; |
924
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
# Where do we end up? |
926
|
|
|
|
|
|
|
my $to_idx = defined $to ? do { |
927
|
|
|
|
|
|
|
my $change = $self->find( $to // '@HEAD' ) // hurl plan => __x( |
928
|
|
|
|
|
|
|
'Cannot find change {change}', |
929
|
58
|
|
|
|
|
176
|
change => $to, |
930
|
|
|
|
|
|
|
); |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
# Include any subsequent tags. |
933
|
51
|
|
|
51
|
1
|
4133
|
if (my @tags = $change->tags) { |
934
|
|
|
|
|
|
|
$change = $tags[-1]; |
935
|
51
|
|
|
|
|
245
|
} |
936
|
|
|
|
|
|
|
$lines->index_of($change); |
937
|
51
|
100
|
100
|
|
|
303
|
} : $#lines; |
938
|
11
|
|
|
|
|
169
|
|
939
|
|
|
|
|
|
|
# Collect the lines to write. |
940
|
|
|
|
|
|
|
@lines = ( |
941
|
11
|
|
|
|
|
93
|
@lines[ 0 .. $head_ends_at ], |
942
|
11
|
|
|
|
|
17
|
@lines[ $from_idx .. $to_idx ], |
943
|
11
|
|
|
|
|
28
|
); |
944
|
37
|
100
|
100
|
|
|
394
|
} |
945
|
|
|
|
|
|
|
|
946
|
26
|
|
|
|
|
87
|
my $fh = $file->open('>:utf8_strict') or hurl io => __x( |
947
|
|
|
|
|
|
|
'Cannot open {file}: {error}', |
948
|
11
|
|
|
|
|
73
|
file => $file, |
949
|
|
|
|
|
|
|
error => $! |
950
|
|
|
|
|
|
|
); |
951
|
|
|
|
|
|
|
$fh->say($_->as_string) for @lines; |
952
|
11
|
100
|
|
|
|
29
|
$fh->close or hurl io => __x( |
953
|
9
|
|
50
|
|
|
63
|
'"Error closing {file}: {error}', |
|
|
|
33
|
|
|
|
|
954
|
|
|
|
|
|
|
file => $file, |
955
|
|
|
|
|
|
|
error => $!, |
956
|
|
|
|
|
|
|
); |
957
|
9
|
|
|
|
|
28
|
return $self; |
958
|
|
|
|
|
|
|
} |
959
|
|
|
|
|
|
|
|
960
|
|
|
|
|
|
|
1; |
961
|
11
|
100
|
|
|
|
26
|
|
962
|
6
|
|
50
|
|
|
36
|
|
|
|
|
33
|
|
|
|
|
963
|
|
|
|
|
|
|
=head1 Name |
964
|
|
|
|
|
|
|
|
965
|
|
|
|
|
|
|
App::Sqitch::Plan - Sqitch Deployment Plan |
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
=head1 Synopsis |
968
|
6
|
100
|
|
|
|
21
|
|
969
|
3
|
|
|
|
|
28
|
my $plan = App::Sqitch::Plan->new( sqitch => $sqitch ); |
970
|
|
|
|
|
|
|
while (my $change = $plan->next) { |
971
|
6
|
|
|
|
|
38
|
say "Deploy ", $change->format_name; |
972
|
|
|
|
|
|
|
} |
973
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
=head1 Description |
975
|
11
|
|
|
|
|
47
|
|
976
|
|
|
|
|
|
|
App::Sqitch::Plan provides the interface for a Sqitch plan. It parses a plan |
977
|
|
|
|
|
|
|
file and provides an iteration interface for working with the plan. |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
=head1 Interface |
980
|
|
|
|
|
|
|
|
981
|
51
|
50
|
|
|
|
191
|
=head2 Constants |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
=head3 C<SYNTAX_VERSION> |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
Returns the current version of the Sqitch plan syntax. Used for the |
986
|
51
|
|
|
|
|
18432
|
C<%sytax-version> pragma. |
987
|
51
|
50
|
|
|
|
620
|
|
988
|
|
|
|
|
|
|
=head2 Class Methods |
989
|
|
|
|
|
|
|
|
990
|
|
|
|
|
|
|
=head3 C<name_regex> |
991
|
|
|
|
|
|
|
|
992
|
51
|
|
|
|
|
5073
|
die "$this has no name" unless $this =~ App::Sqitch::Plan->name_regex; |
993
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
Returns a regular expression that matches names. Note that it is not anchored, |
995
|
|
|
|
|
|
|
so if you need to make sure that a string is a valid name and nothing else, |
996
|
|
|
|
|
|
|
you will need to anchor it yourself, like so: |
997
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
my $name_re = App::Sqitch::Plan->name_regex; |
999
|
|
|
|
|
|
|
die "$this is not a valid name" if $this !~ /\A$name_re\z/; |
1000
|
|
|
|
|
|
|
|
1001
|
|
|
|
|
|
|
=head2 Constructors |
1002
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
=head3 C<new> |
1004
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
my $plan = App::Sqitch::Plan->new( sqitch => $sqitch ); |
1006
|
|
|
|
|
|
|
|
1007
|
|
|
|
|
|
|
Instantiates and returns a App::Sqitch::Plan object. Takes a single parameter: |
1008
|
|
|
|
|
|
|
an L<App::Sqitch> object. |
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
=head2 Accessors |
1011
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
=head3 C<sqitch> |
1013
|
|
|
|
|
|
|
|
1014
|
|
|
|
|
|
|
my $sqitch = $plan->sqitch; |
1015
|
|
|
|
|
|
|
|
1016
|
|
|
|
|
|
|
Returns the L<App::Sqitch> object that instantiated the plan. |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
=head3 C<target> |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
my $target = $plan->target |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
Returns the L<App::Sqitch::Target> passed to the constructor. |
1023
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
=head3 C<file> |
1025
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
my $file = $plan->file; |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
The file name from which to read the plan. |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
=head3 C<position> |
1031
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
Returns the current position of the iterator. This is an integer that's used |
1033
|
|
|
|
|
|
|
as an index into plan. If C<next()> has not been called, or if C<reset()> has |
1034
|
|
|
|
|
|
|
been called, the value will be -1, meaning it is outside of the plan. When |
1035
|
|
|
|
|
|
|
C<next> returns C<undef>, the value will be the last index in the plan plus 1. |
1036
|
|
|
|
|
|
|
|
1037
|
|
|
|
|
|
|
=head3 C<project> |
1038
|
|
|
|
|
|
|
|
1039
|
|
|
|
|
|
|
my $project = $plan->project; |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
Returns the name of the project as set via the C<%project> pragma in the plan |
1042
|
|
|
|
|
|
|
file. |
1043
|
|
|
|
|
|
|
|
1044
|
|
|
|
|
|
|
=head3 C<uri> |
1045
|
|
|
|
|
|
|
|
1046
|
|
|
|
|
|
|
my $uri = $plan->uri; |
1047
|
|
|
|
|
|
|
|
1048
|
|
|
|
|
|
|
Returns the URI for the project as set via the C<%uri> pragma, which is |
1049
|
|
|
|
|
|
|
optional. If it is not present, C<undef> will be returned. |
1050
|
|
|
|
|
|
|
|
1051
|
|
|
|
|
|
|
=head3 C<syntax_version> |
1052
|
|
|
|
|
|
|
|
1053
|
|
|
|
|
|
|
my $syntax_version = $plan->syntax_version; |
1054
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
Returns the plan syntax version, which is always the latest version. |
1056
|
|
|
|
|
|
|
|
1057
|
|
|
|
|
|
|
=head2 Instance Methods |
1058
|
|
|
|
|
|
|
|
1059
|
|
|
|
|
|
|
=head3 C<index_of> |
1060
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
my $index = $plan->index_of('6c2f28d125aff1deea615f8de774599acf39a7a1'); |
1062
|
|
|
|
|
|
|
my $foo_index = $plan->index_of('@foo'); |
1063
|
|
|
|
|
|
|
my $bar_index = $plan->index_of('bar'); |
1064
|
|
|
|
|
|
|
my $bar1_index = $plan->index_of('bar@alpha') |
1065
|
|
|
|
|
|
|
my $bar2_index = $plan->index_of('bar@HEAD'); |
1066
|
|
|
|
|
|
|
|
1067
|
|
|
|
|
|
|
Returns the index of the specified change. Returns C<undef> if no such change |
1068
|
|
|
|
|
|
|
exists. The argument may be any one of: |
1069
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
=over |
1071
|
|
|
|
|
|
|
|
1072
|
|
|
|
|
|
|
=item * An ID |
1073
|
|
|
|
|
|
|
|
1074
|
|
|
|
|
|
|
my $index = $plan->index_of('6c2f28d125aff1deea615f8de774599acf39a7a1'); |
1075
|
|
|
|
|
|
|
|
1076
|
|
|
|
|
|
|
This is the SHA1 hash of a change or tag. Currently, the full 40-character hexed |
1077
|
|
|
|
|
|
|
hash string must be specified. |
1078
|
|
|
|
|
|
|
|
1079
|
|
|
|
|
|
|
=item * A change name |
1080
|
|
|
|
|
|
|
|
1081
|
|
|
|
|
|
|
my $index = $plan->index_of('users_table'); |
1082
|
|
|
|
|
|
|
|
1083
|
|
|
|
|
|
|
The name of a change. Will throw an exception if the named change appears more |
1084
|
|
|
|
|
|
|
than once in the list. |
1085
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
=item * A tag name |
1087
|
|
|
|
|
|
|
|
1088
|
|
|
|
|
|
|
my $index = $plan->index_of('@beta1'); |
1089
|
|
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
The name of a tag, including the leading C<@>. |
1091
|
|
|
|
|
|
|
|
1092
|
|
|
|
|
|
|
=item * A tag-qualified change name |
1093
|
|
|
|
|
|
|
|
1094
|
|
|
|
|
|
|
my $index = $plan->index_of('users_table@beta1'); |
1095
|
|
|
|
|
|
|
|
1096
|
|
|
|
|
|
|
The named change as it was last seen in the list before the specified tag. |
1097
|
|
|
|
|
|
|
|
1098
|
|
|
|
|
|
|
=back |
1099
|
|
|
|
|
|
|
|
1100
|
|
|
|
|
|
|
=head3 C<contains> |
1101
|
|
|
|
|
|
|
|
1102
|
|
|
|
|
|
|
say 'Yes!' if $plan->contains('6c2f28d125aff1deea615f8de774599acf39a7a1'); |
1103
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
Like C<index_of()>, but never throws an exception, and returns true if the |
1105
|
|
|
|
|
|
|
plan contains the specified change, and false if it does not. |
1106
|
|
|
|
|
|
|
|
1107
|
|
|
|
|
|
|
=head3 C<get> |
1108
|
|
|
|
|
|
|
|
1109
|
|
|
|
|
|
|
my $change = $plan->get('6c2f28d125aff1deea615f8de774599acf39a7a1'); |
1110
|
|
|
|
|
|
|
my $foo = $plan->get('@foo'); |
1111
|
|
|
|
|
|
|
my $bar = $plan->get('bar'); |
1112
|
|
|
|
|
|
|
my $bar1 = $plan->get('bar@alpha') |
1113
|
|
|
|
|
|
|
my $bar2 = $plan->get('bar@HEAD'); |
1114
|
|
|
|
|
|
|
|
1115
|
|
|
|
|
|
|
Returns the change corresponding to the specified ID or name. The argument may |
1116
|
|
|
|
|
|
|
be in any of the formats described for C<index_of()>. |
1117
|
|
|
|
|
|
|
|
1118
|
|
|
|
|
|
|
=head3 C<find> |
1119
|
|
|
|
|
|
|
|
1120
|
|
|
|
|
|
|
my $change = $plan->find('6c2f28d125aff1deea615f8de774599acf39a7a1'); |
1121
|
|
|
|
|
|
|
my $foo = $plan->find('@foo'); |
1122
|
|
|
|
|
|
|
my $bar = $plan->find('bar'); |
1123
|
|
|
|
|
|
|
my $bar1 = $plan->find('bar@alpha') |
1124
|
|
|
|
|
|
|
my $bar2 = $plan->find('bar@HEAD'); |
1125
|
|
|
|
|
|
|
|
1126
|
|
|
|
|
|
|
Finds the change corresponding to the specified ID or name. The argument may be |
1127
|
|
|
|
|
|
|
in any of the formats described for C<index_of()>. Unlike C<get()>, C<find()> |
1128
|
|
|
|
|
|
|
will not throw an error if more than one change exists with the specified name, |
1129
|
|
|
|
|
|
|
but will return the first instance. |
1130
|
|
|
|
|
|
|
|
1131
|
|
|
|
|
|
|
=head3 C<first_index_of> |
1132
|
|
|
|
|
|
|
|
1133
|
|
|
|
|
|
|
my $index = $plan->first_index_of($change_name); |
1134
|
|
|
|
|
|
|
my $index = $plan->first_index_of($change_name, $change_or_tag_name); |
1135
|
|
|
|
|
|
|
|
1136
|
|
|
|
|
|
|
Returns the index of the first instance of the named change in the plan. If a |
1137
|
|
|
|
|
|
|
second argument is passed, the index of the first instance of the change |
1138
|
|
|
|
|
|
|
I<after> the index of the second argument will be returned. This is useful |
1139
|
|
|
|
|
|
|
for getting the index of a change as it was deployed after a particular tag, for |
1140
|
|
|
|
|
|
|
example, to get the first index of the F<foo> change since the C<@beta> tag, do |
1141
|
|
|
|
|
|
|
this: |
1142
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
my $index = $plan->first_index_of('foo', '@beta'); |
1144
|
|
|
|
|
|
|
|
1145
|
|
|
|
|
|
|
You can also specify the first instance of a change after another change, |
1146
|
|
|
|
|
|
|
including such a change at the point of a tag: |
1147
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
my $index = $plan->first_index_of('foo', 'users_table@beta1'); |
1149
|
|
|
|
|
|
|
|
1150
|
|
|
|
|
|
|
The second argument must unambiguously refer to a single change in the plan. As |
1151
|
|
|
|
|
|
|
such, it should usually be a tag name or tag-qualified change name. Returns |
1152
|
|
|
|
|
|
|
C<undef> if the change does not appear in the plan, or if it does not appear |
1153
|
|
|
|
|
|
|
after the specified second argument change name. |
1154
|
|
|
|
|
|
|
|
1155
|
|
|
|
|
|
|
=head3 C<last_tagged_change> |
1156
|
|
|
|
|
|
|
|
1157
|
|
|
|
|
|
|
my $change = $plan->last_tagged_change; |
1158
|
|
|
|
|
|
|
|
1159
|
|
|
|
|
|
|
Returns the last tagged change object. Returns C<undef> if no changes have |
1160
|
|
|
|
|
|
|
been tagged. |
1161
|
|
|
|
|
|
|
|
1162
|
|
|
|
|
|
|
=head3 C<change_at> |
1163
|
|
|
|
|
|
|
|
1164
|
|
|
|
|
|
|
my $change = $plan->change_at($index); |
1165
|
|
|
|
|
|
|
|
1166
|
|
|
|
|
|
|
Returns the change at the specified index. |
1167
|
|
|
|
|
|
|
|
1168
|
|
|
|
|
|
|
=head3 C<seek> |
1169
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
$plan->seek('@foo'); |
1171
|
|
|
|
|
|
|
$plan->seek('bar'); |
1172
|
|
|
|
|
|
|
|
1173
|
|
|
|
|
|
|
Move the plan position to the specified change. Dies if the change cannot be found |
1174
|
|
|
|
|
|
|
in the plan. |
1175
|
|
|
|
|
|
|
|
1176
|
|
|
|
|
|
|
=head3 C<reset> |
1177
|
|
|
|
|
|
|
|
1178
|
|
|
|
|
|
|
$plan->reset; |
1179
|
|
|
|
|
|
|
|
1180
|
|
|
|
|
|
|
Resets iteration. Same as C<< $plan->position(-1) >>, but better. |
1181
|
|
|
|
|
|
|
|
1182
|
|
|
|
|
|
|
=head3 C<next> |
1183
|
|
|
|
|
|
|
|
1184
|
|
|
|
|
|
|
while (my $change = $plan->next) { |
1185
|
|
|
|
|
|
|
say "Deploy ", $change->format_name; |
1186
|
|
|
|
|
|
|
} |
1187
|
|
|
|
|
|
|
|
1188
|
|
|
|
|
|
|
Returns the next L<change|App::Sqitch::Plan::Change> in the plan. Returns C<undef> |
1189
|
|
|
|
|
|
|
if there are no more changes. |
1190
|
|
|
|
|
|
|
|
1191
|
|
|
|
|
|
|
=head3 C<last> |
1192
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
my $change = $plan->last; |
1194
|
|
|
|
|
|
|
|
1195
|
|
|
|
|
|
|
Returns the last change in the plan. Does not change the current position. |
1196
|
|
|
|
|
|
|
|
1197
|
|
|
|
|
|
|
=head3 C<current> |
1198
|
|
|
|
|
|
|
|
1199
|
|
|
|
|
|
|
my $change = $plan->current; |
1200
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
Returns the same change as was last returned by C<next()>. Returns C<undef> if |
1202
|
|
|
|
|
|
|
C<next()> has not been called or if the plan has been reset. |
1203
|
|
|
|
|
|
|
|
1204
|
|
|
|
|
|
|
=head3 C<peek> |
1205
|
|
|
|
|
|
|
|
1206
|
|
|
|
|
|
|
my $change = $plan->peek; |
1207
|
|
|
|
|
|
|
|
1208
|
|
|
|
|
|
|
Returns the next change in the plan without incrementing the iterator. Returns |
1209
|
|
|
|
|
|
|
C<undef> if there are no more changes beyond the current change. |
1210
|
|
|
|
|
|
|
|
1211
|
|
|
|
|
|
|
=head3 C<changes> |
1212
|
|
|
|
|
|
|
|
1213
|
|
|
|
|
|
|
my @changes = $plan->changes; |
1214
|
|
|
|
|
|
|
|
1215
|
|
|
|
|
|
|
Returns all of the changes in the plan. This constitutes the entire plan. |
1216
|
|
|
|
|
|
|
|
1217
|
|
|
|
|
|
|
=head3 C<tags> |
1218
|
|
|
|
|
|
|
|
1219
|
|
|
|
|
|
|
my @tags = $plan->tags; |
1220
|
|
|
|
|
|
|
|
1221
|
|
|
|
|
|
|
Returns all of the tags in the plan. |
1222
|
|
|
|
|
|
|
|
1223
|
|
|
|
|
|
|
=head3 C<count> |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
my $count = $plan->count; |
1226
|
|
|
|
|
|
|
|
1227
|
|
|
|
|
|
|
Returns the number of changes in the plan. |
1228
|
|
|
|
|
|
|
|
1229
|
|
|
|
|
|
|
=head3 C<lines> |
1230
|
|
|
|
|
|
|
|
1231
|
|
|
|
|
|
|
my @lines = $plan->lines; |
1232
|
|
|
|
|
|
|
|
1233
|
|
|
|
|
|
|
Returns all of the lines in the plan. This includes all the |
1234
|
|
|
|
|
|
|
L<changes|App::Sqitch::Plan::Change>, L<tags|App::Sqitch::Plan::Tag>, |
1235
|
|
|
|
|
|
|
L<pragmas|App::Sqitch::Plan::Pragma>, and L<blank |
1236
|
|
|
|
|
|
|
lines|App::Sqitch::Plan::Blank>. |
1237
|
|
|
|
|
|
|
|
1238
|
|
|
|
|
|
|
=head3 C<do> |
1239
|
|
|
|
|
|
|
|
1240
|
|
|
|
|
|
|
$plan->do(sub { say $_[0]->name; return $_[0]; }); |
1241
|
|
|
|
|
|
|
$plan->do(sub { say $_->name; return $_; }); |
1242
|
|
|
|
|
|
|
|
1243
|
|
|
|
|
|
|
Pass a code reference to this method to execute it for each change in the plan. |
1244
|
|
|
|
|
|
|
Each change will be stored in C<$_> before executing the code reference, and |
1245
|
|
|
|
|
|
|
will also be passed as the sole argument. If C<next()> has been called prior |
1246
|
|
|
|
|
|
|
to the call to C<do()>, then only the remaining changes in the iterator will |
1247
|
|
|
|
|
|
|
passed to the code reference. Iteration terminates when the code reference |
1248
|
|
|
|
|
|
|
returns false, so be sure to have it return a true value if you want it to |
1249
|
|
|
|
|
|
|
iterate over every change. |
1250
|
|
|
|
|
|
|
|
1251
|
|
|
|
|
|
|
=head3 C<search_changes> |
1252
|
|
|
|
|
|
|
|
1253
|
|
|
|
|
|
|
my $iter = $engine->search_changes( %params ); |
1254
|
|
|
|
|
|
|
while (my $change = $iter->()) { |
1255
|
|
|
|
|
|
|
say '* $change->{event}ed $change->{change}"; |
1256
|
|
|
|
|
|
|
} |
1257
|
|
|
|
|
|
|
|
1258
|
|
|
|
|
|
|
Searches the changes in the plan returns an iterator code reference with the |
1259
|
|
|
|
|
|
|
results. If no parameters are provided, a list of all changes will be returned |
1260
|
|
|
|
|
|
|
from the iterator in plan order. The supported parameters are: |
1261
|
|
|
|
|
|
|
|
1262
|
|
|
|
|
|
|
=over |
1263
|
|
|
|
|
|
|
|
1264
|
|
|
|
|
|
|
=item C<event> |
1265
|
|
|
|
|
|
|
|
1266
|
|
|
|
|
|
|
An array of the type of event to search for. Allowed values are "deploy" and |
1267
|
|
|
|
|
|
|
"revert". |
1268
|
|
|
|
|
|
|
|
1269
|
|
|
|
|
|
|
=item C<name> |
1270
|
|
|
|
|
|
|
|
1271
|
|
|
|
|
|
|
Limit the results to changes with names matching the specified regular |
1272
|
|
|
|
|
|
|
expression. |
1273
|
|
|
|
|
|
|
|
1274
|
|
|
|
|
|
|
=item C<planner> |
1275
|
|
|
|
|
|
|
|
1276
|
|
|
|
|
|
|
Limit the changes to those added by planners matching the specified regular |
1277
|
|
|
|
|
|
|
expression. |
1278
|
|
|
|
|
|
|
|
1279
|
|
|
|
|
|
|
=item C<limit> |
1280
|
|
|
|
|
|
|
|
1281
|
|
|
|
|
|
|
Limit the number of changes to the specified number. |
1282
|
|
|
|
|
|
|
|
1283
|
|
|
|
|
|
|
=item C<offset> |
1284
|
|
|
|
|
|
|
|
1285
|
|
|
|
|
|
|
Skip the specified number of events. |
1286
|
|
|
|
|
|
|
|
1287
|
|
|
|
|
|
|
=item C<direction> |
1288
|
|
|
|
|
|
|
|
1289
|
|
|
|
|
|
|
Return the results in the specified order, which must be a value matching |
1290
|
|
|
|
|
|
|
C</^(:?a|de)sc/i> for "ascending" or "descending". |
1291
|
|
|
|
|
|
|
|
1292
|
|
|
|
|
|
|
=back |
1293
|
|
|
|
|
|
|
|
1294
|
|
|
|
|
|
|
=head3 C<write_to> |
1295
|
|
|
|
|
|
|
|
1296
|
|
|
|
|
|
|
$plan->write_to($file); |
1297
|
|
|
|
|
|
|
$plan->write_to($file, $from, $to); |
1298
|
|
|
|
|
|
|
|
1299
|
|
|
|
|
|
|
Write the plan to the named file, including notes and white space from the |
1300
|
|
|
|
|
|
|
original plan file. If C<from> and/or C<$to> are provided, the plan will be |
1301
|
|
|
|
|
|
|
written only with the pragmas headers and the lines between those specified |
1302
|
|
|
|
|
|
|
changes. |
1303
|
|
|
|
|
|
|
|
1304
|
|
|
|
|
|
|
=head3 C<open_script> |
1305
|
|
|
|
|
|
|
|
1306
|
|
|
|
|
|
|
my $file_handle = $plan->open_script( $change->deploy_file ); |
1307
|
|
|
|
|
|
|
|
1308
|
|
|
|
|
|
|
Opens the script file passed to it and returns a file handle for reading. The |
1309
|
|
|
|
|
|
|
script file must be encoded in UTF-8. |
1310
|
|
|
|
|
|
|
|
1311
|
|
|
|
|
|
|
=head3 C<load> |
1312
|
|
|
|
|
|
|
|
1313
|
|
|
|
|
|
|
my $plan_data = $plan->load; |
1314
|
|
|
|
|
|
|
|
1315
|
|
|
|
|
|
|
Loads the plan data. Called internally, not meant to be called directly, as it |
1316
|
|
|
|
|
|
|
parses the plan file and deploy scripts every time it's called. If you want |
1317
|
|
|
|
|
|
|
the all of the changes, call C<changes()> instead. And if you want to load an |
1318
|
|
|
|
|
|
|
alternate plan, use C<parse()>. |
1319
|
|
|
|
|
|
|
|
1320
|
|
|
|
|
|
|
=head3 C<parse> |
1321
|
|
|
|
|
|
|
|
1322
|
|
|
|
|
|
|
$plan->parse($plan_data); |
1323
|
|
|
|
|
|
|
|
1324
|
|
|
|
|
|
|
Load an alternate plan by passing the complete text of the plan. The text |
1325
|
|
|
|
|
|
|
should be UTF-8 encoded. Useful for loading a plan from a different VCS |
1326
|
|
|
|
|
|
|
branch, for example. |
1327
|
|
|
|
|
|
|
|
1328
|
|
|
|
|
|
|
=head3 C<check_changes> |
1329
|
|
|
|
|
|
|
|
1330
|
|
|
|
|
|
|
@changes = $plan->check_changes( $project, @changes ); |
1331
|
|
|
|
|
|
|
@changes = $plan->check_changes( $project, { '@foo' => 1 }, @changes ); |
1332
|
|
|
|
|
|
|
|
1333
|
|
|
|
|
|
|
Checks a list of changes to validate their dependencies and returns them. If |
1334
|
|
|
|
|
|
|
the second argument is a hash reference, its keys should be previously-seen |
1335
|
|
|
|
|
|
|
change and tag names that can be assumed to be satisfied requirements for the |
1336
|
|
|
|
|
|
|
succeeding changes. |
1337
|
|
|
|
|
|
|
|
1338
|
|
|
|
|
|
|
=head3 C<tag> |
1339
|
|
|
|
|
|
|
|
1340
|
|
|
|
|
|
|
$plan->tag( name => 'whee' ); |
1341
|
|
|
|
|
|
|
|
1342
|
|
|
|
|
|
|
Tags a change in the plan. Exits with a fatal error if the tag already exists |
1343
|
|
|
|
|
|
|
in the plan or if a change cannot be found to tag. The supported parameters |
1344
|
|
|
|
|
|
|
are: |
1345
|
|
|
|
|
|
|
|
1346
|
|
|
|
|
|
|
=over |
1347
|
|
|
|
|
|
|
|
1348
|
|
|
|
|
|
|
=item C<name> |
1349
|
|
|
|
|
|
|
|
1350
|
|
|
|
|
|
|
The tag name to use. Required. |
1351
|
|
|
|
|
|
|
|
1352
|
|
|
|
|
|
|
=item C<change> |
1353
|
|
|
|
|
|
|
|
1354
|
|
|
|
|
|
|
The change to be tagged, specified as a supported change specification as |
1355
|
|
|
|
|
|
|
described in L<sqitchchanges>. Defaults to the last change in the plan. |
1356
|
|
|
|
|
|
|
|
1357
|
|
|
|
|
|
|
=item C<note> |
1358
|
|
|
|
|
|
|
|
1359
|
|
|
|
|
|
|
A brief note about the tag. |
1360
|
|
|
|
|
|
|
|
1361
|
|
|
|
|
|
|
=item C<planner_name> |
1362
|
|
|
|
|
|
|
|
1363
|
|
|
|
|
|
|
The name of the user adding the tag to the plan. Defaults to the value of the |
1364
|
|
|
|
|
|
|
C<user.name> configuration variable. |
1365
|
|
|
|
|
|
|
|
1366
|
|
|
|
|
|
|
=item C<planner_email> |
1367
|
|
|
|
|
|
|
|
1368
|
|
|
|
|
|
|
The email address of the user adding the tag to the plan. Defaults to the |
1369
|
|
|
|
|
|
|
value of the C<user.email> configuration variable. |
1370
|
|
|
|
|
|
|
|
1371
|
|
|
|
|
|
|
=back |
1372
|
|
|
|
|
|
|
|
1373
|
|
|
|
|
|
|
=head3 C<add> |
1374
|
|
|
|
|
|
|
|
1375
|
|
|
|
|
|
|
$plan->add( name => 'whatevs' ); |
1376
|
|
|
|
|
|
|
$plan->add( |
1377
|
|
|
|
|
|
|
name => 'widgets', |
1378
|
|
|
|
|
|
|
requires => [qw(foo bar)], |
1379
|
|
|
|
|
|
|
conflicts => [qw(dr_evil)], |
1380
|
|
|
|
|
|
|
); |
1381
|
|
|
|
|
|
|
|
1382
|
|
|
|
|
|
|
Adds a change to the plan. The supported parameters are the same as those |
1383
|
|
|
|
|
|
|
passed to the L<App::Sqitch::Plan::Change> constructor. Exits with a fatal |
1384
|
|
|
|
|
|
|
error if the change already exists, or if the any of the dependencies are |
1385
|
|
|
|
|
|
|
unknown. |
1386
|
|
|
|
|
|
|
|
1387
|
|
|
|
|
|
|
=head3 C<rework> |
1388
|
|
|
|
|
|
|
|
1389
|
|
|
|
|
|
|
$plan->rework( 'whatevs' ); |
1390
|
|
|
|
|
|
|
$plan->rework( 'widgets', [qw(foo bar)], [qw(dr_evil)] ); |
1391
|
|
|
|
|
|
|
|
1392
|
|
|
|
|
|
|
Reworks an existing change. Said change must already exist in the plan and be |
1393
|
|
|
|
|
|
|
tagged or have a tag following it or an exception will be thrown. The previous |
1394
|
|
|
|
|
|
|
occurrence of the change will have the suffix of the most recent tag added to |
1395
|
|
|
|
|
|
|
it, and a new tag instance will be added to the list. |
1396
|
|
|
|
|
|
|
|
1397
|
|
|
|
|
|
|
=head1 Plan File |
1398
|
|
|
|
|
|
|
|
1399
|
|
|
|
|
|
|
A plan file describes the deployment changes to be run against a database, and |
1400
|
|
|
|
|
|
|
is typically maintained using the L<C<add>|sqitch-add> and |
1401
|
|
|
|
|
|
|
L<C<rework>|sqitch-rework> commands. Its contents must be plain text encoded |
1402
|
|
|
|
|
|
|
as UTF-8. Each line of a plan file may be one of four things: |
1403
|
|
|
|
|
|
|
|
1404
|
|
|
|
|
|
|
=over |
1405
|
|
|
|
|
|
|
|
1406
|
|
|
|
|
|
|
=item * |
1407
|
|
|
|
|
|
|
|
1408
|
|
|
|
|
|
|
A blank line. May include any amount of white space, which will be ignored. |
1409
|
|
|
|
|
|
|
|
1410
|
|
|
|
|
|
|
=item * A Pragma |
1411
|
|
|
|
|
|
|
|
1412
|
|
|
|
|
|
|
Begins with a C<%>, followed by a pragma name, optionally followed by C<=> and |
1413
|
|
|
|
|
|
|
a value. Currently, the only pragma recognized by Sqitch is C<syntax-version>. |
1414
|
|
|
|
|
|
|
|
1415
|
|
|
|
|
|
|
=item * A change. |
1416
|
|
|
|
|
|
|
|
1417
|
|
|
|
|
|
|
A named change change as defined in L<sqitchchanges>. A change may then also |
1418
|
|
|
|
|
|
|
contain a space-delimited list of dependencies, which are the names of other |
1419
|
|
|
|
|
|
|
changes or tags prefixed with a colon (C<:>) for required changes or with an |
1420
|
|
|
|
|
|
|
exclamation point (C<!>) for conflicting changes. |
1421
|
|
|
|
|
|
|
|
1422
|
|
|
|
|
|
|
Changes with a leading C<-> are slated to be reverted, while changes with no |
1423
|
|
|
|
|
|
|
character or a leading C<+> are to be deployed. |
1424
|
|
|
|
|
|
|
|
1425
|
|
|
|
|
|
|
=item * A tag. |
1426
|
|
|
|
|
|
|
|
1427
|
|
|
|
|
|
|
A named deployment tag, generally corresponding to a release name. Begins with |
1428
|
|
|
|
|
|
|
a C<@>, followed by one or more non-blanks characters, excluding "@", ":", |
1429
|
|
|
|
|
|
|
"#", and blanks. The first and last characters must not be punctuation |
1430
|
|
|
|
|
|
|
characters. |
1431
|
|
|
|
|
|
|
|
1432
|
|
|
|
|
|
|
=item * A note. |
1433
|
|
|
|
|
|
|
|
1434
|
|
|
|
|
|
|
Begins with a C<#> and goes to the end of the line. Preceding white space is |
1435
|
|
|
|
|
|
|
ignored. May appear on a line after a pragma, change, or tag. |
1436
|
|
|
|
|
|
|
|
1437
|
|
|
|
|
|
|
=back |
1438
|
|
|
|
|
|
|
|
1439
|
|
|
|
|
|
|
Here's an example of a plan file with a single deploy change and tag: |
1440
|
|
|
|
|
|
|
|
1441
|
|
|
|
|
|
|
%syntax-version=1.0.0 |
1442
|
|
|
|
|
|
|
+users_table |
1443
|
|
|
|
|
|
|
@alpha |
1444
|
|
|
|
|
|
|
|
1445
|
|
|
|
|
|
|
There may, of course, be any number of tags and changes. Here's an expansion: |
1446
|
|
|
|
|
|
|
|
1447
|
|
|
|
|
|
|
%syntax-version=1.0.0 |
1448
|
|
|
|
|
|
|
+users_table |
1449
|
|
|
|
|
|
|
+insert_user |
1450
|
|
|
|
|
|
|
+update_user |
1451
|
|
|
|
|
|
|
+delete_user |
1452
|
|
|
|
|
|
|
@root |
1453
|
|
|
|
|
|
|
@alpha |
1454
|
|
|
|
|
|
|
|
1455
|
|
|
|
|
|
|
Here we have four changes -- "users_table", "insert_user", "update_user", and |
1456
|
|
|
|
|
|
|
"delete_user" -- followed by two tags: "@root" and "@alpha". |
1457
|
|
|
|
|
|
|
|
1458
|
|
|
|
|
|
|
Most plans will have many changes and tags. Here's a longer example with three |
1459
|
|
|
|
|
|
|
tagged deployment points, as well as a change that is deployed and later |
1460
|
|
|
|
|
|
|
reverted: |
1461
|
|
|
|
|
|
|
|
1462
|
|
|
|
|
|
|
%syntax-version=1.0.0 |
1463
|
|
|
|
|
|
|
+users_table |
1464
|
|
|
|
|
|
|
+insert_user |
1465
|
|
|
|
|
|
|
+update_user |
1466
|
|
|
|
|
|
|
+delete_user |
1467
|
|
|
|
|
|
|
+dr_evil |
1468
|
|
|
|
|
|
|
@root |
1469
|
|
|
|
|
|
|
@alpha |
1470
|
|
|
|
|
|
|
|
1471
|
|
|
|
|
|
|
+widgets_table |
1472
|
|
|
|
|
|
|
+list_widgets |
1473
|
|
|
|
|
|
|
@beta |
1474
|
|
|
|
|
|
|
|
1475
|
|
|
|
|
|
|
-dr_evil |
1476
|
|
|
|
|
|
|
+ftw |
1477
|
|
|
|
|
|
|
@gamma |
1478
|
|
|
|
|
|
|
|
1479
|
|
|
|
|
|
|
Using this plan, to deploy to the "beta" tag, all of the changes up to the |
1480
|
|
|
|
|
|
|
"@root" and "@alpha" tags must be deployed, as must changes listed before the |
1481
|
|
|
|
|
|
|
"@beta" tag. To then deploy to the "@gamma" tag, the "dr_evil" change must be |
1482
|
|
|
|
|
|
|
reverted and the "ftw" change must be deployed. If you then choose to revert |
1483
|
|
|
|
|
|
|
to "@alpha", then the "ftw" change will be reverted, the "dr_evil" change |
1484
|
|
|
|
|
|
|
re-deployed, and the "@gamma" tag removed; then "list_widgets" must be |
1485
|
|
|
|
|
|
|
reverted and the associated "@beta" tag removed, then the "widgets_table" |
1486
|
|
|
|
|
|
|
change must be reverted. |
1487
|
|
|
|
|
|
|
|
1488
|
|
|
|
|
|
|
Changes can only be repeated if one or more tags intervene. This allows Sqitch |
1489
|
|
|
|
|
|
|
to distinguish between them. An example: |
1490
|
|
|
|
|
|
|
|
1491
|
|
|
|
|
|
|
%syntax-version=1.0.0 |
1492
|
|
|
|
|
|
|
+users_table |
1493
|
|
|
|
|
|
|
@alpha |
1494
|
|
|
|
|
|
|
|
1495
|
|
|
|
|
|
|
+add_widget |
1496
|
|
|
|
|
|
|
+widgets_table |
1497
|
|
|
|
|
|
|
@beta |
1498
|
|
|
|
|
|
|
|
1499
|
|
|
|
|
|
|
+add_user |
1500
|
|
|
|
|
|
|
@gamma |
1501
|
|
|
|
|
|
|
|
1502
|
|
|
|
|
|
|
+widgets_created_at |
1503
|
|
|
|
|
|
|
@delta |
1504
|
|
|
|
|
|
|
|
1505
|
|
|
|
|
|
|
+add_widget |
1506
|
|
|
|
|
|
|
|
1507
|
|
|
|
|
|
|
Note that the "add_widget" change is repeated after the "@beta" tag, and at |
1508
|
|
|
|
|
|
|
the end. Sqitch will notice the repetition when it parses this file, and allow |
1509
|
|
|
|
|
|
|
it, because at least one tag "@beta" appears between the instances of |
1510
|
|
|
|
|
|
|
"add_widget". When deploying, Sqitch will fetch the instance of the deploy |
1511
|
|
|
|
|
|
|
script as of the "@delta" tag and apply it as the first change, and then, when |
1512
|
|
|
|
|
|
|
it gets to the last change, retrieve the current instance of the deploy |
1513
|
|
|
|
|
|
|
script. How does it find such files? The first instances files will either be |
1514
|
|
|
|
|
|
|
named F<add_widget@delta.sql> or (soon) findable in the VCS history as of a |
1515
|
|
|
|
|
|
|
VCS "delta" tag. |
1516
|
|
|
|
|
|
|
|
1517
|
|
|
|
|
|
|
=head2 Grammar |
1518
|
|
|
|
|
|
|
|
1519
|
|
|
|
|
|
|
Here is the EBNF Grammar for the plan file: |
1520
|
|
|
|
|
|
|
|
1521
|
|
|
|
|
|
|
plan-file = { <pragma> | <change-line> | <tag-line> | <note-line> | <blank-line> }* ; |
1522
|
|
|
|
|
|
|
|
1523
|
|
|
|
|
|
|
blank-line = [ <blanks> ] <eol>; |
1524
|
|
|
|
|
|
|
note-line = <note> ; |
1525
|
|
|
|
|
|
|
change-line = <name> [ "[" { <requires> | <conflicts> } "]" ] ( <eol> | <note> ) ; |
1526
|
|
|
|
|
|
|
tag-line = <tag> ( <eol> | <note> ) ; |
1527
|
|
|
|
|
|
|
pragma = "%" [ <blanks> ] <name> [ <blanks> ] = [ <blanks> ] <value> ( <eol> | <note> ) ; |
1528
|
|
|
|
|
|
|
|
1529
|
|
|
|
|
|
|
tag = "@" <name> ; |
1530
|
|
|
|
|
|
|
requires = <name> ; |
1531
|
|
|
|
|
|
|
conflicts = "!" <name> ; |
1532
|
|
|
|
|
|
|
name = <non-punct> [ [ ? non-blank and not "@", ":", or "#" characters ? ] <non-punct> ] ; |
1533
|
|
|
|
|
|
|
non-punct = ? non-punctuation, non-blank character ? ; |
1534
|
|
|
|
|
|
|
value = ? non-EOL or "#" characters ? |
1535
|
|
|
|
|
|
|
|
1536
|
|
|
|
|
|
|
note = [ <blanks> ] "#" [ <string> ] <EOL> ; |
1537
|
|
|
|
|
|
|
eol = [ <blanks> ] <EOL> ; |
1538
|
|
|
|
|
|
|
|
1539
|
|
|
|
|
|
|
blanks = ? blank characters ? ; |
1540
|
|
|
|
|
|
|
string = ? non-EOL characters ? ; |
1541
|
|
|
|
|
|
|
|
1542
|
|
|
|
|
|
|
And written as regular expressions: |
1543
|
|
|
|
|
|
|
|
1544
|
|
|
|
|
|
|
my $eol = qr/[[:blank:]]*$/ |
1545
|
|
|
|
|
|
|
my $note = qr/(?:[[:blank:]]+)?[#].+$/; |
1546
|
|
|
|
|
|
|
my $punct = q{-!"#$%&'()*+,./:;<=>?@[\\]^`{|}~}; |
1547
|
|
|
|
|
|
|
my $name = qr/[^$punct[:blank:]](?:(?:[^[:space:]:#@]+)?[^$punct[:blank:]])?/; |
1548
|
|
|
|
|
|
|
my $tag = qr/[@]$name/; |
1549
|
|
|
|
|
|
|
my $requires = qr/$name/; |
1550
|
|
|
|
|
|
|
my conflicts = qr/[!]$name/; |
1551
|
|
|
|
|
|
|
my $tag_line = qr/^$tag(?:$note|$eol)/; |
1552
|
|
|
|
|
|
|
my $change_line = qr/^$name(?:[[](?:$requires|$conflicts)+[]])?(?:$note|$eol)/; |
1553
|
|
|
|
|
|
|
my $note_line = qr/^$note/; |
1554
|
|
|
|
|
|
|
my $pragma = qr/^][[:blank:]]*[%][[:blank:]]*$name[[:blank:]]*=[[:blank:]].+?(?:$note|$eol)$/; |
1555
|
|
|
|
|
|
|
my $blank_line = qr/^$eol/; |
1556
|
|
|
|
|
|
|
my $plan = qr/(?:$pragma|$change_line|$tag_line|$note_line|$blank_line)+/ms; |
1557
|
|
|
|
|
|
|
|
1558
|
|
|
|
|
|
|
=head1 See Also |
1559
|
|
|
|
|
|
|
|
1560
|
|
|
|
|
|
|
=over |
1561
|
|
|
|
|
|
|
|
1562
|
|
|
|
|
|
|
=item L<sqitch> |
1563
|
|
|
|
|
|
|
|
1564
|
|
|
|
|
|
|
The Sqitch command-line client. |
1565
|
|
|
|
|
|
|
|
1566
|
|
|
|
|
|
|
=back |
1567
|
|
|
|
|
|
|
|
1568
|
|
|
|
|
|
|
=head1 Author |
1569
|
|
|
|
|
|
|
|
1570
|
|
|
|
|
|
|
David E. Wheeler <david@justatheory.com> |
1571
|
|
|
|
|
|
|
|
1572
|
|
|
|
|
|
|
=head1 License |
1573
|
|
|
|
|
|
|
|
1574
|
|
|
|
|
|
|
Copyright (c) 2012-2022 iovation Inc., David E. Wheeler |
1575
|
|
|
|
|
|
|
|
1576
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
1577
|
|
|
|
|
|
|
of this software and associated documentation files (the "Software"), to deal |
1578
|
|
|
|
|
|
|
in the Software without restriction, including without limitation the rights |
1579
|
|
|
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
1580
|
|
|
|
|
|
|
copies of the Software, and to permit persons to whom the Software is |
1581
|
|
|
|
|
|
|
furnished to do so, subject to the following conditions: |
1582
|
|
|
|
|
|
|
|
1583
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in all |
1584
|
|
|
|
|
|
|
copies or substantial portions of the Software. |
1585
|
|
|
|
|
|
|
|
1586
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
1587
|
|
|
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
1588
|
|
|
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
1589
|
|
|
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
1590
|
|
|
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
1591
|
|
|
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
1592
|
|
|
|
|
|
|
SOFTWARE. |
1593
|
|
|
|
|
|
|
|
1594
|
|
|
|
|
|
|
=cut |