+sub multi_inbox ($$$) {
+ my ($self, $path, $m) = @_;
+ my $incl = $self->{lei}->{opt}->{include};
+ my $excl = $self->{lei}->{opt}->{exclude};
+
+ # assuming everything not v2 is v1, for now
+ my @v1 = sort grep(!m!.+/git/[0-9]+\.git\z!, keys %$m);
+ my @v2_epochs = sort grep(m!.+/git/[0-9]+\.git\z!, keys %$m);
+ my $v2 = {};
+
+ for (@v2_epochs) {
+ m!\A(/.+)/git/[0-9]+\.git\z! or die "BUG: $_";
+ push @{$v2->{$1}}, $_;
+ }
+ my $n = scalar(keys %$v2) + scalar(@v1);
+ my @orig = defined($incl // $excl) ? (keys %$v2, @v1) : ();
+ if (defined $incl) {
+ my $re = '(?:'.join('|', map {
+ $self->{lei}->glob2re($_) // qr/\A\Q$_\E\z/
+ } @$incl).')';
+ my @gone = delete @$v2{grep(!/$re/, keys %$v2)};
+ delete @$m{map { @$_ } @gone} and $self->{-culled_manifest} = 1;
+ delete @$m{grep(!/$re/, @v1)} and $self->{-culled_manifest} = 1;
+ @v1 = grep(/$re/, @v1);
+ }
+ if (defined $excl) {
+ my $re = '(?:'.join('|', map {
+ $self->{lei}->glob2re($_) // qr/\A\Q$_\E\z/
+ } @$excl).')';
+ my @gone = delete @$v2{grep(/$re/, keys %$v2)};
+ delete @$m{map { @$_ } @gone} and $self->{-culled_manifest} = 1;
+ delete @$m{grep(/$re/, @v1)} and $self->{-culled_manifest} = 1;
+ @v1 = grep(!/$re/, @v1);
+ }
+ my $ret; # { v1 => [ ... ], v2 => { "/$inbox_name" => [ epochs ] }}
+ $ret->{v1} = \@v1 if @v1;
+ $ret->{v2} = $v2 if keys %$v2;
+ $ret //= @orig ? "Nothing to clone, available repositories:\n\t".
+ join("\n\t", sort @orig)
+ : "Nothing available to clone\n";
+ my $path_pfx = '';
+
+ # PSGI mount prefixes and manifest.js.gz prefixes don't always align...
+ if (@v2_epochs) {
+ until (grep(m!\A\Q$$path\E/git/[0-9]+\.git\z!,
+ @v2_epochs) == @v2_epochs) {
+ $$path =~ s!\A(/[^/]+)/!/! or last;
+ $path_pfx .= $1;
+ }
+ } elsif (@v1) {
+ while (!defined($m->{$$path}) && $$path =~ s!\A(/[^/]+)/!/!) {
+ $path_pfx .= $1;
+ }
+ }
+ ($path_pfx, $n, $ret);
+}
+
+# FIXME: this gets confused by single inbox instance w/ global manifest.js.gz