File Coverage

blib/lib/App/SVN/Bisect.pm
Criterion Covered Total %
statement 192 228 84.2
branch 80 124 64.5
condition 1 3 33.3
subroutine 27 28 96.4
pod 22 22 100.0
total 322 405 79.5


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;