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
}