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

use lib $ENV{GL_LIBDIR};
use Gitolite::Easy;

=for admins

BUNDLE SUPPORT

    (1) For each repo in gitolite.conf for which you want bundle support (or
        '@all', if you wish), add the following line:

            option bundle       =   1

        Or you can say:

            option bundle.ttl   =   <number>

        A bundle file that is more than <number> seconds old (default value
        86400, i.e., 1 day) is recreated on the next bundle request.  Increase
        this if your repo is not terribly active.

        Note: a bundle file is also deleted and recreated if it contains a ref
        that was then either deleted or rewound in the repo.  This is checked
        on every invocation.

    (2) Add 'rsync' to the ENABLE list in the rc file

=cut

=for usage
rsync helper for gitolite

BUNDLE SUPPORT

    Admins: see src/commands/rsync for setup instructions

    Users:
        rsync git@host:repo.bundle .
            # downloads a file called "<basename of repo>.bundle"; repeat as
            # needed till the whole thing is downloaded
        git clone repo.bundle repo
        cd repo
        git remote set-url origin git@host:repo
        git fetch origin    # and maybe git pull, etc. to freshen the clone

    NOTE on options to the rsync command: you are only allowed to use the
    "-v", "-n", "-q", and "-P" options.

=cut

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

# rsync driver program.  Several things can be done later, but for now it
# drives just the 'bundle' transfer.

if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^rsync --server --sender (?:-[vn]*(?:e\d*\.\w*)? )?\. (\S+)\.bundle$/ ) {

    my $repo = $1;
    $repo =~ s/\.git$//;

    # all errors have the same message to avoid leaking info
    can_read($repo) or _die "you are not authorised";
    my %config = config( $repo, "gitolite-options.bundle" ) or _die "you are not authorised";

    my $ttl = $config{'gitolite-options.bundle.ttl'} || 86400;    # in seconds (default 1 day)

    my $bundle = bundle_create( $repo, $ttl );

    $ENV{SSH_ORIGINAL_COMMAND} =~ s( \S+\.bundle)( $bundle);
    trace( 1, "rsync bundle", $ENV{SSH_ORIGINAL_COMMAND} );
    Gitolite::Common::_system( split ' ', $ENV{SSH_ORIGINAL_COMMAND} );
    exit 0;
}

_warn "Sorry, you are only allowed to use the '-v', '-n', '-q', and '-P' options.";
usage();

# ----------------------------------------------------------------------
# helpers
# ----------------------------------------------------------------------

sub bundle_create {
    my ( $repo, $ttl ) = @_;
    my $bundle = "$repo.bundle";
    $bundle =~ s(.*/)();
    my $recreate = 0;

    my ( %b, %r );
    if ( -f $bundle ) {
        %b = map { chomp; reverse split; } `git ls-remote --heads --tags $bundle`;
        %r = map { chomp; reverse split; } `git ls-remote --heads --tags .`;

        for my $ref ( sort keys %b ) {

            my $mtime = ( stat $bundle )[9];
            if ( time() - $mtime > $ttl ) {
                trace( 1, "bundle too old" );
                $recreate++;
                last;
            }

            if ( not $r{$ref} ) {
                trace( 1, "ref '$ref' deleted in repo" );
                $recreate++;
                last;
            }

            if ( $r{$ref} eq $b{$ref} ) {
                # same on both sides; ignore
                delete $r{$ref};
                delete $b{$ref};
                next;
            }

            `git rev-list --count --left-right $b{$ref}...$r{$ref}` =~ /^(\d+)\s+(\d+)$/ or _die "git too old";
            if ($1) {
                trace( 1, "ref '$ref' rewound in repo" );
                $recreate++;
                last;
            }

        }

    } else {
        trace( 1, "no bundle found" );
        $recreate++;
    }

    return $bundle if not $recreate;

    trace( 1, "creating bundle for '$repo'" );
    -f $bundle and ( unlink $bundle or die "a horrible death" );
    system("git bundle create $bundle --branches --tags >&2");

    return $bundle;
}

sub trace {
    Gitolite::Common::trace(@_);
}
