Staging
v0.5.1
swh:1:snp:c5feb7ee9221a3820c8879e85e8a18470c0b3afa
Raw File
Tip revision: 181785487ecd48c7a3760ca31a6a245aabb2cbc8 authored by Junio C Hamano on 22 January 2022, 03:05:07 UTC
What's cooking (2022/01 #06)
Tip revision: 1817854
worklog
#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Long;
use Time::Local;

################################################################

sub seconds_in_a_week () {
	return 7 * 24 * 3600;
}

# Convert seconds from epoch to datestring and dow
sub seconds_to_date {
	my $time = shift;
	my @time = localtime($time);
	return (sprintf("%04d-%02d-%02d",
			$time[5]+1900, $time[4]+1, $time[3]),
		$time[6]);
}

sub date_to_seconds {
	my $datestring = shift;
	my ($year, $mon, $mday) = ($datestring =~ /^(\d{4})-(\d{2})-(\d{2})$/);
	unless (defined $year && defined $mon && defined $mday &&
		1 <= $mon && $mon <= 12 &&
		1 <= $mday && $mday <= 31) {
		die "Bad datestring specification: $datestring";
	}
	return timelocal(0, 0, 0, $mday, $mon - 1, $year - 1900);
}

sub next_date {
	my $datestring = shift;
	my $time = date_to_seconds($datestring);
	return seconds_to_date($time + 3600 * 36);
}

sub prev_date {
	my $datestring = shift;
	my $time = date_to_seconds($datestring);
	return seconds_to_date($time - 3600 * 12);
}

sub beginning_of_reporting_week {
	my ($datestring, $bow) = @_;
	my ($date, $dow) = seconds_to_date(date_to_seconds($datestring));
	do {
		($date, $dow) = prev_date($date);
	} while ($dow != $bow);
	return $date;
}

sub bow {
	my ($week, $bow) = @_;
	my $time;
	if (!defined $week) {
		$time = time;
	} elsif ($week =~ /^\d+$/) {
		$time = time - seconds_in_a_week * $week;
	} else {
		$time = date_to_seconds($week);
	}
	my ($datestring, $dow) = seconds_to_date($time);
	return beginning_of_reporting_week($datestring, $bow);
}

sub date_within {
	my ($date, $bottom, $top) = @_;
	return ($bottom le $date && $date le $top);
}

################################################################

my $verbose = 0;
my $quiet = 0;
my $reporting_date;
my $weeks;
my $bow = 0;
my $bottom_date;

if (!GetOptions(
	     "verbose!" => \$verbose,
	     "quiet!" => \$quiet,
	     "date=s" => \$reporting_date,
	     "weeks=i" => \$weeks,
	     "bow=i" => \$bow,
    )) {
	print STDERR "$0 [-v|-q] [-d date] [-w weeks] [-b bow]\n";
	exit 1;
}

if ($verbose && $quiet) {
	print STDERR "Which? Verbose, or Quiet?\n";
	exit 1;
}

if (!defined $reporting_date) {
	($reporting_date, my $dow) = seconds_to_date(time);
	while ($dow != $bow) {
		($reporting_date, $dow) = next_date($reporting_date);
	}
}

$bottom_date = beginning_of_reporting_week($reporting_date, $bow);

if (!defined $weeks || $weeks < 0) {
	$weeks = 0;
}

for (my $i = 0; $i < $weeks; $i++) {
	for (my $j = 0; $j < 7; $j++) {
		($bottom_date, undef) = prev_date($bottom_date);
	}
}
($bottom_date, undef) = next_date($bottom_date);

my $cull_old = "--since=" . date_to_seconds($bottom_date);

sub plural {
	my ($number, $singular, $plural) = @_;
	return ($number == 1) ? "$number $singular": "$number $plural";
}

sub fmt_join {
	my ($leader, $limit, $joiner, @ary) = @_;
	my @result = ();
	my $width = 0;
	my $wj = length($joiner);
	my $wl = length($leader);

	for my $item (@ary) {
		my $need_joiner;
		if ($width == 0) {
			$width += $wl;
			push @result, $leader;
			$need_joiner = 0;
		} else {
			$need_joiner = 1;
		}
		my $len = length($item);
		if (($need_joiner ? $wj : 0) + $len + $width < $limit) {
			if ($need_joiner) {
				$width += $wj;
				push @result, $joiner;
			}
			$width += $len;
			push @result, $item;
		} else {
			if ($width) {
				push @result, "\n";
				$width = 0;
			}
			$width += $wl;
			push @result, $leader;
			$width += $len;
			push @result, $item;
		}
	}
	push @result, "\n" unless ($result[-1] eq "\n");
	return join("", @result);
}

################################################################
# Collection

# Map sha1 to patch information [$sha1, $author, $subject]
# or merge information [$sha1, undef, $branch].
my %patch;

# $dates{"YYYY-MM-DD"} exists iff something happened
my %dates;

# List of $sha1 of patches applied, grouped by date
my %patch_by_date;

# List of $sha1 of merges, grouped by date
my %merge_by_branch_date;

# List of tags, grouped by date
my %tag_by_date;

# List of integration branches.
my @integrate = (['master', ', to include in the next release'],
		 ['next', ' for public testing'],
		 ['maint', ', to include in the maintenance release'],
);

# Collect individial patch application
open I, "-|", ("git", "log",
	       "--pretty=%ci %H %an <%ae>\001%s",
	       $cull_old, "--glob=refs/heads",
	       "--no-merges") or die;

while (<I>) {
	my ($date, $sha1, $rest) = /^([-0-9]+) [:0-9]+ [-+][0-9]{4} ([0-9a-f]+) (.*)$/;
	next unless date_within($date, $bottom_date, $reporting_date);

	$patch_by_date{$date} ||= [];
	push @{$patch_by_date{$date}}, $sha1;
	my ($name, $subject) = split(/\001/, $rest, 2);
	$patch{$sha1} = [$sha1, $name, $subject];
	$dates{$date}++;
}
close (I) or die;

for my $branch (map { $_->[0] } @integrate) {
	open I, "-|", ("git", "log", "--pretty=%ci %H %s",
		       $cull_old,
		       "--first-parent",
		       "--merges",
		       $branch) or die;
	while (<I>) {
		my ($date, $sha1, $rest) = /^([-0-9]+) [:0-9]+ [-+][0-9]{4} ([0-9a-f]+) (.*)$/;
		next unless date_within($date, $bottom_date, $reporting_date);
		my $msg = $rest;
		$msg =~ s/^Merge branch //;
		$msg =~ s/ into \Q$branch\E$//;
		$msg =~ s/^'(.*)'$/$1/;

		next if (grep { $_ eq $msg } map { $_->[0] } @integrate);

		$merge_by_branch_date{$branch} ||= {};
		$merge_by_branch_date{$branch}{$date} ||= [];
		push @{$merge_by_branch_date{$branch}{$date}}, $sha1;
		$patch{$sha1} = [$sha1, undef, $msg];
		$dates{$date}++;
	}
	close (I) or die;
}

open I, "-|", ("git", "for-each-ref",
	       "--format=%(refname:short) %(taggerdate:iso)",
	       "refs/tags") or die;
while (<I>) {
	my ($tagname, $tagdate) = /^(\S+) ([-0-9]+) [:0-9]+ [-+][0-9]{4}$/;
	
	if (!defined $tagdate || 
	    !date_within($tagdate, $bottom_date, $reporting_date)) {
		next;
	}
	$dates{$tagdate}++;
	$tag_by_date{$tagdate} ||= [];
	push @{$tag_by_date{$tagdate}}, $tagname;
}

################################################################
# Summarize

my $sep = ""; 
my @dates = sort keys %dates;

sub day_summary {
	my ($date, $total_names, $total_merges, $total_patches, $total_tags) = @_;
	return if (!exists $dates{$date});

	print "$sep$date\n" if (!$quiet);
	if (exists $tag_by_date{$date}) {
		for my $tagname (@{$tag_by_date{$date}}) {
			$$total_tags++;
			print "Tagged $tagname.\n" if (!$quiet);
		}
	}

	if (exists $patch_by_date{$date}) {
		my $count = scalar @{$patch_by_date{$date}};
		my %names = ();
		for my $patch (map { $patch{$_} } (@{$patch_by_date{$date}})) {
			my $name = $patch->[1];
			$names{$name}++;
			$total_names->{$name}++;
		}
		my $people = scalar @{[keys %names]};
		$$total_patches += $count;

		$count = plural($count, "patch", "patches");
		$people = plural($people, "person", "people");
		print "Queued $count from $people.\n" if (!$quiet);
		if ($verbose) {
			for my $patch (map { $patch{$_} } @{$patch_by_date{$date}}) {
				print "  $patch->[2]\n";
			}
		}
	}

	for my $branch_data (@integrate) {
		my ($branch, $purpose) = @{$branch_data};
		next unless (exists $merge_by_branch_date{$branch}{$date});
		my $merges = $merge_by_branch_date{$branch}{$date};
		my $count = scalar @$merges;
		next unless $count;

		$total_merges->{$branch} ||= 0;
		$total_merges->{$branch} += $count;
		$count = plural($count, "topic", "topics");
		print "Merged $count to '$branch' branch$purpose.\n" if (!$quiet);
		if ($verbose) {
			my @pieces = map { $patch{$_}->[2] . "," } @$merges;
			$pieces[-1] =~ s/,$/./;
			print fmt_join("  ", 72, " ", @pieces);
		}
	}
	$sep = "\n" if (!$quiet);
}

sub range_summary {
	my ($range, $bottom, $date, $total_n, $total_m, $total_p, $total_t) = @_;
	(my $last_date, undef) = prev_date($date);

	print "$sep$range $bottom..$last_date\n";

	if ($total_t) {
		my $count = plural($total_t, "release", "releases");
		print "Tagged $count.\n";
	}
	if ($total_p) {
		my $people = plural(scalar @{[keys %{$total_n}]}, "person", "people");
		my$count = plural($total_p, "patch", "patches");
		print "Queued $count from $people.\n";
	}
	for my $branch_data (@integrate) {
		my ($branch, $purpose) = @{$branch_data};
		next unless $total_m->{$branch};
		my $count = plural($total_m->{$branch}, "merge", "merges");
		print "Made $count to '$branch' branch$purpose.\n";
	}
	$sep = "\n";
}

sub weekly_summary {
	my ($bottom, $total_names, $total_merges,
	    $total_patches, $total_tags) = @_;
	my $date = $bottom;
	my $shown = 0;

	my ($total_p, $total_t, %total_n, %total_m) = (0, 0);
	for (my $i = 0; $i < 7; $i++) {
		day_summary($date, \%total_n, \%total_m,
			    \$total_p, \$total_t);
		($date, undef) = next_date($date);
	}
	for my $name (keys %total_n) {
		$total_names->{$name}++;
		$shown++;
	}
	for my $merge (keys %total_m) {
		$total_merges->{$merge}++;
		$shown++;
	}
	$$total_patches += $total_p;
	$$total_tags += $total_t;
	if ($shown) {
		range_summary("Week of", $bottom, $date,
			      \%total_n, \%total_m, $total_p, $total_t);
	}
	return $date;
}

my %total_names;
my %total_merges;
my $total_patches = 0;
my $total_tags = 0;

my $date;
for ($date = $bottom_date; $date le $reporting_date; ) {
	$date = weekly_summary($date, \%total_names, \%total_merges,
			       \$total_patches, \$total_tags);
}

if ($weeks) {
	range_summary("Between", $bottom_date, $date,
		      \%total_names, \%total_merges,
		      $total_patches, $total_tags);
}
back to top