#!/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.6;
my ($name)   = $PROGRAM_NAME =~ m{^.*/(.*?)$}mxs;
my $workflow = Git::Workflow->new;

my %option = (
    max_age     => 60 * 60 * 24 * ( $ENV{GIT_WORKFLOW_MAX_AGE} || $workflow->config('workflow.max', 120) ),
    tag_prefix  => '',
    tag_postfix => '',
    verbose     => 0,
    man         => 0,
    help        => 0,
    VERSION     => 0,
);

main();
exit 0;

sub main {

    Getopt::Long::Configure('bundling');
    GetOptions(
        \%option,
        'remote|r',
        'all|a',
        'max_age|max-age|m=i',
        'min_age|min-age|n=i',
        'tag|t!',
        'tag_prefix|tag-prefix|p=s',
        'tag_postfix|tag-postfix|q=s',
        'test!',
        '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 );
    }

    # get the list of branches to look at
    my @branches = $workflow->branches($option{remote} ? 'remote' : $option{all} ? 'both' : undef );
    my ($total, $deleted) = (0, 0);
    my $max = 0;

    BRANCH:
    for my $branch (@branches) {
        # skip master branches
        next BRANCH if $branch =~ m{^ (?:[^/]+/)? master $}xms;

        # get branch details
        my $details = $workflow->commit_details($branch, branches => 1);

        # don't delete young branches even if merged
        next BRANCH if $option{min_age} && $details->{time} > time - $option{min_age} * 60 * 60 * 24;

        $max = $details->{time} if $max < $details->{time};

        if ( in_master($details) || too_old($details) ) {
            warn "deleting branch $branch\n";
            $deleted++;

            my ($remote, $name) = $branch =~ m{/} ? split m{/}, $branch, 2 : (undef, $branch);

            if ( $option{tag} ) {
                my $tag = $option{tag_prefix} . $name . $option{tag_suffix};
                $workflow->runner(qw/git tag -a -m /, "Converting '$name' to the tag '$tag'", $tag) if !$option{test};
            }

            if ( !$option{test} ) {
                if ($remote) {
                    $workflow->runner(qw/git push/, $remote, ":refs/heads/$name");
                }
                else {
                    $workflow->runner(qw/git branch -D/, "$name");
                }
            }
        }
        $total++;
    }

    warn "Deleted $deleted of $total branches\nMax = " . (int $max/60/60/24) . "\n";

    return;
}

sub in_master {
    my ($details) = @_;

    return 1 if $details->{branches}{master};

    for my $branch (keys %{ $details->{branches} }) {
        return 1 if $branch =~ m{^ (?:[^/]+/)? master$}xms;
    }

    return;
}

sub too_old {
    my ($details) = @_;

    return if !$option{max_age};

    return time - $option{max_age} * 60 * 60 * 24 > $details->{time};
}

__DATA__

=head1 NAME

git-branch-clean - Clean old branches out of the repository

=head1 VERSION

This documentation refers to git-branch-clean version 0.6

=head1 SYNOPSIS

   git-branch-clean [option]

 OPTIONS:
  -r --remote   Only remote branches (defaults to local branches)
  -a --all      All branches
  -m --max-age[=]days
                Maximum age of a branch with out changes before it is cleaned
                weather it's merged to master or not. (Default 0, no max age)
  -n --min-age[=]days
                Leave branches this number of days or new alone even if merged
                to master. (default 7 days)
  -t --tag      Create tags of the same name as the branch
  -p --tag-prefix[=]str
                When converting a branch to a tag prepend it with "str"
  -p --tag-postfix[=]str
                When converting a branch to a tag apend it with "str"
     --test     Don't actually delete branches just report on what branches
                would be deleted.

  -v --verbose  Show more detailed option
     --VERSION  Prints the version information
     --help     Prints this help information
     --man      Prints the full documentation for git-branch-clean

=head1 DESCRIPTION

C<git-branch-clean> deletes branches merged to master (but not newer than
C<--min-age> days). Optionally also deleting branches that haven't been
modified more than C<--max-age> days. When deleting branches they can be
converted to tags (C<--tag>) with optional an prefix (C<--tag-prefix>) and/or
an optional postfix (C<--tag-postfix>) added.

=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
