#!/usr/bin/perl
use strict;
use warnings;

my $tid;

BEGIN {
    $tid = $ENV{GL_TID} || 0;
    delete $ENV{GL_TID};
}

use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;
use Gitolite::Conf::Load;

=for usage
Usage 1:    gitolite mirror push <slave> <repo>
Usage 2:    ssh git@master-server mirror push <slave> <repo>

Forces a push of one repo to one slave.

Usage 1 is directly on the master server.  Nothing is checked; if the slave
accepts it, the push happens, even if the slave is not in any slaves
option.  This is how you do delayed or lagged pushes to servers that do not
need real-time updates or have bandwidth/connectivity issues.

Usage 2 can be initiated by *any* user who has *any* gitolite access to the
master server, but it checks that the slave is in one of the slaves options
before doing the push.

MIRROR STATUS: To find the status of the last mirror push to any slave, run
the same command except with 'status' instead of 'push'.  With usage 1, you
can use the special name "all" to get the status of all slaves for the given
repo.  (Admins wishing to find the status of all slaves for ALL repos will
have to script it using the output of "gitolite list-phy-repos".)

SERVER LIST: 'gitolite mirror list master <reponame>' and 'gitolite mirror
list slaves <reponame>' will show you the name of the master server, and list
the slave servers, for the repo.  They only work on the server command line
(any server), but not remotely (from a normal user).
=cut

usage() if not @ARGV or $ARGV[0] eq '-h';

_die "HOSTNAME not set" if not $rc{HOSTNAME};

my ( $cmd, $host, $repo ) = @ARGV;
usage() if not $repo;

if ( $cmd eq 'push' ) {
    valid_slave( $host, $repo ) if exists $ENV{GL_USER};
    # will die if host not in slaves for repo

    trace( 1, "TID=$tid host=$host repo=$repo", "gitolite mirror push started" );
    _chdir( $rc{GL_REPO_BASE} );
    _chdir("$repo.git");

    if ( -f "gl-creator" ) {
        # try to propagate the wild repo, including creator name and gl-perms
        my $creator = `cat gl-creator`; chomp($creator);
        trace( 1, `cat gl-perms 2>/dev/null | ssh $host CREATOR=$creator perms -c \\'$repo\\' 2>/dev/null` );
    }

    my $errors = 0;
    my $glss = '';
    for (`git push --mirror $host:$repo 2>&1`) {
        $errors = 1 if $?;
        print STDERR "$_" if -t STDERR or exists $ENV{GL_USER};
        $glss .= $_;
        chomp;
        if (/FATAL/) {
            $errors = 1;
            gl_log( 'mirror', $_ );
        } else {
            trace( 1, "mirror: $_" );
        }
    }
    # save the mirror push status for this slave if the word 'fatal' is found,
    # else remove the status file.  We don't store "success" output messages;
    # you can always get those from the log files if you really need them.
    if ( $glss =~ /fatal/i ) {
        my $glss_prefix = Gitolite::Common::gen_ts() . "\t$ENV{GL_TID}\t";
        $glss =~ s/^/$glss_prefix/gm;
        _print("gl-slave-$host.status", $glss);
    } else {
        unlink "gl-slave-$host.status";
    }

    exit $errors;
} elsif ($cmd eq 'status') {
    valid_slave( $host, $repo ) if exists $ENV{GL_USER};
    # will die if host not in slaves for repo

    _chdir( $rc{GL_REPO_BASE} );
    _chdir("$repo.git");

    $host = '*' if $host eq 'all';
    map { print_status($_) } sort glob("gl-slave-$host.status");
} else {
    # strictly speaking, we could allow some of the possible commands remotely
    # also, at least for admins.  However, these commands are mainly intended
    # for server-side scripting so I don't care.
    usage() if $ENV{GL_USER};

    server_side_commands(@ARGV);
}

# ----------------------------------------------------------------------

sub valid_slave {
    my ( $host, $repo ) = @_;
    _die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;

    my %list = repo_slaves($repo);
    _die "'$host' not a valid slave for '$repo'" unless $list{$host};
}

sub repo_slaves {
    my $repo = shift;

    my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.slaves.*" );
    my %list = map { $_ => 1 } map { split } values %$ref;

    return %list;
}

sub repo_master {
    my $repo = shift;

    my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.master\$" );
    my @list = map { split } values %$ref;
    _die "'$repo' seems to have more than one master" if @list > 1;

    return $list[0] || '';
}

sub print_status {
    my $file = shift;
    return unless -f $file;
    my $slave = $1 if $file =~ /^gl-slave-(.+)\.status$/;
    print "----------\n";
    print "WARNING: previous mirror push to host '$slave' failed, status is:\n";
    print slurp($file);
    print "----------\n";
}

# ----------------------------------------------------------------------
# server side commands.  Very little error checking.
#   gitolite mirror list master <repo>
#   gitolite mirror list slaves <repo>

sub server_side_commands {
    if ( $cmd eq 'list' ) {
        if ( $host eq 'master' ) {
            say repo_master($repo);
        } elsif ( $host eq 'slaves' ) {
            my %list = repo_slaves($repo);
            say join( " ", sort keys %list );
        } else {
            _die "gitolite mirror list master|slaves <reponame>";
        }
    } else {
        _die "invalid command";
    }
}
