+sub up_fp_done {
+ my ($self) = @_;
+ return if !keep_going($self);
+ my $fh = delete $self->{-show_ref_up} // die 'BUG: no show-ref output';
+ seek($fh, SEEK_SET, 0) or die "seek(show_ref): $!";
+ $self->{-ent} // die 'BUG: no -ent';
+ my $A = $self->{-ent}->{fingerprint} // die 'BUG: no fingerprint';
+ my $B = sha1_hex(do { local $/; <$fh> } // die("read(show_ref): $!"));
+ return if $A eq $B;
+ $self->{-ent}->{fingerprint} = $B;
+ push @{$self->{chg}->{fp_mismatch}}, $self->{-key};
+}
+
+sub atomic_write ($$$) {
+ my ($dn, $bn, $raw) = @_;
+ my $ft = File::Temp->new(DIR => $dn, TEMPLATE => "$bn-XXXX");
+ print $ft $raw or die "print($ft): $!";
+ $ft->flush or die "flush($ft): $!";
+ ft_rename($ft, "$dn/$bn", 0666);
+}
+
+sub run_next_puh {
+ my ($self) = @_;
+ my $puh = shift @{$self->{-puh_todo}} // return delete($self->{-fini});
+ my $fini = PublicInbox::OnDestroy->new($$, \&run_next_puh, $self);
+ my $cmd = [ @$puh, ($self->{cur_dst} // $self->{dst}) ];
+ my $opt = +{ map { $_ => $self->{lei}->{$_} } (0..2) };
+ start_cmd($self, $cmd, undef, $opt, $fini);
+}
+
+sub run_puh {
+ my ($self, $fini) = @_;
+ $self->{-fini} = $fini;
+ @{$self->{-puh_todo}} = @PUH;
+ run_next_puh($self);
+}
+
+# modifies the to-be-written manifest entry, and sets values from it, too
+sub update_ent {
+ my ($self) = @_;
+ my $key = $self->{-key} // die 'BUG: no -key';
+ my $new = $self->{-ent}->{fingerprint};
+ my $cur = $self->{-local_manifest}->{$key}->{fingerprint} // "\0";
+ my $dst = $self->{cur_dst} // $self->{dst};
+ if (defined($new) && $new ne $cur) {
+ my $cmd = ['git', "--git-dir=$dst", 'show-ref'];
+ my $opt = { 2 => $self->{lei}->{2} };
+ open($opt->{1}, '+>', undef) or die "open(tmp): $!";
+ $self->{-show_ref_up} = $opt->{1};
+ my $done = PublicInbox::OnDestroy->new($$, \&up_fp_done, $self);
+ start_cmd($self, $cmd, $opt, $done);
+ }
+ $new = $self->{-ent}->{head};
+ $cur = $self->{-local_manifest}->{$key}->{head} // "\0";
+ if (defined($new) && $new ne $cur) {
+ # n.b. grokmirror writes raw contents to $dst/HEAD w/o locking
+ my $cmd = [ 'git', "--git-dir=$dst" ];
+ if ($new =~ s/\Aref: //) {
+ push @$cmd, qw(symbolic-ref HEAD), $new;
+ } elsif ($new =~ /\A[a-f0-9]{40,}\z/) {
+ push @$cmd, qw(update-ref --no-deref HEAD), $new;
+ } else {
+ undef $cmd;
+ warn "W: $key: {head} => `$new' not understood\n";
+ }
+ start_cmd($self, $cmd, { 2 => $self->{lei}->{2} }) if $cmd;
+ }
+ if (my $symlinks = $self->{-ent}->{symlinks}) {
+ my $top = File::Spec->rel2abs($self->{dst});
+ push @{$self->{-new_symlinks}}, @$symlinks;
+ for my $p (@$symlinks) {
+ my $ln = "$top/$p";
+ $ln =~ tr!/!/!s;
+ my (undef, $dn, $bn) = File::Spec->splitpath($ln);
+ File::Path::mkpath($dn);
+ my $tgt = "$top/$key";
+ $tgt = File::Spec->abs2rel($tgt, $dn);
+ if (lstat($ln)) {
+ if (-l _) {
+ next if readlink($ln) eq $tgt;
+ unlink($ln) or die "unlink($ln): $!";
+ } else {
+ push @{$self->{chg}->{badlink}}, $p;
+ }
+ }
+ symlink($tgt, $ln) or die "symlink($tgt, $ln): $!";
+ ++$self->{chg}->{nr_chg};
+ }
+ }
+ if (defined(my $t = $self->{-ent}->{modified})) {
+ my ($dn, $bn) = ("$dst/info/web", 'last-modified');
+ my $orig = PublicInbox::Git::try_cat("$dn/$bn");
+ $t = strftime('%F %T', gmtime($t))." +0000\n";
+ File::Path::mkpath($dn);
+ atomic_write($dn, $bn, $t) if $orig ne $t;
+ }
+
+ $new = $self->{-ent}->{owner} // return;
+ $cur = $self->{-local_manifest}->{$key}->{owner} // "\0";
+ return if $cur eq $new;
+ my $cmd = [ qw(git config -f), "$dst/config", 'gitweb.owner', $new ];
+ start_cmd($self, $cmd, { 2 => $self->{lei}->{2} });
+}
+