| 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 |