]>
Commit | Line | Data |
---|---|---|
d1d05d1d JH |
1 | #!/usr/bin/perl |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | use Getopt::Long; | |
6 | use Time::Local; | |
7 | ||
9015711e JH |
8 | ################################################################ |
9 | ||
d1d05d1d JH |
10 | sub seconds_in_a_week () { |
11 | return 7 * 24 * 3600; | |
12 | } | |
13 | ||
9015711e JH |
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 | } | |
d1d05d1d | 22 | |
9015711e JH |
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 | } | |
d1d05d1d | 54 | |
9015711e JH |
55 | sub bow { |
56 | my ($week, $bow) = @_; | |
57 | my $time; | |
d1d05d1d JH |
58 | if (!defined $week) { |
59 | $time = time; | |
60 | } elsif ($week =~ /^\d+$/) { | |
61 | $time = time - seconds_in_a_week * $week; | |
62 | } else { | |
9015711e | 63 | $time = date_to_seconds($week); |
d1d05d1d | 64 | } |
9015711e JH |
65 | my ($datestring, $dow) = seconds_to_date($time); |
66 | return beginning_of_reporting_week($datestring, $bow); | |
67 | } | |
d1d05d1d | 68 | |
9015711e JH |
69 | sub date_within { |
70 | my ($date, $bottom, $top) = @_; | |
71 | return ($bottom le $date && $date le $top); | |
d1d05d1d JH |
72 | } |
73 | ||
9015711e JH |
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 | ||
d1d05d1d JH |
83 | if (!GetOptions( |
84 | "verbose!" => \$verbose, | |
9015711e JH |
85 | "quiet!" => \$quiet, |
86 | "date=s" => \$reporting_date, | |
87 | "weeks=i" => \$weeks, | |
d1d05d1d JH |
88 | "bow=i" => \$bow, |
89 | )) { | |
9015711e JH |
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"; | |
d1d05d1d JH |
96 | exit 1; |
97 | } | |
98 | ||
9015711e JH |
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; | |
d1d05d1d | 110 | } |
9015711e JH |
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); | |
d1d05d1d JH |
115 | } |
116 | } | |
9015711e | 117 | ($bottom_date, undef) = next_date($bottom_date); |
d1d05d1d | 118 | |
9015711e | 119 | my $cull_old = "--since=" . date_to_seconds($bottom_date); |
d1d05d1d JH |
120 | |
121 | sub plural { | |
122 | my ($number, $singular, $plural) = @_; | |
123 | return ($number == 1) ? "$number $singular": "$number $plural"; | |
124 | } | |
125 | ||
126 | sub fmt_join { | |
127 | my ($leader, $limit, $joiner, @ary) = @_; | |
128 | my @result = (); | |
129 | my $width = 0; | |
130 | my $wj = length($joiner); | |
131 | my $wl = length($leader); | |
132 | ||
133 | for my $item (@ary) { | |
134 | my $need_joiner; | |
135 | if ($width == 0) { | |
136 | $width += $wl; | |
137 | push @result, $leader; | |
138 | $need_joiner = 0; | |
139 | } else { | |
140 | $need_joiner = 1; | |
141 | } | |
142 | my $len = length($item); | |
143 | if (($need_joiner ? $wj : 0) + $len + $width < $limit) { | |
144 | if ($need_joiner) { | |
145 | $width += $wj; | |
146 | push @result, $joiner; | |
147 | } | |
148 | $width += $len; | |
149 | push @result, $item; | |
150 | } else { | |
151 | if ($width) { | |
152 | push @result, "\n"; | |
153 | $width = 0; | |
154 | } | |
155 | $width += $wl; | |
156 | push @result, $leader; | |
157 | $width += $len; | |
158 | push @result, $item; | |
159 | } | |
160 | } | |
161 | push @result, "\n" unless ($result[-1] eq "\n"); | |
162 | return join("", @result); | |
163 | } | |
164 | ||
9015711e JH |
165 | ################################################################ |
166 | # Collection | |
167 | ||
168 | # Map sha1 to patch information [$sha1, $author, $subject] | |
169 | # or merge information [$sha1, undef, $branch]. | |
d1d05d1d | 170 | my %patch; |
9015711e JH |
171 | |
172 | # $dates{"YYYY-MM-DD"} exists iff something happened | |
d1d05d1d | 173 | my %dates; |
9015711e JH |
174 | |
175 | # List of $sha1 of patches applied, grouped by date | |
d1d05d1d | 176 | my %patch_by_date; |
9015711e JH |
177 | |
178 | # List of $sha1 of merges, grouped by date | |
d1d05d1d | 179 | my %merge_by_branch_date; |
9015711e JH |
180 | |
181 | # List of tags, grouped by date | |
182 | my %tag_by_date; | |
183 | ||
184 | # List of integration branches. | |
d1d05d1d JH |
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 | ||
9015711e JH |
190 | # Collect individial patch application |
191 | open I, "-|", ("git", "log", | |
192 | "--pretty=%ci %H %an <%ae>\001%s", | |
d1d05d1d JH |
193 | $cull_old, "--glob=refs/heads", |
194 | "--no-merges") or die; | |
9015711e | 195 | |
d1d05d1d JH |
196 | while (<I>) { |
197 | my ($date, $sha1, $rest) = /^([-0-9]+) [:0-9]+ [-+][0-9]{4} ([0-9a-f]+) (.*)$/; | |
9015711e JH |
198 | next unless date_within($date, $bottom_date, $reporting_date); |
199 | ||
d1d05d1d JH |
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]; | |
204 | $dates{$date}++; | |
205 | } | |
206 | close (I) or die; | |
207 | ||
208 | for my $branch (map { $_->[0] } @integrate) { | |
209 | open I, "-|", ("git", "log", "--pretty=%ci %H %s", | |
210 | $cull_old, | |
211 | "--first-parent", | |
212 | "--merges", | |
213 | $branch) or die; | |
214 | while (<I>) { | |
215 | my ($date, $sha1, $rest) = /^([-0-9]+) [:0-9]+ [-+][0-9]{4} ([0-9a-f]+) (.*)$/; | |
9015711e | 216 | next unless date_within($date, $bottom_date, $reporting_date); |
d1d05d1d JH |
217 | my $msg = $rest; |
218 | $msg =~ s/^Merge branch //; | |
219 | $msg =~ s/ into \Q$branch\E$//; | |
220 | $msg =~ s/^'(.*)'$/$1/; | |
221 | ||
222 | next if (grep { $_ eq $msg } map { $_->[0] } @integrate); | |
223 | ||
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]; | |
228 | $dates{$date}++; | |
229 | } | |
230 | close (I) or die; | |
231 | } | |
232 | ||
9015711e JH |
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 | ||
d1d05d1d | 251 | my $sep = ""; |
d1d05d1d JH |
252 | my @dates = sort keys %dates; |
253 | ||
9015711e JH |
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++; | |
ca2a4aa4 | 262 | print "Tagged $tagname.\n" if (!$quiet); |
9015711e JH |
263 | } |
264 | } | |
265 | ||
d1d05d1d JH |
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}++; | |
9015711e | 272 | $total_names->{$name}++; |
d1d05d1d JH |
273 | } |
274 | my $people = scalar @{[keys %names]}; | |
9015711e | 275 | $$total_patches += $count; |
d1d05d1d JH |
276 | |
277 | $count = plural($count, "patch", "patches"); | |
278 | $people = plural($people, "person", "people"); | |
43990219 | 279 | print "Queued $count from $people.\n" if (!$quiet); |
d1d05d1d JH |
280 | if ($verbose) { |
281 | for my $patch (map { $patch{$_} } @{$patch_by_date{$date}}) { | |
9015711e | 282 | print " $patch->[2]\n"; |
d1d05d1d JH |
283 | } |
284 | } | |
285 | } | |
9015711e | 286 | |
d1d05d1d JH |
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; | |
292 | next unless $count; | |
293 | ||
9015711e JH |
294 | $total_merges->{$branch} ||= 0; |
295 | $total_merges->{$branch} += $count; | |
d1d05d1d | 296 | $count = plural($count, "topic", "topics"); |
43990219 | 297 | print "Merged $count to '$branch' branch$purpose.\n" if (!$quiet); |
d1d05d1d | 298 | if ($verbose) { |
9015711e JH |
299 | my @pieces = map { $patch{$_}->[2] . "," } @$merges; |
300 | $pieces[-1] =~ s/,$/./; | |
301 | print fmt_join(" ", 72, " ", @pieces); | |
d1d05d1d JH |
302 | } |
303 | } | |
9015711e | 304 | $sep = "\n" if (!$quiet); |
d1d05d1d JH |
305 | } |
306 | ||
9015711e JH |
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); | |
d1d05d1d | 310 | |
9015711e | 311 | print "$sep$range $bottom..$last_date\n"; |
d1d05d1d | 312 | |
9015711e JH |
313 | if ($total_t) { |
314 | my $count = plural($total_t, "release", "releases"); | |
43990219 | 315 | print "Tagged $count.\n"; |
9015711e JH |
316 | } |
317 | if ($total_p) { | |
318 | my $people = plural(scalar @{[keys %{$total_n}]}, "person", "people"); | |
319 | my$count = plural($total_p, "patch", "patches"); | |
43990219 | 320 | print "Queued $count from $people.\n"; |
9015711e | 321 | } |
d1d05d1d JH |
322 | for my $branch_data (@integrate) { |
323 | my ($branch, $purpose) = @{$branch_data}; | |
9015711e JH |
324 | next unless $total_m->{$branch}; |
325 | my $count = plural($total_m->{$branch}, "merge", "merges"); | |
43990219 | 326 | print "Made $count to '$branch' branch$purpose.\n"; |
9015711e JH |
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++; | |
d1d05d1d | 346 | } |
9015711e JH |
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); | |
d1d05d1d | 375 | } |