| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package SVN::Dump::Reader; | 
| 2 |  |  |  |  |  |  | $SVN::Dump::Reader::VERSION = '0.08'; | 
| 3 | 10 |  |  | 10 |  | 364940 | use strict; | 
|  | 10 |  |  |  |  | 58 |  | 
|  | 10 |  |  |  |  | 232 |  | 
| 4 | 10 |  |  | 10 |  | 43 | use warnings; | 
|  | 10 |  |  |  |  | 13 |  | 
|  | 10 |  |  |  |  | 186 |  | 
| 5 | 10 |  |  | 10 |  | 4231 | use IO::Handle; | 
|  | 10 |  |  |  |  | 48729 |  | 
|  | 10 |  |  |  |  | 366 |  | 
| 6 | 10 |  |  | 10 |  | 66 | use Carp; | 
|  | 10 |  |  |  |  | 15 |  | 
|  | 10 |  |  |  |  | 548 |  | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | our @ISA = qw( IO::Handle ); | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | # SVN::Dump elements | 
| 11 | 10 |  |  | 10 |  | 3948 | use SVN::Dump::Headers; | 
|  | 10 |  |  |  |  | 21 |  | 
|  | 10 |  |  |  |  | 263 |  | 
| 12 | 10 |  |  | 10 |  | 3743 | use SVN::Dump::Property; | 
|  | 10 |  |  |  |  | 22 |  | 
|  | 10 |  |  |  |  | 278 |  | 
| 13 | 10 |  |  | 10 |  | 3639 | use SVN::Dump::Text; | 
|  | 10 |  |  |  |  | 21 |  | 
|  | 10 |  |  |  |  | 293 |  | 
| 14 | 10 |  |  | 10 |  | 3935 | use SVN::Dump::Record; | 
|  | 10 |  |  |  |  | 21 |  | 
|  | 10 |  |  |  |  | 9710 |  | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | # some useful definitions | 
| 17 |  |  |  |  |  |  | my $NL = "\012"; | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | # prepare the digest checkers | 
| 20 |  |  |  |  |  |  | my @digest = grep { | 
| 21 |  |  |  |  |  |  | eval { require Digest; Digest->new(uc) } | 
| 22 |  |  |  |  |  |  | } qw( md5 sha1 ); | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | # the object is a filehandle | 
| 25 |  |  |  |  |  |  | sub new { | 
| 26 | 68 |  |  | 68 | 1 | 28141 | my ($class, $fh, $args) = @_; | 
| 27 |  |  |  |  |  |  | croak 'SVN::Dump::Reader parameter is not a filehandle' | 
| 28 | 68 | 100 | 100 |  |  | 105 | unless eval { $fh && ref $fh && $fh->can('getline') && $fh->can('read') }; | 
|  | 68 | 100 | 100 |  |  | 1201 |  | 
| 29 | 64 | 100 |  |  |  | 106 | %{*$fh} = %{ $args || {} }; | 
|  | 64 |  |  |  |  | 200 |  | 
|  | 64 |  |  |  |  | 203 |  | 
| 30 | 64 |  |  |  |  | 224 | return bless $fh, $class; | 
| 31 |  |  |  |  |  |  | } | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | sub read_record { | 
| 34 | 740 |  |  | 740 | 1 | 1002 | my ($fh) = @_; | 
| 35 |  |  |  |  |  |  |  | 
| 36 | 740 |  |  |  |  | 1415 | my $record = SVN::Dump::Record->new(); | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | # first get the headers | 
| 39 | 740 | 100 |  |  |  | 1069 | my $headers = $fh->read_header_block() or return; | 
| 40 | 693 |  |  |  |  | 1656 | $record->set_headers_block( $headers ); | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | # get the property block | 
| 43 |  |  |  |  |  |  | $record->set_property_block( $fh->read_property_block() ) | 
| 44 |  |  |  |  |  |  | if ( | 
| 45 |  |  |  |  |  |  | exists $headers->{'Prop-content-length'} and | 
| 46 | 693 | 50 | 66 |  |  | 1992 | $headers->{'Prop-content-length'} | 
| 47 |  |  |  |  |  |  | ); | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | # get the text block | 
| 50 | 693 | 100 | 100 |  |  | 1325 | if ( exists $headers->{'Text-content-length'} | 
| 51 |  |  |  |  |  |  | and $headers->{'Text-content-length'} ) | 
| 52 |  |  |  |  |  |  | { | 
| 53 | 97 |  |  |  |  | 161 | my $text = $fh->read_text_block( $headers->{'Text-content-length'} ); | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | # verify checksums (but not in delta dumps) | 
| 56 | 97 | 100 | 66 |  |  | 145 | if (${*$fh}{check_digest} | 
|  | 97 |  | 66 |  |  | 329 |  | 
| 57 |  |  |  |  |  |  | && (  !$headers->{'Text-delta'} | 
| 58 |  |  |  |  |  |  | || $headers->{'Text-delta'} ne 'true' ) | 
| 59 |  |  |  |  |  |  | ) | 
| 60 |  |  |  |  |  |  | { | 
| 61 | 7 |  |  |  |  | 13 | for my $algo ( grep { $headers->{"Text-content-$_"} } @digest ) { | 
|  | 7 |  |  |  |  | 28 |  | 
| 62 | 7 |  |  |  |  | 14 | my $digest = $text->digest($algo); | 
| 63 |  |  |  |  |  |  | croak | 
| 64 |  |  |  |  |  |  | qq{$algo checksum mismatch: got $digest, expected $headers->{"Text-content-$algo"}} | 
| 65 | 7 | 100 |  |  |  | 373 | if $headers->{"Text-content-$algo"} ne $digest; | 
| 66 |  |  |  |  |  |  | } | 
| 67 |  |  |  |  |  |  | } | 
| 68 |  |  |  |  |  |  |  | 
| 69 | 96 |  |  |  |  | 233 | $record->set_text_block($text); | 
| 70 |  |  |  |  |  |  | } | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | # some safety checks | 
| 73 |  |  |  |  |  |  | croak "Inconsistent record size" | 
| 74 |  |  |  |  |  |  | if ( $headers->{'Prop-content-length'} || 0 ) | 
| 75 |  |  |  |  |  |  | + ( $headers->{'Text-content-length'} || 0 ) | 
| 76 | 692 | 100 | 100 |  |  | 3053 | != ( $headers->{'Content-length'} || 0 ); | 
|  |  |  | 100 |  |  |  |  | 
|  |  |  | 100 |  |  |  |  | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | # if we have a delete record with a 'Node-kind' header | 
| 79 |  |  |  |  |  |  | # we have to recurse for an included record | 
| 80 | 691 | 100 | 100 |  |  | 1569 | if (   exists $headers->{'Node-action'} | 
|  |  |  | 100 |  |  |  |  | 
| 81 |  |  |  |  |  |  | && $headers->{'Node-action'} eq 'delete' | 
| 82 |  |  |  |  |  |  | && exists $headers->{'Node-kind'} ) | 
| 83 |  |  |  |  |  |  | { | 
| 84 | 1 |  |  |  |  | 4 | my $included = $fh->read_record(); | 
| 85 | 1 |  |  |  |  | 2 | $record->set_included_record( $included ); | 
| 86 |  |  |  |  |  |  | } | 
| 87 |  |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | # uuid and format record only contain headers | 
| 89 | 691 |  |  |  |  | 1226 | return $record; | 
| 90 |  |  |  |  |  |  | } | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | sub read_header_block { | 
| 93 | 746 |  |  | 746 | 1 | 934 | my ($fh) = @_; | 
| 94 |  |  |  |  |  |  |  | 
| 95 | 746 |  |  |  |  | 1879 | local $/ = $NL; | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | # skip empty lines | 
| 98 | 746 |  |  |  |  | 788 | my $line; | 
| 99 | 746 |  |  |  |  | 798 | while(1) { | 
| 100 | 1490 |  |  |  |  | 2903 | $line = <$fh>; | 
| 101 | 1490 | 100 |  |  |  | 2505 | return if !defined $line; | 
| 102 | 1443 |  |  |  |  | 1622 | chop $line; | 
| 103 | 1443 | 100 |  |  |  | 2135 | last unless $line eq ''; | 
| 104 |  |  |  |  |  |  | } | 
| 105 |  |  |  |  |  |  |  | 
| 106 | 699 |  |  |  |  | 1432 | my $headers = SVN::Dump::Headers->new(); | 
| 107 | 699 |  |  |  |  | 809 | while(1) { | 
| 108 | 2901 |  |  |  |  | 5666 | my ($key, $value) = split /: /, $line, 2; | 
| 109 | 2901 |  |  |  |  | 4771 | $headers->{$key} = $value; | 
| 110 |  |  |  |  |  |  |  | 
| 111 | 2901 |  |  |  |  | 3729 | $line = <$fh>; | 
| 112 | 2901 | 100 |  |  |  | 3954 | croak _eof() if !defined $line; | 
| 113 | 2900 |  |  |  |  | 3003 | chop $line; | 
| 114 | 2900 | 100 |  |  |  | 4219 | last if $line eq ''; # stop on empty line | 
| 115 |  |  |  |  |  |  | } | 
| 116 |  |  |  |  |  |  |  | 
| 117 | 698 | 50 |  |  |  | 1473 | croak "Empty line found instead of a header block line $." | 
| 118 |  |  |  |  |  |  | if ! keys %$headers; | 
| 119 |  |  |  |  |  |  |  | 
| 120 | 698 |  |  |  |  | 1856 | return $headers; | 
| 121 |  |  |  |  |  |  | } | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | sub read_property_block { | 
| 124 | 476 |  |  | 476 | 1 | 675 | my ($fh) = @_; | 
| 125 | 476 |  |  |  |  | 984 | my $property = SVN::Dump::Property->new(); | 
| 126 |  |  |  |  |  |  |  | 
| 127 | 476 |  |  |  |  | 1311 | local $/ = $NL; | 
| 128 | 476 |  |  |  |  | 638 | my @buffer; | 
| 129 | 476 |  |  |  |  | 603 | while(1) { | 
| 130 | 1277 |  |  |  |  | 1965 | my $line = <$fh>; | 
| 131 | 1277 | 100 |  |  |  | 1925 | croak _eof() if !defined $line; | 
| 132 | 1275 |  |  |  |  | 1493 | chop $line; | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | # read a key/value pair | 
| 135 | 1275 | 100 |  |  |  | 3823 | if( $line =~ /\AK (\d+)\z/ ) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 136 | 799 |  |  |  |  | 1241 | my $key = $fh->_read_string( $1 ); | 
| 137 |  |  |  |  |  |  |  | 
| 138 | 799 |  |  |  |  | 1895 | $line = <$fh>; | 
| 139 | 799 | 100 |  |  |  | 1230 | croak _eof() if !defined $line; | 
| 140 | 798 |  |  |  |  | 1217 | chop $line; | 
| 141 |  |  |  |  |  |  |  | 
| 142 | 798 | 100 |  |  |  | 1990 | if( $line =~ /\AV (\d+)\z/ ) { | 
| 143 | 797 |  |  |  |  | 1166 | my $value = $fh->_read_string( $1 ); | 
| 144 |  |  |  |  |  |  |  | 
| 145 | 797 |  |  |  |  | 1544 | $property->set( $key => $value ); | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | # FIXME what happens if we see duplicate keys? | 
| 148 |  |  |  |  |  |  | } | 
| 149 |  |  |  |  |  |  | else { | 
| 150 | 1 |  |  |  |  | 88 | croak "Corrupted property"; # FIXME better error message | 
| 151 |  |  |  |  |  |  | } | 
| 152 |  |  |  |  |  |  | } | 
| 153 |  |  |  |  |  |  | # or a deleted key (only with fs-format-version >= 3) | 
| 154 |  |  |  |  |  |  | # FIXME shall we fail if fs-format-version < 3? | 
| 155 |  |  |  |  |  |  | elsif( $line =~ /\AD (\d+)\z/ ) { | 
| 156 | 4 |  |  |  |  | 17 | my $key = $fh->_read_string( $1 ); | 
| 157 |  |  |  |  |  |  |  | 
| 158 | 4 |  |  |  |  | 12 | $property->set( $key => undef ); # undef means deleted | 
| 159 |  |  |  |  |  |  | } | 
| 160 |  |  |  |  |  |  | # end of properties | 
| 161 |  |  |  |  |  |  | elsif( $line =~ /\APROPS-END\z/ ) { | 
| 162 | 471 |  |  |  |  | 610 | last; | 
| 163 |  |  |  |  |  |  | } | 
| 164 |  |  |  |  |  |  | # inconsistent data | 
| 165 |  |  |  |  |  |  | else { | 
| 166 | 1 |  |  |  |  | 73 | croak "Corrupted property"; # FIXME better error message | 
| 167 |  |  |  |  |  |  | } | 
| 168 |  |  |  |  |  |  | } | 
| 169 |  |  |  |  |  |  |  | 
| 170 | 471 |  |  |  |  | 1522 | return $property; | 
| 171 |  |  |  |  |  |  | } | 
| 172 |  |  |  |  |  |  |  | 
| 173 |  |  |  |  |  |  | sub read_text_block { | 
| 174 | 98 |  |  | 98 | 1 | 164 | my ($fh, $size) = @_; | 
| 175 |  |  |  |  |  |  |  | 
| 176 | 98 |  |  |  |  | 159 | return SVN::Dump::Text->new( $fh->_read_string( $size ) ); | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | sub _read_string { | 
| 180 |  |  |  |  |  |  |  | 
| 181 | 1698 |  |  | 1698 |  | 3333 | my ( $fh, $size ) = @_; | 
| 182 |  |  |  |  |  |  |  | 
| 183 | 1698 |  |  |  |  | 3387 | local $/ = $NL; | 
| 184 |  |  |  |  |  |  |  | 
| 185 | 1698 |  |  |  |  | 1740 | my $text; | 
| 186 | 1698 |  |  |  |  | 3544 | my $characters_read = read( $fh, $text, $size ); | 
| 187 |  |  |  |  |  |  |  | 
| 188 | 1698 | 50 |  |  |  | 2243 | if ( defined($characters_read) ) { | 
| 189 | 1698 | 100 |  |  |  | 2547 | if ( $characters_read != $size ) { | 
| 190 | 1 |  |  |  |  | 3 | croak _eof(); | 
| 191 |  |  |  |  |  |  | }; | 
| 192 |  |  |  |  |  |  | } else { | 
| 193 | 0 |  |  |  |  | 0 | croak $!; | 
| 194 |  |  |  |  |  |  | }; | 
| 195 |  |  |  |  |  |  |  | 
| 196 | 1697 |  |  |  |  | 2351 | <$fh>; # clear trailing newline | 
| 197 |  |  |  |  |  |  |  | 
| 198 | 1697 |  |  |  |  | 4027 | return $text; | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | }; | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | # FIXME make this more explicit | 
| 203 | 5 |  |  | 5 |  | 516 | sub _eof { return "Unexpected EOF line $.", } | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | __END__ |