+sub parse_epochs ($$) {
+ my ($opt_epochs, $v2_epochs) = @_; # $epcohs "LOW..HIGH"
+ $opt_epochs // return; # undef => all epochs
+ my ($lo, $dotdot, $hi, @extra) = split(/(\.\.)/, $opt_epochs);
+ undef($lo) if ($lo // '') eq '';
+ my $re = qr/\A~?[0-9]+\z/;
+ if (@extra || (($lo // '0') !~ $re) ||
+ (($hi // '0') !~ $re) ||
+ !(grep(defined, $lo, $hi))) {
+ die <<EOM;
+--epoch=$opt_epochs not in the form of `LOW..HIGH', `LOW..', nor `..HIGH'
+EOM
+ }
+ my @n = sort { $a <=> $b } keys %$v2_epochs;
+ for (grep(defined, $lo, $hi)) {
+ if (/\A[0-9]+\z/) {
+ $_ > $n[-1] and die
+"`$_' exceeds maximum available epoch ($n[-1])\n";
+ $_ < $n[0] and die
+"`$_' is lower than minimum available epoch ($n[0])\n";
+ } elsif (/\A~([0-9]+)/) {
+ my $off = -$1 - 1;
+ $n[$off] // die "`$_' is out of range\n";
+ $_ = $n[$off];
+ } else { die "`$_' not understood\n" }
+ }
+ defined($lo) && defined($hi) && $lo > $hi and die
+"low value (`$lo') exceeds high (`$hi')\n";
+ $lo //= $n[0] if $dotdot;
+ $hi //= $n[-1] if $dotdot;
+ $hi //= $lo;
+ my $want = {};
+ for ($lo..$hi) {
+ if (defined $v2_epochs->{$_}) {
+ $want->{$_} = 1;
+ } else {
+ warn
+"# epoch $_ is not available (non-fatal, $lo..$hi)\n";
+ }
+ }
+ $want
+}
+
+sub init_placeholder ($$) {
+ my ($src, $edst) = @_;
+ PublicInbox::Import::init_bare($edst);
+ my $f = "$edst/config";
+ open my $fh, '>>', $f or die "open($f): $!";
+ print $fh <<EOM or die "print($f): $!";
+[remote "origin"]
+ url = $src
+ fetch = +refs/*:refs/*
+ mirror = true
+
+; This git epoch was created read-only and "public-inbox-fetch"
+; will not fetch updates for it unless write permission is added.
+EOM
+ close $fh or die "close:($f): $!";
+}
+
+sub clone_v2 ($$;$) {
+ my ($self, $v2_epochs, $m) = @_; # $m => manifest.js.gz hashref