+sub get_fingerprint2 {
+ my ($git_dir) = @_;
+ require Digest::SHA;
+ my $rd = popen_rd([qw(git show-ref)], undef, { -C => $git_dir });
+ Digest::SHA::sha256(do { local $/; <$rd> });
+}
+
+sub writable_dir ($) {
+ my ($dir) = @_;
+ return unless -d $dir && -w _;
+ my @st = stat($dir);
+ $st[2] & 0222; # any writable bits set? (in case of root)
+}
+
+sub do_fetch { # main entry point
+ my ($cls, $lei, $cd) = @_;
+ my $ibx_ver;
+ $lei->{curl} //= PublicInbox::LeiCurl->new($lei) or return;
+ my $dir = PublicInbox::Admin::resolve_inboxdir($cd, \$ibx_ver);
+ my ($ibx_uri, @git_dir, @epochs, $mg, @new_epoch, $skip);
+ if ($ibx_ver == 1) {
+ my $url = remote_url($lei, $dir) //
+ die "E: $dir missing remote.*.url\n";
+ $ibx_uri = URI->new($url);
+ } else { # v2:
+ require PublicInbox::MultiGit;
+ $mg = PublicInbox::MultiGit->new($dir, 'all.git', 'git');
+ @epochs = $mg->git_epochs;
+ my ($git_url, $epoch);
+ for my $nr (@epochs) { # try newest epoch, first
+ my $edir = "$dir/git/$nr.git";
+ if (!writable_dir($edir)) {
+ $skip->{$nr} = 1;
+ next;
+ }
+ next if defined $git_url;
+ if (defined(my $url = remote_url($lei, $edir))) {
+ $git_url = $url;
+ $epoch = $nr;
+ } else {
+ warn "W: $edir missing remote.*.url\n";
+ my $pid = spawn([qw(git config -l)], undef,
+ { 1 => $lei->{2}, 2 => $lei->{2} });
+ waitpid($pid, 0);
+ $lei->child_error($?) if $?;
+ }
+ }
+ @epochs = grep { !$skip->{$_} } @epochs if $skip;
+ $skip //= {}; # makes code below easier
+ $git_url or die "Unable to determine git URL\n";
+ my $inbox_url = $git_url;
+ $inbox_url =~ s!/git/$epoch(?:\.git)?/?\z!! or
+ $inbox_url =~ s!/$epoch(?:\.git)?/?\z!! or die <<EOM;
+Unable to infer inbox URL from <$git_url>
+EOM
+ $ibx_uri = URI->new($inbox_url);
+ }
+ PublicInbox::LeiMirror::write_makefile($dir, $ibx_ver);
+ $lei->qerr("# inbox URL: $ibx_uri/");
+ my $res = do_manifest($lei, $dir, $ibx_uri) or return;
+ my ($code, $muri, $v1_path, $v2_epochs, $ft, $mf, $m1) = @$res;
+ if ($code == 404) {
+ # any pre-manifest.js.gz instances running? Just fetch all
+ # existing ones and unconditionally try cloning the next
+ $v2_epochs = [ map { "$dir/git/$_.git" } @epochs ];
+ if (@epochs) {
+ my $n = $epochs[-1] + 1;
+ push @$v2_epochs, "$dir/git/$n.git" if !$skip->{$n};
+ }
+ } else {
+ $code == 200 or die "BUG unexpected code $code\n";
+ }
+ my $mculled;
+ if ($ibx_ver == 2) {
+ defined($v1_path) and warn <<EOM;
+E: got v1 `$v1_path' when expecting v2 epoch(s) in <$muri>, WTF?
+EOM
+ @git_dir = map { "$dir/git/$_.git" } sort { $a <=> $b } map {
+ my ($nr) = (m!/([0-9]+)\.git\z!g);
+ $skip->{$nr} ? () : $nr;
+ } @$v2_epochs;
+ if ($m1 && scalar keys %$skip) {
+ my $re = join('|', keys %$skip);
+ my @del = grep(m!/git/$re\.git\z!, keys %$m1);
+ delete @$m1{@del};
+ $mculled = 1;
+ }
+ } else {
+ $git_dir[0] = $dir;
+ }