
=head1 DESCRIPTION

This tests the Mojolicious::Plugin::Export::Git plugin side.

=cut

use Mojo::Base -strict;
use Test::More;
use Mojo::File qw( path tempdir );
use File::Temp;
use Mojolicious;
use Mojolicious::Plugin::Export::Git;

BEGIN {
    my $git_version = Mojolicious::Plugin::Export::Git->_git_version;
    plan skip_all => 'Git not installed' unless $git_version;
    diag "Git version: $git_version";
    plan skip_all => 'Git 1.7.2 or higher required' unless $git_version >= 1.007002;
};

# Allow NO_CLEANUP envvar to prevent tempdir cleanup
$File::Temp::KEEP_ALL = $ENV{NO_CLEANUP};
*_git_run = \&Mojolicious::Plugin::Export::Git::_git_run;

subtest 'export to master branch' => sub {
    my $tmpdir = tempdir();
    diag "TMP: " . $tmpdir if $File::Temp::KEEP_ALL;

    my ( $workdir, $remotedir ) = make_repo( $tmpdir );
    my $git = Git::Repository->new( work_tree => "$workdir" );
    my $remotegit = Git::Repository->new( work_tree => "$remotedir" );

    my $app = make_app();
    $app->export->export({
        to => $workdir,
    });

    is current_branch( $git ), 'master', 'export leaves us on the branch we came from';

    ok -e $workdir->child( 'index.html' ), 'root exists';
};

subtest 'export to deploy branch' => sub {
    my $tmpdir = tempdir();
    diag "TMP: " . $tmpdir if $File::Temp::KEEP_ALL;

    my ( $workdir, $remotedir ) = make_repo( $tmpdir );
    my $git = Git::Repository->new( work_tree => "$workdir" );
    my $remotegit = Git::Repository->new( work_tree => "$remotedir" );

    my $app = make_app();
    $app->export->export({
        to => $workdir,
        branch => 'deploy',
    });

    is current_branch( $git ), 'master', 'export leaves us on the branch we came from';
    my $master_commit_id = $git->run( 'rev-parse' => 'HEAD' );

    _git_run( $git, checkout => 'deploy' );

    my $log = $git->run( log => -u => -n => 1 );
    my ( $commit_id ) = $log =~ /commit (\S+)/;
    like $log, qr{Site update}, 'commit message is correct';

    my $prev_log = $git->run( 'log' );
    unlike $prev_log, qr{$master_commit_id}, 'deploy branch is orphaned (does not contain master commit)';

    subtest 'files are correct' => sub {
        ok -e $workdir->child( 'index.html' ), 'root exists';
    };

    _git_run( $git, checkout => 'master' );

    subtest 'deploy performs git push' => sub {
        _git_run( $remotegit, checkout => 'deploy' );
        ok -e $remotedir->child( 'index.html' ), 'root exists in deploy branch';
        ok !-e $remotedir->child( 'README' ), 'deploy branch is orphan and clean';
        _git_run( $remotegit, checkout => 'master' );
    };

    subtest 'nothing to deploy bails out without commit' => sub {
        $app->export->export({
            to => $workdir,
            branch => 'deploy',
            clean => 0,
        });
        is current_branch( $git ), 'master', 'deploy leaves us on the branch we came from';
        _git_run( $git, checkout => 'deploy' );
        my $log = $git->run( log => -u => -n => 1 );
        like $log, qr/commit $commit_id/, 'no new commit created';
    };

    subtest 'nothing to deploy still pushes' => sub {
        # Add a commit to the deploy branch, but don't push it
        _git_run( $git, checkout => 'deploy' );
        $workdir->child( 'TEST_PUSH' )->spurt( 'Push!' );
        _git_run( $git, add => 'TEST_PUSH' );
        _git_run( $git, commit => '-m' => 'Add test' );

        my $log = $git->run( log => -u => -n => 1 );
        my ( $commit_id ) = $log =~ /commit (\S+)/;

        _git_run( $git, checkout => 'master' );
        $app->export->export({
            to => $workdir,
            branch => 'deploy',
            clean => 0,
        });

        is current_branch( $git ), 'master', 'deploy leaves us on the branch we came from';
        _git_run( $git, checkout => 'deploy' );
        $log = $git->run( log => -u => -n => 1 );
        like $log, qr/commit $commit_id/, 'no new commit created';

        _git_run( $remotegit, checkout => 'deploy' );
        $log = $git->run( log => -u => -n => 1 );
        my ( $remote_commit_id ) = $log =~ /commit (\S+)/;
        ok -e $remotedir->child( 'TEST_PUSH' ), 'deploy branch was pushed';
        is $remote_commit_id, $commit_id, 'local commit exists on remote branch';
        _git_run( $remotegit, checkout => 'master' );
    };
};

subtest 'deploy to subdirectory in git repo' => sub {
    my $tmpdir = tempdir();
    diag "TMP: " . $tmpdir if $File::Temp::KEEP_ALL;

    my ( $workdir, $remotedir )
        = make_repo( $tmpdir, branch => 'master', remote => 'origin' );

    $workdir->child( 'subdir' )->make_path;

    my $app = make_app();
    $app->export->export({
        to => $workdir->child( 'subdir' ),
    });

    my $remotework = $tmpdir->child( 'remote_work' );
    $remotework->make_path;

    my $git = Git::Repository->new( work_tree => "$workdir" );
    my $remotegit = Git::Repository->new( git_dir => "$remotedir", work_tree => "$remotework" );

    my $master_commit_id = $git->run( 'rev-parse' => 'HEAD' );

    _git_run( $remotegit, checkout => '-f', 'master' );
    ok -e $remotework->child( 'subdir/index.html' ), 'root exists';
};

subtest 'deploy with clean' => sub {
    my $tmpdir = tempdir();
    diag "TMP: " . $tmpdir if $File::Temp::KEEP_ALL;

    my ( $workdir, $remotedir ) = make_repo( $tmpdir );
    my $remotework = $tmpdir->child( 'remote_work' );
    $remotework->make_path;
    my $remotegit = Git::Repository->new( git_dir => "$remotedir", work_tree => "$remotework" );

    # Add some files that should be cleaned up
    $workdir->child( 'needs-cleaning.txt' )->spurt( 'Ha ha!' );
    my $workgit = Git::Repository->new( work_tree => "$workdir" );
    # Create the branch first
    _git_run( $workgit, checkout => '--orphan', 'deploy' );
    _git_run( $workgit, 'rm', '-r', '-f', '.' );
    _git_run( $workgit, add => 'needs-cleaning.txt' );
    _git_run( $workgit, commit => '-m' => 'add new file outside of site' );
    _git_run( $workgit, push => origin => 'deploy' );
    _git_run( $workgit, checkout => 'master' );

    my $app = make_app();

    subtest 'deploy without clean does not remove files' => sub {
        $app->export->export({
            to => $workdir,
            branch => 'deploy',
            clean => 0,
        });
        _git_run( $workgit, checkout => 'deploy' );
        ok -f $workdir->child( 'needs-cleaning.txt' ), 'default deploy did not remove file';
        _git_run( $workgit, checkout => 'master' );
        _git_run( $remotegit, checkout => '-f', 'deploy' );
        ok -f $remotework->child( 'needs-cleaning.txt' ), 'pushed to remote';
    };

    subtest 'deploy with clean removes files first' => sub {
        $app->export->export({
            to => $workdir,
            branch => 'deploy',
            clean => 1,
        });
        _git_run( $workgit, checkout => 'deploy' );
        ok !-f $workdir->child( 'needs-cleaning.txt' ), 'default deploy remove files';
        _git_run( $workgit, checkout => 'master' );
        _git_run( $remotegit, checkout => '-f', 'deploy' );
        ok !-f $remotework->child( 'needs-cleaning.txt' ), 'pushed to remote';
    };

    subtest 'clean dies when content/deploy are sharing the same branch' => sub {
        $workdir->child( qw( static.txt ) )->spurt( 'Foo' );
        eval {
            $app->export->export({
                to => $workdir,
                branch => 'master',
                clean => 1,
            });
        };
        ok $@, 'clean on current branch dies';
        ok -e $workdir->child( qw( static.txt ) ),
            'content file /static.txt is not destroyed';
    };

};

subtest 'errors' => sub {
    subtest 'not in a git repo' => sub {
        my $tmpdir = tempdir;

        my ( undef, undef ) = make_repo( $tmpdir );
        my $not_git_path = tempdir;

        my $app = make_app();
        eval {
            $app->export->export({
                to => $not_git_path,
            });
        };
        like $@, qr{Export path "$not_git_path" is not in a git repository\n},
            'export dies when not a git repo';
    };

    subtest 'deploy from branch not yet born' => sub {
        my $tmpdir = tempdir();
        diag "TMP: " . $tmpdir if $File::Temp::KEEP_ALL;
        my $work_git = make_git( $tmpdir );

        my $app = make_app();
        eval {
            $app->export->export({
                to => $tmpdir,
            });
        };
        like $@, qr{Repository has no branches\. Please create a commit before deploying\n},
            'export dies when git branch has no commits';
    };

};

done_testing;

sub make_app {
    my ( %opt ) = @_;
    my $app = Mojolicious->new;
    $app->plugin( 'Export::Git' => \%opt );

    unshift @{$app->renderer->classes}, 'main';
    unshift @{$app->static->classes}, 'main';
    $app->static->warmup;

    $app->routes->get( '/' )->name( 'index' );
    $app->routes->get( '/docs' )->name( 'docs' );
    $app->routes->get( '/docs/more' )->name( 'docs/more' )->to( cb => sub {
        my ( $c ) = @_;
        $c->res->headers->content_type( 'text/html;charset=UTF-8' );
        $c->render( inline => "<h1>\x{2603}</h1>" );
    });
    $app->routes->get( '/about' )->name( 'about' );
    $app->routes->get( '/redirect' )->to( cb => sub {
        shift->redirect_to( 'http://example.com' )
    });

    return $app;
}

sub make_git {
    my ( $dir, %args ) = @_;

    Git::Repository->run( "init", ( $args{bare} ? ( '--bare' ) : () ), "$dir" );

    my $git = Git::Repository->new( work_tree => "$dir" );

    # Set some config so Git knows who we are (and doesn't complain)
    _git_run( $git, config => 'user.name' => 'Test User' );
    _git_run( $git, config => 'user.email' => 'test@example.com' );

    return $git;
}

sub make_repo {
    my ( $tmpdir, %args ) = @_;

    $args{ remote } ||= "origin";
    $args{ branch } ||= "gh-pages";

    my $workdir = $tmpdir->child( 'workdir' )->make_path;
    my $remotedir = $tmpdir->child( 'remotedir' )->make_path;

    my $remotegit = make_git( $remotedir, bare => 1 );
    my $workgit = make_git( $workdir );
    _git_run( $workgit, remote => add => $args{remote} => "$remotedir" );

    # Create a commit on the remote to make sure we create an orphan
    # branch locally (this commit should not be on the branch created)
    $remotedir->child( "README" )->spurt( "Repository readme, not in export branch" );
    _git_run( $remotegit, add => 'README' );

    _git_run( $remotegit, commit => -m => 'Initial commit' );
    _git_run( $workgit, pull => $args{remote} => 'master' );

    return ( $workdir, $remotedir );
}

sub current_branch {
    my ( $git ) = @_;
    my @branches = map { s/^\*\s+//; $_ } grep { /^\*/ } $git->run( 'branch' );
    return $branches[0];
}

__DATA__
@@ index.html.ep
<head>
    <link rel="stylesheet" href="//cdnjs.org/bootstrap.css">
</head>
<h1>Export</h1>

@@ logo-white-2x.png (base64)
 
