#! /usr/bin/perl

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# This script is maintained at https://github.com/openSUSE/ssob
#
# If you're in another project, this is just a copy.
# You may update it to the latest version from time to time...
#
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


use strict;

use Getopt::Long;

use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Terse = 1;
$Data::Dumper::Indent = 1;

sub usage;
sub get_branch_tags;
sub get_branch;
sub get_parent_branch;
sub get_version;

usage 0 if !@ARGV;

my @deps = qw ( .git/HEAD .git/refs/heads .git/refs/tags );

my $branch;
my $current_version;
my @tags;
my @all_tags;

my $opt_log;
my $opt_version;
my $opt_branch;
my $opt_update;
my $opt_file;

GetOptions(
  'help'          => sub { usage 0 },
  'version'       => \$opt_version,
  'branch'        => \$opt_branch,
  'update'        => \$opt_update,
  'log|changelog' => \$opt_log,
) || usage 1;

usage 1 if @ARGV > 1 || !($opt_log || $opt_version || $opt_branch);
$opt_file = @ARGV ? shift : '-';

die "no git repo\n" unless -d ".git";

if($opt_update && $opt_file ne '-' && -f($opt_file)) {
  my $ok = 1;

  my $t = (stat $opt_file)[9];

  for (@deps) {
    $ok = 0 if (stat)[9] > $t;
  }

  exit 0 if $ok;
}

@all_tags = `git tag`;
chomp @all_tags;

$branch = get_branch;
die "no branch?\n" unless $branch;

@tags = get_branch_tags;
die "no tags at all?\n" unless @tags;

if($branch ne 'master') {
  if(!grep { /^$branch\-/ } @tags) {
    $branch = get_parent_branch;
    die "sorry, can't determine branch\n" unless $branch;

    @tags = get_branch_tags;
    die "no tags at all?\n" unless @tags;
  }
}
else {
  @tags = get_branch_tags;
  die "no tags at all?\n" unless @tags;
}

if($opt_branch) {
  open my $f, ">$opt_file";
  print $f "$branch\n";
  close $f;

  exit 0;
}

$current_version = get_version;

if($opt_version) {
  open my $f, ">$opt_file";
  print $f "$current_version\n";
  close $f;

  exit 0;
}

if($branch ne 'master') {
  my ($i1, $i2, $bi);

  for (my $i = 0; $i < @tags; $i++) {
    if($tags[$i] =~ /^$branch\-(\S+)/) {
      $i2 = $i;
      $bi = $1;
      last;
    }
  }

  # print STDERR ">> $branch-$bi\n";

  warn "no tags in this branch yet\n" unless $bi;

  for (my $i = 0; $i < $i2; $i++) {
    if($tags[$i] ge $bi) {
      if($tags[$i] eq $bi) {
        $i1 = $i;
      }
      elsif($i > 0) {
        $i1 = $i - 1;
      }
      last;
    }
  }

  splice @tags, $i1, $i2 - $i1;
}

map { s/(\d+)/$1 + 0/eg } @tags;

push @tags, "HEAD";

# print Dumper(\@tags);

open F, ">$opt_file";

for (my $i = @tags - 1; $i > 0; $i--) {
  my ($date, @t2);

  my @t = `git log --pretty=medium --date=iso '$tags[$i-1]..$tags[$i]'`;

  # print "\n--- $tags[$i-1]..$tags[$i] ---\n", @t, "---\n";

  my $merge = 0;
  for (@t) {
    $merge = 1 if /^Merge: /;
    $merge = 0 if /^commit /;
    push @t2, $_ if !$merge;
  }
  @t = @t2;

  undef @t2;
  my $detail = 0;
  for (@t) {
    $detail = 1 if /^    $/;
    $detail = 2 if /^    Conflicts:$/;
    $detail = 0 if /^commit /;
    if(!$detail) {
      push @t2, $_ if $detail < 2;
    }
  }
  @t = @t2;

  # print "\n--- $tags[$i-1]..$tags[$i] ---\n", @t;

  chomp @t;
  for (@t) {
    if(/^Date:\s*(\S+)/) {
      $date = $1;
      last;
    }
  }

  # handle white space in every first line once and for all
  my $empty = 1;
  for (@t) {
    $empty = 1, $_ = "", next if $_ =~ /^\s*$/;
    next if !$empty;
    s/^\s*//;
    $empty = 0;
  }

  @t = grep { !/^(commit|Author:|Date:|Merge:|\s*$)|created.*tag/ } @t;
  if(@t) {
    # rewrite a bit to have it look more consistent
    map { s/(fate|bnc|bsc)#/$1 #/g } @t;
    map { s/(fate|bnc|bsc)\s*(\d{4})/$1 #$2/g } @t;
    map { s/\(#/(bnc #/g } @t;
    map { s/bug\s*#/bnc #/g } @t;
    map { s/feat(\.|ure)?\s*#?(\d+)/fate #$2/g } @t;
    map { s/^ {4}// } @t;
    map { s/^ {8}// } @t;
    map { s/^ +/  / } @t;
    map { s/^\s*[+\-][\-\s]*/- / } @t;
    map { s/^([^ \-])/- $1/ } @t;
    map { s/^/\t/ } @t;
    map { s/\\'/'/ } @t;

    # print "\n--- $tags[$i-1]..$tags[$i] ---\n", join("\n", @t);

    my $t = $tags[$i];
    $t = "${branch}-$t" if $branch ne 'master' && $t eq "HEAD";
    $t =~ s/HEAD/$current_version/;
    print F "$date:\t$t\n";
    print F join("\n", @t), "\n\n";
  }
}

close F;


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub usage
{
  my $err = shift;

  print <<"  usage";
Usage: git2log [OPTIONS] [FILE]
Create changelog and project version from git repo.
  --changelog   Write changelog to FILE.
  --version     Write version number to FILE.
  --branch      Write current branch to FILE.
  --update      Write changelog or version only if FILE is outdated.
  --help        Print this help text.
  usage

  exit $err;
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub get_branch_tags
{
  my @ntags;

  for (@all_tags) {
    if(/^\d/) {
      s/(\d+)/sprintf "%04d", $1/eg;
      push @ntags, $_;
    }
    elsif(s/^$branch\-//) {
      s/(\d+)/sprintf "%04d", $1/eg;
      push @ntags, "$branch-$_";
    }
  }

  return sort @ntags;
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub get_branch
{
  my $b;

  for (`git branch`) {
    if(/^\*\s+(\S+)/) {
      $b = $1;
      last;
    }
  }

  $b = "master" if $b eq '(no';

  return $b;
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub get_parent_branch
{
  my $p;

  for (`git log -g --pretty=oneline`) {
    $p = $1 if /checkout: moving from (\S+) to $branch/;
  }

  # print "parent = $p\n";

  return $p || "master";
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub get_version
{
  my $v = $tags[-1];
  $v =~ s/(\d+)/$1 + 0/eg;

  if(`git log --pretty=medium --date=iso '$v..HEAD'`) {
    $v =~ s/(\d+)$/$1 + 1/e;
  }

  $v =~ s/^$branch\-//;

  return $v;
}

