#!/usr/local/bin/perl
# $Id: /bazaar/RTx-Foundry/sbin/foundry-syncdata 6011 2004-11-23T13:07:10.530188Z autrijus  $

#
# exec myself, take care the PID file
#
if (my $interval = shift) {
    while (my $pid = fork) {
        sleep $interval;
        waitpid($pid, 0);
    }
}

# inhibit l10n
use Locale::Maketext::Lexicon;
BEGIN { $Locale::Maketext::Lexicon::VERSION = 0 }

use strict;
use FindBin;
use DBI;
use SVK;
use URI;
use File::Spec;
use File::Copy;
use File::Path;
use Proc::PID_File;

die "SVN/Perl too old, need 1.1.0" unless SVN::Core->VERSION ge '1.1.0';

eval {

my $tmp = File::Spec->tmpdir.'/foundry';
mkpath([$tmp], 0, 0755) unless -d $tmp;
chmod(0777, $tmp);
exit if hold_pid_file("$tmp/syncdata.lock");

eval { require "/usr/local/etc/foundry.conf" }                                  || eval { require "$FindBin::Bin/../etc/foundry.conf" }
|| eval { require "$FindBin::Bin/../etc/Foundry/foundry.conf" }
|| die $@;

my ($svn_base) = grep { defined and -d } (
    $ENV{SVNROOT}, 
    '/home/svn',
    '/home/svn-repositories',
) or die "Cannot find SVNROOT";
my ($cvs_base) = grep { defined and -d } (
    $ENV{CVSROOT},
    '/home/cvs',
    '/usr/depot/cvsrepo'
) or die "Cannot find CVSROOT";

my $svn_user = $ENV{WWWUSER} || 'www';
my $svn_group = $ENV{WWWGROUP} || 'www';
my $svn_uid = (getpwnam($svn_user))[2] or die "Cannot find user $svn_user";
my $svn_gid = (getgrnam($svn_user))[2] or die "Cannot find user $svn_user";

$svn_base .= "/foundry-repos";
$cvs_base .= "/foundry-repos";

my $rt_dsn = $ENV{RT_DSN} || join(';',
    "dbi:$ENV{DB_TYPE}:dbname=".($ENV{DB_NAME}||='rt3'),
    map { $ENV{"DB_$_"} ? qq(\L$_\E=$ENV{"DB_$_"}) : () }
	qw(SID HOST PORT REQUIRESSL),
);
my $cvs_user = $ENV{CVSUSER} || 'cvs';
my $cvs_group = $ENV{CVSGROUP} || 'cvs';
my $cvs_uid = (getpwnam($cvs_user))[2] or warn "Cannot find user $cvs_user";
my $dbh = DBI->connect($rt_dsn, $ENV{DB_DBA_USER}, $ENV{DB_DBA_PASSWORD}) or exit;

if (!-d "$svn_base/.snapshot") {
    mkpath(["$svn_base/.snapshot"], 0, 0755);
    system(qw( chown -R ), "$svn_user:$svn_group", $svn_base);
    system(qw( chown -R ), "$svn_user:$svn_group", "$svn_base/.snapshot");
}

my $commitcheck = "$FindBin::Bin/foundry-cvs-commitcheck";
my $start_commit = "$FindBin::Bin/foundry-svn-start-commit";
my $pre_revprop_change = "$FindBin::Bin/foundry-svn-pre-revprop-change";
my $post_commit = "$FindBin::Bin/foundry-svn-post-commit";

#
# this block build the CVS Repostiory, run only once 
#  XXX should it be set in the setup procedure
#
if ($cvs_uid and not -d $cvs_base) {
    mkpath([$cvs_base], 0, 0755);
    system(qw( cvs -d ), $cvs_base, 'init');

    my $_commitcheck = "$cvs_base/CVSROOT/commitcheck";
    my $_commitinfo = "$cvs_base/CVSROOT/commitinfo";
    open my $fh, ">", $_commitcheck or die $!;
    print $fh "#!/bin/sh\n";
    print $fh "exec $commitcheck \$\@\n";
    close $fh;
    chmod(0755, $_commitcheck);
    open $fh, ">>", $_commitinfo or die $!;
    print $fh "ALL     \$CVSROOT/CVSROOT/commitcheck\n";
    close $fh;
    system(qw( chown -R ), "$cvs_user:$cvs_group", $cvs_base);
}

#
# The following block get user id from database
#
my @users = @{ $dbh->selectcol_arrayref(q{
    SELECT Users.Name
      FROM Users, GroupMembers
     WHERE Users.Password != '*NO-PASSWORD*'
       AND Users.Id = GroupMembers.MemberId
  GROUP BY Users.Name;
}) || [] };

foreach my $name (@users) {
    next if defined getpwnam($name);
    system(
	'pw',
	useradd => $name,
	-h	=> '-',
	-s	=> '/bin/sh',
	-d	=> $cvs_base,
	-u	=> $cvs_uid,
	-g	=> $cvs_group,
	-o	=> ()
    );
}

#
# The following block get Project Id and VCS state from database
#
my @projects = @{ $dbh->selectcol_arrayref(q{
    SELECT TicketCustomFieldValues.Content
      FROM TicketCustomFieldValues, Queues, CustomFields
     WHERE TicketCustomFieldValues.Ticket = Queues.DefaultDueIn
       AND Queues.Disabled = 0
       AND TicketCustomFieldValues.CustomField = CustomFields.Id
       AND CustomFields.Name = 'UnixName'
  GROUP BY TicketCustomFieldValues.Content
}) || [] };

my %vcs = @{ $dbh->selectcol_arrayref(q{
    SELECT UnixName.Content, VCS.Content
      FROM TicketCustomFieldValues UnixName,
           TicketCustomFieldValues VCS,
           CustomFields UnixName_CF,
           CustomFields VCS_CF
     WHERE UnixName.Ticket = VCS.Ticket
       AND UnixName_CF.Name = 'UnixName'
       AND UnixName.CustomField = UnixName_CF.id
       AND VCS_CF.Name = 'VCS'
       AND VCS.CustomField = VCS_CF.id
}, { Columns => [1, 2] }) || [] };

my %remote = @{ $dbh->selectcol_arrayref(q{
    SELECT UnixName.Content, RemoteVCS.Content
      FROM TicketCustomFieldValues UnixName,
           TicketCustomFieldValues RemoteVCS,
           CustomFields UnixName_CF,
           CustomFields RemoteVCS_CF
     WHERE UnixName.Ticket = RemoteVCS.Ticket
       AND UnixName_CF.Name = 'UnixName'
       AND UnixName.CustomField = UnixName_CF.id
       AND RemoteVCS_CF.Name = 'RemoteVCS'
       AND RemoteVCS.CustomField = RemoteVCS_CF.id
}, { Columns => [1, 2] }) || [] };

$dbh->disconnect;

chdir $tmp;

#
# The following block setup base SVK Repositories
#

my %new;
my $svk = SVK->new(
    xd => SVK::XD->new(
        depotmap => {
            map {
		$new{"$svn_base/$_"}++ unless -d "$svn_base/$_";
                ($_ => "$svn_base/$_")
            } grep {
                $vcs{$_} ne 'none'
            } @projects
        },
        checkout => Data::Hierarchy->new,
    ),
);
{
    no warnings 'redefine';
    local *SVK::Util::get_prompt = sub { 'y' };
    $svk->depotmap('--init');
}

foreach my $svn_name (sort keys %new) {
    system(qw( chown -R ), "$svn_user:$svn_group", $svn_name);
    chmod(0755, $svn_name);
}

my %sync;

#
# The following block setup mirrors
#
foreach my $name (@projects) {
    my $cvs_name = "$cvs_base/$name";
    my $svn_name = "$svn_base/$name";
    my $vcs_type = $vcs{$name} || 'cvs';

    if ($vcs_type eq 'cvs') {
	if (!-d $cvs_name) {
	    mkpath([$cvs_name], 0, 0777);
	    system(qw( chown -R ), "$cvs_user:$cvs_group", $cvs_name);
	}
        set_mirror($name => "cvs:/$cvs_base:$name/...");
        next;
    }

    # Not using CVS: Deny reading from the CVS repository
    chmod(0, $cvs_name) if -d $cvs_name;

    if ($vcs_type eq 'vcp') {
        set_mirror($name => $remote{$name});
        next;
    }
    elsif ($vcs_type eq 'svn') {
        set_mirror($name => '');
        next;
    }

    # Not using SVN: Deny reading from the SVN repository
    chmod(0, $svn_name) if -d $svn_name;
}

sub set_mirror {
    my ($name, $remote) = @_;

    local($>, $)) = ($svn_uid, $svn_gid);

    $sync{$name}++ if $remote;

    my $old_remote = '';
    my $output;
    $svk->{output} = \$output;
    $svk->mirror('--list' => "/$name/");
    $old_remote = $1 if ($output =~ m!^/\Q$name\E/\s+(.*)!m);
    delete $svk->{output};

    my $svn_name = "$svn_base/$name"; 

    if (!-e "$svn_name/hooks/post-commit") {
        # symbolic link post-commit to a common post-commit handler
        symlink $post_commit => "$svn_name/hooks/post-commit";
    }

    if (!-e "$svn_name/hooks/pre-revprop-change") {
        # symbolic link post-commit to a common post-commit handler
        symlink $pre_revprop_change => "$svn_name/hooks/pre-revprop-change";
    }


    if (my $n = $remote and my $o = $old_remote) {
        # URI-wise comparison
        $n =~ s{/+$}{}; $o =~ s{/+$}{};

        return if URI::eq(
            URI->new($n),
            URI->new($o),
        );
    }
    return if $remote eq $old_remote;

    if (!$remote) {
	print "==> Removed /$name/ from mirroring $old_remote\n";
	$svk->mirror('--delete' => "/$name/");
	return;
    }

    if (-e "$svn_name/db/revs/0") {
	print "==> Moving away old /$name/\n";
	rename($svn_name => "$svn_name.".time()) or return(warn $!);

	local $svk->{xd}{depotmap} = { $name => $svn_name };
	local *SVK::Util::get_prompt = sub { 'y' };
	$svk->depotmap('--init');
    }

    print "==> Setting /$name/ to mirror $remote\n";
    $svk->mirror($remote => "/$name/");
}

#
# The following block forks and runs "svk sync" for each mirrored repos,
# taking care of not intruding on ongoing syncs
#

# --- XXX --- Use this for now --- XXX ---

local($>, $)) = ($svn_uid, $svn_gid);
$svk->sync("/$_/") foreach sort keys %sync;

=comment BROKEN

foreach my $name (sort keys %sync) {
    fork and next;
    exit if hold_pid_file("$tmp/syncdata.$name.lock");

    local($>, $)) = ($svn_uid, $svn_gid);
    $svk->sync("/$name/");
}

=cut

};

die $@ if $@;
