#!/usr/bin/perl
use strict;
use warnings;
use SVN::Dump;
use Shell qw/svnadmin cp find svn rm/;
use Cwd qw/abs_path/;
use Data::Dump qw/dump/;

my $base = shift @ARGV;
$base ||= 'svn/';

my $checkout = "$base/checkout";

my $to_dump = 0;

die "usage: $0 temp-base-directory (default: $base)\n" unless ( -e $base );

my $mode = 'move';
my ( $from,      $to      ) = ( "$base/from",     "$base/to/" );
my ( $from_list, $to_list ) = ( "$base/from.lst", "$base/to.lst" );

sub rep_url {
	return 'file:///' . abs_path(shift);
}

sub create_list {
	my ( $dir, $path ) = @_;
	my $list = svn('ls', '-R', rep_url($from));
	open(my $fh, '>', $path) || die "can't create $path: $!";
	print $fh $list;
	close($fh);
	if ( ! -s $path ) {
		unlink($path);
		die "ABORT: created zero size $path\n";
	}
	warn "created: $path ", -s $path, "bytes\n";
}

die "please copy repository on which to perfrom operation to $from\n" if ( ! -e $from );

warn "##> collect all paths from repository $from\n";
create_list( $from, $from_list );

if ( ! -e $to_list ) {
	cp( $from_list, $to_list );
	die "created $to_list\nplease edit it to reflect new layout\n";
}
	
my $map;
my $mkdir;

open(my $fh_from, '<', $from_list ) || die "can't open $from_list: $!";
open(my $fh_to,   '<', $to_list   ) || die "can't open $to_list: $!";
while( my $path_from = <$fh_from> ) {
	chomp($path_from);
	my $path_to = <$fh_to> || die "list shorter, probably corrupt: $to_list\n";
	chomp($path_to);
	$map->{$path_from} = $path_to;
	if ( $path_to =~ m{^(.+)/\Q$path_from\E$}) {
		if ( ! $mkdir->{$1} ) {
			$mkdir->{$1}++;
			warn "##> will create $1 in new layout\n";
		}
	}
}

sub remap {
	my ($path,$kind) = @_;
	$path =~ s|/*$|/| if $kind && $kind eq 'dir';
	if (defined( $map->{$path} )) {
		my $to = $map->{$path};
		warn "##> $path -> $to\n";
		return $to;
	} else {
		warn "??> '$path'\n" if $path;
		return $path;
	}
}

warn "map = ",dump( $map ),$/;
warn "mkdir = ", dump( $mkdir ),$/;

if ( -e $to ) {
	#die "$to allready exists! remove it to re-create repository\n";
	rm('-Rf',$to);
}

svnadmin('create', $to);
open(my $fh_in,  '-|', "svnadmin dump $from") || die "can't dump $from: $!";
open(my $fh_out, '|-', "svnadmin load $to")   || die "can't load $to: $!";
open($fh_out, '>', "$base/to.dump") || die "can't dump to $base/to.dump: $!" if $to_dump;

my $dump = SVN::Dump->new( { fh => $fh_in } );

warn "## converting subversion repository $from -> $to\n";

while ( my $rec = $dump->next_record() ) {

	if ( $rec->type() eq 'revision' && $rec->get_header( 'Revision-number' ) == 1 && $mkdir ) {
		# copy revision record
		print $fh_out $rec->as_string();
		# fetch dirs sorted by length
		foreach my $dir ( sort { length($a) <=> length($b) } keys %$mkdir ) {
			print $fh_out <<"__NODE_ADD_DIR__";

Node-path: $dir
Node-kind: dir
Node-action: add
Prop-content-length: 10
Content-length: 10

PROPS-END


__NODE_ADD_DIR__
			warn "##> inserted mkdir $dir\n";
		}
		next;
	}

	my $path = $rec->get_header('Node-path');
	my $kind = $rec->get_header('Node-kind');
	if ( $path ) {
		my $new_path = remap($path,$kind);
		if ( $new_path eq '' ) {
			warn "##> skipped $kind $path\n";
			next;
		}
		$rec->set_header('Node-path', $new_path );
		if ( $path = $rec->get_header('Node-copyfrom-path') ) {
			$rec->set_header('Node-copyfrom-path', remap($path,$kind) );
		}
	}

	print $fh_out $rec->as_string();
}

if ($@) {
	warn "ERROR: $@\n";
	warn "##> content imported into $to\n";
	svn('ls','-R', rep_url($to));
}

close($fh_in);
close($fh_out);

if ( $to_dump ) {
	svnadmin('verify',$to);
} else {
	rm('-Rf', $checkout) if -e $checkout;
	svn('co', rep_url($to), $checkout);
}
