| SVN-Dump documentation | Contained in the SVN-Dump distribution. |
SVN::Dump::Reader - A Subversion dump reader
# !!! You should use SVN::Dump, not SVN::Dump::Reader !!!
use SVN::Dump::Reader;
my $reader = SVN::Dump::Reader->new( $fh );
my $record = $reader->read_record();
The SVN::Dump::Reader class implements a reader object for Subversion
dumps.
The following methods are available:
Create a new SVN::Dump::Reader attached to the $fh filehandle.
The only supported option is check_digest, which is disabled
by default.
Read and return a new SVN::Dump::Record object from the dump filehandle.
If the option check_digest is enabled, this method will recompute
the digests for a dump without deltas, based on the information in the
Text-content-md5 and Text-content-sha1 headers (if the corresponding
Digest:: module is availabled). In case of a mismatch, the routine
will die() with an exception complaining about a checksum mismatch.
Read and return a new SVN::Dump::Headers object from the dump filehandle.
Read and return a new SVN::Dump::Property object from the dump filehandle.
Read and return a new SVN::Dump::Text object from the dump filehandle.
The read_... methods will die horribly if asked to read inconsistent
data from a stream.
Copyright 2006-2011 Philippe 'BooK' Bruhat, All Rights Reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| SVN-Dump documentation | Contained in the SVN-Dump distribution. |
package SVN::Dump::Reader; use strict; use warnings; use IO::Handle; use Carp; our @ISA = qw( IO::Handle ); # SVN::Dump elements use SVN::Dump::Headers; use SVN::Dump::Property; use SVN::Dump::Text; use SVN::Dump::Record; # some useful definitions my $NL = "\012"; # prepare the digest checkers my @digest = grep { eval { require Digest; Digest->new(uc) } } qw( md5 sha1 ); # the object is a filehandle sub new { my ($class, $fh, $args) = @_; croak 'SVN::Dump::Reader parameter is not a filehandle' if !( $fh && ref $fh && ref($fh) eq 'GLOB' ); %{*$fh} = %{ $args || {} }; return bless $fh, $class; } sub read_record { my ($fh) = @_; my $record = SVN::Dump::Record->new(); # first get the headers my $headers = $fh->read_header_block() or return; $record->set_headers_block( $headers ); # get the property block $record->set_property_block( $fh->read_property_block() ) if ( exists $headers->{'Prop-content-length'} and $headers->{'Prop-content-length'} ); # get the text block if ( exists $headers->{'Text-content-length'} and $headers->{'Text-content-length'} ) { my $text = $fh->read_text_block( $headers->{'Text-content-length'} ); # verify checksums (but not in delta dumps) if (${*$fh}{check_digest} && ( !$headers->{'Text-delta'} || $headers->{'Text-delta'} ne 'true' ) ) { for my $algo ( grep { $headers->{"Text-content-$_"} } @digest ) { my $digest = $text->digest($algo); croak qq{$algo checksum mismatch: got $digest, expected $headers->{"Text-content-$algo"}} if $headers->{"Text-content-$algo"} ne $digest; } } $record->set_text_block($text); } # some safety checks croak "Inconsistent record size" if ( $headers->{'Prop-content-length'} || 0 ) + ( $headers->{'Text-content-length'} || 0 ) != ( $headers->{'Content-length'} || 0 ); # if we have a delete record with a 'Node-kind' header # we have to recurse for an included record if ( exists $headers->{'Node-action'} && $headers->{'Node-action'} eq 'delete' && exists $headers->{'Node-kind'} ) { my $included = $fh->read_record(); $record->set_included_record( $included ); } # uuid and format record only contain headers return $record; } sub read_header_block { my ($fh) = @_; local $/ = $NL; # skip empty lines my $line; while(1) { $line = <$fh>; return if !defined $line; chop $line; last unless $line eq ''; } my $headers = SVN::Dump::Headers->new(); while(1) { my ($key, $value) = split /: /, $line, 2; $headers->{$key} = $value; $line = <$fh>; croak _eof() if !defined $line; chop $line; last if $line eq ''; # stop on empty line } croak "Empty line found instead of a header block line $." if ! keys %$headers; return $headers; } sub read_property_block { my ($fh) = @_; my $property = SVN::Dump::Property->new(); local $/ = $NL; my @buffer; while(1) { my $line = <$fh>; croak _eof() if !defined $line; chop $line; # read a key/value pair if( $line =~ /\AK (\d+)\z/ ) { my $key = ''; $key .= <$fh> while length($key) < $1; chop $key; # remove the last $NL $line = <$fh>; croak _eof() if !defined $line; chop $line; if( $line =~ /\AV (\d+)\z/ ) { my $value = ''; $value .= <$fh> while length($value) <= $1; chop $value; # remove the last $NL $property->set( $key => $value ); # FIXME what happens if we see duplicate keys? } else { croak "Corrupted property"; # FIXME better error message } } # or a deleted key (only with fs-format-version >= 3) # FIXME shall we fail if fs-format-version < 3? elsif( $line =~ /\AD (\d+)\z/ ) { my $key = ''; $key .= <$fh> while length($key) < $1; chop $key; # remove the last $NL $property->set( $key => undef ); # undef means deleted } # end of properties elsif( $line =~ /\APROPS-END\z/ ) { last; } # inconsistent data else { croak "Corrupted property"; # FIXME better error message } } return $property; } sub read_text_block { my ($fh, $size) = @_; local $/ = $NL; my $text = ''; while( length($text) <= $size ) { my $line = <$fh>; croak _eof() if ! defined $line; $text .= $line; } # remove extra $NL chop $text while length($text) > $size; return SVN::Dump::Text->new( $text ); } # FIXME make this more explicit sub _eof { return "Unexpected EOF line $.", } __END__