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