use PublicInbox::Inbox;
use PublicInbox::LeiCurl;
use PublicInbox::OnDestroy;
-use Digest::SHA qw(sha256_hex);
+use Digest::SHA qw(sha256_hex sha1_hex);
our $LIVE; # pid => callback
$ret;
}
-sub start_clone {
+sub start_cmd {
my ($self, $cmd, $opt, $fini) = @_;
do_reap($self);
$self->{lei}->qerr("# @$cmd");
}
sub upr { # feed `git update-ref --stdin -z' verbosely
- my ($lei, $w, $op, $ref, $oid) = @_;
- $lei->qerr("# $op $ref $oid") if $lei->{opt}->{verbose};
- print $w "$op $ref\0$oid\0" or die "print(w): $!";
+ my ($lei, $w, $op, @rest) = @_; # ($ref, $oid) = @rest
+ $lei->qerr("# $op @rest") if $lei->{opt}->{verbose};
+ print $w "$op ", join("\0", @rest, '') or die "print(w): $!";
}
sub fgrp_update {
my $new = delete $src{$ref};
my $old = $dst{$ref};
if (defined $new) {
- upr($lei, $w, 'update', $ref, $new) if $new ne $old;
+ $new eq $old or
+ upr($lei, $w, 'update', $ref, $new, $old);
} else {
upr($lei, $w, 'delete', $ref, $old);
}
upr($lei, $w, 'create', $ref, $oid);
}
close($w) or warn "E: close(update-ref --stdin): $! (need git 1.8.5+)\n";
- $LIVE->{$pid} = [ \&reap_cmd, $fgrp, $cmd ];
+ my $pack = PublicInbox::OnDestroy->new($$, \&pack_dst, $fgrp);
+ $LIVE->{$pid} = [ \&reap_cmd, $fgrp, $cmd, $pack ];
+}
+
+sub pack_dst { # packs lightweight satellite repos
+ my ($fgrp) = @_;
pack_refs($fgrp, $fgrp->{cur_dst});
}
}
}
my $key = $self->{-key} // die 'BUG: no -key';
- my $rn = $key;
- $rn =~ s!\A[\./]+!!s;
- $rn =~ s/\.*?(?:\.git)?\.*?\z//s;
- $rn =~ s![\@\{\}/:\?\[\]\^~\s\f[:cntrl:]\*]!_!isg;
- $rn .= '-'.substr(sha256_hex($key), 0, 16);
+ 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/*",
bless { %$self, -osdir => $dir, -remote => $rn }, __PACKAGE__;
}
+sub fp_done {
+ my ($self, $go_fetch) = @_;
+ my $fh = delete $self->{-show_ref} // 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 ne $B; # $go_fetch->DESTROY fires
+ $go_fetch->cancel;
+ $self->{lei}->qerr("# $self->{-key} up-to-date");
+}
+
+sub cmp_fp_fetch {
+ my ($self, $go_fetch) = @_;
+ my $dst = $self->{cur_dst} // $self->{dst};
+ 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} = $opt->{1};
+ my $done = PublicInbox::OnDestroy->new($$, \&fp_done, $self, $go_fetch);
+ start_cmd($self, $cmd, $opt, $done);
+}
+
+sub resume_fetch_maybe {
+ my ($self, $uri, $fini) = @_;
+ my $go_fetch = PublicInbox::OnDestroy->new($$, \&resume_fetch, @_);
+ cmp_fp_fetch($self, $go_fetch) if $self->{-ent} &&
+ defined($self->{-ent}->{fingerprint});
+}
+
+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 fgrp_enqueue_maybe {
+ my ($self, $fgrp) = @_;
+ my $enq = PublicInbox::OnDestroy->new($$, \&fgrp_enqueue, $self, $fgrp);
+ cmp_fp_fetch($self, $enq) if $self->{-ent} &&
+ defined($self->{-ent}->{fingerprint});
+ # $enq->DESTROY calls fgrp_enqueue otherwise
+}
+
+sub fgrp_enqueue {
+ my ($self, $fgrp) = @_;
+ push @{$self->{fgrp_todo}->{$fgrp->{-osdir}}}, $fgrp;
+}
+
sub clone_v1 {
my ($self, $nohang) = @_;
my $lei = $self->{lei};
$self->{-torsocks} //= $curl->torsocks($lei, $uri) or return;
my $dst = $self->{cur_dst} // $self->{dst};
my $fini = PublicInbox::OnDestroy->new($$, \&v1_done, $self);
+ my $resume = -d $dst;
if (my $fgrp = forkgroup_prep($self, $uri)) {
$fgrp->{-fini} = $fini;
- push @{$self->{fgrp_todo}->{$fgrp->{-osdir}}}, $fgrp;
- } else { # normal fetch
+ $resume ? fgrp_enqueue_maybe($self, $fgrp) :
+ fgrp_enqueue($self, $fgrp);
+ } elsif ($resume) {
+ resume_fetch_maybe($self, $uri, $fini);
+ } else { # normal clone
my $cmd = [ @{$self->{-torsocks}},
clone_cmd($lei, my $opt = {}), "$uri", $dst ];
if (defined($self->{-ent})) {
"$self->{dst}$ref";
}
}
- start_clone($self, $cmd, $opt, $fini);
+ start_cmd($self, $cmd, $opt, $fini);
}
if (!$self->{-is_epoch} && $lei->{opt}->{'inbox-config'} =~
/\A(?:always|v1)\z/s) {
$? = 0; # don't let it influence normal exit
if ($cerr) {
kill('TERM', keys %$LIVE);
- $self->{lei}->child_error($cerr, "@$cmd failed");
+ $self->{lei}->child_error($cerr, "@$cmd failed (\$?=$cerr)");
}
}
last; # restart %$todo iteration
}
}
+ do_reap($self, 1); # finish all fingerprint checks
fgrp_fetch_all($self);
do_reap($self, 1);
}