#!/usr/bin/perl

# Created on: 2014-03-11 20:58:59
# Create by:  Ivan Wills
# $Id$
# $Revision$, $HeadURL$, $Date$
# $Revision$, $Source$, $Date$

use strict;
use warnings;
use Getopt::Long;
use Pod::Usage ();
use Data::Dumper qw/Dumper/;
use English qw/ -no_match_vars /;
use Git::Workflow;

our $VERSION = 0.5;
my ($name)   = $PROGRAM_NAME =~ m{^.*/(.*?)$}mxs;
my $workflow = Git::Workflow->new;

my %option = (
    max      => 10,
    sleep    => 60,
    pull_cmd => 'pull',
    verbose  => 0,
    man      => 0,
    help     => 0,
    VERSION  => 0,
);
my %actions = (
    show => 1,
    do   => 1,
);

main();
exit 0;

sub main {

    Getopt::Long::Configure('bundling');
    GetOptions(
        \%option,
        'all|a',
        'branch|b=s',
        'pull|p',
        'pull_cmd|pull-cmd|P=s',
        'file|f=s',
        'max|m=i',
        'once|1',
        'quiet|q',
        'remote|r=s',
        'sleep|s=i',
        'verbose|v+',
        'man',
        'help',
        'VERSION!',
    ) or Pod::Usage::pod2usage(2);

    if ( $option{'VERSION'} ) {
        print "$name Version = $VERSION\n";
        exit 1;
    }
    elsif ( $option{'man'} ) {
        Pod::Usage::pod2usage( -verbose => 2 );
    }
    elsif ( $option{'help'} ) {
        Pod::Usage::pod2usage( -verbose => 1 );
    }

    # do stuff here
    my $action = @ARGV && $actions{$ARGV[0]} ? shift @ARGV : @ARGV ? 'do' : 'show';
    my $once   = $option{once} ? -1 : 1;
    my ($last) = git_state();

    while ($once) {
        my ($id, @rest) = git_state();

        if ( !$last || $last ne $id ) {
            $once++;
            my $changes = changes($last, $id, @rest);

            if ( found($changes) ) {
                if ( $action eq 'show' ) {
                    my $time = $option{verbose} ? ' @ ' . localtime $changes->{time} : '';
                    print "$id$time\n";

                    if ( !$option{quiet} ) {
                        my $join = $option{verbose} ? "\n    " : '';
                        print "  Branches: ";
                        print $join, join +($join || ', '), keys %{ $changes->{branches} };
                        print "\n";
                        print "  Files:    ";
                        print $join, join +($join || ', '), keys %{ $changes->{files} };
                        print "\n";
                        print "  Users:    ";
                        print $join, join +($join || ', '), keys %{ $changes->{user} };
                        print "\n\n";
                    }
                }
                else {
                    $ENV{WATCH_SHA}      = $id;
                    $ENV{WATCH_USERS}    = join ',', keys %{ $changes->{user} };
                    $ENV{WATCH_EMAILS}   = join ',', keys %{ $changes->{email} };
                    $ENV{WATCH_FILES}    = join ',', keys %{ $changes->{files} };
                    $ENV{WATCH_BRANCHES} = join ',', keys %{ $changes->{branches} };
                    system @ARGV;
                }
            }
        }

        $last = $id;
        sleep $option{sleep};
    }

    return;
}

sub git_state {
    my @out;

    if ( $option{all} || $option{remote} ) {
        `git fetch 2>/dev/null`;
        @out = `git rev-list --all -$option{max}`;
    }
    else {
        `git $option{pull_cmd}` if $option{pull};
        @out = `git log --oneline -$option{max}`;
    }

    return map {/^([0-9a-f]+)\s*/; $1} @out;
}

sub found {
    my ($changes) = @_;

    if ($option{file}) {
        return 1 if grep {/$option{file}/} keys %{ $changes->{files} };
    }

    if ($option{branch}) {
        return 1 if grep {/$option{branch}/} keys %{ $changes->{branches} };
    }

    return !$option{file} && !$option{branch};
}

sub changes {
    my ($last, $newest, @ids) = @_;
    my $changes = $workflow->commit_details($newest, branches => 1, files => 1, user => 1 );

    $changes->{user}  = { $changes->{user} => 1 };
    $changes->{email} = { $changes->{email} => 1 };

    for my $id (@ids) {
        last if $id eq $last;
        my $change  = $workflow->commit_details($id, branches => 1, files => 1, user => 1 );

        $changes->{files}    = { %{$changes->{files}}, %{$change->{files}} };
        $changes->{branches} = { %{$changes->{branches}}, %{$change->{branches}} };
        $changes->{user}     = { %{$changes->{user}}, $change->{user} => 1 };
        $changes->{email}    = { %{$changes->{email}}, $change->{email} => 1 };
    }

    return $changes;
}

__DATA__

=head1 NAME

git-watch - Watch for changes in repository up-stream

=head1 VERSION

This documentation refers to git-watch version 0.5

=head1 SYNOPSIS

   git-watch show [-1|--once] [(-f|--file) file ...]
   git-watch [do] [-1|--once] [(-f|--file) file ...] [--] cmd

 SUB-COMMAND
  show          Simply show when a file
  do            Execute a shell script cmd when a change occurs

 OPTIONS:
  -1 --once     Run once then exit
  -p --pull     Before checking if anything has changed do a git pull to the
                current branch.
  -P --pull-options[=]flags
                When using --pull add these options to the pull command
  -f --file[=]regex
                Watch file any files changing that match "regex"
  -b --branch[=]regex
                Watch for any changes to branches matching "regex"
                by default looks only at local branches
  -r --remote   With --branch only look at remote branches
  -a --all      With --branch look at all branches (local and remote)
  -m --max[=]int
                Look only --max changes back in history to see what is
                happening (Default 10)
  -s --sleep[=]int
                Sleep time between fetches (devault 60s)
  -q --quiet    Suppress notifying of what files and branches have changed
  -v --verbose  Show more detailes
     --VERSION  Prints the version information
     --help     Prints this help information
     --man      Prints the full documentation for git-watch

=head1 DESCRIPTION

The C<git-watch> command allows you to run a command when something changes.
The simple option is C<show> which just shows what has changed when it changes
and nothing else, this is useful for seeing what is happening in the
repository. The the C<do> sub-command actually runs a script every time a
change is detected.

=head2 show

The output of C<show> is changed with the C<--quiet> and C<--verbose> options to
show more or less information.

=head2 do

When the C<do> sub-command runs it sets the environment variables C<$WATCH_SHA>,
C<$WATCH_FILES> and C<$WATCH_BRANCHES> with the latest commit SHA, the files
that have changed and the branches that have changed respectively. The files
and branches are comma separated for your command to inspect.

A simple example:

  git watch 'echo $WATCH_FILES'

This would just echo the files that have changed with each change.

=head1 SUBROUTINES/METHODS

=head1 DIAGNOSTICS

=head1 CONFIGURATION AND ENVIRONMENT

=head1 DEPENDENCIES

=head1 INCOMPATIBILITIES

=head1 BUGS AND LIMITATIONS

There are no known bugs in this module.

Please report problems to Ivan Wills (ivan.wills@gmail.com).

Patches are welcome.

=head1 AUTHOR

Ivan Wills - (ivan.wills@gmail.com)

=head1 LICENSE AND COPYRIGHT

Copyright (c) 2014 Ivan Wills (14 Mullion Close, Hornsby Heights, NSW Australia 2077).
All rights reserved.

This module is free software; you can redistribute it and/or modify it under
the same terms as Perl itself. See L<perlartistic>.  This program is
distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

=cut
