Meta/worklog: summarize weekly progress

Junio C Hamano committed Apr 28, 2011 at 21:14 UTC 9015711ed28b797f53189d10ffbe2247c504302e
1 file changed +214 -58
worklog
+214 -58
@@ -5,67 +5,118 @@ use warnings;
5 use Getopt::Long;
6 use Time::Local;
7
8 +################################################################
9 +
10 sub seconds_in_a_week () {
11 return 7 * 24 * 3600;
12 }
13
12 -my $verbose = 0;
13 -my $week;
14 -my $cycles;
15 -my $bow = 0;
14 +# Convert seconds from epoch to datestring and dow
15 +sub seconds_to_date {
16 + my $time = shift;
17 + my @time = localtime($time);
18 + return (sprintf("%04d-%02d-%02d",
19 + $time[5]+1900, $time[4]+1, $time[3]),
20 + $time[6]);
21 +}
22
17 -sub bow {
18 - my ($week) = @_;
19 - my (@time, $time);
23 +sub date_to_seconds {
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";
30 + }
31 + return timelocal(0, 0, 0, $mday, $mon - 1, $year - 1900);
32 +}
33 +
34 +sub next_date {
35 + my $datestring = shift;
36 + my $time = date_to_seconds($datestring);
37 + return seconds_to_date($time + 3600 * 36);
38 +}
39 +
40 +sub prev_date {
41 + my $datestring = shift;
42 + my $time = date_to_seconds($datestring);
43 + return seconds_to_date($time - 3600 * 12);
44 +}
45 +
46 +sub beginning_of_reporting_week {
47 + my ($datestring, $bow) = @_;
48 + my ($date, $dow) = seconds_to_date(date_to_seconds($datestring));
49 + do {
50 + ($date, $dow) = prev_date($date);
51 + } while ($dow != $bow);
52 + return $date;
53 +}
54
55 +sub bow {
56 + my ($week, $bow) = @_;
57 + my $time;
58 if (!defined $week) {
59 $time = time;
60 } elsif ($week =~ /^\d+$/) {
61 $time = time - seconds_in_a_week * $week;
62 } else {
26 - my ($year, $mon, $mday) = ($week =~ /^(\d{4})-(\d{2})-(\d{2})$/);
27 - unless (defined $year && defined $mon && defined $mday &&
28 - 1 <= $mon && $mon <= 12 &&
29 - 1 <= $mday && $mday <= 31) {
30 - die "Bad week specification: $week";
31 - }
32 - $time = timelocal(0, 0, 0, $mday, $mon - 1, $year - 1900);
63 + $time = date_to_seconds($week);
64 }
65 + my ($datestring, $dow) = seconds_to_date($time);
66 + return beginning_of_reporting_week($datestring, $bow);
67 +}
68
35 - @time = localtime($time);
36 - if ($time[6] <= $bow) {
37 - $time -= seconds_in_a_week;
38 - @time = localtime($time);
39 - }
40 - $time -= $time[0] + 60 * ($time[1] + 60 * ($time[2] + 24 * ($time[6] - $bow)));
41 - return $time;
69 +sub date_within {
70 + my ($date, $bottom, $top) = @_;
71 + return ($bottom le $date && $date le $top);
72 }
73
74 +################################################################
75 +
76 +my $verbose = 0;
77 +my $quiet = 0;
78 +my $reporting_date;
79 +my $weeks;
80 +my $bow = 0;
81 +my $bottom_date;
82 +
83 if (!GetOptions(
84 "verbose!" => \$verbose,
46 - "week=s" => \$week,
47 - "cycles=i" => \$cycles,
85 + "quiet!" => \$quiet,
86 + "date=s" => \$reporting_date,
87 + "weeks=i" => \$weeks,
88 "bow=i" => \$bow,
89 )) {
50 - print STDERR "$0 [-v]\n";
90 + print STDERR "$0 [-v|-q] [-d date] [-w weeks] [-b bow]\n";
91 + exit 1;
92 +}
93 +
94 +if ($verbose && $quiet) {
95 + print STDERR "Which? Verbose, or Quiet?\n";
96 exit 1;
97 }
98
54 -if (defined $cycles && !defined $week) {
55 - $week = bow($week);
56 - $week -= ($cycles - 1) * seconds_in_a_week;
57 -} else {
58 - $week = bow($week);
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);
103 + }
104 +}
105 +
106 +$bottom_date = beginning_of_reporting_week($reporting_date, $bow);
107 +
108 +if (!defined $weeks || $weeks < 0) {
109 + $weeks = 0;
110 }
60 -if (!defined $cycles) {
61 - my $ends = time();
62 - my $clock = $week;
63 - for ($cycles = 0; $clock < $ends; $cycles++) {
64 - $clock += seconds_in_a_week;
111 +
112 +for (my $i = 0; $i < $weeks; $i++) {
113 + for (my $j = 0; $j < 7; $j++) {
114 + ($bottom_date, undef) = prev_date($bottom_date);
115 }
116 }
117 +($bottom_date, undef) = next_date($bottom_date);
118
68 -my $cull_old = "--since=" . $week;
119 +my $cull_old = "--since=" . date_to_seconds($bottom_date);
120
121 sub plural {
122 my ($number, $singular, $plural) = @_;
@@ -111,20 +162,41 @@ sub fmt_join {
162 return join("", @result);
163 }
164
165 +################################################################
166 +# Collection
167 +
168 +# Map sha1 to patch information [$sha1, $author, $subject]
169 +# or merge information [$sha1, undef, $branch].
170 my %patch;
171 +
172 +# $dates{"YYYY-MM-DD"} exists iff something happened
173 my %dates;
174 +
175 +# List of $sha1 of patches applied, grouped by date
176 my %patch_by_date;
177 +
178 +# List of $sha1 of merges, grouped by date
179 my %merge_by_branch_date;
180 +
181 +# List of tags, grouped by date
182 +my %tag_by_date;
183 +
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'],
188 );
189
123 -open I, "-|", ("git", "log", "--pretty=%ci %H %an <%ae>\001%s",
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;
195 +
196 while (<I>) {
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);
199 +
200 $patch_by_date{$date} ||= [];
201 push @{$patch_by_date{$date}}, $sha1;
202 my ($name, $subject) = split(/\001/, $rest, 2);
@@ -141,6 +213,7 @@ for my $branch (map { $_->[0] } @integrate) {
213 $branch) or die;
214 while (<I>) {
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);
217 my $msg = $rest;
218 $msg =~ s/^Merge branch //;
219 $msg =~ s/ into \Q$branch\E$//;
@@ -157,34 +230,60 @@ for my $branch (map { $_->[0] } @integrate) {
230 close (I) or die;
231 }
232
233 +open I, "-|", ("git", "for-each-ref",
234 + "--format=%(refname:short) %(taggerdate:iso)",
235 + "refs/tags") or die;
236 +while (<I>) {
237 + my ($tagname, $tagdate) = /^(\S+) ([-0-9]+) [:0-9]+ [-+][0-9]{4}$/;
238 +
239 + if (!defined $tagdate ||
240 + !date_within($tagdate, $bottom_date, $reporting_date)) {
241 + next;
242 + }
243 + $dates{$tagdate}++;
244 + $tag_by_date{$tagdate} ||= [];
245 + push @{$tag_by_date{$tagdate}}, $tagname;
246 +}
247 +
248 +################################################################
249 +# Summarize
250 +
251 my $sep = "";
161 -my %total_names = ();
162 -my %total_merges = ();
163 -my $total_patch = 0;
252 my @dates = sort keys %dates;
253
166 -for my $date (@dates) {
167 - print "$sep$date\n";
254 +sub day_summary {
255 + my ($date, $total_names, $total_merges, $total_patches, $total_tags) = @_;
256 + return if (!exists $dates{$date});
257 +
258 + print "$sep$date\n" if (!$quiet);
259 + if (exists $tag_by_date{$date}) {
260 + for my $tagname (@{$tag_by_date{$date}}) {
261 + $$total_tags++;
262 + print " Tagged $tagname.\n" if (!$quiet);
263 + }
264 + }
265 +
266 if (exists $patch_by_date{$date}) {
267 my $count = scalar @{$patch_by_date{$date}};
268 my %names = ();
269 for my $patch (map { $patch{$_} } (@{$patch_by_date{$date}})) {
270 my $name = $patch->[1];
271 $names{$name}++;
174 - $total_names{$name}++;
272 + $total_names->{$name}++;
273 }
274 my $people = scalar @{[keys %names]};
177 - $total_patch += $count;
275 + $$total_patches += $count;
276
277 $count = plural($count, "patch", "patches");
278 $people = plural($people, "person", "people");
181 - print "Queued $count from $people.\n";
279 + print " Queued $count from $people.\n" if (!$quiet);
280 if ($verbose) {
281 for my $patch (map { $patch{$_} } @{$patch_by_date{$date}}) {
184 - print " $patch->[2]\n";
282 + print " $patch->[2]\n";
283 }
284 }
285 }
286 +
287 for my $branch_data (@integrate) {
288 my ($branch, $purpose) = @{$branch_data};
289 next unless (exists $merge_by_branch_date{$branch}{$date});
@@ -192,28 +291,85 @@ for my $date (@dates) {
291 my $count = scalar @$merges;
292 next unless $count;
293
195 - $total_merges{$branch} ||= 0;
196 - $total_merges{$branch} += $count;
294 + $total_merges->{$branch} ||= 0;
295 + $total_merges->{$branch} += $count;
296 $count = plural($count, "topic", "topics");
198 - print "Merged $count to '$branch' branch$purpose.\n";
297 + print " Merged $count to '$branch' branch$purpose.\n" if (!$quiet);
298 if ($verbose) {
200 - print fmt_join(" ", 72, " ", (map { $patch{$_}->[2] } @$merges));
299 + my @pieces = map { $patch{$_}->[2] . "," } @$merges;
300 + $pieces[-1] =~ s/,$/./;
301 + print fmt_join(" ", 72, " ", @pieces);
302 }
303 }
203 - $sep = "\n";
304 + $sep = "\n" if (!$quiet);
305 }
306
206 -if (1 < @dates) {
207 - print "${sep}Between $dates[0]..$dates[-1]\n";
307 +sub range_summary {
308 + my ($range, $bottom, $date, $total_n, $total_m, $total_p, $total_t) = @_;
309 + (my $last_date, undef) = prev_date($date);
310
209 - my $people = plural(scalar @{[keys %total_names]}, "person", "people");
210 - my $count = plural($total_patch, "patch", "patches");
211 - print "Queued $count from $people.\n";
311 + print "$sep$range $bottom..$last_date\n";
312
313 + if ($total_t) {
314 + my $count = plural($total_t, "release", "releases");
315 + print " Tagged $count.\n";
316 + }
317 + if ($total_p) {
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";
321 + }
322 for my $branch_data (@integrate) {
323 my ($branch, $purpose) = @{$branch_data};
215 - next unless $total_merges{$branch};
216 - my $count = plural($total_merges{$branch}, "merge", "merges");
217 - print "Made $count to '$branch' branch$purpose.\n";
324 + next unless $total_m->{$branch};
325 + my $count = plural($total_m->{$branch}, "merge", "merges");
326 + print " Made $count to '$branch' branch$purpose.\n";
327 + }
328 + $sep = "\n";
329 +}
330 +
331 +sub weekly_summary {
332 + my ($bottom, $total_names, $total_merges,
333 + $total_patches, $total_tags) = @_;
334 + my $date = $bottom;
335 + my $shown = 0;
336 +
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);
342 + }
343 + for my $name (keys %total_n) {
344 + $total_names->{$name}++;
345 + $shown++;
346 }
347 + for my $merge (keys %total_m) {
348 + $total_merges->{$merge}++;
349 + $shown++;
350 + }
351 + $$total_patches += $total_p;
352 + $$total_tags += $total_t;
353 + if ($shown) {
354 + range_summary("Week of", $bottom, $date,
355 + \%total_n, \%total_m, $total_p, $total_t);
356 + }
357 + return $date;
358 +}
359 +
360 +my %total_names;
361 +my %total_merges;
362 +my $total_patches = 0;
363 +my $total_tags = 0;
364 +
365 +my $date;
366 +for ($date = $bottom_date; $date le $reporting_date; ) {
367 + $date = weekly_summary($date, \%total_names, \%total_merges,
368 + \$total_patches, \$total_tags);
369 +}
370 +
371 +if ($weeks) {
372 + range_summary("Between", $bottom_date, $date,
373 + \%total_names, \%total_merges,
374 + $total_patches, $total_tags);
375 }