8 ################################################################
10 sub seconds_in_a_week
() {
14 # Convert seconds from epoch to datestring and dow
17 my @time = localtime($time);
18 return (sprintf("%04d-%02d-%02d",
19 $time[5]+1900, $time[4]+1, $time[3]),
24 my $datestring = shift;
25 my ($year, $mon, $mday) = ($datestring =~ /^(\d{4})-(\d{2})-(\d{2})$/);
26 unless (defined $year && defined $mon && defined $mday &&
27 1 <= $mon && $mon <= 12 &&
28 1 <= $mday && $mday <= 31) {
29 die "Bad datestring specification: $datestring";
31 return timelocal
(0, 0, 0, $mday, $mon - 1, $year - 1900);
35 my $datestring = shift;
36 my $time = date_to_seconds
($datestring);
37 return seconds_to_date
($time + 3600 * 36);
41 my $datestring = shift;
42 my $time = date_to_seconds
($datestring);
43 return seconds_to_date
($time - 3600 * 12);
46 sub beginning_of_reporting_week
{
47 my ($datestring, $bow) = @_;
48 my ($date, $dow) = seconds_to_date
(date_to_seconds
($datestring));
50 ($date, $dow) = prev_date
($date);
51 } while ($dow != $bow);
56 my ($week, $bow) = @_;
60 } elsif ($week =~ /^\d+$/) {
61 $time = time - seconds_in_a_week
* $week;
63 $time = date_to_seconds
($week);
65 my ($datestring, $dow) = seconds_to_date
($time);
66 return beginning_of_reporting_week
($datestring, $bow);
70 my ($date, $bottom, $top) = @_;
71 return ($bottom le $date && $date le $top);
74 ################################################################
84 "verbose!" => \
$verbose,
86 "date=s" => \
$reporting_date,
90 print STDERR
"$0 [-v|-q] [-d date] [-w weeks] [-b bow]\n";
94 if ($verbose && $quiet) {
95 print STDERR
"Which? Verbose, or Quiet?\n";
99 if (!defined $reporting_date) {
100 ($reporting_date, my $dow) = seconds_to_date
(time);
101 while ($dow != $bow) {
102 ($reporting_date, $dow) = next_date
($reporting_date);
106 $bottom_date = beginning_of_reporting_week
($reporting_date, $bow);
108 if (!defined $weeks || $weeks < 0) {
112 for (my $i = 0; $i < $weeks; $i++) {
113 for (my $j = 0; $j < 7; $j++) {
114 ($bottom_date, undef) = prev_date
($bottom_date);
117 ($bottom_date, undef) = next_date
($bottom_date);
119 my $cull_old = "--since=" . date_to_seconds
($bottom_date);
122 my ($number, $singular, $plural) = @_;
123 return ($number == 1) ?
"$number $singular": "$number $plural";
127 my ($leader, $limit, $joiner, @ary) = @_;
130 my $wj = length($joiner);
131 my $wl = length($leader);
133 for my $item (@ary) {
137 push @result, $leader;
142 my $len = length($item);
143 if (($need_joiner ?
$wj : 0) + $len + $width < $limit) {
146 push @result, $joiner;
156 push @result, $leader;
161 push @result, "\n" unless ($result[-1] eq "\n");
162 return join("", @result);
165 ################################################################
168 # Map sha1 to patch information [$sha1, $author, $subject]
169 # or merge information [$sha1, undef, $branch].
172 # $dates{"YYYY-MM-DD"} exists iff something happened
175 # List of $sha1 of patches applied, grouped by date
178 # List of $sha1 of merges, grouped by date
179 my %merge_by_branch_date;
181 # List of tags, grouped by date
184 # List of integration branches.
185 my @integrate = (['master', ', to include in the next release'],
186 ['next', ' for public testing'],
187 ['maint', ', to include in the maintenance release'],
190 # Collect individial patch application
191 open I
, "-|", ("git", "log",
192 "--pretty=%ci %H %an <%ae>\001%s",
193 $cull_old, "--glob=refs/heads",
194 "--no-merges") or die;
197 my ($date, $sha1, $rest) = /^([-0-9]+) [:0-9]+ [-+][0-9]{4} ([0-9a-f]+) (.*)$/;
198 next unless date_within
($date, $bottom_date, $reporting_date);
200 $patch_by_date{$date} ||= [];
201 push @
{$patch_by_date{$date}}, $sha1;
202 my ($name, $subject) = split(/\001/, $rest, 2);
203 $patch{$sha1} = [$sha1, $name, $subject];
208 for my $branch (map { $_->[0] } @integrate) {
209 open I
, "-|", ("git", "log", "--pretty=%ci %H %s",
215 my ($date, $sha1, $rest) = /^([-0-9]+) [:0-9]+ [-+][0-9]{4} ([0-9a-f]+) (.*)$/;
216 next unless date_within
($date, $bottom_date, $reporting_date);
218 $msg =~ s/^Merge branch //;
219 $msg =~ s/ into \Q$branch\E$//;
220 $msg =~ s/^'(.*)'$/$1/;
222 next if (grep { $_ eq $msg } map { $_->[0] } @integrate);
224 $merge_by_branch_date{$branch} ||= {};
225 $merge_by_branch_date{$branch}{$date} ||= [];
226 push @
{$merge_by_branch_date{$branch}{$date}}, $sha1;
227 $patch{$sha1} = [$sha1, undef, $msg];
233 open I
, "-|", ("git", "for-each-ref",
234 "--format=%(refname:short) %(taggerdate:iso)",
237 my ($tagname, $tagdate) = /^(\S+) ([-0-9]+) [:0-9]+ [-+][0-9]{4}$/;
239 if (!defined $tagdate ||
240 !date_within
($tagdate, $bottom_date, $reporting_date)) {
244 $tag_by_date{$tagdate} ||= [];
245 push @
{$tag_by_date{$tagdate}}, $tagname;
248 ################################################################
252 my @dates = sort keys %dates;
255 my ($date, $total_names, $total_merges, $total_patches, $total_tags) = @_;
256 return if (!exists $dates{$date});
258 print "$sep$date\n" if (!$quiet);
259 if (exists $tag_by_date{$date}) {
260 for my $tagname (@
{$tag_by_date{$date}}) {
262 print " Tagged $tagname.\n" if (!$quiet);
266 if (exists $patch_by_date{$date}) {
267 my $count = scalar @
{$patch_by_date{$date}};
269 for my $patch (map { $patch{$_} } (@
{$patch_by_date{$date}})) {
270 my $name = $patch->[1];
272 $total_names->{$name}++;
274 my $people = scalar @
{[keys %names]};
275 $$total_patches += $count;
277 $count = plural
($count, "patch", "patches");
278 $people = plural
($people, "person", "people");
279 print " Queued $count from $people.\n" if (!$quiet);
281 for my $patch (map { $patch{$_} } @
{$patch_by_date{$date}}) {
282 print " $patch->[2]\n";
287 for my $branch_data (@integrate) {
288 my ($branch, $purpose) = @
{$branch_data};
289 next unless (exists $merge_by_branch_date{$branch}{$date});
290 my $merges = $merge_by_branch_date{$branch}{$date};
291 my $count = scalar @
$merges;
294 $total_merges->{$branch} ||= 0;
295 $total_merges->{$branch} += $count;
296 $count = plural
($count, "topic", "topics");
297 print " Merged $count to '$branch' branch$purpose.\n" if (!$quiet);
299 my @pieces = map { $patch{$_}->[2] . "," } @
$merges;
300 $pieces[-1] =~ s/,$/./;
301 print fmt_join
(" ", 72, " ", @pieces);
304 $sep = "\n" if (!$quiet);
308 my ($range, $bottom, $date, $total_n, $total_m, $total_p, $total_t) = @_;
309 (my $last_date, undef) = prev_date
($date);
311 print "$sep$range $bottom..$last_date\n";
314 my $count = plural
($total_t, "release", "releases");
315 print " Tagged $count.\n";
318 my $people = plural
(scalar @
{[keys %{$total_n}]}, "person", "people");
319 my$count = plural
($total_p, "patch", "patches");
320 print " Queued $count from $people.\n";
322 for my $branch_data (@integrate) {
323 my ($branch, $purpose) = @
{$branch_data};
324 next unless $total_m->{$branch};
325 my $count = plural
($total_m->{$branch}, "merge", "merges");
326 print " Made $count to '$branch' branch$purpose.\n";
332 my ($bottom, $total_names, $total_merges,
333 $total_patches, $total_tags) = @_;
337 my ($total_p, $total_t, %total_n, %total_m) = (0, 0);
338 for (my $i = 0; $i < 7; $i++) {
339 day_summary
($date, \
%total_n, \
%total_m,
340 \
$total_p, \
$total_t);
341 ($date, undef) = next_date
($date);
343 for my $name (keys %total_n) {
344 $total_names->{$name}++;
347 for my $merge (keys %total_m) {
348 $total_merges->{$merge}++;
351 $$total_patches += $total_p;
352 $$total_tags += $total_t;
354 range_summary
("Week of", $bottom, $date,
355 \
%total_n, \
%total_m, $total_p, $total_t);
362 my $total_patches = 0;
366 for ($date = $bottom_date; $date le $reporting_date; ) {
367 $date = weekly_summary
($date, \
%total_names, \
%total_merges,
368 \
$total_patches, \
$total_tags);
372 range_summary
("Between", $bottom_date, $date,
373 \
%total_names, \
%total_merges,
374 $total_patches, $total_tags);