#!/usr/bin/perl

=encoding UTF-8

=head1 NAME

is_git_synced - script to find out if the local git repo is fully synced

=head1 VERSION

Version 0.01

=head1 SYNOPSIS

is_git_synced [options] dir1 [dir2 ...]

 Options:
  
      --quiet           Script will not output anything
      --only_errors     Script will write only dirs with errors
      --help            Show this message
      --version         Show version number

Script checks every specifeid dir if it is a git repo and it has ho local
changes that are not in remote repsitory origin. Script by default will output
information about every checked dir in separate line. The exit status will
be 0 if everything is synced and 1 otherwise.

Project url: https://github.com/bessarabov/App-IsGitSynced

=head1 AUTHOR

Ivan Bessarabov, C<< <ivan@bessarabov.ru> >>

=head1 SOURCE CODE

The source code for this module is hosted on GitHub L<https://github.com/bessarabov/App-IsGitSynced>

=head1 BUGS

Please report any bugs or feature requests to C<bug-app-isgitsynced at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=App-IsGitSynced>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.

Or you can use GitHub Issues L<https://github.com/bessarabov/App-IsGitSynced/issues>

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc App::IsGitSynced


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=App-IsGitSynced>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/App-IsGitSynced>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/App-IsGitSynced>

=item * Search CPAN

L<http://search.cpan.org/dist/App-IsGitSynced/>

=back


=head1 ACKNOWLEDGEMENTS


=head1 LICENSE AND COPYRIGHT

Copyright 2012 Ivan Bessarabov.

This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.

=cut

use strict;
use warnings FATAL => 'all';
use Pod::Usage;

our $VERSION = 0.01;

# 'constants'
my $true  = 1;
my $false = '';

# global vars
my $quiet = $false;
my $only_errors = $false;
my $can_print_in_color = eval 'require Term::ANSIColor; 1';

if ($can_print_in_color) {
    import Term::ANSIColor ':constants';
} else {
    *RESET  = sub { };
    *RED    = sub { };
}

# subs
sub error {
    my ($message) = @_;

    if (!$quiet) {
        if ($can_print_in_color and -t STDOUT) {
            print RED();
            print "Error: $message\n";
            print RESET();
        } else {
            print "Error: $message\n";
        }
    }
}

sub is_git_repo {
    my ($path) = @_;

    `cd $path; git status 2>&1`;
    return ${^CHILD_ERROR_NATIVE} ? $false : $true;
}

sub has_unstaged_changes {
    my ($path) = @_;

    `cd $path; git diff --exit-code 2>&1`;
    return ${^CHILD_ERROR_NATIVE} ? $false : $true;
}

sub has_staged_changes {
    my ($path) = @_;

    `cd $path; git diff --cached --exit-code 2>&1`;
    return ${^CHILD_ERROR_NATIVE} ? $false : $true;
}

sub has_untracked {
    my ($path) = @_;

    my $output = `cd $path; git status --porcelain`;
    my @remotes = split(/\n/, $output);

    foreach my $line (@remotes) {
        return $true if $line =~ /^\?\?/;
    }

    return $false;
}

sub has_origin {
    my ($path) = @_;

    my $output = `cd $path; git remote`;
    my @remotes = split(/\n/, $output);

    foreach my $remote (@remotes) {
        return $true if $remote eq 'origin';
    }

    return $false;
}

# http://stackoverflow.com/questions/8830833/check-that-the-local-git-repo-has-everything-commited-and-pushed-to-master
sub has_divergences_with_origin {
    my ($path) = @_;

    my $output = `cd $path; git branch`;
    my @branches = map { s/..(.*)/$1/; $_; } split(/\n/, $output);

    foreach my $branch (@branches) {
        my $local = `cd $path; git rev-parse --verify $branch 2>&1`;
        my $origin = `cd $path; git rev-parse --verify origin/$branch 2>&1`;
        return $true if $local ne $origin;
    }

    return $false;
}

my $checks = [
    {
        text => sub { error("path '$_[0]' is a file") },
        func => sub { return -f $_[0] },
    },
    {
        text => sub { error("path '$_[0]' does not exist") },
        func => sub { return !-d $_[0] },
    },
    {
        text => sub { error("path '$_[0]' is not a git repository") },
        func => sub { return !is_git_repo($_[0]) },
    },
    {
        text => sub { error("path '$_[0]' has unstaged changes") },
        func => sub { return !has_unstaged_changes($_[0]) },
    },
    {
        text => sub { error("path '$_[0]' has staged changes") },
        func => sub { return !has_staged_changes($_[0]) },
    },
    {
        text => sub { error("path '$_[0]' has untracked files") },
        func => sub { return has_untracked($_[0]) },
    },
    {
        text => sub { error("path '$_[0]' has no remote 'origin'") },
        func => sub { return !has_origin($_[0]) },
    },
    {
        text => sub { error("path '$_[0]' has some divergences with remote 'origin'") },
        func => sub { return has_divergences_with_origin($_[0]) },
    },
];

# main
my @paths;

foreach my $argv (@ARGV) {
    if ($argv eq '--quiet') {
        $quiet = $true;
    } elsif ($argv eq '--only_errors') {
        $only_errors = $true;
    } elsif ($argv eq '--help') {
        pod2usage(); 
    } elsif ($argv eq '--version') {
        print "is_git_synced $VERSION\n";
        exit 2;
    } else {
        push @paths, $argv;
    }
}

my $was_error;

if (!@paths) {
    error("no required path specified");
    $was_error++;
}

foreach my $path (@paths) {
    my $local_error;

    CHECKS:
    foreach my $check (@{$checks}) {
        if ($check->{func}->($path)) {
            $check->{text}->($path);
            $local_error = 1;
            last CHECKS;
        }
    };

    if ($local_error) {
        $was_error++;
    } else {
        print "Success: path '$path' has no local changes and fully synced to remote\n" if (!$quiet && !$only_errors);
    }
}

exit 1 if $was_error;
