+ my $todo = delete $self->{fgrp_todo} or return;
+ keys(%$todo) or return;
+
+ # Rely on the fgrptmp remote groups in the config file rather
+ # than listing all remotes since the remote name list may exceed
+ # system argv limits:
+ my $grp = 'fgrptmp';
+
+ my @git = (@{$self->{-torsocks}}, 'git');
+ my $j = $self->{lei}->{opt}->{jobs};
+ my $opt = {};
+ my @fetch = do {
+ local $self->{lei}->{opt}->{jobs} = 1;
+ (fetch_args($self->{lei}, $opt),
+ qw(--no-tags --multiple));
+ };
+ push(@fetch, "-j$j") if $j;
+ my $pid;
+ while (my ($osdir, $fgrpv) = each %$todo) {
+ my $f = "$osdir/config";
+
+ # clobber group from previous run atomically
+ my $cmd = ['git', "--git-dir=$osdir", qw(config -f),
+ $f, '--unset-all', "remotes.$grp"];
+ $self->{lei}->qerr("# @$cmd");
+ if (!$self->{dry_run}) {
+ $pid = spawn($cmd);
+ waitpid($pid, 0) // die "waitpid: $!";
+ die "E: @$cmd: \$?=$?" if ($? && ($? >> 8) != 5);
+
+ # update the config atomically via O_APPEND while
+ # respecting git-config locking
+ sysopen(my $lk, "$f.lock", O_CREAT|O_EXCL|O_WRONLY)
+ or die "open($f.lock): $!";
+ open my $fh, '>>', $f or die "open(>>$f): $!";
+ $fh->autoflush(1);
+ my $buf = join('', "[remotes]\n",
+ map { "\t$grp = $_->{-remote}\n" } @$fgrpv);
+ print $fh $buf or die "print($f): $!";
+ close $fh or die "close($f): $!";
+ unlink("$f.lock") or die "unlink($f.lock): $!";
+ }
+
+ $cmd = [ @git, "--git-dir=$osdir", @fetch, $grp ];
+ do_reap($self);
+ $self->{lei}->qerr("# @$cmd");
+ my $end = PublicInbox::OnDestroy->new($$, \&fgrpv_done, $fgrpv);
+ return if $self->{dry_run};
+ $pid = spawn($cmd, undef, $opt);
+ $LIVE->{$pid} = [ \&reap_cmd, $self, $cmd, $end ];
+ }
+}
+
+# keep this idempotent for future use by public-inbox-fetch
+sub forkgroup_prep {
+ my ($self, $uri) = @_;
+ $self->{-ent} // return;
+ my $os = $self->{-objstore} // return;
+ my $fg = $self->{-ent}->{forkgroup} // return;
+ my $dir = "$os/$fg.git";
+ my @cmd = ('git', "--git-dir=$dir", 'config');
+ my $opt = +{ map { $_ => $self->{lei}->{$_} } (0..2) };
+ if (!-d $dir && !$self->{dry_run}) {
+ PublicInbox::Import::init_bare($dir);
+ for ('repack.useDeltaIslands=true',
+ 'pack.island=refs/remotes/([^/]+)/') {
+ run_die([@cmd, split(/=/, $_, 2)], undef, $opt);
+ }
+ }
+ my $key = $self->{-key} // die 'BUG: no -key';
+ my $rn = substr(sha256_hex($key), 0, 16);
+ unless ($self->{dry_run}) {
+ # --no-tags is required to avoid conflicts
+ for ("url=$uri", "fetch=+refs/*:refs/remotes/$rn/*",
+ 'tagopt=--no-tags') {
+ my @kv = split(/=/, $_, 2);
+ $kv[0] = "remote.$rn.$kv[0]";
+ run_die([@cmd, @kv], undef, $opt);
+ }
+ }
+ if (!-d $self->{cur_dst} && !$self->{dry_run}) {
+ my $alt = File::Spec->rel2abs("$dir/objects");
+ PublicInbox::Import::init_bare($self->{cur_dst});
+ my $o = "$self->{cur_dst}/objects";
+ my $f = "$o/info/alternates";
+ my $l = File::Spec->abs2rel($alt, File::Spec->rel2abs($o));
+ open my $fh, '+>>', $f or die "open($f): $!";
+ seek($fh, SEEK_SET, 0) or die "seek($f): $!";
+ chomp(my @cur = <$fh>);
+ if (!grep(/\A\Q$l\E\z/, @cur)) {
+ say $fh $l or die "say($f): $!";
+ }
+ close $fh or die "close($f): $!";
+ $f = "$self->{cur_dst}/config";
+ open $fh, '+>>', $f or die "open:($f): $!";
+ print $fh <<EOM or die "print($f): $!";
+; rely on the "$rn" remote in the
+; $fg fork group for fetches
+; only uncomment the following iff you detach from fork groups
+; [remote "origin"]
+; url = $uri
+; fetch = +refs/*:refs/*
+; mirror = true
+EOM
+ close $fh or die "close($f): $!";
+ }
+ bless { %$self, -osdir => $dir, -remote => $rn }, __PACKAGE__;
+}
+
+sub resume_fetch {
+ my ($self, $uri, $fini) = @_;
+ my $dst = $self->{cur_dst} // $self->{dst};
+ my @git = ('git', "--git-dir=$dst");
+ my $opt = +{ map { $_ => $self->{lei}->{$_} } (0..2) };
+ my $rn = 'origin'; # configurable?
+ for ("url=$uri", "fetch=+refs/*:refs/*", 'mirror=true') {
+ my @kv = split(/=/, $_, 2);
+ $kv[0] = "remote.$rn.$kv[0]";
+ next if $self->{dry_run};
+ run_die([@git, 'config', @kv], undef, $opt);
+ }
+ my $cmd = [ @{$self->{-torsocks}}, @git,
+ fetch_args($self->{lei}, $opt), $rn ];
+ start_cmd($self, $cmd, $opt, $fini);
+}
+
+sub clone_v1 {
+ my ($self, $nohang) = @_;