| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package App::SVN::Bisect; |
|
2
|
1
|
|
|
1
|
|
97625
|
use strict; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
38
|
|
|
3
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
28
|
|
|
4
|
|
|
|
|
|
|
|
|
5
|
1
|
|
|
1
|
|
6
|
use Carp; |
|
|
1
|
|
|
|
|
7
|
|
|
|
1
|
|
|
|
|
88
|
|
|
6
|
1
|
|
|
1
|
|
13
|
use File::Spec; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
32
|
|
|
7
|
1
|
|
|
1
|
|
1002
|
use IO::All; |
|
|
1
|
|
|
|
|
14095
|
|
|
|
1
|
|
|
|
|
9
|
|
|
8
|
1
|
|
|
1
|
|
1005
|
use YAML::Syck; |
|
|
1
|
|
|
|
|
2357
|
|
|
|
1
|
|
|
|
|
1880
|
|
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
our $VERSION = '1.1'; |
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 NAME |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
App::SVN::Bisect - binary search through svn revisions |
|
15
|
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
my $bisect = App::SVN::Bisect->new( |
|
19
|
|
|
|
|
|
|
Action => $action, |
|
20
|
|
|
|
|
|
|
Min => $min, |
|
21
|
|
|
|
|
|
|
Max => $max |
|
22
|
|
|
|
|
|
|
); |
|
23
|
|
|
|
|
|
|
$bisect->do_something_intelligent(@ARGV); |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
This module implements the backend of the "svn-bisect" command line tool. See |
|
29
|
|
|
|
|
|
|
the POD documentation of that tool, for usage details. |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
=head1 API METHODS |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=cut |
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
my %actions = ( |
|
38
|
|
|
|
|
|
|
'after' => { read_config => 1, write_config => 1, handler => \&after }, |
|
39
|
|
|
|
|
|
|
'bad' => { read_config => 1, write_config => 1, handler => \&after }, |
|
40
|
|
|
|
|
|
|
'before' => { read_config => 1, write_config => 1, handler => \&before }, |
|
41
|
|
|
|
|
|
|
'good' => { read_config => 1, write_config => 1, handler => \&before }, |
|
42
|
|
|
|
|
|
|
'help' => { read_config => 0, write_config => 0, handler => \&help }, |
|
43
|
|
|
|
|
|
|
'reset' => { read_config => 1, write_config => 0, handler => \&reset }, |
|
44
|
|
|
|
|
|
|
'run' => { read_config => 1, write_config => 1, handler => \&run }, |
|
45
|
|
|
|
|
|
|
'skip' => { read_config => 1, write_config => 1, handler => \&skip }, |
|
46
|
|
|
|
|
|
|
'start' => { read_config => 0, write_config => 1, handler => \&start }, |
|
47
|
|
|
|
|
|
|
'unskip' => { read_config => 1, write_config => 1, handler => \&unskip }, |
|
48
|
|
|
|
|
|
|
'view' => { read_config => 1, write_config => 0, handler => \&view }, |
|
49
|
|
|
|
|
|
|
); |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=head2 new |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
$self = App::SVN::Bisect->new(Action => "bad", Min => 0, Max => undef); |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
Create an App::SVN::Bisect object. The arguments are typically parsed from |
|
56
|
|
|
|
|
|
|
the command line. |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
The Action argument must be listed in the %actions table. The "read_config" |
|
59
|
|
|
|
|
|
|
attribute of the action determines whether the metadata file (typically named |
|
60
|
|
|
|
|
|
|
.svn/bisect.yaml) will be read. |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=cut |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
sub new { |
|
65
|
21
|
|
|
21
|
1
|
30515
|
my ($package, %args) = @_; |
|
66
|
21
|
|
|
|
|
392
|
my $metadata = File::Spec->catfile(".svn", "bisect.yaml"); |
|
67
|
21
|
100
|
|
|
|
111
|
die("You must specify an action! Try running \"$0 help\".\n") |
|
68
|
|
|
|
|
|
|
unless defined $args{Action}; |
|
69
|
20
|
|
|
|
|
42
|
my $action = $args{Action}; |
|
70
|
20
|
100
|
|
|
|
86
|
die("Unknown action $action! Try running \"$0 help\".\n") |
|
71
|
|
|
|
|
|
|
unless exists $actions{$action}; |
|
72
|
19
|
|
|
|
|
155
|
my $self = { |
|
73
|
|
|
|
|
|
|
args => \%args, |
|
74
|
|
|
|
|
|
|
action => $action, |
|
75
|
|
|
|
|
|
|
config => { |
|
76
|
|
|
|
|
|
|
skip => {}, |
|
77
|
|
|
|
|
|
|
}, |
|
78
|
|
|
|
|
|
|
metadata => $metadata, |
|
79
|
|
|
|
|
|
|
}; |
|
80
|
19
|
100
|
|
|
|
189
|
if($actions{$action}{read_config}) { |
|
81
|
12
|
100
|
|
|
|
258
|
die("A bisect is not in progress! Try \"$0 help start\".\n") |
|
82
|
|
|
|
|
|
|
unless -f $metadata; |
|
83
|
11
|
|
|
|
|
48
|
$$self{config} = Load(io($metadata)->all); |
|
84
|
|
|
|
|
|
|
} |
|
85
|
18
|
|
|
|
|
15497
|
$ENV{LC_MESSAGES} = 'C'; |
|
86
|
18
|
|
|
|
|
392
|
return bless($self, $package); |
|
87
|
|
|
|
|
|
|
} |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
=head2 do_something_intelligent |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
$self->do_something_intelligent(@ARGV); |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
Executes the action specified by the user. See the "Action methods" section, |
|
95
|
|
|
|
|
|
|
below, for the details. |
|
96
|
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
If the action's "write_config" bit is set in the %actions table, the metadata |
|
98
|
|
|
|
|
|
|
file will be written after executing the action. If the bit was not set, the |
|
99
|
|
|
|
|
|
|
metadata file is removed. |
|
100
|
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
=cut |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
sub do_something_intelligent { |
|
104
|
23
|
|
|
23
|
1
|
8036
|
my $self = shift; |
|
105
|
23
|
|
|
|
|
92
|
my $handler = $actions{$$self{action}}{handler}; |
|
106
|
23
|
|
|
|
|
147
|
my $rv = &$handler($self, @_); |
|
107
|
11
|
|
|
|
|
1241
|
unlink($$self{metadata}); |
|
108
|
11
|
100
|
|
|
|
78
|
io($$self{metadata}) < Dump($$self{config}) |
|
109
|
|
|
|
|
|
|
if $actions{$$self{action}}{write_config}; |
|
110
|
11
|
|
|
|
|
22014
|
return $rv; |
|
111
|
|
|
|
|
|
|
} |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=head1 ACTION METHODS |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=head2 start |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
Begins a bisect session. Sets up the parameters, queries some stuff about the |
|
119
|
|
|
|
|
|
|
subversion repository, and starts the user off with the first bisect. |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=cut |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
sub start { |
|
124
|
4
|
|
|
4
|
1
|
7
|
my $self = shift; |
|
125
|
4
|
100
|
|
|
|
90
|
die("A bisect is already in progress. Try \"$0 help reset\".\n") |
|
126
|
|
|
|
|
|
|
if -f $$self{metadata}; |
|
127
|
3
|
50
|
|
|
|
15
|
$$self{config}{min} = $$self{args}{Min} if defined $$self{args}{Min}; |
|
128
|
3
|
|
|
|
|
88
|
$$self{config}{orig} = $self->find_cur(); |
|
129
|
3
|
|
|
|
|
21
|
my $max = $self->find_max(); |
|
130
|
3
|
50
|
|
|
|
14
|
if(defined($$self{args}{Max})) { |
|
131
|
3
|
50
|
|
|
|
15
|
$$self{args}{Max} = substr($$self{args}{Max},1) if substr($$self{args}{Max},0,1) eq 'r'; |
|
132
|
3
|
|
|
|
|
10
|
$$self{config}{max} = $$self{args}{Max}; |
|
133
|
3
|
100
|
|
|
|
26
|
die("Given 'max' value is greater than the working directory maximum $max!\n") |
|
134
|
|
|
|
|
|
|
if $$self{config}{max} > $max; |
|
135
|
|
|
|
|
|
|
} |
|
136
|
2
|
|
|
|
|
25
|
return $self->next_rev(); |
|
137
|
|
|
|
|
|
|
} |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
=head2 before |
|
141
|
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
Sets the "min" parameter to the specified (or current) revision, and |
|
143
|
|
|
|
|
|
|
then moves the user to the middle of the resulting range. |
|
144
|
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
=cut |
|
146
|
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
sub before { |
|
148
|
3
|
|
|
3
|
1
|
8
|
my $self = shift; |
|
149
|
3
|
|
|
|
|
6
|
my $rev = shift; |
|
150
|
3
|
100
|
|
|
|
11
|
$rev = $$self{config}{cur} unless defined $rev; |
|
151
|
3
|
50
|
|
|
|
12
|
$rev = $$self{config}{cur} = $self->find_cur() unless defined $rev; |
|
152
|
3
|
50
|
|
|
|
13
|
$rev = substr($rev, 1) if substr($rev, 0, 1) eq 'r'; |
|
153
|
3
|
50
|
|
|
|
16
|
if($self->ready) { |
|
154
|
3
|
100
|
|
|
|
22
|
die("\"$rev\" is not a revision or is out of range.\n") |
|
155
|
|
|
|
|
|
|
unless exists($$self{config}{extant}{$rev}); |
|
156
|
|
|
|
|
|
|
} |
|
157
|
2
|
|
|
|
|
6
|
$$self{config}{min} = $rev; |
|
158
|
2
|
|
|
|
|
11
|
return $self->next_rev(); |
|
159
|
|
|
|
|
|
|
} |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=head2 after |
|
163
|
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
Sets the "max" parameter to the specified (or current) revision, and |
|
165
|
|
|
|
|
|
|
then moves the user to the middle of the resulting range. |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=cut |
|
168
|
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
sub after { |
|
170
|
3
|
|
|
3
|
1
|
7
|
my $self = shift; |
|
171
|
3
|
|
|
|
|
5
|
my $rev = shift; |
|
172
|
3
|
100
|
|
|
|
13
|
$rev = $$self{config}{cur} unless defined $rev; |
|
173
|
3
|
50
|
|
|
|
9
|
$rev = $$self{config}{cur} = $self->find_cur() unless defined $rev; |
|
174
|
3
|
50
|
|
|
|
16
|
$rev = substr($rev, 1) if substr($rev, 0, 1) eq 'r'; |
|
175
|
3
|
50
|
|
|
|
14
|
if($self->ready) { |
|
176
|
3
|
100
|
|
|
|
22
|
die("\"$rev\" is not a revision or is out of range.\n") |
|
177
|
|
|
|
|
|
|
unless exists($$self{config}{extant}{$rev}); |
|
178
|
|
|
|
|
|
|
} else { |
|
179
|
0
|
|
|
|
|
0
|
my $max = $self->find_max(); |
|
180
|
0
|
0
|
|
|
|
0
|
die("$rev is greater than the working directory maximum $max!\n") |
|
181
|
|
|
|
|
|
|
if $max < $rev; |
|
182
|
|
|
|
|
|
|
} |
|
183
|
2
|
|
|
|
|
6
|
$$self{config}{max} = $rev; |
|
184
|
2
|
|
|
|
|
13
|
return $self->next_rev(); |
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=head2 reset |
|
189
|
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
Cleans up after a bisect session. If --back is passed, it also moves |
|
191
|
|
|
|
|
|
|
the working tree back to the original revision it had when "start" was |
|
192
|
|
|
|
|
|
|
first called. |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=cut |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
sub reset { |
|
197
|
1
|
|
|
1
|
1
|
3
|
my $self = shift; |
|
198
|
1
|
|
|
|
|
2
|
my $arg = $$self{args}{Back}; |
|
199
|
1
|
|
|
|
|
3
|
my $orig = $$self{config}{orig}; |
|
200
|
1
|
50
|
33
|
|
|
5
|
if(defined($arg) && $arg) { |
|
201
|
0
|
|
|
|
|
0
|
$self->stdout("Resetting your checkout back to r$orig.\n"); |
|
202
|
0
|
|
|
|
|
0
|
return $self->update_to($orig); |
|
203
|
|
|
|
|
|
|
} else { |
|
204
|
1
|
|
|
|
|
11
|
my $cur = $self->find_cur(); |
|
205
|
1
|
|
|
|
|
7
|
$self->stdout("Cleaned up. Your checkout is still at rev r$cur.\n"); |
|
206
|
1
|
|
|
|
|
14
|
return 0; |
|
207
|
|
|
|
|
|
|
} |
|
208
|
|
|
|
|
|
|
} |
|
209
|
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=head2 skip |
|
212
|
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
Tells svn-bisect to ignore the specified (or current) revision, and |
|
214
|
|
|
|
|
|
|
then moves the user to another, strategically useful revision. |
|
215
|
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
You may specify as many revisions at once as you like. |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
=cut |
|
219
|
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
sub skip { |
|
221
|
4
|
|
|
4
|
1
|
9
|
my $self = shift; |
|
222
|
4
|
|
|
|
|
9
|
my @rev = @_; |
|
223
|
4
|
100
|
|
|
|
19
|
@rev = $$self{config}{cur} unless scalar @rev; |
|
224
|
4
|
|
|
|
|
11
|
foreach my $rev (@rev) { |
|
225
|
4
|
50
|
|
|
|
18
|
$rev = substr($rev, 1) if substr($rev, 0, 1) eq 'r'; |
|
226
|
4
|
100
|
|
|
|
26
|
die("\"$rev\" is not a revision or is out of range.\n") |
|
227
|
|
|
|
|
|
|
unless exists($$self{config}{extant}{$rev}); |
|
228
|
3
|
|
|
|
|
13
|
$$self{config}{skip}{$rev} = 1; |
|
229
|
|
|
|
|
|
|
} |
|
230
|
3
|
|
|
|
|
49
|
return $self->next_rev(); |
|
231
|
|
|
|
|
|
|
} |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
=head2 unskip |
|
235
|
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
Tells svn-bisect to stop ignoring the specified revision, then moves |
|
237
|
|
|
|
|
|
|
the user to another, strategically useful revision. |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
You may specify as many revisions at once as you like. |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=cut |
|
242
|
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
sub unskip { |
|
244
|
3
|
|
|
3
|
1
|
4
|
my $self = shift; |
|
245
|
3
|
|
|
|
|
9
|
my @rev = @_; |
|
246
|
3
|
100
|
|
|
|
16
|
die("Usage: unskip \n") unless scalar @rev; |
|
247
|
2
|
|
|
|
|
4
|
foreach my $rev (@rev) { |
|
248
|
2
|
50
|
|
|
|
8
|
$rev = substr($rev, 1) if substr($rev, 0, 1) eq 'r'; |
|
249
|
2
|
100
|
|
|
|
17
|
die("\"$rev\" is not a revision or is out of range.\n") |
|
250
|
|
|
|
|
|
|
unless exists($$self{config}{extant}{$rev}); |
|
251
|
1
|
|
|
|
|
5
|
delete($$self{config}{skip}{$rev}); |
|
252
|
|
|
|
|
|
|
} |
|
253
|
1
|
|
|
|
|
8
|
return $self->next_rev(); |
|
254
|
|
|
|
|
|
|
} |
|
255
|
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
=head2 run |
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
Runs a command repeatedly to automate the bisection process. |
|
260
|
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
We run the command and arguments until a conclusion is reached. The |
|
262
|
|
|
|
|
|
|
command (usually a shell script) tells us about the current revision |
|
263
|
|
|
|
|
|
|
by way of its return code. The following return codes are handled: |
|
264
|
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
0: This revision is before the change we're looking for |
|
266
|
|
|
|
|
|
|
1-124, 126-127: This revision includes the change we're looking for |
|
267
|
|
|
|
|
|
|
125: This revision is untestable and should be skipped |
|
268
|
|
|
|
|
|
|
any other value: The command failed to run, abort bisection. |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
The normal caveats apply. In particular, if your script makes any |
|
271
|
|
|
|
|
|
|
changes, don't forget to clean up afterwards. |
|
272
|
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=cut |
|
274
|
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
sub run { |
|
276
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
277
|
0
|
|
|
|
|
0
|
my @cmd = @_; |
|
278
|
0
|
0
|
|
|
|
0
|
die("Usage: run [arguments...]\n") unless scalar @cmd; |
|
279
|
0
|
0
|
|
|
|
0
|
die("You have not yet defined a min and max.\n") unless $self->ready(); |
|
280
|
0
|
|
|
|
|
0
|
my @revs = $self->list_revs(); |
|
281
|
0
|
0
|
|
|
|
0
|
die("There are no revisions left to bisect.\n") unless scalar @revs; |
|
282
|
0
|
|
|
|
|
0
|
while(1) { |
|
283
|
0
|
|
|
|
|
0
|
@revs = $self->list_revs(); |
|
284
|
0
|
0
|
|
|
|
0
|
exit(0) unless scalar @revs; |
|
285
|
0
|
|
|
|
|
0
|
system(@cmd); |
|
286
|
0
|
0
|
|
|
|
0
|
if($? == -1) { |
|
287
|
0
|
|
|
|
|
0
|
die("Failed to execute " . join(" ",@cmd) . "\n"); |
|
288
|
|
|
|
|
|
|
} |
|
289
|
0
|
0
|
|
|
|
0
|
if($? & 127) { |
|
290
|
0
|
|
|
|
|
0
|
die(sprintf("Command died with signal %d.\n", $? & 127)); |
|
291
|
|
|
|
|
|
|
} |
|
292
|
0
|
|
|
|
|
0
|
my $rv = $? >> 8; |
|
293
|
0
|
0
|
|
|
|
0
|
if($rv > 127) { |
|
294
|
0
|
|
|
|
|
0
|
die("Command failed, returned $rv.\n"); |
|
295
|
|
|
|
|
|
|
} |
|
296
|
0
|
0
|
|
|
|
0
|
if($rv == 0) { |
|
|
|
0
|
|
|
|
|
|
|
297
|
0
|
|
|
|
|
0
|
$self->before(); |
|
298
|
0
|
|
|
|
|
0
|
unlink($$self{metadata}); |
|
299
|
0
|
|
|
|
|
0
|
io($$self{metadata}) < Dump($$self{config}); |
|
300
|
|
|
|
|
|
|
} |
|
301
|
|
|
|
|
|
|
elsif($rv != 125) { |
|
302
|
0
|
|
|
|
|
0
|
$self->after(); |
|
303
|
0
|
|
|
|
|
0
|
unlink($$self{metadata}); |
|
304
|
0
|
|
|
|
|
0
|
io($$self{metadata}) < Dump($$self{config}); |
|
305
|
|
|
|
|
|
|
} else { |
|
306
|
0
|
|
|
|
|
0
|
$self->skip(); |
|
307
|
0
|
|
|
|
|
0
|
unlink($$self{metadata}); |
|
308
|
0
|
|
|
|
|
0
|
io($$self{metadata}) < Dump($$self{config}); |
|
309
|
|
|
|
|
|
|
} |
|
310
|
|
|
|
|
|
|
} |
|
311
|
|
|
|
|
|
|
} |
|
312
|
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
=head2 help |
|
315
|
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
Allows the user to get some descriptions and usage information. |
|
317
|
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
This function calls exit() directly, to prevent do_something_intelligent() |
|
319
|
|
|
|
|
|
|
from removing the metadata file. |
|
320
|
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
=cut |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
sub help { |
|
324
|
4
|
|
|
4
|
1
|
9
|
my ($self, $subcommand) = @_; |
|
325
|
4
|
100
|
|
|
|
16
|
$subcommand = '_' unless defined $subcommand; |
|
326
|
4
|
|
|
|
|
109
|
my %help = ( |
|
327
|
|
|
|
|
|
|
'_' => <<"END", |
|
328
|
|
|
|
|
|
|
Usage: $0 |
|
329
|
|
|
|
|
|
|
where subcommand is one of: |
|
330
|
|
|
|
|
|
|
after (alias: "bad") |
|
331
|
|
|
|
|
|
|
before (alias: "good") |
|
332
|
|
|
|
|
|
|
help (hey, that's me!) |
|
333
|
|
|
|
|
|
|
reset |
|
334
|
|
|
|
|
|
|
run |
|
335
|
|
|
|
|
|
|
skip |
|
336
|
|
|
|
|
|
|
start |
|
337
|
|
|
|
|
|
|
unskip |
|
338
|
|
|
|
|
|
|
view |
|
339
|
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
For more info on a subcommand, try: $0 help |
|
341
|
|
|
|
|
|
|
END |
|
342
|
|
|
|
|
|
|
'after' => <<"END", |
|
343
|
|
|
|
|
|
|
Usage: $0 after [rev] |
|
344
|
|
|
|
|
|
|
Alias: $0 bad [rev] |
|
345
|
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
Tells the bisect routine that the specified (or current) checkout is |
|
347
|
|
|
|
|
|
|
*after* the wanted change - after the bug was introduced, after the |
|
348
|
|
|
|
|
|
|
change in behavior, whatever. |
|
349
|
|
|
|
|
|
|
END |
|
350
|
|
|
|
|
|
|
'before' => <<"END", |
|
351
|
|
|
|
|
|
|
Usage: $0 before [rev] |
|
352
|
|
|
|
|
|
|
Alias: $0 good [rev] |
|
353
|
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
Tells the bisect routine that the specified (or current) checkout is |
|
355
|
|
|
|
|
|
|
*before* the wanted change - before the bug was introduced, before the |
|
356
|
|
|
|
|
|
|
change in behavior, whatever. |
|
357
|
|
|
|
|
|
|
END |
|
358
|
|
|
|
|
|
|
'reset' => <<"END", |
|
359
|
|
|
|
|
|
|
Usage: $0 [--back] reset |
|
360
|
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
Cleans up after a bisect, removes the temporary data file. if you |
|
362
|
|
|
|
|
|
|
specify --back, it will also reset your checkout back to the original |
|
363
|
|
|
|
|
|
|
version. |
|
364
|
|
|
|
|
|
|
END |
|
365
|
|
|
|
|
|
|
'skip' => <<"END", |
|
366
|
|
|
|
|
|
|
Usage: $0 skip [ [...]] |
|
367
|
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
This will tell $0 to ignore the specified (or current) |
|
369
|
|
|
|
|
|
|
revision. You might want to do this if, for example, the current rev |
|
370
|
|
|
|
|
|
|
does not compile for reasons unrelated to the current session. You |
|
371
|
|
|
|
|
|
|
may specify more than one revision, and they will all be skipped at |
|
372
|
|
|
|
|
|
|
once. |
|
373
|
|
|
|
|
|
|
END |
|
374
|
|
|
|
|
|
|
'start' => <<"END", |
|
375
|
|
|
|
|
|
|
Usage: $0 [--min ] [--max ] start |
|
376
|
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
Starts a new bisect session. You may specify the initial upper and lower |
|
378
|
|
|
|
|
|
|
bounds, with the --min and --max options. These will be updated during the |
|
379
|
|
|
|
|
|
|
course of the bisection, with the "before" and "after" commands. |
|
380
|
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
This command will prepare the checkout for a bisect session, and start off |
|
382
|
|
|
|
|
|
|
with a rev in the middle of the list of suspect revisions. |
|
383
|
|
|
|
|
|
|
END |
|
384
|
|
|
|
|
|
|
'unskip' => <<"END", |
|
385
|
|
|
|
|
|
|
Usage: $0 unskip [...] |
|
386
|
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
Undoes the effects of "skip ", putting the specified revision |
|
388
|
|
|
|
|
|
|
back into the normal rotation (if it is still within the range of revisions |
|
389
|
|
|
|
|
|
|
currently under scrutiny). The revision argument is required. You may |
|
390
|
|
|
|
|
|
|
specify more than one revision, and they will all be unskipped at once. |
|
391
|
|
|
|
|
|
|
END |
|
392
|
|
|
|
|
|
|
'run' => <<"END", |
|
393
|
|
|
|
|
|
|
Usage: $0 run [arguments...] |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
Runs a command repeatedly to automate the bisection process. |
|
396
|
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
The command is run with the specified arguments until a conclusion is |
|
398
|
|
|
|
|
|
|
reached. The command (usually a shell script) tells us about the |
|
399
|
|
|
|
|
|
|
current revision by way of its return code. The following return codes |
|
400
|
|
|
|
|
|
|
are handled: |
|
401
|
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
0: This revision is before the change we're looking for |
|
403
|
|
|
|
|
|
|
1-124, 126-127: This revision includes the change we're looking for |
|
404
|
|
|
|
|
|
|
125: This revision is untestable and should be skipped |
|
405
|
|
|
|
|
|
|
any other value: The command failed to run, abort bisection. |
|
406
|
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
The normal caveats apply. In particular, if your script makes any |
|
408
|
|
|
|
|
|
|
changes, don't forget to clean up afterwards. |
|
409
|
|
|
|
|
|
|
END |
|
410
|
|
|
|
|
|
|
'view' => <<"END", |
|
411
|
|
|
|
|
|
|
Usage: $0 view |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
Outputs some descriptive information about where we're at, and about |
|
414
|
|
|
|
|
|
|
the revisions remaining to be tested. The output looks like: |
|
415
|
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
There are currently 7 revisions under scrutiny. |
|
417
|
|
|
|
|
|
|
The last known-unaffected rev is 28913. |
|
418
|
|
|
|
|
|
|
The first known- affected rev is 28928. |
|
419
|
|
|
|
|
|
|
Currently testing 28924. |
|
420
|
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
Revision chart: |
|
422
|
|
|
|
|
|
|
28913] 28914 28918 28921 28924 28925 28926 28927 [28928 |
|
423
|
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
END |
|
425
|
|
|
|
|
|
|
); |
|
426
|
4
|
100
|
|
|
|
23
|
die("No known help topic \"$subcommand\". Try \"$0 help\" for a list of topics.\n") |
|
427
|
|
|
|
|
|
|
unless exists $help{$subcommand}; |
|
428
|
3
|
|
|
|
|
23
|
$self->stdout($help{$subcommand}); |
|
429
|
3
|
|
|
|
|
68
|
$self->exit(0); |
|
430
|
|
|
|
|
|
|
} |
|
431
|
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
=head2 view |
|
434
|
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
Allows the user to get some information about the current state of things. |
|
436
|
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
This function calls exit() directly, to prevent do_something_intelligent() |
|
438
|
|
|
|
|
|
|
from removing the metadata file. |
|
439
|
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
=cut |
|
441
|
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
sub view { |
|
443
|
1
|
|
|
1
|
1
|
3
|
my $self = shift; |
|
444
|
1
|
|
|
|
|
4
|
my $min = $$self{config}{min}; |
|
445
|
1
|
|
|
|
|
3
|
my $max = $$self{config}{max}; |
|
446
|
1
|
|
|
|
|
2
|
my %skips; |
|
447
|
1
|
50
|
|
|
|
7
|
if($self->ready) { |
|
448
|
1
|
|
|
|
|
6
|
my @revs = $self->list_revs(); |
|
449
|
1
|
|
|
|
|
5
|
my $cur = $$self{config}{cur}; |
|
450
|
1
|
|
|
|
|
8
|
$self->stdout("There are currently " |
|
451
|
|
|
|
|
|
|
. scalar(@revs) |
|
452
|
|
|
|
|
|
|
. " revisions under scrutiny.\n"); |
|
453
|
1
|
|
|
|
|
22
|
$self->stdout("The last known unaffected rev is: $min.\n"); |
|
454
|
1
|
|
|
|
|
16
|
$self->stdout("The first known affected rev is: $max.\n"); |
|
455
|
1
|
|
|
|
|
16
|
$self->stdout("Currently testing $cur.\n\n"); |
|
456
|
1
|
50
|
|
|
|
14
|
if(@revs < 30) { |
|
457
|
1
|
|
|
|
|
4
|
$self->stdout("Revision chart:\n"); |
|
458
|
1
|
|
|
|
|
16
|
$self->stdout("$min] " . join(" ", @revs) . " [$max\n"); |
|
459
|
|
|
|
|
|
|
} |
|
460
|
|
|
|
|
|
|
} else { |
|
461
|
0
|
|
|
|
|
0
|
$self->stdout("Not enough information has been given to start yet.\n"); |
|
462
|
0
|
|
|
|
|
0
|
$self->stdout("Bisecting may begin when a starting and ending revision are specified.\n"); |
|
463
|
0
|
0
|
|
|
|
0
|
$self->stdout("The last known unaffected rev is: $min.\n") if defined $min; |
|
464
|
0
|
0
|
|
|
|
0
|
$self->stdout("The first known affected rev is: $max.\n") if defined $max; |
|
465
|
|
|
|
|
|
|
} |
|
466
|
1
|
|
|
|
|
15
|
$self->exit(0); |
|
467
|
|
|
|
|
|
|
} |
|
468
|
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
=head1 INTERNAL METHODS |
|
471
|
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
=head2 cmd |
|
473
|
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
my $stdout = $self->cmd("svn info"); |
|
475
|
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
Runs a command, returns its output. |
|
477
|
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
=cut |
|
479
|
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
sub cmd { |
|
481
|
1
|
|
|
1
|
1
|
440
|
my ($self, $cmd) = @_; |
|
482
|
1
|
|
|
|
|
18
|
$self->verbose("Running: $cmd\n"); |
|
483
|
1
|
|
|
|
|
3098
|
my $output = qx($cmd); |
|
484
|
1
|
|
|
|
|
22
|
my $rv = $? >> 8; |
|
485
|
1
|
50
|
|
|
|
22
|
if($rv) { |
|
486
|
1
|
|
|
|
|
40
|
$self->stdout("Failure to execute \"$cmd\".\n"); |
|
487
|
1
|
|
|
|
|
43
|
$self->stdout("Please fix that, and then re-run this command.\n"); |
|
488
|
1
|
|
|
|
|
21
|
$self->exit($rv); |
|
489
|
|
|
|
|
|
|
} |
|
490
|
0
|
|
|
|
|
0
|
return $output; |
|
491
|
|
|
|
|
|
|
} |
|
492
|
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
=head2 ready |
|
495
|
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
$self->next_rev() if $self->ready(); |
|
497
|
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
Returns a true value if we have enough information to begin bisecting. |
|
499
|
|
|
|
|
|
|
Specifically, this returns true if we have been given at least one "bad" |
|
500
|
|
|
|
|
|
|
and one "good" revision. These can be specified as arguments to the |
|
501
|
|
|
|
|
|
|
"before" and "after" commands, or as --min and --max arguments to the |
|
502
|
|
|
|
|
|
|
"start" command. |
|
503
|
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
=cut |
|
505
|
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
sub ready { |
|
507
|
31
|
|
|
31
|
1
|
2360
|
my $self = shift; |
|
508
|
31
|
100
|
|
|
|
99
|
return 0 unless defined $$self{config}{min}; |
|
509
|
30
|
100
|
|
|
|
175
|
return 0 unless defined $$self{config}{max}; |
|
510
|
29
|
50
|
|
|
|
94
|
$$self{config}{min} = substr($$self{config}{min},1) if substr($$self{config}{min},0,1) eq 'r'; |
|
511
|
29
|
50
|
|
|
|
84
|
$$self{config}{max} = substr($$self{config}{max},1) if substr($$self{config}{max},0,1) eq 'r'; |
|
512
|
29
|
100
|
|
|
|
99
|
$$self{config}{extant} = $self->fetch_log_revs() |
|
513
|
|
|
|
|
|
|
unless defined $$self{config}{extant}; |
|
514
|
29
|
|
|
|
|
96
|
return 1; |
|
515
|
|
|
|
|
|
|
} |
|
516
|
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
=head2 next_rev |
|
519
|
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
$self->next_rev(); |
|
521
|
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
Find a spot in the middle of the current "suspect revisions" list, and calls |
|
523
|
|
|
|
|
|
|
"svn update" to move the checkout directory to that revision. |
|
524
|
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
=cut |
|
526
|
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
sub next_rev { |
|
528
|
10
|
|
|
10
|
1
|
17
|
my $self = shift; |
|
529
|
10
|
50
|
|
|
|
38
|
return 0 unless $self->ready(); |
|
530
|
10
|
|
|
|
|
55
|
my @revs = $self->list_revs(); |
|
531
|
10
|
100
|
|
|
|
27
|
unless(scalar @revs) { |
|
532
|
2
|
|
|
|
|
6
|
my $max = $$self{config}{max}; |
|
533
|
2
|
|
|
|
|
6
|
$$self{config}{min} = $$self{config}{cur} = $max; |
|
534
|
2
|
|
|
|
|
10
|
my $previous_skips = 0; |
|
535
|
2
|
|
|
|
|
4
|
my @previous_revisions = sort { $b <=> $a } keys %{$$self{config}{extant}}; |
|
|
17
|
|
|
|
|
22
|
|
|
|
2
|
|
|
|
|
14
|
|
|
536
|
2
|
|
|
|
|
5
|
@previous_revisions = grep { $_ < $max } @previous_revisions; |
|
|
8
|
|
|
|
|
14
|
|
|
537
|
2
|
|
|
|
|
5
|
foreach my $rev (@previous_revisions) { |
|
538
|
3
|
100
|
|
|
|
9
|
if(exists($$self{config}{skip}{$rev})) { |
|
539
|
2
|
|
|
|
|
5
|
$previous_skips++; |
|
540
|
|
|
|
|
|
|
} else { |
|
541
|
1
|
|
|
|
|
2
|
last; |
|
542
|
|
|
|
|
|
|
} |
|
543
|
|
|
|
|
|
|
} |
|
544
|
2
|
|
|
|
|
11
|
$self->stdout("This is the end of the road!\n"); |
|
545
|
2
|
100
|
|
|
|
32
|
if($previous_skips) { |
|
546
|
1
|
|
|
|
|
7
|
$self->stdout("The change occurred in r$max, or one of the " |
|
547
|
|
|
|
|
|
|
."$previous_skips skipped revs preceding it.\n"); |
|
548
|
|
|
|
|
|
|
} else { |
|
549
|
1
|
|
|
|
|
5
|
$self->stdout("The change occurred in r$max.\n"); |
|
550
|
|
|
|
|
|
|
} |
|
551
|
2
|
|
|
|
|
29
|
return $self->update_to($max); |
|
552
|
|
|
|
|
|
|
} |
|
553
|
8
|
|
|
|
|
19
|
my $ent = 0; |
|
554
|
8
|
100
|
|
|
|
32
|
$ent = scalar @revs >> 1 if scalar @revs > 1; |
|
555
|
8
|
|
|
|
|
28
|
my $rev = $$self{config}{cur} = $revs[$ent]; |
|
556
|
8
|
|
|
|
|
53
|
$self->stdout("There are ", scalar @revs, " revs left in the pool." |
|
557
|
|
|
|
|
|
|
." Choosing r$rev.\n"); |
|
558
|
8
|
|
|
|
|
178
|
return $self->update_to($rev); |
|
559
|
|
|
|
|
|
|
} |
|
560
|
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
=head2 list_revs |
|
563
|
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
my @revs = $self->list_revs(); |
|
565
|
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
Returns the set of valid revisions between the current "min" and "max" values, |
|
567
|
|
|
|
|
|
|
exclusive. |
|
568
|
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
This is smart about revisions that don't affect the current tree (because they |
|
570
|
|
|
|
|
|
|
won't be returned by fetch_log_revs, below) and about skipped revisions (which |
|
571
|
|
|
|
|
|
|
the user may specify with the "skip" command). |
|
572
|
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
=cut |
|
574
|
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
sub list_revs { |
|
576
|
11
|
|
|
11
|
1
|
22
|
my $self = shift; |
|
577
|
11
|
50
|
|
|
|
27
|
confess("called when not ready") unless $self->ready(); |
|
578
|
11
|
|
|
|
|
37
|
my $min = $$self{config}{min} + 1; |
|
579
|
11
|
|
|
|
|
26
|
my $max = $$self{config}{max} - 1; |
|
580
|
11
|
|
|
|
|
17
|
my @rv; |
|
581
|
11
|
|
|
|
|
47
|
foreach my $rev ($min..$max) { |
|
582
|
218
|
100
|
|
|
|
640
|
next if exists $$self{config}{skip}{$rev}; |
|
583
|
209
|
100
|
|
|
|
537
|
push(@rv, $rev) if exists $$self{config}{extant}{$rev}; |
|
584
|
|
|
|
|
|
|
} |
|
585
|
11
|
|
|
|
|
57
|
return @rv; |
|
586
|
|
|
|
|
|
|
} |
|
587
|
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
=head2 stdout |
|
590
|
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
$self->stdout("Hello, world!\n"); |
|
592
|
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
Output a message to stdout. This is basically just the "print" function, but |
|
594
|
|
|
|
|
|
|
we use a method so the testsuite can override it through subclassing. |
|
595
|
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
=cut |
|
597
|
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
sub stdout { |
|
599
|
1
|
|
|
1
|
1
|
3
|
my $self = shift; |
|
600
|
1
|
|
|
|
|
71
|
print(@_); |
|
601
|
|
|
|
|
|
|
} |
|
602
|
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
=head2 verbose |
|
605
|
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
$self->verbose("Hello, world!\n"); |
|
607
|
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
Output a message to stdout, if the user specified the --verbose option. This |
|
609
|
|
|
|
|
|
|
is basically just a conditional wrapper around the "print" function. |
|
610
|
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
=cut |
|
612
|
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
sub verbose { |
|
614
|
2
|
|
|
2
|
1
|
2169
|
my $self = shift; |
|
615
|
2
|
100
|
|
|
|
12
|
return unless $$self{args}{Verbose}; |
|
616
|
1
|
|
|
|
|
140
|
print(@_); |
|
617
|
|
|
|
|
|
|
} |
|
618
|
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
=head2 exit |
|
621
|
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
$self->exit(0); |
|
623
|
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
Exits. This allows the test suite to override exiting; it does not |
|
625
|
|
|
|
|
|
|
provide any other features above and beyond what the normal exit |
|
626
|
|
|
|
|
|
|
system call provides. |
|
627
|
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
=cut |
|
629
|
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
sub exit { |
|
631
|
1
|
|
|
1
|
1
|
1546
|
my ($self, $rv) = @_; |
|
632
|
1
|
|
|
|
|
179
|
exit($rv); |
|
633
|
|
|
|
|
|
|
} |
|
634
|
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
=head1 SUBVERSION ACCESSOR METHODS |
|
637
|
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
=head2 update_to |
|
639
|
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
$self->update_to(25000); |
|
641
|
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
Calls 'svn update' to move to the specified revision. |
|
643
|
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
=cut |
|
645
|
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
sub update_to { |
|
647
|
10
|
|
|
10
|
1
|
21
|
my ($self, $rev) = @_; |
|
648
|
10
|
|
|
|
|
24
|
my $cmd = "svn update -r$rev"; |
|
649
|
10
|
|
|
|
|
37
|
$self->cmd($cmd); |
|
650
|
|
|
|
|
|
|
} |
|
651
|
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
=head2 fetch_log_revs |
|
654
|
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
my $hashref = $self->fetch_log_revs(); |
|
656
|
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
Calls "svn log" and parses the output. Returns a hash reference whose keys |
|
658
|
|
|
|
|
|
|
are valid revision numbers; so you can use exists() to find out whether a |
|
659
|
|
|
|
|
|
|
number is in the list. This hash reference is used by list_revs(), above. |
|
660
|
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
=cut |
|
662
|
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
sub fetch_log_revs { |
|
664
|
3
|
|
|
3
|
1
|
6
|
my $self = shift; |
|
665
|
3
|
|
|
|
|
7
|
my $min = $$self{config}{min}; |
|
666
|
3
|
|
|
|
|
6
|
my $max = $$self{config}{max}; |
|
667
|
3
|
50
|
|
|
|
10
|
$self->stdout("Fetching history from r$min to r$max; it may take a while.\n") |
|
668
|
|
|
|
|
|
|
if(($max - $min) > 100); |
|
669
|
3
|
|
|
|
|
7
|
my %rv; |
|
670
|
3
|
|
|
|
|
16
|
my $log = $self->cmd("svn log -q -r$min:$max"); |
|
671
|
3
|
|
|
|
|
40
|
$log =~ s/\r//; |
|
672
|
3
|
|
|
|
|
22
|
foreach my $line (split(/\n+/, $log)) { |
|
673
|
17
|
100
|
|
|
|
66
|
if($line =~ /^r(\d+) /) { |
|
674
|
8
|
|
|
|
|
26
|
$rv{$1} = 1; |
|
675
|
|
|
|
|
|
|
} |
|
676
|
|
|
|
|
|
|
} |
|
677
|
3
|
|
|
|
|
16
|
return \%rv; |
|
678
|
|
|
|
|
|
|
} |
|
679
|
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
=head2 find_max |
|
682
|
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
my $rev = $self->find_max(); |
|
684
|
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
Plays some tricks with "svn log" to figure out the latest revision contained |
|
686
|
|
|
|
|
|
|
within the repository. |
|
687
|
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
=cut |
|
689
|
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
sub find_max { |
|
691
|
5
|
|
|
5
|
1
|
1954
|
my $self = shift; |
|
692
|
5
|
|
|
|
|
21
|
my $log = $self->cmd("svn log -q -rHEAD:PREV"); |
|
693
|
5
|
|
|
|
|
75
|
$log =~ s/\r//; |
|
694
|
5
|
|
|
|
|
55
|
foreach my $line (split(/\n+/, $log)) { |
|
695
|
8
|
100
|
|
|
|
58
|
if($line =~ /^r(\d+) /) { |
|
696
|
4
|
|
|
|
|
26
|
return $1; |
|
697
|
|
|
|
|
|
|
} |
|
698
|
|
|
|
|
|
|
} |
|
699
|
1
|
|
|
|
|
14
|
die("Cannot find highest revision in repository."); |
|
700
|
|
|
|
|
|
|
} |
|
701
|
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
=head2 find_cur |
|
704
|
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
my $rev = $self->find_cur(); |
|
706
|
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
Parses the output of "svn info" to figure out what the current revision is. |
|
708
|
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
=cut |
|
710
|
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
sub find_cur { |
|
712
|
6
|
|
|
6
|
1
|
43
|
my $self = shift; |
|
713
|
6
|
|
|
|
|
21
|
my $info = $self->cmd("svn info"); |
|
714
|
6
|
|
|
|
|
82
|
$info =~ s/\r//; |
|
715
|
|
|
|
|
|
|
# parse the "Last Changed Rev:" entry |
|
716
|
6
|
|
|
|
|
40
|
foreach my $line (split(/\n+/, $info)) { |
|
717
|
10
|
100
|
|
|
|
39
|
if($line =~ /^Last Changed Rev: (\d+)/) { |
|
718
|
5
|
|
|
|
|
26
|
return $1; |
|
719
|
|
|
|
|
|
|
} |
|
720
|
|
|
|
|
|
|
} |
|
721
|
1
|
|
|
|
|
10
|
die("Cannot find current revision of checkout."); |
|
722
|
|
|
|
|
|
|
} |
|
723
|
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
=head1 AUTHOR |
|
726
|
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
Mark Glines |
|
728
|
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
=head1 THANKS |
|
731
|
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
* Thanks to the git-bisect author(s), for coming up with a user interface that |
|
733
|
|
|
|
|
|
|
I actually like. |
|
734
|
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
* Thanks to Will Coleda for inspiring me to actually write and release this. |
|
736
|
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
* Thanks to the Parrot project for having so much random stuff going on as to |
|
738
|
|
|
|
|
|
|
make a tool like this necessary. |
|
739
|
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
742
|
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
App::SVNBinarySearch by Will Coleda: L |
|
744
|
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
747
|
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
This software is copyright (c) 2008-2009 Mark Glines. |
|
749
|
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
It is distributed under the terms of the Artistic License 2.0. For details, |
|
751
|
|
|
|
|
|
|
see the "LICENSE" file packaged alongside this module. |
|
752
|
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
=cut |
|
754
|
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
1; |