SVN-Dump-0.06000755001750001750 012152160751 12057 5ustar00bookbook000000000000SVN-Dump-0.06/Build.PL000444001750001750 104512152160751 13510 0ustar00bookbook000000000000use strict; use warnings; use Module::Build; my $builder = Module::Build->new( module_name => 'SVN::Dump', license => 'perl', dist_author => 'Philippe "BooK" Bruhat ', dist_version_from => 'lib/SVN/Dump.pm', requires => { 'Test::More' => 0, }, add_to_cleanup => [ 'SVN-Dump-*' ], meta_merge => { resources => { repository => 'http://github.com/book/SVN-Dump', }, }, license => 'perl', ); $builder->create_build_script(); SVN-Dump-0.06/Makefile.PL000444001750001750 71012152160751 14144 0ustar00bookbook000000000000use ExtUtils::MakeMaker; WriteMakefile( NAME => 'SVN::Dump', VERSION_FROM => 'lib/SVN/Dump.pm', PREREQ_PM => { 'Test::More' => 0, }, PL_FILES => {}, ABSTRACT_FROM => 'lib/SVN/Dump.pm', AUTHOR => 'Philippe "BooK" Bruhat ', META_MERGE => { resources => { repository => 'http://github.com/book/SVN-Dump', }, }, LICENSE => 'perl', ); SVN-Dump-0.06/MANIFEST000444001750001750 312612152160751 13347 0ustar00bookbook000000000000Build.PL Changes eg/svndump_identity.pl eg/svndump_replace_author.pl eg/svndump_stats.pl lib/SVN/Dump.pm lib/SVN/Dump/Headers.pm lib/SVN/Dump/Property.pm lib/SVN/Dump/Reader.pm lib/SVN/Dump/Record.pm lib/SVN/Dump/Text.pm Makefile.PL MANIFEST This list of files META.yml README t/00load.t t/09reader.t t/10headers.t t/11property.t t/12text.t t/13record.t t/15dump.t t/20headers.t t/21property.t t/23record.t t/24reader.t t/25dump.t t/26dump_gz.t t/27transform.t t/29fail.t t/dump/fail/h_noblank.svn t/dump/fail/p_empty.svn t/dump/fail/p_eof.svn t/dump/fail/p_eofval.svn t/dump/fail/p_noend.svn t/dump/fail/p_noval.svn t/dump/fail/r_badsize.svn t/dump/fail/r_badsum.svn t/dump/fail/t_eof.svn t/dump/full/test123-r0-r10-v16.svn t/dump/full/test123-r0-r10.svn t/dump/full/test123-r0-r3.svn t/dump/full/test123-r0-r4.svn t/dump/full/test123-r0-r6.svn t/dump/full/test123-r0.svn t/dump/full/test123-r11-r14-v3.svn t/dump/full/test123-v3.svn t/dump/full/test456-replace.svn t/dump/gzip/test123-r0-r4.svn.gz t/dump/headers/dir-node.svn t/dump/headers/file-node.svn t/dump/headers/format-record.svn t/dump/headers/revision-record.svn t/dump/headers/uuid-record.svn t/dump/property/file-node.svn t/dump/property/revision.svn t/dump/records/format-record.svn t/dump/records/included.svn t/dump/records/node-dir-add.svn t/dump/records/node-file-add-copy.svn t/dump/records/node-file-add-empty.svn t/dump/records/node-file-add-noeol.svn t/dump/records/node-file-add.svn t/dump/records/node-file-delete.svn t/dump/records/revision-record.svn t/dump/records/uuid-record.svn xt/pod-coverage.t xt/pod.t t/README.txt t/Utils.pm META.json SVN-Dump-0.06/Changes000444001750001750 614512152160751 13515 0ustar00bookbook000000000000Revision history for SVN-Dump 0.06 Fri May 31 2013 [ENHANCEMENTS] - speed optimization when reading a dump (thanks to Andrew Sayers) [DOCUMENTATION] - a few documentation tweaks (thanks to David Landgren) - imoproved inter-documentation links 0.05 Sat Feb 19 03:51:57 CET 2011 [ENHANCEMENTS] - Add support for Text-content-sha1 node property - Don't create text or property blocks if they don't exist in a dump. (Rocco Caputo) - Add a digest() method to SVN::Dump::Text (Inspired by a patch by Scott MacPhee, RT #56868) - Add support for Text-content-sha1 / Text-copy-source-sha1 (RT #60207) - New option check_digest that will, when reading a non-delta dump, ensure that the content digest are valid - Properly ignore blank lines between records (RT #25467, #28645) 0.04 Thu Jun 12 16:48:23 CEST 2008 [ENHANCEMENTS] - doesn't lose the PerlIO layers when given a filehandle (e.g. after a binmode( $fh, ':gzip' ) call) 0.03 Tue Nov 21 01:14:43 CET 2006 [ENHANCEMENTS] - Allow the use of '_' for '-' for header names in SVN::Dump::Headers - SVN::Dump::new() now accepts parameters 'version' and 'uuid' - SVN::Dump::format() is an alias for SVN::Dump::version() - SVN::Dump::Record::update_headers() will recompute the record headers after a text or properties modification - SVN::Dump::Property::delete() can remove property keys - SVN::Dump::Record::delete_property() does the same - SVN::Dump::Headers::new() can initialise the headers with a hashref 0.02 Sat Nov 4 01:34:08 CET 2006 *** WARNING: INCOMPATIBLES API CHANGES FROM VERSION 0.01 *** [ENHANCEMENTS] - SVN::Dump->new() now also accepts a filehandle (fh) (thanks to clkao, RT ticket #22429) - Renamed the record manipulation methods of SVN::Dump::Record to set_headers_block() / get_headers_block(), set_property_block() / get_property_block(), set_text_block() / get_text_block(), set_included_bloc() / get_included_block. - Added new methods to SVN::Dump::Record to handle the blocks' data: set_header() / get_header(), set_property() / get_property(), set_text() / get_text(). - new helper methods: property_length() and text_length() [TESTS] - 100% coverage for SVN::Dump too [EXAMPLES] - eg/svndump_replace_author.pl - replace an author by another in a dump *** WARNING: INCOMPATIBLES API CHANGES FROM VERSION 0.01 *** 0.01 Wed Oct 18 18:25:31 CEST 2006 [FEATURES] - SVN::Dump is able to read dumps in version 2 and 3 and output an identical dump [TESTS] - 100% coverage for SVN::Dump::Headers, SVN::Dump::Property, SVN::Dump::Reader, SVN::Dump::Record, SVN::Dump::Text [EXAMPLES] - eg/svndump_identidy.pl - read a dump and prints it out - eg/svndump_stats.pl - produces a set of statistic about a dump SVN-Dump-0.06/META.yml000444001750001750 165412152160751 13473 0ustar00bookbook000000000000--- abstract: 'A Perl interface to Subversion dumps' author: - "Philippe \"BooK\" Bruhat " build_requires: {} configure_requires: Module::Build: 0.38 dynamic_config: 1 generated_by: 'Module::Build version 0.38, CPAN::Meta::Converter version 2.120921' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: SVN-Dump provides: SVN::Dump: file: lib/SVN/Dump.pm version: 0.06 SVN::Dump::Headers: file: lib/SVN/Dump/Headers.pm version: 0 SVN::Dump::Property: file: lib/SVN/Dump/Property.pm version: 0 SVN::Dump::Reader: file: lib/SVN/Dump/Reader.pm version: 0 SVN::Dump::Record: file: lib/SVN/Dump/Record.pm version: 0 SVN::Dump::Text: file: lib/SVN/Dump/Text.pm version: 0 requires: Test::More: 0 resources: license: http://dev.perl.org/licenses/ repository: http://github.com/book/SVN-Dump version: 0.06 SVN-Dump-0.06/README000444001750001750 62312152160751 13055 0ustar00bookbook000000000000SVN::Dump --------- This module lets you manage SVN dumps with Perl. The reference document for Subversion dumpfiles is at: http://svn.apache.org/repos/asf/subversion/trunk/notes/dump-load-format.txt PLEASE NOTE THAT THIS CODE IS OF IN ALPHA QUALITY. I DO NOT MASTER YET ALL THE CONCEPTS BEHIND THE SUBVERSION FILESYSTEM, BUT WILL EVENTUALLY LEARN. THE INTERFACE IS SUBJECT TO CHANGE IN THE FUTURE. SVN-Dump-0.06/META.json000444001750001750 276212152160751 13644 0ustar00bookbook000000000000{ "abstract" : "A Perl interface to Subversion dumps", "author" : [ "Philippe \"BooK\" Bruhat " ], "dynamic_config" : 1, "generated_by" : "Module::Build version 0.38, CPAN::Meta::Converter version 2.120921", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "SVN-Dump", "prereqs" : { "configure" : { "requires" : { "Module::Build" : "0.38" } }, "runtime" : { "requires" : { "Test::More" : "0" } } }, "provides" : { "SVN::Dump" : { "file" : "lib/SVN/Dump.pm", "version" : "0.06" }, "SVN::Dump::Headers" : { "file" : "lib/SVN/Dump/Headers.pm", "version" : 0 }, "SVN::Dump::Property" : { "file" : "lib/SVN/Dump/Property.pm", "version" : 0 }, "SVN::Dump::Reader" : { "file" : "lib/SVN/Dump/Reader.pm", "version" : 0 }, "SVN::Dump::Record" : { "file" : "lib/SVN/Dump/Record.pm", "version" : 0 }, "SVN::Dump::Text" : { "file" : "lib/SVN/Dump/Text.pm", "version" : 0 } }, "release_status" : "stable", "resources" : { "license" : [ "http://dev.perl.org/licenses/" ], "repository" : { "url" : "http://github.com/book/SVN-Dump" } }, "version" : "0.06" } SVN-Dump-0.06/eg000755001750001750 012152160751 12452 5ustar00bookbook000000000000SVN-Dump-0.06/eg/svndump_replace_author.pl000444001750001750 100712152160751 17713 0ustar00bookbook000000000000#!/usr/bin/perl use strict; use warnings; use SVN::Dump; die "svndump_replace_author.pl [file]" if @ARGV < 2; my ( $from, $to ) = splice( @ARGV, 0, 2 ); my $dump = SVN::Dump->new( { file => @ARGV ? $ARGV[0] : '-' } ); while ( my $rec = $dump->next_record() ) { if ( $rec->type() eq 'revision' && $rec->get_header( 'Revision-number' ) != 0 && $rec->get_property('svn:author') eq $from ) { $rec->set_property( 'svn:author' => $to ); } print $rec->as_string(); } SVN-Dump-0.06/eg/svndump_stats.pl000444001750001750 125512152160751 16061 0ustar00bookbook000000000000#!/usr/bin/perl use strict; use warnings; use SVN::Dump; my $dump = SVN::Dump->new( { file => @ARGV ? $ARGV[0] : '-' } ); my $file = @ARGV ? $ARGV[0] : "on STDIN"; # compute some stats my %type; my %kind; while ( my $record = $dump->next_record() ) { $type{ $record->type() }++; $kind{ $record->get_header('Node-action') }++ if $record->type() eq 'node'; } # print the results print "Statistics for dump $file:\n", " version: ", $dump->version(), "\n", " uuid: ", $dump->uuid(), "\n", " revisions: ", $type{revision}, "\n", " nodes: ", $type{node}, "\n"; print map { sprintf " - %-7s: %d\n", $_, $kind{$_} } sort keys %kind; SVN-Dump-0.06/eg/svndump_identity.pl000444001750001750 30312152160751 16525 0ustar00bookbook000000000000#!/usr/bin/perl use strict; use warnings; use SVN::Dump; my $dump = SVN::Dump->new({file => @ARGV ? $ARGV[0] : '-'}); while ( my $rec = $dump->next_record() ) { print $rec->as_string(); } SVN-Dump-0.06/lib000755001750001750 012152160751 12625 5ustar00bookbook000000000000SVN-Dump-0.06/lib/SVN000755001750001750 012152160751 13273 5ustar00bookbook000000000000SVN-Dump-0.06/lib/SVN/Dump.pm000444001750001750 1200012152160751 14704 0ustar00bookbook000000000000package SVN::Dump; use strict; use warnings; use Carp; use SVN::Dump::Reader; our $VERSION = '0.06'; sub new { my ( $class, $args ) = @_; my $self = bless {}, $class; # FIXME - croak() if incompatible options # we have a reader if ( exists $args->{fh} || exists $args->{file} ) { my ( $fh, $file ) = delete @{$args}{qw( fh file )}; if ( !$fh ) { open $fh, $file or croak "Can't open $file: $!"; } $self->{reader} = SVN::Dump::Reader->new( $fh, $args ); } # we don't have a reader else { if( exists $args->{version} ) { $self->{format} = SVN::Dump::Record->new(); $self->{format}->set_header( 'SVN-fs-dump-format-version' => $args->{version} ); } if( exists $args->{uuid} ) { $self->{uuid} = SVN::Dump::Record->new(); $self->{uuid}->set_header( 'UUID' => $args->{uuid} ); } } return $self; } sub next_record { my ($self) = @_; my $record; RECORD: { $record = $self->{reader}->read_record(); return unless $record; # keep the first records in the dump itself my $type = $record->type(); if ( $type =~ /\A(?:format|uuid)\z/ ) { $self->{$type} = $record; } } return $record; } sub version { my ($self) = @_; return $self->{format} ? $self->{format}->get_header('SVN-fs-dump-format-version') : ''; } *format = \&version; sub uuid { my ($self) = @_; return $self->{uuid} ? $self->{uuid}->get_header('UUID') : ''; } sub as_string { return join '', map { $_[0]->{$_}->as_string() } qw( format uuid ); } 1; __END__ =head1 NAME SVN::Dump - A Perl interface to Subversion dumps =head1 SYNOPSIS #!/usr/bin/perl use strict; use warnings; use SVN::Dump; my $file = shift; my $dump = SVN::Dump->new( { file => $file } ); # compute some stats my %type; my %kind; while ( my $record = $dump->next_record() ) { $type{ $record->type() }++; $kind{ $record->get_header('Node-action') }++ if $record->type() eq 'node'; } # print the results print "Statistics for dump $file:\n", " version: ", $dump->version(), "\n", " uuid: ", $dump->uuid(), "\n", " revisions: ", $type{revision}, "\n", " nodes: ", $type{node}, "\n"; print map { sprintf " - %-7s: %d\n", $_, $kind{$_} } sort keys %kind; =head1 DESCRIPTION An SVN::Dump object represents a Subversion dump. This module follow the semantics used in the reference document (the file F in the Subversion source tree): =over 4 =item * A dump is a collection of records (L objects). =item * A record is composed of a set of headers (a L object), a set of properties (a L object) and an optional bloc of text (a L object). =item * Some special records (C records with a C header) recursively contain included records. =back Each class has a C method that prints its content in the dump format. The most basic thing you can do with SVN::Dump is simply copy a dump: use SVN::Dump; my $dump = SVN::Dump->new( 'mydump.svn' ); print $dump->as_string(); # only print the dump header while( $rec = $dump->next_record() ) { print $rec->as_string(); } After the operation, the resulting dump should be identical to the original dump. =head1 METHODS SVN::Dump provides the following methods: =over 4 =item new( \%args ) Return a new SVN::Dump object. The argument list is a hash reference. If the SVN::Dump object will read information from a file, the arguments C is used (as usal, C<-> means C); if the dump is read from a filehandle, C is used. Extra options will be passed to the L object that is created. If the SVN::Dump isn't used to read information, the parameters C and C can be used to initialise the values of the C and C headers. =item next_record() Return the next record read from the dump. This is a L object. =item version() =item format() Return the dump format version, if the version record has already been read, or if it was given in the constructor. =item uuid() Return the dump UUID, if there is an UUID record and it has been read, or if it was given in the constructor. =item as_string() Return a string representation of the dump specific blocks (the C and C blocks only). =back =head1 SEE ALSO L, L. The reference document for Subversion dumpfiles is at: L =head1 COPYRIGHT Copyright 2006-2013 Philippe Bruhat (BooK), All Rights Reserved. =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut SVN-Dump-0.06/lib/SVN/Dump000755001750001750 012152160751 14200 5ustar00bookbook000000000000SVN-Dump-0.06/lib/SVN/Dump/Text.pm000444001750001750 306212152160751 15620 0ustar00bookbook000000000000package SVN::Dump::Text; use strict; use warnings; my $NL = "\012"; # blessed string reference sub new { my ( $class, @args ) = @_; return bless \( join '', @args ), $class; } sub set { my ( $self, $text ) = @_; $$self = $text; } sub get { ${ $_[0] } } *as_string = \&get; sub digest { my ( $self, $algo ) = @_; return eval { require Digest; Digest->new( uc $algo )->add($$self)->hexdigest; }; } 1; __END__ =head1 NAME SVN::Dump::Text - A text block from a svn dump =head1 SYNOPSIS # SVN::Dump::Text objects are returned by the read_text_block() # method of SVN::Dump::Reader =head1 DESCRIPTION A SVN::Dump::Text object represents the text of a SVN dump record. =head1 METHODS The following methods are available: =over 4 =item new( $text ) Create a new SVN::Dump::Text object, initialised with the given text. =item get() Return the text of the SVN::Dump::Text object. =item set( $text ) Set the text of the SVN::Dump::Text object. =item as_string() Return a string representation of the text block. =item digest( $algo ) Return a digest of the text computed with the C<$algo> algorithm in hexadecimal form. See the L module for valid values of C<$algo>. Return C if the digest algorithm is not supported. =back =head1 SEE ALSO L, L. =head1 COPYRIGHT Copyright 2006-2013 Philippe Bruhat (BooK), All Rights Reserved. =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut SVN-Dump-0.06/lib/SVN/Dump/Reader.pm000444001750001750 1532212152160751 16120 0ustar00bookbook000000000000package 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 = $fh->_read_string( $1 ); $line = <$fh>; croak _eof() if !defined $line; chop $line; if( $line =~ /\AV (\d+)\z/ ) { my $value = $fh->_read_string( $1 ); $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 = $fh->_read_string( $1 ); $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) = @_; return SVN::Dump::Text->new( $fh->_read_string( $size ) ); } sub _read_string { my ( $fh, $size ) = @_; local $/ = $NL; my $text; my $characters_read = read( $fh, $text, $size ); if ( defined($characters_read) ) { if ( $characters_read != $size ) { croak _eof(); }; } else { croak $!; }; <$fh>; # clear trailing newline return $text; }; # FIXME make this more explicit sub _eof { return "Unexpected EOF line $.", } __END__ =head1 NAME SVN::Dump::Reader - A Subversion dump reader =head1 SYNOPSIS # !!! 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(); =head1 DESCRIPTION The SVN::Dump::Reader class implements a reader object for Subversion dumps. =head1 METHODS The following methods are available: =over 4 =item new( $fh, \%options ) Create a new SVN::Dump::Reader attached to the C<$fh> filehandle. The only supported option is C, which is disabled by default. =item read_record( ) Read and return a new L object from the dump filehandle. If the option C is enabled, this method will recompute the digests for a dump without deltas, based on the information in the C and C headers (if the corresponding L module is availabled). In case of a mismatch, the routine will C with an exception complaining about a C. =item read_header_block( ) Read and return a new L object from the dump filehandle. =item read_property_block( ) Read and return a new L object from the dump filehandle. =item read_text_block( ) Read and return a new L object from the dump filehandle. =back The C methods will die horribly if asked to read inconsistent data from a stream. =head1 SEE ALSO L, L, L, L, L. =head1 COPYRIGHT Copyright 2006-2013 Philippe Bruhat (BooK), All Rights Reserved. =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut SVN-Dump-0.06/lib/SVN/Dump/Property.pm000444001750001750 431312152160751 16520 0ustar00bookbook000000000000package SVN::Dump::Property; use strict; use warnings; my $NL = "\012"; # FIXME should I use Tie::Hash::IxHash or Tie::Hash::Indexed? sub new { my ( $class, @args ) = @_; return bless { keys => [], hash => {}, }, $class; } sub set { my ( $self, $k, $v ) = @_; push @{ $self->{keys} }, $k if !exists $self->{hash}->{$k}; $self->{hash}{$k} = $v; } sub get { return $_[0]{hash}{ $_[1] }; } sub keys { return @{ $_[0]{keys} }; } sub values { return @{ $_[0]{hash} }{ @{ $_[0]{keys} } }; } sub delete { my ( $self, @keys ) = @_; return if !@keys; my $re = qr/^@{[join '|', map { quotemeta } @keys]}$/; $self->{keys} = [ grep { !/$re/ } @{ $self->{keys} } ]; delete @{ $self->{hash} }{@keys}; } sub as_string { my ($self) = @_; my $string = ''; $string .= defined $self->{hash}{$_} # existing key ? ( "K " . length($_) . $NL ) . "$_$NL" . ( "V " . length( $self->{hash}{$_} ) . $NL ) . "$self->{hash}{$_}$NL" # deleted key (v3) : ( "D " . length($_) . "$NL$_$NL" ) for @{ $self->{keys} }; # end marker $string .= "PROPS-END$NL"; return $string; } 1; __END__ =head1 NAME SVN::Dump::Property - A property block from a svn dump =head1 SYNOPSIS =head1 DESCRIPTION The SVN::Dump::Property class represents a property block in a svn dump. =head1 METHODS The following methods are available: =over 4 =item new() Create a new empty property block. =item set( $key => $value) Set the C<$key> property with value C<$value>. =item get( $key ) Get the value of property C<$key>. =item delete( @keys ) Delete the keys C<@keys>. Behaves like the builtin C on a hash. =item keys() Return the property block keys, in the order they were entered. =item values() Return the property block values, in the order they were entered. =item as_string() Return a string representation of the property block. =back =head1 SEE ALSO L, L. =head1 COPYRIGHT Copyright 2006-2013 Philippe Bruhat (BooK), All Rights Reserved. =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut SVN-Dump-0.06/lib/SVN/Dump/Headers.pm000444001750001750 747312152160751 16261 0ustar00bookbook000000000000package SVN::Dump::Headers; use strict; use warnings; use Carp; use Scalar::Util qw( reftype ); my $NL = "\012"; sub new { my ( $class, $headers ) = @_; croak 'First parameter must be a HASH reference' if defined $headers && !( ref $headers && reftype $headers eq 'HASH' ); my $self = bless {}, $class; $self->set( $_ => $headers->{$_} ) for keys %{ $headers || {} }; return $self; } my %headers = ( revision => [ qw( Revision-number Prop-content-length Content-length ) ], node => [ qw( Node-path Node-kind Node-action Node-copyfrom-rev Node-copyfrom-path Prop-delta Prop-content-length Text-copy-source-md5 Text-copy-source-sha1 Text-delta Text-content-length Text-content-md5 Text-content-sha1 Content-length ) ], uuid => ['UUID'], format => ['SVN-fs-dump-format-version'], ); # FIXME Prop-delta and Text-delta in version 3 sub as_string { my ($self) = @_; my $string = ''; for my $k ( @{ $headers{ $self->type() } } ) { $string .= "$k: $self->{$k}$NL" if exists $self->{$k}; } return $string . $NL; } sub type { my ($self) = @_; my $type = exists $self->{'Node-path'} ? 'node' : exists $self->{'Revision-number'} ? 'revision' : exists $self->{'UUID'} ? 'uuid' : exists $self->{'SVN-fs-dump-format-version'} ? 'format' : croak 'Unable to determine the record type'; return $type; } sub set { my ($self, $h, $v) = @_; # FIXME shall we check that the header value is valid? $h =~ tr/_/-/; # allow _ for - simplification return $self->{$h} = $v; } sub get { my ($self, $h) = @_; $h =~ tr/_/-/; # allow _ for - simplification return $self->{$h}; } sub keys { my ($self) = @_; return grep { exists $self->{$_} } @{ $headers{$self->type()} }; } 1; __END__ =head1 NAME SVN::Dump::Headers - Headers of a SVN dump record =head1 SYNOPSIS # SVN::Dump::Headers objects are returned by the read_header_block() # method of SVN::Dump::Reader =head1 DESCRIPTION A SVN::Dump::Headers object represents the headers of a SVN dump record. =head1 METHODS SVN::Dump::Headers provides the following methods: =over 4 =item new( [$hashref] ) Create and return a new empty L object. If C<$hashref> is given (it can be a blessed hash reference), the keys from the hash are used to initialise the headers. =item set($h, $v) Set the C<$h> header to the value C<$v>. C<_> can be used as a replacement for C<-> in the header name. =item get($h) Get the value of header C<$h>. C<_> can be used as a replacement for C<-> in the header name. =item keys() Return the list of headers, in canonical order. =item as_string() Return a string that represents the record headers. =item type() It is possible to guess the record type from its headers. This method returns a string that represents the record type. The string is one of C, C, C or C. The method dies if it can't determine the record type. =back =head1 ENCAPSULATION When using L to manipulate a SVN dump, one should not directly access the L component of a L, but use the C and C methods of the record object. =head1 SEE ALSO L, L, L. =head1 COPYRIGHT Copyright 2006-2011 Philippe Bruhat (BooK), All Rights Reserved. =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut SVN-Dump-0.06/lib/SVN/Dump/Record.pm000444001750001750 2022712152160751 16134 0ustar00bookbook000000000000package SVN::Dump::Record; use strict; use warnings; use SVN::Dump::Headers; use SVN::Dump::Property; use SVN::Dump::Text; my $NL = "\012"; sub new { my ($class, @args) = @_; return bless {}, $class } for my $attr (qw( headers_block property_block text_block included_record )) { no strict 'refs'; *{"set_$attr"} = sub { $_[0]->{$attr} = $_[1]; }; *{"get_$attr"} = sub { $_[0]->{$attr} }; } sub type { my ($self) = @_; return $self->{headers_block} ? $self->{headers_block}->type() : ''; } sub has_text { return defined $_[0]->get_text_block(); } sub has_prop { return defined $_[0]->get_property_block(); } sub has_prop_only { return defined $_[0]->get_property_block() && !defined $_[0]->get_text_block(); } sub has_prop_or_text { return defined $_[0]->get_property_block() || defined $_[0]->get_text_block(); } # length methods sub property_length { my ($self) = @_; my $prop = $self->get_property_block(); return defined $prop ? length( $prop->as_string() ) : 0; } sub text_length { my ($self) = @_; my $text = $self->get_text(); return defined $text ? length($text) : 0; } sub as_string { my ($self) = @_; my $headers_block = $self->get_headers_block(); # the headers block my $string = $headers_block->as_string(); # the properties $string .= $self->get_property_block()->as_string() if $self->has_prop(); # the text $string .= $self->get_text_block()->as_string() if $self->has_text(); # is there an included record? if( my $included = $self->get_included_record() ) { $string .= $included->as_string() . $NL; } # add a record separator if needed my $type = $self->type(); return $string if $type =~ /\A(?:format|uuid)\z/; $string .= $type eq 'revision' ? $NL : $self->has_prop_or_text() ? $NL x 2 : $NL ; return $string; } sub update_headers { my ($self) = @_; my $proplen = $self->property_length(); my $textlen = $self->text_length(); $self->set_header( 'Text-content-length' => $textlen ) if defined $self->get_text_block(); $self->set_header( 'Prop-content-length', $proplen ) if $proplen; $self->set_header( 'Content-length' => $proplen + $textlen ); } # access methods to the inner blocks sub set_header { my ($self, $h, $v) = @_; my $headers = $self->get_headers_block() || $self->set_headers_block( SVN::Dump::Headers->new() ); $headers->set( $h, $v ); } sub get_header { my ($self, $h) = @_; return $self->get_headers_block()->get($h); } sub set_property { my ( $self, $k, $v ) = @_; my $prop = $self->get_property_block() || $self->set_property_block( SVN::Dump::Property->new() ); $prop->set( $k, $v ); $self->update_headers(); return $v; } sub get_property { my ($self, $k) = @_; return $self->get_property_block()->get($k); } sub delete_property { my ( $self, @keys ) = @_; my $prop = $self->get_property_block() || $self->set_property_block( SVN::Dump::Property->new() ); my @result = $prop->delete(@keys); $self->update_headers(); return wantarray ? @result : pop @result; # behave like delete() } sub set_text { my ($self, $t) = @_; my $text_block = $self->get_text_block() || $self->set_text_block( SVN::Dump::Text->new() ); $text_block->set( $t ); $self->update_headers(); return $t; } sub get_text { my ($self) = @_; my $text_block = $self->get_text_block(); return defined $text_block ? $text_block->get() : undef; } 1; __END__ =head1 NAME SVN::Dump::Record - A SVN dump record =head1 SYNOPSIS # SVN::Dump::Record objects are returned by the next_record() # method of SVN::Dump =head1 DESCRIPTION An SVN::Dump::Record object represents a Subversion dump record. =head1 METHODS L provides the following gourps of methods: =head2 Record methods =over 4 =item new() Create a new empty SVN::Dump::Record object. =item type() Return the record type, as guessed from its headers. The method dies if the record type cannot be determined. =item set_header( $h, $v ) Set the header C<$h> to the value C<$v>. =item get_header( $h ) Get the value of header C<$h>. =item set_property( $p, $v ) Set the property C<$p> to the value C<$v>. =item get_property( $p ) Get the value of property C<$p>. =item delete_property( @k ) Delete the properties named in C<@p>. Properties that do not exist in the record will be silently ignored. =item set_text( $t ) Set the value of the text block. =item get_text() Get the value of the text block. =back =head2 Inner blocks manipulation A SVN::Dump::Record is composed of several inner blocks of various kinds: L, L and L. The following methods provide access to these blocks: =over 4 =item set_headers_block( $headers ) =item get_headers_block() Get or set the L object that represents the record headers. =item set_property_block( $property ) =item get_property_block() Get or set the L object that represents the record property block. =item delete_property( @keys ) Delete the given properties. Behaves like the builtin C. =item set_text_block( $text ) =item get_text_block() Get or set the L object that represents the record text block. =item set_included_record( $record ) =item get_included_record() Some special record are actually output recursiveley by B. The "record in the record" is stored within the parent record, so they are parsed as a single record with an included record. C / C give access to the included record. According to the Subversion sources (F), this is a "delete original, then add-with-history" node. The dump looks like this: Node-path: tags/mytag/myfile Node-kind: file Node-action: delete Node-path: tags/mytag/myfile Node-kind: file Node-action: add Node-copyfrom-rev: 23 Node-copyfrom-path: trunk/myfile Note that there is a single blank line after the first header block, and four after the included one. =item update_headers() Update the various C<...-length> headers. Used internally. B will be inconsistent.> =back =head2 Information methods =over 4 =item has_prop() Return a boolean value indicating if the record has a property block. =item has_text() Return a boolean value indicating if the record has a text block. =item has_prop_only() Return a boolean value indicating if the record has only a property block (and no text block). =item has_prop_or_text() Return a boolean value indicating if the record has a property block or a text block. =item property_length() Return the length of the property block. =item text_length() Return the length of the text block. =back =head2 Output method =over 4 =item as_string() Return a string representation of the record that will be understood by other Subversion tools, such as C. B dumping a record currently returns the information that was read from the original dump. This means that if you modified the property or text block of a record, the headers will be inconsistent. =back =head1 ENCAPSULATION When using L to manipulate a SVN dump, one should not access the L, L and L components of a L object directly, but use the appropriate C and C methods of the record object. These methods compute the appropriate modifications of the header values, so that the C method outputs the correct information after any modification of the record. =head1 SEE ALSO L, L, L, L, L. =head1 COPYRIGHT Copyright 2006-2013 Philippe Bruhat (BooK), All Rights Reserved. =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut SVN-Dump-0.06/xt000755001750001750 012152160751 12512 5ustar00bookbook000000000000SVN-Dump-0.06/xt/pod-coverage.t000444001750001750 60612152160751 15371 0ustar00bookbook000000000000use Test::More; use File::Find; eval "use Test::Pod::Coverage 1.04"; plan skip_all => "Test::Pod::Coverage 1.04 required for testing POD coverage" if $@; my @modules; find( sub { push @modules, $File::Find::name if /\.pm$/ }, 'blib/lib' ); @modules = sort map { s!/!::!g; s/\.pm$//; s/^blib::lib:://; $_ } @modules; plan tests => scalar @modules; pod_coverage_ok($_) for @modules; SVN-Dump-0.06/xt/pod.t000444001750001750 21412152160751 13573 0ustar00bookbook000000000000#!perl -T use Test::More; eval "use Test::Pod 1.14"; plan skip_all => "Test::Pod 1.14 required for testing POD" if $@; all_pod_files_ok(); SVN-Dump-0.06/t000755001750001750 012152160751 12322 5ustar00bookbook000000000000SVN-Dump-0.06/t/15dump.t000444001750001750 255712152160751 13770 0ustar00bookbook000000000000use Test::More; use strict; use warnings; use File::Spec::Functions; use SVN::Dump; plan tests => 10; # non-existing dumpfile eval { my $dump = SVN::Dump->new( { file => 'krunch' } ); }; like( $@, qr/^Can't open krunch: /, "new() fails with non-existing file" ); # a SVN::Dump with a reader my $dump = SVN::Dump->new( { file => catfile(qw( t dump full test123-r0.svn)) } ); is( $dump->version(), '', 'No dump format version yet' ); $dump->next_record(); is( $dump->version(), '2', 'Read dump format version' ); is( $dump->uuid(), '', 'No UUID yet' ); $dump->next_record(); is( $dump->uuid(), '2785358f-ed1c-0410-8d81-93a2a39f1216', 'Read UUID' ); my $as_string = join "\012", 'SVN-fs-dump-format-version: 2', "\012UUID: 2785358f-ed1c-0410-8d81-93a2a39f1216", "\012"; is( $dump->as_string(), $as_string, 'as_string()' ); # a SVN::Dump without a reader $dump = SVN::Dump->new( { version => 3 } ); is( $dump->version(), '3', 'version set by new()' ); $dump = SVN::Dump->new( { uuid => 'bc4ef365-ce1c-0410-99c4-bdd0034106c0' } ); is( $dump->uuid(), 'bc4ef365-ce1c-0410-99c4-bdd0034106c0', 'uuid set by new()' ); $dump = SVN::Dump->new( { version => 2, uuid => '77f6eb63-2709-0410-a607-da1692a51919' } ); is( $dump->version(), '2', 'version set by new()' ); is( $dump->uuid(), '77f6eb63-2709-0410-a607-da1692a51919', 'uuid set by new()' ); SVN-Dump-0.06/t/13record.t000444001750001750 653512152160751 14277 0ustar00bookbook000000000000use strict; use warnings; use Test::More; use t::Utils; use SVN::Dump::Record; plan tests => 30; # the record object my $rec = SVN::Dump::Record->new(); isa_ok( $rec, 'SVN::Dump::Record' ); # no headers yet is( $rec->type(), '', q{No headers yet, can't determine type} ); # create some headers $rec->set_header( @$_ ) for ( [ 'Node-path' => 'trunk/latin' ], [ 'Node-kind' => 'file' ], [ 'Node-action' => 'add' ], ); # give the record some headers is( $rec->type(), 'node', 'Type given by the headers' ); ok( !$rec->has_prop(), 'Record has no property block' ); is( $rec->property_length(), 0, 'Prop-length == 0' ); ok( ! $rec->has_text(), 'Record has no text block' ); is( $rec->text_length(), 0, 'Text-length == 0' ); is( $rec->get_text(), undef, 'No text block' ); # create a property block my @props = ( [ 'svn:log' => 'lorem ipsum sint' ], [ 'svn:author' => 'book' ], [ 'svn:date' => '2006-01-06T02:36:55.834244Z' ], ); $rec->set_property( @$_ ) for @props; # check the headers were updated is( $rec->get_header( $_->[0] ), $_->[1], "$_->[0] header" ) for ( [ 'Prop-content-length' => '115' ], [ 'Content-length' => '115' ], ); # check the properties were updated is( $rec->get_property( $_->[0] ), $_->[1], "$_->[0] property" ) for @props; ok( $rec->has_prop(), 'Record has a property block' ); ok( ! $rec->has_text(), 'Record has no text block' ); ok( $rec->has_prop_only(), 'Record has only a property block' ); ok( $rec->has_prop_or_text(), 'Record has a property or text block' ); # create a text block my $t = << 'EOT'; eos magnam a incidunt ipsum enim sint sed voluptatum adipisicing temporibus officia earum accusamus animi et possimus deserunt eveniet esse reiciendis laboriosam facere voluptas repellendus mollitia hic ipsam aliquid illum qui numquam amet quisquam provident lorem similique minus sapiente exercitation cupiditate nostrum EOT # set some text $rec->set_text( 'zlonk bam kapow' ); is( $rec->text_length(), 15, 'Text-length == 15' ); # add some text $rec->set_text( $t ); # check the headers were updated is( $rec->get_header( $_->[0] ), $_->[1], "$_->[0] header" ) for ( [ 'Text-content-length' => '322' ], [ 'Content-length' => '437' ], ); # check the text is available is( $rec->text_length(), length($t), "Text-length = @{[length($t)]}" ); is( $rec->get_text(), $t, 'Text block' ); ok( $rec->has_prop(), 'Record has a property block' ); ok( $rec->has_text(), 'Record has a text block' ); ok( ! $rec->has_prop_only(), 'Record has not only a property block' ); ok( $rec->has_prop_or_text(), 'Record has a property or text block' ); # check that delete_property() behaves like the builtin delete() $rec->set_property(@$_) for ( [ foo => 11 ], [ bar => 22 ], [ baz => 33 ] ); my $scalar = $rec->delete_property('foo'); is( $scalar, 11, '$scalar is 11 (perldoc -f delete)' ); $scalar = $rec->delete_property(qw(foo bar)); is( $scalar, 22, '$scalar is 22 (perldoc -f delete)' ); my @array = $rec->delete_property(qw(foo bar baz)); is_deeply( \@array, [ undef, undef, 33 ], '@array is (undef, undef,33)' ); # test a record without properties $rec = SVN::Dump::Record->new; $rec->set_header( "Node-path", "trunk/fubar.txt" ); $rec->set_header( "Node-kind", "file" ); $rec->set_header( "Node-action", "change" ); $rec->set_text("some text"); ok( $rec->as_string !~ /^Prop-content-length: 0$/m, "No Prop-content-length: 0" ); SVN-Dump-0.06/t/21property.t000444001750001750 105712152160751 14676 0ustar00bookbook000000000000use strict; use warnings; use Test::More; use t::Utils; use File::Spec::Functions; use SVN::Dump::Reader; my @files = glob catfile( 't', 'dump', 'property', '*' ); plan tests => 2 * @files; for my $f (@files) { my $expected = file_content($f); open my $fh, $f or do { fail("Failed to open $f: $!") for 1 .. 2; next; }; my $dump = SVN::Dump::Reader->new($fh); my $h = $dump->read_property_block(); is_same_string( $h->as_string(), $expected, "Read $f property" ); is( tell($fh), -s $f, "Read all of $f" ); } SVN-Dump-0.06/t/11property.t000444001750001750 503612152160751 14676 0ustar00bookbook000000000000use strict; use warnings; use Test::More; use t::Utils; use SVN::Dump::Property; my @tests = ( [ bloop => 'zwapp' ], [ zap => 'glipp' ], [ bap => 'kapow' ], [ thwapp => 'owww' ], ); # the expected string representation my $as_string = << 'END_OF_PROPERTY'; K 5 bloop V 5 zwapp K 3 zap V 5 glipp K 3 bap V 5 kapow K 6 thwapp V 4 owww PROPS-END END_OF_PROPERTY plan tests => 20 + 2 * @tests; # create a new empty property block my $p = SVN::Dump::Property->new(); isa_ok( $p, 'SVN::Dump::Property' ); is( $p->as_string(), "PROPS-END\012", 'empty property block' ); # try setting some values for my $kv (@tests) { is( $p->set(@$kv), $kv->[1], "Set $kv->[0] => $kv->[1]" ); is( $p->get($kv->[0]), $kv->[1], "Get $kv->[0] as $kv->[1]" ); } # check the order of the keys is_deeply( [ $p->keys() ], [ map { $_->[0] } @tests ], "Keys in order" ); is_deeply( [ $p->values() ], [ map { $_->[1] } @tests ], "Values in order" ); # check the string serialisation is_same_string( $p->as_string(), $as_string, 'Property serialisation' ); # change a value is( $p->set( bap => 'urkkk' ), 'urkkk', "Changed 'bap' value" ); is( $p->get('bap'), 'urkkk', "Really changed 'bap' value" ); # check the order $tests[2] = [ bap => 'urkkk' ]; is_deeply( [ $p->keys() ], [ map { $_->[0] } @tests ], "Keys in order" ); is_deeply( [ $p->values() ], [ map { $_->[1] } @tests ], "Values in order" ); # add a new key is( $p->set( swish => 'ker_sploosh' ), 'ker_sploosh', "Added 'swish' value" ); is( $p->get('swish'), 'ker_sploosh', "Really added 'swish' value" ); # check the order again push @tests, [swish => 'ker_sploosh' ]; is_deeply( [ $p->keys() ], [ map { $_->[0] } @tests ], "Keys in order" ); is_deeply( [ $p->values() ], [ map { $_->[1] } @tests ], "Values in order" ); # delete the new key is( $p->delete('swish'), 'ker_sploosh', 'delete() returns the value' ); is( $p->delete('swish'), undef, 'delete() non-existing key' ); is( $p->delete(), undef, 'delete() no key' ); # update the expected result $as_string =~ s/kapow/urkkk/; # same length is_same_string( $p->as_string(), $as_string, 'Property serialisation' ); # check that delete() behaves like the builtin delete() $p->set(@$_) for ( [ foo => 11 ], [ bar => 22 ], [ baz => 33 ] ); my $scalar = $p->delete('foo'); is( $scalar, 11, '$scalar is 11 (perldoc -f delete)' ); $scalar = $p->delete(qw(foo bar)); is( $scalar, 22, '$scalar is 22 (perldoc -f delete)' ); my @array = $p->delete(qw(foo bar baz)); is_deeply( \@array, [ undef, undef, 33 ], '@array is (undef, undef,33)' ); SVN-Dump-0.06/t/10headers.t000444001750001750 357012152160751 14425 0ustar00bookbook000000000000use strict; use warnings; use Test::More; use List::Util 'shuffle'; use SVN::Dump::Headers; my @valid = ( [ 'Revision-number' => 1 ], [ 'Prop-content-length' => 125 ], [ 'Content-length' => 125 ], ); my %init = ( Zlonk => 'Kapow', Bam_Kapow => 'Zowie', 'Eee-yow' => 'Glurpp' ); plan tests => 14 + 2 * @valid + 2 * keys %init; # test new() for my $args ( 'zlonk', [], ( bless [], 'zlonk' ) ) { eval { my $h = SVN::Dump::Headers->new('zlonk'); }; like( $@, qr/^First parameter must be a HASH reference/, 'new() expects a hashref' ); } my $h = SVN::Dump::Headers->new(); isa_ok( $h, 'SVN::Dump::Headers' ); eval { $h->type() }; like( $@, qr/^Unable to determine the record type/, 'No type yet (type)' ); eval { $h->keys() }; like( $@, qr/^Unable to determine the record type/, 'No type yet (keys)' ); # test the set/get methods is( $h->set( Zlonk => 'Kapow' ), 'Kapow', 'set() returns the new value' ); is( $h->get('Zlonk'), 'Kapow', 'get() method returns the value' ); is( $h->get('Vronk'), undef, 'get() returns undef for non-existent header' ); is( $h->set( Bam_Kapow => 'Zowie' ), 'Zowie', 'set() returns the new value' ); is( $h->get( 'Bam-Kapow' ), 'Zowie', '_ and - work the same' ); is( $h->get( 'Bam_Kapow' ), 'Zowie', '_ and - work the same' ); eval { $h->keys() }; like( $@, qr/^Unable to determine the record type/, 'No type yet (keys)' ); my $c = 0; for my $p (@valid) { is( $h->set(@$p), $p->[1], "set() method returns the new value for $p->[0]" ); $c++; is( scalar $h->keys(), $c, "$c valid keys" ); } is_deeply( [ $h->keys() ], [ map { $_->[0] } @valid ], 'keys() return the valid keys in order' ); # test new() with parameters for my $init ( \%init, ( bless { %init }, 'zlonk' ) ) { $h = SVN::Dump::Headers->new( $init ); is( $h->get($_), $init{$_}, "$_ value" ) for keys %init; } SVN-Dump-0.06/t/25dump.t000444001750001750 160612152160751 13763 0ustar00bookbook000000000000use strict; use warnings; use Test::More; use t::Utils; use File::Spec::Functions; use SVN::Dump; my @files = glob catfile( 't', 'dump', 'full', '*' ); plan tests => 2 * @files; my $i = 0; for my $f (@files) { my $expected = file_content($f); my $dump; # test each file twice my $fh; # once with a filehandle if ( $i % 2 ) { open $fh, $f or do { fail("Failed to open $f: $!") for 1 .. 2; next; }; $dump = SVN::Dump->new( { fh => $fh, check_digest => 1 } ); } # once with a filename else { $dump = SVN::Dump->new( { file => $f } ); } my $as_string = ''; while ( my $r = $dump->next_record() ) { $as_string .= $r->as_string(); } is_same_string( $as_string, $expected, "Read $f dump" ); is( tell($dump->{reader}), -s $f, "Read all of $f (@{[-s $f]} bytes)" ); $i++; } SVN-Dump-0.06/t/27transform.t000444001750001750 301112152160751 15023 0ustar00bookbook000000000000use strict; use warnings; use Test::More; use t::Utils; use File::Spec::Functions; use File::Temp qw( tempfile ); use SVN::Dump; my @files = glob catfile( 't', 'dump', 'full', '*' ); plan tests => 4 * @files; my $i = 0; for my $f (@files) { # open the original dump my $dump = SVN::Dump->new( { file => $f } ); my $expected = file_content($f); # open a target file my ( $fh, $tempfile ) = tempfile( 'dump-XXXX', SUFFIX => '.svn' ); # read the dump my $as_string = ''; while ( my $r = $dump->next_record() ) { $as_string .= $r->as_string(); # transform the dump $r->set_text('dummy') if defined $r->get_text(); # replace text delete ${ $r->get_headers_block() }{'Text-delta'} # no more delta if $r->get_header('Text-delta'); print $fh $r->as_string(); } close $fh; # quick check that identity still works is_same_string( $as_string, $expected, "Read $f dump" ); is( tell( $dump->{reader} ), -s $f, "Read all of $f (@{[-s $f]} bytes)" ); # read the transformed version $expected = file_content($tempfile); # check round trip $dump = SVN::Dump->new( { file => $tempfile } ); $as_string = ''; while ( my $r = $dump->next_record() ) { $as_string .= $r->as_string(); } is_same_string( $as_string, $expected, "Read $tempfile dump (transformed $f)" ); is( tell( $dump->{reader} ), -s $tempfile, "Read all of $tempfile (@{[-s $tempfile]} bytes)" ); unlink $tempfile; } SVN-Dump-0.06/t/09reader.t000444001750001750 62012152160751 14235 0ustar00bookbook000000000000use strict; use Test::More; use SVN::Dump::Reader; # the many way to fail my %test = ( false => '', string => 'file', reference => \'file', object => bless( {}, 'Zlonk'), ); plan tests => scalar keys %test; for my $t ( keys %test ) { eval { my $r = SVN::Dump::Reader->new( $test{$t} ); }; like( $@, qr/^SVN::Dump::Reader parameter is not a filehandle/, $t ); } SVN-Dump-0.06/t/README.txt000444001750001750 172712152160751 14164 0ustar00bookbook000000000000The test suite is written so as to make it easy to tests various dump formats. The t/dump directory contains various bits and pieces of both valid and broken dumps. It contains several subdirectories: t/dump/full contains full dumps (such as those produced by svnadmin dump) the tests read the dump with SVN::Dump, and compare the dump produced by SVN::Dump to the original. Any difference is a bug in SVN::Dump. t/dump/records contains individual records (uuid, format, headers, revision, node, dir, etc) t/dump/property contains only property blocks t/dump/headers contains only header blocks t/dump/fail contains broken elements (record, headers, property or text blocks). The first line is actually a regular expression that should match the error message produced by SVN::Dump. To add new dump excerpts to be tested, simply copy them in the relevant directory. SVN-Dump-0.06/t/26dump_gz.t000444001750001750 146012152160751 14462 0ustar00bookbook000000000000use strict; use warnings; use Test::More; use t::Utils; use File::Spec::Functions; use SVN::Dump; eval { require PerlIO::gzip; }; plan skip_all => 'PerlIO::gzip required to test gziped streams' if $@; my @files = glob catfile( 't', 'dump', 'gzip', '*' ); plan tests => scalar @files; for my $f (@files) { my $src = $f; $src =~ s/gzip/full/; $src =~ s/\.gz$//; my $expected = file_content($src); my $dump; # open a gzipped filehandle open my $fh, $f or do { fail("Failed to open $f: $!") for 1 .. 2; next; }; binmode( $fh, ':gzip' ); $dump = SVN::Dump->new( { fh => $fh } ); my $as_string = ''; while ( my $r = $dump->next_record() ) { $as_string .= $r->as_string(); } is_same_string( $as_string, $expected, "Read $f dump" ); } SVN-Dump-0.06/t/23record.t000444001750001750 111412152160751 14264 0ustar00bookbook000000000000use strict; use warnings; use Test::More; use t::Utils; use File::Spec::Functions; use SVN::Dump::Reader; my @files = glob catfile( 't', 'dump', 'records', '*' ); plan tests => 2 * @files; for my $f (@files) { my $expected = file_content($f); open my $fh, $f or do { fail("Failed to open $f: $!") for 1 .. 2; next; }; my $dump = SVN::Dump::Reader->new($fh); my $r = $dump->read_record(); is_same_string( $r->as_string(), $expected, "Read $f record" ); $r = $dump->read_record(); ok( !$r && tell($fh) == -s $f, "Read all of $f" ); } SVN-Dump-0.06/t/24reader.t000444001750001750 117112152160751 14254 0ustar00bookbook000000000000use strict; use warnings; use Test::More; use t::Utils; use File::Spec::Functions; use SVN::Dump::Reader; my @files = glob catfile( 't', 'dump', 'full', '*' ); plan tests => 2 * @files; for my $f (@files) { my $expected = file_content($f); open my $fh, $f or do { fail( "Failed to open $f: $!" ) for 1..2; next; }; my $dump = SVN::Dump::Reader->new($fh); my $as_string = ''; while( my $r = $dump->read_record() ) { $as_string .= $r->as_string(); } is_same_string( $as_string, $expected, "Read $f dump" ); is( tell($fh), -s $f, "Read all of $f (@{[-s $f]} bytes)" ); } SVN-Dump-0.06/t/12text.t000444001750001750 66112152160751 13756 0ustar00bookbook000000000000use strict; use warnings; use Test::More; use t::Utils; use SVN::Dump::Text; plan tests => 5; # create a text block my $t = SVN::Dump::Text->new( 'clash sock swish bam' ); isa_ok( $t, 'SVN::Dump::Text' ); is( $t->get(), 'clash sock swish bam', 'Got the text' ); is( $t->set( 'urkkk whamm' ), 'urkkk whamm', 'Changed the text'); is( $t->get(), 'urkkk whamm', 'Changed the text'); is( $t->as_string(), $t->get(), 'as_string()' ); SVN-Dump-0.06/t/20headers.t000444001750001750 116012152160751 14417 0ustar00bookbook000000000000use strict; use warnings; use Test::More; use t::Utils; use File::Spec::Functions; use SVN::Dump::Reader; my @files = glob catfile( 't', 'dump', 'headers', '*' ); plan tests => 3 * @files; for my $f (@files) { my $expected = file_content($f); open my $fh, $f or do { fail("Failed to open $f: $!") for 1 .. 3; next; }; my $dump = SVN::Dump::Reader->new($fh); my $h = $dump->read_header_block(); is_same_string( $h->as_string(), $expected, "Read $f headers" ); like( $f, qr/@{[$h->type()]}/, "Correct type detected for $f" ); is( tell($fh), -s $f, "Read all of $f" ); } SVN-Dump-0.06/t/00load.t000444001750001750 35112152160751 13702 0ustar00bookbook000000000000use Test::More; use File::Find; my @modules; find( sub { push @modules, $File::Find::name if /\.pm$/ }, 'blib/lib' ); plan tests => scalar @modules; use_ok($_) for sort map { s!/!::!g; s/\.pm$//; s/^blib::lib:://; $_ } @modules; SVN-Dump-0.06/t/29fail.t000444001750001750 136112152160751 13733 0ustar00bookbook000000000000use strict; use Test::More; use File::Spec::Functions; use SVN::Dump::Reader; my @files = glob catfile( 't', 'dump', 'fail', '*' ); plan tests => 2 * @files; for my $f (@files) { open my $fh, $f or do { fail("Failed to open $f: $!") for 1 .. 2; next; }; # read the test information from the test file itself my $func = <$fh>; # first line contains the method name my $err = <$fh>; # second line contains the error regexp chop for ($err, $func); ($func, my @args) = split / /, $func; my $r = SVN::Dump::Reader->new( $fh, { check_digest => 1 } ); eval { $r->$func(@args); }; ok( $@, "$func(@{[join',',@args]}) failed for $f" ); like( $@, qr/$err/, " with the expected error ($err)" ); } SVN-Dump-0.06/t/Utils.pm000444001750001750 120112152160751 14107 0ustar00bookbook000000000000use strict; use warnings; eval { require Test::LongString; import Test::LongString; }; my $has_test_longstring = $@ eq ''; # our own string comparison test function sub is_same_string { my ($got, $expected, $name) = @_; if ($has_test_longstring) { is_string( $got, $expected, $name); } else { is( $got, $expected, $name); } } sub file_content { my ($file) = @_; local $/; open my $fh, $file or do { diag "Can't open $file: $!"; return '' }; my $content = join '', <$fh>; close $fh; return $content; } 1; __END__ =head1 NAME t::Util - Some utility functions for the tests = SVN-Dump-0.06/t/dump000755001750001750 012152160751 13267 5ustar00bookbook000000000000SVN-Dump-0.06/t/dump/fail000755001750001750 012152160751 14202 5ustar00bookbook000000000000SVN-Dump-0.06/t/dump/fail/p_noend.svn000444001750001750 24212152160751 16467 0ustar00bookbook000000000000read_property_block ^Corrupted property K 7 svn:log V 20 standard directories K 10 svn:author V 4 book K 8 svn:date V 27 2005-11-18T11:07:02.060507Z PROPS-FINISH SVN-Dump-0.06/t/dump/fail/p_noval.svn000444001750001750 22612152160751 16505 0ustar00bookbook000000000000read_property_block ^Corrupted property K 7 svn:log V 20 standard directories K 10 svn:author K 8 svn:date V 27 2005-11-18T11:07:02.060507Z PROPS-END SVN-Dump-0.06/t/dump/fail/r_badsum.svn000444001750001750 263412152160751 16670 0ustar00bookbook000000000000read_record ^md5 checksum mismatch: got 60262fd14bd1b59416820cc37e4ee982, expected 6772871c2cf5bcf281c5ee148135a2db Node-path: trunk/loremipsum.txt Node-kind: file Node-action: add Prop-content-length: 40 Text-content-length: 1090 Text-content-md5: 6772871c2cf5bcf281c5ee148135a2db Content-length: 1130 K 13 svn:eol-style V 6 native PROPS-END quo fugiat quos quam voluptate nostrum perferendis eum animi fugit unde nam accusantium laboriosam quaerat asperiores assumenda praesentium iure eaque dicta explicabo autem vero voluptates sed itaque repellat dignissimos quia laudantium temporibus consequuntur beatae dolore eligendi doloremque quas recusandae proident sapiente mollit aliqua veniam culpa cillum voluptatem tempore exercitationem ullamco nobis nihil corrupti anim dolores soluta reiciendis necessitatibus sequi officia do dolorum a nisi consectetur quisquam illum amet delectus minim porro provident error dolorem repudiandae pariatur occaecat rem aspernatur tempora excepturi ab similique repellendus voluptatum neque totam aliquip adipisicing cum vitae eveniet minus placeat nemo quibusdam laborum velit saepe aute fuga reprehenderit eius ratione magnam aperiam sit mollitia lorem facilis debitis distinctio non facere natus maiores alias iste aliquid rerum ullam sunt possimus minima qui nostrud nesciunt at et deleniti tempor hic corporis suscipit commodo odio quis quae harum molestiae exercitation inventore cumque SVN-Dump-0.06/t/dump/fail/p_eofval.svn000444001750001750 14312152160751 16640 0ustar00bookbook000000000000read_property_block ^Unexpected EOF line \d+ K 7 svn:log V 20 standard directories K 10 svn:author SVN-Dump-0.06/t/dump/fail/p_eof.svn000444001750001750 12312152160751 16133 0ustar00bookbook000000000000read_property_block ^Unexpected EOF line \d+ K 7 svn:log V 20 standard directories SVN-Dump-0.06/t/dump/fail/h_noblank.svn000444001750001750 36712152160751 17010 0ustar00bookbook000000000000read_header_block ^Unexpected EOF line \d+ Node-path: css/style.css Node-kind: file Node-action: add Prop-content-length: 87 Text-content-length: 970 Text-content-md5: 1f97ca311e48901bfc430d9f3e335dd0 Content-length: 1057 SVN-Dump-0.06/t/dump/fail/t_eof.svn000444001750001750 23612152160751 16144 0ustar00bookbook000000000000read_text_block 200 ^Unexpected EOF line \d+ wham_eth crunch blurp powie aiieee spla_a_t glipp whamm awkkkkkk swish qunckkk clunk krunch pam bam rip ker_plop SVN-Dump-0.06/t/dump/fail/r_badsize.svn000444001750001750 251612152160751 17035 0ustar00bookbook000000000000read_record ^Inconsistent record size Node-path: trunk/loremipsum.txt Node-kind: file Node-action: add Prop-content-length: 40 Text-content-length: 1090 Text-content-md5: 60262fd14bd1b59416820cc37e4ee982 Content-length: 1100 K 13 svn:eol-style V 6 native PROPS-END quo fugiat quos quam voluptate nostrum perferendis eum animi fugit unde nam accusantium laboriosam quaerat asperiores assumenda praesentium iure eaque dicta explicabo autem vero voluptates sed itaque repellat dignissimos quia laudantium temporibus consequuntur beatae dolore eligendi doloremque quas recusandae proident sapiente mollit aliqua veniam culpa cillum voluptatem tempore exercitationem ullamco nobis nihil corrupti anim dolores soluta reiciendis necessitatibus sequi officia do dolorum a nisi consectetur quisquam illum amet delectus minim porro provident error dolorem repudiandae pariatur occaecat rem aspernatur tempora excepturi ab similique repellendus voluptatum neque totam aliquip adipisicing cum vitae eveniet minus placeat nemo quibusdam laborum velit saepe aute fuga reprehenderit eius ratione magnam aperiam sit mollitia lorem facilis debitis distinctio non facere natus maiores alias iste aliquid rerum ullam sunt possimus minima qui nostrud nesciunt at et deleniti tempor hic corporis suscipit commodo odio quis quae harum molestiae exercitation inventore cumque SVN-Dump-0.06/t/dump/fail/p_empty.svn000444001750001750 5512152160751 16504 0ustar00bookbook000000000000read_property_block ^Unexpected EOF line \d+ SVN-Dump-0.06/t/dump/full000755001750001750 012152160751 14231 5ustar00bookbook000000000000SVN-Dump-0.06/t/dump/full/test123-r0-r4.svn000444001750001750 565512152160751 17200 0ustar00bookbook000000000000SVN-fs-dump-format-version: 2 UUID: 2785358f-ed1c-0410-8d81-93a2a39f1216 Revision-number: 0 Prop-content-length: 56 Content-length: 56 K 8 svn:date V 27 2006-09-08T09:09:02.348832Z PROPS-END Revision-number: 1 Prop-content-length: 125 Content-length: 125 K 7 svn:log V 26 Standard repository layout K 10 svn:author V 4 book K 8 svn:date V 27 2006-09-08T11:48:32.736884Z PROPS-END Node-path: branches Node-kind: dir Node-action: add Prop-content-length: 10 Content-length: 10 PROPS-END Node-path: tags Node-kind: dir Node-action: add Prop-content-length: 10 Content-length: 10 PROPS-END Node-path: trunk Node-kind: dir Node-action: add Prop-content-length: 10 Content-length: 10 PROPS-END Revision-number: 2 Prop-content-length: 116 Content-length: 116 K 7 svn:log V 17 Add an empty file K 10 svn:author V 4 book K 8 svn:date V 27 2006-09-08T11:49:03.913665Z PROPS-END Node-path: trunk/empty.txt Node-kind: file Node-action: add Prop-content-length: 40 Text-content-length: 0 Text-content-md5: d41d8cd98f00b204e9800998ecf8427e Content-length: 40 K 13 svn:eol-style V 6 native PROPS-END Revision-number: 3 Prop-content-length: 120 Content-length: 120 K 7 svn:log V 21 some dummy latin text K 10 svn:author V 4 book K 8 svn:date V 27 2006-09-08T12:35:49.304317Z PROPS-END Node-path: trunk/loremipsum.txt Node-kind: file Node-action: add Prop-content-length: 40 Text-content-length: 1090 Text-content-md5: 60262fd14bd1b59416820cc37e4ee982 Content-length: 1130 K 13 svn:eol-style V 6 native PROPS-END quo fugiat quos quam voluptate nostrum perferendis eum animi fugit unde nam accusantium laboriosam quaerat asperiores assumenda praesentium iure eaque dicta explicabo autem vero voluptates sed itaque repellat dignissimos quia laudantium temporibus consequuntur beatae dolore eligendi doloremque quas recusandae proident sapiente mollit aliqua veniam culpa cillum voluptatem tempore exercitationem ullamco nobis nihil corrupti anim dolores soluta reiciendis necessitatibus sequi officia do dolorum a nisi consectetur quisquam illum amet delectus minim porro provident error dolorem repudiandae pariatur occaecat rem aspernatur tempora excepturi ab similique repellendus voluptatum neque totam aliquip adipisicing cum vitae eveniet minus placeat nemo quibusdam laborum velit saepe aute fuga reprehenderit eius ratione magnam aperiam sit mollitia lorem facilis debitis distinctio non facere natus maiores alias iste aliquid rerum ullam sunt possimus minima qui nostrud nesciunt at et deleniti tempor hic corporis suscipit commodo odio quis quae harum molestiae exercitation inventore cumque Revision-number: 4 Prop-content-length: 134 Content-length: 134 K 7 svn:log V 35 renamed loremipsum.txt to latin.txt K 10 svn:author V 4 book K 8 svn:date V 27 2006-09-08T14:02:27.413127Z PROPS-END Node-path: trunk/latin.txt Node-kind: file Node-action: add Node-copyfrom-rev: 3 Node-copyfrom-path: trunk/loremipsum.txt Node-path: trunk/loremipsum.txt Node-action: delete SVN-Dump-0.06/t/dump/full/test123-v3.svn000444001750001750 1475212152160751 16702 0ustar00bookbook000000000000SVN-fs-dump-format-version: 3 UUID: 2785358f-ed1c-0410-8d81-93a2a39f1216 Revision-number: 0 Prop-content-length: 56 Content-length: 56 K 8 svn:date V 27 2006-09-08T09:09:02.348832Z PROPS-END Revision-number: 1 Prop-content-length: 125 Content-length: 125 K 7 svn:log V 26 Standard repository layout K 10 svn:author V 4 book K 8 svn:date V 27 2006-09-08T11:48:32.736884Z PROPS-END Node-path: branches Node-kind: dir Node-action: add Prop-content-length: 10 Content-length: 10 PROPS-END Node-path: tags Node-kind: dir Node-action: add Prop-content-length: 10 Content-length: 10 PROPS-END Node-path: trunk Node-kind: dir Node-action: add Prop-content-length: 10 Content-length: 10 PROPS-END Revision-number: 2 Prop-content-length: 116 Content-length: 116 K 7 svn:log V 17 Add an empty file K 10 svn:author V 4 book K 8 svn:date V 27 2006-09-08T11:49:03.913665Z PROPS-END Node-path: trunk/empty.txt Node-kind: file Node-action: add Prop-content-length: 40 Text-delta: true Text-content-length: 4 Text-content-md5: d41d8cd98f00b204e9800998ecf8427e Content-length: 44 K 13 svn:eol-style V 6 native PROPS-END SVN Revision-number: 3 Prop-content-length: 120 Content-length: 120 K 7 svn:log V 21 some dummy latin text K 10 svn:author V 4 book K 8 svn:date V 27 2006-09-08T12:35:49.304317Z PROPS-END Node-path: trunk/loremipsum.txt Node-kind: file Node-action: add Prop-content-length: 40 Text-delta: true Text-content-length: 964 Text-content-md5: 60262fd14bd1b59416820cc37e4ee982 Content-length: 1004 K 13 svn:eol-style V 6 native PROPS-END SVNBEDERJEE GQDD-F DENKHkD7G E-EDtD{F D4FwDEEeD+D6F:G DGG1DBEkEVFmDDQHFIDDIDQDhDDWD!DGVDhE&DDfw~T8FB|YufJe[EgX7Vͺj)G:p:XyܒRx7H6]ob)~9e'n$rY>MOqVVq*w.5^ۨQHO ztsDnUd5ǽoֹǯMmj,}13TL*M=c쎭Ht̪(cW+V{EFgoMbVgc)>ʞñպj[gz~ݮC|NTnmLD,*<o+ieK,tߵxBAx)|i޺ԭ˳XFXPcԛfam S MU/,eݛݚjXղK .n>c%S;6[51^In@2y< 3VAe`o={c(?<權4w*&t,]#Ch Б~d,|T44c95ƸG܄GgLφO(7^8i26}WӄKPwOR?q-i)' O5(#%4( }q~t )1dtrGf%S'b'O{,?$GfOf+?iJT P֦qd"ӍC"#[#͗=! $Q  SnԳh~rܳNNJ5fN[qt3ן^(K%[yA876|Ӟo+^mf!i`B#_.\\y~oqSڪ}uvcXx:﹗wIaR7&=[ڥ SVN-Dump-0.06/t/dump/property000755001750001750 012152160751 15153 5ustar00bookbook000000000000SVN-Dump-0.06/t/dump/property/file-node.svn000444001750001750 5012152160751 17635 0ustar00bookbook000000000000K 13 svn:eol-style V 6 native PROPS-END SVN-Dump-0.06/t/dump/property/revision.svn000444001750001750 16712152160751 17662 0ustar00bookbook000000000000K 7 svn:log V 20 standard directories K 10 svn:author V 4 book K 8 svn:date V 27 2005-11-18T11:07:02.060507Z PROPS-END