The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Test::Code::TidyAll::Git;

use Capture::Tiny qw(capture_stdout capture_stderr capture);
use Code::TidyAll::Git::Util qw(git_files_to_commit git_modified_files);
use Code::TidyAll::Util qw(pushd tempdir_simple);
use Code::TidyAll;
use IPC::System::Simple qw(capturex run);
use Path::Tiny qw(path);
use Test::Class::Most parent => 'Code::TidyAll::Test::Class';

my ( $precommit_hook_template, $prereceive_hook_template, $tidyall_ini_template );

sub test_git : Tests {
    my ($self) = @_;

    $self->require_executable('git');

    my $temp_dir  = tempdir_simple;
    my $work_dir  = $temp_dir->child('work');
    my $hooks_dir = $work_dir->child(qw( .git hooks ));
    my $output;

    my $committed = sub {
        like( capturex( 'git', 'status' ), qr/nothing to commit/, "committed" );
    };
    my $uncommitted = sub {
        unlike( capturex( 'git', 'status' ), qr/nothing to commit/, "committed" );
    };

    my $pushed = sub {
        unlike( capturex( 'git', 'status' ), qr/Your branch is ahead/, "pushed" );
    };
    my $unpushed = sub {
        like( capturex( 'git', 'status' ), qr/Your branch is ahead/, "unpushed" );
    };

    my $lib_dirs = join q{ }, map { path($_)->realpath } qw( lib t/lib );

    # Create the repo
    #
    run( "git", "init", $work_dir );
    ok( -d $_, "$_ exists" ) for ( $work_dir, $hooks_dir );
    my $pushd = pushd($work_dir);

    # Add tidyall.ini and .gitignore
    #
    $work_dir->child('tidyall.ini')->spew( sprintf($tidyall_ini_template) );
    $work_dir->child('.gitignore')->spew('.tidyall.d');
    run( "git", "add", "tidyall.ini", ".gitignore" );
    run( "git", "commit", "-q", "-m", "added", "tidyall.ini", ".gitignore" );

    # Add foo.txt, which needs tidying
    #
    $work_dir->child('foo.txt')->spew("abc\n");
    cmp_deeply( [ git_files_to_commit($work_dir) ], [], "no files to commit" );

    # git add foo.txt and make sure it is now in uncommitted list
    #
    run(qw( git add foo.txt ));
    cmp_deeply(
        [ map { $_->stringify } git_files_to_commit($work_dir) ],
        [ $work_dir->child('foo.txt')->stringify ], "one file to commit"
    );

    # Add pre-commit hook
    #
    my $precommit_hook_file = $hooks_dir->child('pre-commit');
    my $precommit_hook = sprintf( $precommit_hook_template, $lib_dirs );
    $precommit_hook_file->spew($precommit_hook);
    $precommit_hook_file->chmod(0755);

    # Try to commit, make sure we get error
    #
    $output = capture_stderr { system( "git", "commit", "-m", "changed", "-a" ) };
    like( $output, qr/1 file did not pass tidyall check/, "1 file did not pass tidyall check" );
    like( $output, qr/needs tidying/, "needs tidying" );
    $uncommitted->();

    # Fix file and commit successfully
    #
    $work_dir->child('foo.txt')->spew("ABC\n");
    $output = capture_stderr { run( "git", "commit", "-m", "changed", "-a" ) };
    like( $output, qr/\[checked\] foo\.txt/, "checked foo.txt" );
    $committed->();

    $work_dir->child('bar.txt')->spew("ABC");
    run( "git", "add", "bar.txt" );
    run( "git", "commit", "-q", "-m", "bar.txt" );

    $work_dir->child('bar.txt')->spew("def");
    cmp_deeply( [ git_files_to_commit($work_dir) ], [], "no files to commit" );
    cmp_deeply(
        [ map { $_->stringify } git_modified_files($work_dir) ],
        ["$work_dir/bar.txt"],
        "one file was modified"
    );

    # Create a bare shared repo, then a clone of that
    #
    my $shared_dir = $temp_dir->child('shared');
    my $clone_dir  = $temp_dir->child('clone');
    run( "git", "clone", "-q", "--bare", $work_dir, $shared_dir );
    run( "git", "clone", "-q", $shared_dir, $clone_dir );
    chdir($clone_dir);
    $committed->();

    # Add prereceive hook to shared repo
    #
    my $prereceive_hook_file = $shared_dir->child(qw( hooks pre-receive ));
    my $prereceive_hook = sprintf( $prereceive_hook_template, $lib_dirs );
    $prereceive_hook_file->spew($prereceive_hook);
    $prereceive_hook_file->chmod(0775);

    # Unfix file and commit
    #
    $clone_dir->child('foo.txt')->spew("def\n");
    run( "git", "commit", "-m", "changed", "-a" );
    $committed->();

    # Try to push, make sure we get error back
    #
    $unpushed->();
    $output = capture_stderr { system( "git", "push" ) };
    like( $output, qr/master -> master/,                  "master -> master" );
    like( $output, qr/1 file did not pass tidyall check/, "1 file did not pass tidyall check" );
    like( $output, qr/needs tidying/,                     "needs tidying" );
    $unpushed->();

    # Fix file and push successfully
    #
    $clone_dir->child('foo.txt')->spew("DEF\n");
    $output = capture_stderr { run( "git", "commit", "-m", "changed", "-a" ) };
    $committed->();
    $output = capture_stderr { system( "git", "push" ) };
    like( $output, qr/master -> master/, "master -> master" );
    $pushed->();

    # Unfix file and commit
    #
    $clone_dir->child('foo.txt')->spew("def\n");
    run( "git", "commit", "-m", "changed", "-a" );
    $committed->();

    # Try #1: make sure we get error back
    #
    $unpushed->();
    $output = capture_stderr { system( "git", "push" ) };
    like( $output, qr/needs tidying/, "needs tidying" );
    $unpushed->();

    # Try #2: make sure we get error and repeat notification back
    #
    $unpushed->();
    $output = capture_stderr { system( "git", "push" ) };
    like( $output, qr/needs tidying/, "needs tidying" );
    like( $output, qr/Identical push seen 2 times/, "Identical push seen 2 times" );
    $unpushed->();

}

$precommit_hook_template = '#!' . $^X . "\n" . <<'EOF';
use lib qw(%s);
use Code::TidyAll::Git::Precommit;
use strict;
use warnings;

Code::TidyAll::Git::Precommit->check(
    tidyall_options => { verbose => 1 }
);
EOF

$prereceive_hook_template = '#!' . $^X . "\n" . <<'EOF';
use lib qw(%s);
use Code::TidyAll::Git::Prereceive;
use strict;
use warnings;

Code::TidyAll::Git::Prereceive->check();
EOF

$tidyall_ini_template = <<'EOF';
[+Code::TidyAll::Test::Plugin::UpperText]
select = **/*.txt
EOF

1;