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