#!/usr/bin/env perl
#***************************************************************************
#                                  _   _ ____  _
#  Project                     ___| | | |  _ \| |
#                             / __| | | | |_) | |
#                            | (__| |_| |  _ <| |___
#                             \___|\___/|_| \_\_____|
#
# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://curl.se/docs/copyright.html.
#
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
# copies of the Software, and permit persons to whom the Software is
# furnished to do so, under the terms of the COPYING file.
#
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
# KIND, either express or implied.
#
# SPDX-License-Identifier: curl
#
###########################################################################

# Display changes done in the repository from [tag] until now.
#
# Uses git for repo data.
# Uses docs/THANKS and RELEASE-NOTES for current status.
#
# In the git clone root, invoke 'scripts/delta [release tag]'

use strict;
use warnings;

use POSIX;
use IPC::Open3;
use Time::Piece;

sub cmd {
    my @out;
    my $as_string = shift if($_[0] eq '$');  # return as string (vs. list of lines)
    my $hideerr = shift if($_[0] eq '2>');  # 2>/dev/null
    my $pid = open3(my $in, my $out, $hideerr ? '>/dev/null' : '>&STDERR', @_);
    close $in;
    push @out, <$out>;
    waitpid($pid, 0);
    return $as_string ? join('', @out) : @out;
}

my $start = $ARGV[0] || '';

if($start eq "-h") {
    print "Usage: summary [tag]\n";
    exit;
}
elsif($start eq "") {
    my @tags = cmd('git', 'tag', '--sort=taggerdate', '--no-column', '--list', 'curl-*');
    $start = $tags[-1];  # latest tag
    chomp $start;
}

my $commits = cmd('$', 'git', 'rev-list', '--count', "$start..");
my $committers = cmd('git', 'shortlog', '-s', "$start..");
my $bcommitters = cmd('git', 'shortlog', '-s', $start);

my $acommits = cmd('$', 'git', 'rev-list', '--count', 'HEAD');
my $acommitters = cmd('git', 'shortlog', '-s', 'HEAD');

# delta from now compared to before
my $ncommitters = $acommitters - $bcommitters;

# number of contributors right now
my $acontribs = cmd('./scripts/contrithanks.sh', 'stdout');
# number when the tag was set
my $bcontribs = cmd('$', 'git', 'grep', '-h', '-c', '^[^ ]', "$start:docs/THANKS");
# delta
my $contribs = $acontribs - $bcontribs;

# number of setops:
sub setopts {
    my $mode = shift;
    my $opts = 0;
    if(open(H, $mode, @_)) {
        while(<H>) {
            if(/^  CURLOPT(|DEPRECATED)\(/ && ($_ !~ /OBSOLETE/)) {
                $opts++;
            }
        }
        close(H);
    }
    else {
        die join(' ', @_) . ": $!\n";
    }
    return $opts;
}
my $asetopts = setopts('<', 'include/curl/curl.h');
my $bsetopts = setopts('-|', 'git', 'show', "$start:include/curl/curl.h");
my $nsetopts = $asetopts - $bsetopts;

# Number of command line options:
my $aoptions = cmd('$', '2>', 'git', 'grep', '-h', '-c', '{ *"....--', 'src/tool_listhelp.c');
my $boptions = cmd('$', '2>', 'git', 'grep', '-h', '-c', '{ *"....--', "$start:src/tool_listhelp.c");
my $noptions = $aoptions - $boptions;

# current local branch
my $branch = cmd('$', '2>', 'git', 'rev-parse', '--abbrev-ref', 'HEAD');
chomp $branch;
# Number of files in git
my $afiles = cmd('git', 'ls-files');
my $deletes = cmd('2>', 'git', 'diff-tree', '--diff-filter=A', '-r', '--summary', "origin/$branch", $start);
my $creates = cmd('2>', 'git', 'diff-tree', '--diff-filter=D', '-r', '--summary', "origin/$branch", $start);

# Time since that tag
sub tagstamp {
    my $col = shift;
    foreach my $line (@_) {
        if(index($line, "$start ") == 0) {
            my @cols = split(/\|/, $line);
            return $cols[$col - 1];
        }
    }
}
my @tagged = cmd('git', 'for-each-ref', '--format=%(refname:short) | %(taggerdate:unix) | %(creatordate)', 'refs/tags/*');
my $tagged = tagstamp(2, @tagged); # Unix timestamp
my $taggednice = tagstamp(3, @tagged); # human readable time
chomp $taggednice;
my $now = POSIX::strftime("%s", localtime());
my $elapsed = $now - $tagged; # number of seconds since tag
my $total = $now - Time::Piece->strptime('19980320', '%Y%m%d')->epoch;
my $totalhttpget = $now - Time::Piece->strptime('19961111', '%Y%m%d')->epoch;

# Number of public functions in libcurl
my $apublic = cmd('git', 'grep', '^CURL_EXTERN', '--', 'include/curl');
my $bpublic = cmd('git', 'grep', '^CURL_EXTERN', $start, '--', 'include/curl');
my $public = $apublic - $bpublic;

# diffstat
my ($fileschanged, $insertions, $deletions);
my @diffstat = cmd('2>', 'git', 'diff', '--stat', "$start..");
my $diffstat = $diffstat[-1];
if($diffstat =~ /^ *(\d+) files changed, (\d+) insertions\(\+\), (\d+)/) {
    ($fileschanged, $insertions, $deletions) = ($1, $2, $3);
}

# Changes/bug-fixes currently logged
my $numchanges = 0;
my $numbugfixes = 0;
my $numcontributors = 0;
open(F, "<RELEASE-NOTES");
my $mode = 0;
while(<F>) {
    if($_ =~ /following changes:/) {
        $mode = 1;
    }
    elsif($_ =~ /following bugfixes:/) {
        $mode = 2;
    }
    elsif($_ =~ /known bugs:/) {
        $mode = 3;
    }
    elsif($_ =~ /like these:/) {
        $mode = 4;
    }
    if($_ =~ /^ o /) {
        if($mode == 1) {
            $numchanges++;
        }
        elsif($mode == 2) {
            $numbugfixes++;
        }
    }
    if(($mode == 4) && ($_ =~ /^  \((\d+) contributors/)) {
        $numcontributors = $1;
    }
}
close(F);

########################################################################
# Produce the summary

print "== Since $start $taggednice ==\n";
my $days = $elapsed / 3600 / 24;
printf "Elapsed time:                   %.1f days (total %d / %d)\n",
    $days, $total / 3600 / 24, $totalhttpget / 3600 / 24;
printf "Commits:                        %d (total %d)\n",
    $commits, $acommits;
printf "Commit authors:                 %d, %d new (total %d)\n",
    $committers, $ncommitters, $acommitters;
printf "Contributors:                   %d, %d new (total %d)\n",
    $numcontributors, $contribs, $acontribs;
printf "New public functions:           %d (total %d)\n",
    $public, $apublic;
printf "New curl_easy_setopt() options: %d (total %d)\n",
    $nsetopts, $asetopts;
printf "New command line options:       %d (total %d)\n",
    $noptions, $aoptions;
printf "Changes logged:                 %d\n", $numchanges;
printf "Bugfixes logged:                %d (%.2f per day)\n",
    $numbugfixes, $days ? $numbugfixes / $days : $numbugfixes;
printf "Added files:                    %d (total %d)\n",
    $creates, $afiles;
printf "Deleted files:                  %d (delta: %d)\n", $deletes,
    $creates - $deletes;
print "Diffstat:$diffstat" if(!$fileschanged);
printf "Files changed:                  %d (%.2f%%)\n", $fileschanged, $fileschanged * 100 / $afiles;
printf "Lines inserted:                 %d\n", $insertions;
printf "Lines deleted:                  %d (delta: %d)\n", $deletions,
    $insertions - $deletions;
