package SVK::Command::Sync;
use strict;
use SVK::Version;  our $VERSION = $SVK::VERSION;

use base qw( SVK::Command );
use SVK::I18N;
use SVK::Util qw( HAS_SVN_MIRROR find_prev_copy find_local_mirror );

sub options {
    ('s|skipto=s'	=> 'skip_to',
     'a|all'		=> 'sync_all',
     't|torev=s'	=> 'torev');
}

sub parse_arg {
    my ($self, @arg) = @_;
    return (@arg ? @arg : undef) if $self->{sync_all};

    return map {$self->arg_uri_maybe ($_)} @arg;
}

sub copy_notify {
    my ($self, $m, $path, $from_path, $from_rev) = @_;
    # XXX: on anchor, try to get a external copy cache
    return unless $m->{target_path} ne $path;
    return find_local_mirror ($m->{repos}, $m->{rsource_uuid}, $from_path, $from_rev);
}

sub lock_message {
    my $target = shift;
    my $i = 0;
    sub {
	my ($mirror, $what, $who) = @_;
	print loc("Waiting for %1 lock on %2: %3.\n", $what, $target->{depotpath}, $who);
	if (++$i % 3 == 0) {
	    print loc ("
The mirror is currently locked. This might be because the mirror is
in the middle of a sensitive operation or because a process holding
the lock hung or died.  To check if the mirror lock is stalled,  see
if $who is a running, valid process

If the mirror lock is stalled, please interrupt this process and run:
    svk mirror --unlock %1
", $target->{depotpath});
	}
    }
}

sub run {
    my ( $self, @arg ) = @_;
    die loc("cannot load SVN::Mirror") unless HAS_SVN_MIRROR;

    die loc("argument skipto not allowed when multiple target specified")
        if $self->{skip_to} && ( $self->{sync_all} || $#arg > 0 );

    if ( $self->{sync_all} ) {
        local $@;
        my %arg = ( !defined( $arg[0] ) ? () : map { $_ => 1 } @arg );
        @arg = (
            defined( $arg[0] )
            ? @arg
            : sort keys %{ $self->{xd}{depotmap} } );
        my @newarg;
        foreach my $arg (@arg) {
            my $orig_arg = $arg;
            $arg = "/$arg/" if $arg !~ m{/};
            $arg = "$arg/" unless $arg =~ m{/$};

            my ($depot) = eval { $self->arg_depotname($arg) };
            unless ( defined $depot ) {
                if ( $arg =~ m{^/[^/]+/$} ) {
                    print loc( "%1 is not a valid depotname\n", $arg );
                }
                else {
                    print loc( "%1 does not contain a valid depotname\n",
                        $arg );
                }
                next;
            }

            my $target = eval { $self->arg_depotpath($arg) };
            unless ($target) {
                print $@;
                next;
            }

            my $arg_re     = qr/^\Q$arg\E/;
            my @tempnewarg =
                map {
                "/$depot$_/" =~ /$arg_re/
                    ? $self->arg_depotpath("/$depot$_")
                    : ()
                } SVN::Mirror::list_mirror( $target->{repos} );

            unless ( @tempnewarg
                || !exists $arg{$orig_arg}
                || $arg =~ m{^/[^/]*/$} )
            {
                print loc( "no mirrors found underneath %1\n", $arg );
                next;
            }
            push @newarg, @tempnewarg;
        }
        @arg = @newarg;
    }

    for my $target (@arg) {
        my $repos = $target->{repos};
        my $fs    = $repos->fs;
        my $m     = SVN::Mirror->new(
            target_path    => $target->{path},
            target         => $target->{repospath},
            repos          => $repos,
            pool           => SVN::Pool->new,
            config         => $self->{svnconfig},
            cb_copy_notify => sub { $self->copy_notify(@_) },
            lock_message   => lock_message($target),
            revprop        => ['svk:signature'],
            get_source     => 1,
            skip_to        => $self->{skip_to}
        );
        $m->init();

        if ( $self->{sync_all} ) {
            print loc( "Starting to synchronize %1\n", $target->{depotpath} );
            eval {
                $m->run( $self->{torev} );
                find_prev_copy( $fs, $fs->youngest_rev );
                1;
            };
            if ($@) {
                warn $@;
                last if ( $@ =~ /^Interrupted\.$/m );
            }
            next;
        }
        else {
            $m->run( $self->{torev} );

            # build the copy cache after sync.
            # we should do this in svn::mirror::committed, with a
            # hook provided here.
            find_prev_copy( $fs, $fs->youngest_rev );
        }
    }
    return;
}

1;

__DATA__

=head1 NAME

SVK::Command::Sync - Synchronize a mirrored depotpath

=head1 SYNOPSIS

 sync DEPOTPATH
 sync --all [DEPOTNAME|DEPOTPATH...]

=head1 OPTIONS

 -a [--all]             : synchronize all mirrored paths under
                          the DEPOTNAME/DEPOTPATH(s) provided
 -s [--skipto] REV      : start synchronization at revision REV
 -t [--torev] REV       : stop synchronization at revision REV

=head1 AUTHORS

Chia-liang Kao E<lt>clkao@clkao.orgE<gt>

=head1 COPYRIGHT

Copyright 2003-2005 by Chia-liang Kao E<lt>clkao@clkao.orgE<gt>.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

See L<http://www.perl.com/perl/misc/Artistic.html>

=cut
