clone/update: mark project changed on failure
[girocco.git] / toolbox / show-pack-counts.pl
blob2f2a58f69d55fb3eed5812cca35dab3647c2abea
1 #!/usr/bin/perl
3 # Show projects' pack and/or object counts
4 # Use "show-pack-counts.pl --sorted" to see in pack count order
5 # Use the --objects option to show pack object counts instead of pack counts
7 use strict;
8 use warnings;
9 use vars qw($VERSION);
10 BEGIN {*VERSION = \'2.0'}
11 use File::Basename;
12 use Getopt::Long;
13 use Pod::Usage;
14 use lib "__BASEDIR__";
15 use Girocco::Config;
16 use Girocco::Project;
17 use Girocco::CLIUtil;
19 my $shbin;
20 BEGIN {
21 $shbin = $Girocco::Config::posix_sh_bin;
22 defined($shbin) && $shbin ne "" or $shbin = "/bin/sh";
25 $| = 1;
26 select((select(STDERR), $| = 1)[0]);
27 exit(&main(@ARGV)||0);
29 my ($quiet, $progress);
31 sub die_usage {
32 pod2usage(-exitval => 2);
35 sub die_usage_msg {
36 warn(@_);
37 pod2usage(-exitval => 2);
40 sub do_help {
41 pod2usage(-verbose => 2, -exitval => 0);
44 sub do_version {
45 print basename($0), " version ", $VERSION, "\n";
46 exit 0;
49 sub defval {
50 return defined($_[0]) ? $_[0] : $_[1];
53 my ($lastpct, $lastts);
55 sub progress {
56 return unless $progress;
57 my ($prog, $total, $prefix) = @_;
58 $total or $total = 100;
59 defined $prefix or $prefix = "";
60 my $now = time();
61 defined $lastts or $lastts = $now + 1;
62 if ($now > $lastts) {
63 $lastts = $now;
64 defined $lastpct or $lastpct = "";
65 my $pct = int($prog * 100 / $total);
66 $pct = 100 if $pct > 100;
67 if ($pct ne $lastpct) {
68 $lastpct = $pct;
69 $prefix ne "" and $prefix .= " ";
70 printf STDERR "\r%s%3d%%", $prefix, $pct;
75 sub progress_done {
76 return unless $progress && defined($lastpct);
77 my $prefix = $_[0];
78 defined $prefix or $prefix = "";
79 $prefix ne "" and $prefix .= " ";
80 printf STDERR "\r%s100%%, done.\n", $prefix;
83 my ($packs, $packobjs, $loose, $sorton);
85 sub main {
86 local *ARGV = \@_;
87 my ($help, $version, $asc);
89 close(DATA) if fileno(DATA);
90 $progress = -t 2;
91 Getopt::Long::Configure('bundling');
92 $_ eq "--sorted" || $_ eq "--sort" and $_ .= "=" foreach @_;
93 GetOptions(
94 'help|h' => sub {do_help},
95 'version|V' => sub {do_version},
96 'quiet|q' => sub {$progress = 0},
97 'progress|P' => \$progress,
98 'objects|packed' => sub {$packobjs = 1},
99 'no-objects|no-packed' => sub {$packobjs = 0},
100 'packs' => sub {$packs = 1},
101 'no-packs' => sub {$packs = 0},
102 'loose' => sub {$loose = 1},
103 'no-loose' => sub {$loose = 0},
104 'full' => sub {$packs = $packobjs = $loose = 1;
105 $sorton = "objects"},
106 'sorted|sort:s' => \$sorton,
107 ) or die_usage;
108 $packs = 1 if !defined($packs) && !defval($packobjs,0) && !defval($loose,0);
109 $packobjs = 1 if !defined($packobjs) && defined($packs) && !$packs && !defval($loose,0);
110 $packobjs || $packs || $loose or
111 die_usage_msg "At least one of --packs, --objects or --loose must be active\n";
112 if (defined($sorton)) {
113 $sorton =~ /^\+/ and $asc = 1;
114 $sorton =~ s/^[+-]//;
115 $sorton eq "" and $sorton = $packs ? "packs" : "objects";
116 $sorton =~ tr/A-Z/a-z/;
117 if ($sorton eq "packs") {
118 $packs or die_usage_msg "Sorting on \"packs\" requires the --packs option\n";
119 } elsif ($sorton eq "loose") {
120 $loose or die_usage_msg "Sorting on \"loose\" requires the --loose option\n";
121 } elsif ($sorton eq "packed") {
122 $packobjs or die_usage_msg "Sorting on \"packed\" requires the --objects option\n";
123 } elsif ($sorton eq "objects") {
124 $loose || $packobjs or
125 die_usage_msg "Sorting on \"objects\" requires the --objects and/or --loose option\n";
126 if ($loose && !$packobjs) {
127 $sorton = "loose";
128 } elsif ($packobjs && !$loose) {
129 $sorton = "packed";
131 } else {
132 die_usage_msg "Unknown sort field: $sorton\n";
134 } else {
135 $progress = 0;
138 nice_me();
139 my @projlist = ();
140 my $reporoot = $Girocco::Config::reporoot;
141 if (@_) {
142 my %projs = map {($_ => 1)} Girocco::Project::get_full_list;
143 foreach (@_) {
144 s,/+$,,;
145 my $p = $_;
146 exists($projs{$p}) and push(@projlist, $p), next;
147 $p =~ s/\.git$//i && exists($projs{$p}) and push(@projlist, $p), next;
148 $p =~ s,^\Q$reporoot\E/,, && exists($projs{$p}) and push(@projlist, $p), next;
149 warn "Ignoring unknown project: $_\n";
151 @projlist or exit 1;
152 } else {
153 @projlist = sort({lc($a) cmp lc($b)} Girocco::Project::get_full_list);
156 my @results = ();
157 my $pmsg = "Inspecting projects:";
158 foreach my $proj (@projlist) {
159 my ($pcks, $pobjs, $lobjs) =
160 inspect_proj("$reporoot/$proj.git", $packs, $packobjs, $loose);
161 if ($sorton) {
162 push(@results, [$proj, $pcks, $pobjs, $lobjs]);
163 progress(scalar(@results), scalar(@projlist), $pmsg);
164 } else {
165 show_one($proj, $pcks, $pobjs, $lobjs);
168 if ($sorton) {
169 progress_done($pmsg);
170 my $sorter;
171 my $desc = $asc ? 1 : -1;
172 $sorton eq "packs" and $sorter = sub {
173 my $x = $$a[1] <=> $$b[1];
174 $x *= $desc;
175 $x or $x = lc($$a[0]) cmp lc($$b[0]);
176 return $x;
178 $sorton eq "packed" and $sorter = sub {
179 my $x = $$a[2] <=> $$b[2];
180 $x *= $desc;
181 $x or $x = lc($$a[0]) cmp lc($$b[0]);
182 return $x;
184 $sorton eq "loose" and $sorter = sub {
185 my $x = $$a[3] <=> $$b[3];
186 $x *= $desc;
187 $x or $x = lc($$a[0]) cmp lc($$b[0]);
188 return $x;
190 $sorton eq "objects" and $sorter = sub {
191 my $x = ($$a[2] + $$a[3]) <=> ($$b[2] + $$b[3]);
192 $x *= $desc;
193 $x or $x = lc($$a[0]) cmp lc($$b[0]);
194 return $x;
196 foreach my $info (sort($sorter @results)) {
197 show_one(@$info);
200 exit 0;
203 sub show_one {
204 my ($name, $pcks, $pobjs, $lobjs) = @_;
205 my $line = "";
206 if (defined($sorton)) {
207 $sorton eq "packs" and $line .= $pcks;
208 $sorton eq "packed" and $line .= $pobjs;
209 $sorton eq "loose" and $line .= $lobjs;
210 $sorton eq "objects" and $line .= ($pobjs + $lobjs);
211 } else {
212 if ($packs) {
213 $line .= $pcks;
214 } elsif ($packobjs && $loose) {
215 $line .= ($pobjs + $lobjs);
216 } elsif ($packobjs) {
217 $line .= $pobjs;
218 } else {
219 $line .= $lobjs;
222 $line .= "\t$name";
223 my @info = ();
224 $packs and push(@info, "packs: " . $pcks);
225 $packobjs and push(@info, "packed: " . $pobjs);
226 $loose and push(@info, "loose: " . $lobjs);
227 if (@info > 1) {
228 length($name) < 8 and $line .= "\t";
229 length($name) < 16 and $line .= "\t";
230 length($name) < 24 and $line .= "\t";
231 $line .= " \t(" . join(", ", @info) . ")";
233 print $line, "\n";
236 my ($lpbin, @lpcmd);
237 BEGIN {
238 $lpbin = $Girocco::Config::basedir . "/bin/list_packs";
239 @lpcmd = ($lpbin, "--exclude-no-idx");
242 sub count_proj_packs {
243 my $projdir = shift;
244 my $objs = shift;
245 my $pd = $projdir . "/objects/pack";
246 -d $pd or warn("no such project path (anymore) $pd\n"), return undef;
247 open LPCMD, '-|', @lpcmd, ($objs ? "--count-objects" : "--count"), $pd or
248 warn("failed to run list_packs for project dir $pd\n"), return undef;
249 my $pcount = <LPCMD>;
250 chomp($pcount);
251 close LPCMD;
252 $pcount =~ /^\d+$/ or
253 warn("list_packs produced invalid output for project dir $pd\n"), return undef;
254 return 0 + $pcount;
257 my ($octet, $octet19);
258 BEGIN {
259 $octet = '[0-9a-f][0-9a-f]';
260 $octet19 = $octet x 19;
263 sub count_proj_loose {
264 my $projdir = shift;
265 my $od = $projdir . "/objects";
266 -d $od or warn("no such project path (anymore) $od\n"), return undef;
267 open FINDCMD, '-|', $shbin, '-c', "cd " . quotemeta($od) . " && " .
268 "find -L $octet -maxdepth 1 -name '$octet19*' -print 2>/dev/null | wc -l" or
269 warn("failed to run find for project dir $od\n"), return undef;
270 my $ocount = <FINDCMD>;
271 $ocount =~ s/\s+//gs;
272 close FINDCMD;
273 $ocount =~ /^\d+$/ or
274 warn("find + wc produced invalid output for project dir $od\n"), return undef;
275 return 0 + $ocount;
278 sub inspect_proj {
279 my $path = shift;
280 my ($pcks, $pobjs, $lobjs);
281 $pcks = count_proj_packs($path) || 0 if $packs;
282 $pobjs = count_proj_packs($path, 1) || 0 if $packobjs;
283 $lobjs = count_proj_loose($path) || 0 if $loose;
284 return ($pcks, $pobjs, $lobjs);
287 __END__
289 =head1 NAME
291 show-pack-counts.pl - show projects' pack and/or object counts
293 =head1 SYNOPSIS
295 show-pack-counts.pl [<options>] [<projname>...]
297 Options:
298 -h | --help detailed instructions
299 -V | --version show version
300 -q | --quiet suppress progress messages
301 -P | --progress show progress messages (default)
302 --sorted[=[+]<field>] sort output on <field>
303 --objects count objects in packs instead of packs
304 --no-objects omit objects in packs count (default)
305 --packs always include pack count
306 --no-packs always omit pack count
307 --loose count loose objects (implies --no-packs)
308 --no-loose omit loose objects count (default)
309 --full shortcut meta option that sets
310 --packs --objects --loose --sorted=objects
312 <field> can be loose, packed, packs or objects and may be preceded by an
313 optional + to sort in ascending rather than descending order.
314 If no sorted option is given output will be shown unsorted as it's gathered.
315 The default sort field depends on the options being packs if pack counts are
316 included and objects otherwise. Using a field name of + changes the
317 default sorting order to ascending without changing the default sort field.
319 <projname>... if given, only inspect these projects
321 =head1 OPTIONS
323 =over 8
325 =item B<-h>, B<--help>
327 Print the full description of show-pack-counts.pl's options.
329 =item B<-V>, B<--version>
331 Print the version of show-pack-counts.pl.
333 =item B<-q>, B<--quiet>
335 Suppress progress messages. There are never any progress messages unless
336 sorted output is active.
338 =item B<-P>, B<--progress>
340 Show progress information to STDERR while collecting sorted output. This is
341 the default unless STDERR is not a TTY.
343 =item B<--sorted[=>I<<fieldE<gt>>B<]>
345 Collect all the output first and then show it in sorted order.
347 Possible I<<fieldE<gt>> names are:
349 =over 8
351 =item B<[+]loose>
353 Sort by number of loose objects (requires B<--loose> option too)
355 =item B<[+]packed>
357 Sort by total number of objects in packs (requires B<--objects> option too)
359 =item B<[+]packs>
361 Sort by total number of packs (requires possibly implied B<--packs> option)
363 =item B<[+]<objects>
365 Sort by total objects counted (requires B<--objects> and/or B<--loose>)
367 =item B<[+]<packs>
369 Sort by total number of packs (requires possibly implied B<--packs> option)
371 =item B<+>
373 Keep the default sort field but sort in ascending order rather than descending
374 order. When prefixed to one of the other sort field names, sort on that field
375 but in ascending rather than descending order.
377 =back
379 If the B<--sorted=[+]<field>> option is given more than once then later options
380 override earlier ones (there is no multi-field sorting). This can be used to
381 change the sorting order used with B<--full>.
383 If B<--sorted> is used (or B<--sorted=+>) the sort field is chosen based on
384 what information is being collected. If packs are being counted the sort will
385 be on the number of packs (B<--sorted=packs>).
387 If packs are not being counted the sort will be on the total number of objects
388 in each project being either objects in packs, loose objects or the sum of
389 both objects in packs and loose objects depending on what options have been
390 given (B<--sorted=objects>).
392 =item B<--objects>
394 Instead of counting packs, count objects in them. Implies B<--no-packs>
395 unless an explicit B<--packs> is given too.
397 =item B<--no-objects>
399 Omit the count of objects in packs. Included for completeness. Reactivates
400 implicit B<--packs> if given.
402 =item B<--packs>
404 Count the number of packs present. This is implied by default but may be
405 given explicitly to always include the packs count.
407 =item B<--no-packs>
409 Omit the count of packs. At least one other counting option must be specified
410 with this (either B<--objects> or B<--loose>).
412 =item B<--loose>
414 Count loose objects.
416 =item B<--no-loose>
418 Do not count loose objects.
420 =item B<--full>
422 A shortcut meta option that behaves the same as replacing it with B<--packs>
423 B<--objects> B<--loose> B<--sorted=objects> at that point.
425 =item I<<projnameE<gt>>
427 If no project names are specified then I<all> projects are processed.
429 If one or more project names are specified then only those projects are
430 processed. Specifying non-existent projects produces a warning for them,
431 but the rest of the projects specified will still be processed.
433 Each B<projname> may be either a full absolute path starting with
434 $Girocco::Config::reporoot or just the project name part with or without
435 a trailing C<.git>.
437 Any explicitly specified projects that do exist but are not known to
438 Girocco will be skipped (with a warning).
440 =back
442 =head1 DESCRIPTION
444 Count the number of packs, objects contained in packs and/or loose objects
445 and show the results.
447 Output can be sorted and the projects inspected can be limited.
449 =cut