about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--Documentation/public-inbox-clone.pod9
-rw-r--r--lib/PublicInbox/LeiMirror.pm30
-rwxr-xr-xscript/public-inbox-clone2
-rw-r--r--t/clone-coderepo.t21
4 files changed, 54 insertions, 8 deletions
diff --git a/Documentation/public-inbox-clone.pod b/Documentation/public-inbox-clone.pod
index 50a290df..152fed73 100644
--- a/Documentation/public-inbox-clone.pod
+++ b/Documentation/public-inbox-clone.pod
@@ -176,6 +176,15 @@ calls on incremental clones.
 
 This is a new option in public-inbox 2.0+
 
+=item --purge
+
+Deletes entire repos which no longer exist in the remote manifest,
+or are filtered out by C<--include=> or C<--exclude=>.
+
+This is only useful when using C<--manifest>
+
+This is a new option in public-inbox 2.0+
+
 =item --exit-code
 
 Exit with C<127> if no updates are done when relying on a manifest.
diff --git a/lib/PublicInbox/LeiMirror.pm b/lib/PublicInbox/LeiMirror.pm
index 8b7e48ab..abb68f70 100644
--- a/lib/PublicInbox/LeiMirror.pm
+++ b/lib/PublicInbox/LeiMirror.pm
@@ -1094,7 +1094,9 @@ sub dump_project_list ($$) {
         my %new;
 
         open my $dh, '<', '.' or die "open(.): $!";
-        chdir($self->{dst}) or die "chdir($self->{dst}): $!";
+        if (!$self->{dry_run} || -d $self->{dst}) {
+                chdir($self->{dst}) or die "chdir($self->{dst}): $!";
+        }
         my @local = grep { -e $_ ? ($new{$_} = undef) : 1 } split(/\n/s, $old);
         chdir($dh) or die "chdir(restore): $!";
 
@@ -1104,10 +1106,22 @@ sub dump_project_list ($$) {
         my %lnk = map { substr($_, 1) => undef } @{$self->{-new_symlinks}};
         @remote = grep { !exists($lnk{$_}) } @remote;
 
-        warn <<EOM if @remote;
+        if (@remote) {
+                warn <<EOM;
 The following local repositories are ignored/gone from $self->{src}:
 EOM
-        warn "\t", $_, "\n" for @remote;
+                warn "\t", $_, "\n" for @remote;
+
+                if ($self->{lei}->{opt}->{purge} && !$self->{dry_run}) {
+                        my $o = {};
+                        $o->{verbose} = 1 if $self->{lei}->{opt}->{verbose};
+                        my $dst = $self->{dst};
+                        File::Path::remove_tree(map { "$dst/$_" } @remote, $o);
+                        my %rm = map { $_ => undef } @remote;
+                        @list = grep { !exists($rm{$_}) } @list;
+                        $self->{lei}->qerr('# purged ');
+                }
+        }
         if (defined($f) && @local) {
                 warn <<EOM;
 The following repos in $f no longer exist on the filesystem:
@@ -1115,7 +1129,7 @@ EOM
                 warn "\t", $_, "\n" for @local;
         }
         $self->{chg}->{nr_chg} += scalar(@remote) + scalar(@local);
-        $f // return;
+        return if !defined($f) || $self->{dry_run};
         my (undef, $dn, $bn) = File::Spec->splitpath($f);
         my $new = join("\n", @list, '');
         atomic_write($dn, $bn, $new) if $new ne $old;
@@ -1222,7 +1236,7 @@ EOM
         }
         delete local $lei->{opt}->{epoch} if defined($v2);
         clone_all($self, $m);
-        return if $self->{dry_run} || !keep_going($self);
+        return if !keep_going($self);
 
         # set by clone_v2_prep/-I/--exclude
         my $mis = delete $self->{chg}->{fp_mismatch};
@@ -1239,13 +1253,15 @@ EOM
 W: The above fingerprints may never match without --prune
 EOM
         }
-        dump_manifest($m => $ft) if delete($self->{chg}->{manifest}) || $mis;
+        if ((delete($self->{chg}->{manifest}) || $mis) && !$self->{dry_run}) {
+                dump_manifest($m => $ft);
+        }
         my $bad = delete $self->{chg}->{badlink};
         warn(<<EOM, map { ("\t", $_, "\n") } @$bad) if $bad;
 W: The following exist and have not been converted to symlinks
 EOM
         dump_project_list($self, $m);
-        ft_rename($ft, $manifest, 0666);
+        ft_rename($ft, $manifest, 0666) if !$self->{dry_run};
         !$self->{chg}->{nr_chg} && $lei->{opt}->{'exit-code'} and
                 $lei->child_error(127 << 8);
 }
diff --git a/script/public-inbox-clone b/script/public-inbox-clone
index 5b365df7..c3e64485 100755
--- a/script/public-inbox-clone
+++ b/script/public-inbox-clone
@@ -32,7 +32,7 @@ EOF
 GetOptions($opt, qw(help|h quiet|q verbose|v+ C=s@ c=s@ include|I=s@ exclude=s@
         inbox-config=s inbox-version=i objstore=s manifest=s
         remote-manifest=s project-list|projectslist=s post-update-hook=s@
-        prune|p keep-going|k exit-code
+        prune|p keep-going|k exit-code purge
         dry-run|n jobs|j=i no-torsocks torsocks=s epoch=s)) or die $help;
 if ($opt->{help}) { print $help; exit };
 require PublicInbox::Admin; # loads Config
diff --git a/t/clone-coderepo.t b/t/clone-coderepo.t
index 3a5997c9..e117293e 100644
--- a/t/clone-coderepo.t
+++ b/t/clone-coderepo.t
@@ -131,6 +131,27 @@ is(PublicInbox::Git::try_cat($dst_pl), "a.git\nb.git\n",
         like($err, qr/no longer exist.*\bgone\.git\b/s, 'gone.git noted');
 }
 
+{ # --purge
+        open my $fh, '>>', $dst_pl or xbail $!;
+        print $fh "gone-rdonly.git\n" or xbail $!;
+        close $fh or xbail $!;
+        my $ro = "$tmpdir/dst/gone-rdonly.git";
+        PublicInbox::Import::init_bare($ro);
+        ok(-d $ro, 'gone-rdonly.git created');
+        my @st = stat($ro) or xbail "stat($ro): $!";
+        chmod($st[2] & 0555, $ro) or xbail "chmod($ro): $!";
+
+        utime($t0, $t0, $dst_mf) or xbail "utime: $!";
+        my $rdr = { 2 => \(my $err = '') };
+        my $xcmd = [ @$cmd, '--purge' ];
+        ok(run_script($xcmd, undef, $rdr), 'clone again for expired gone.git');
+        is(PublicInbox::Git::try_cat($dst_pl), "a.git\nb.git\n",
+                'project list cleaned');
+        like($err, qr!ignored/gone.*?\bgone-rdonly\.git\b!s,
+                'gone-rdonly.git noted');
+        ok(!-d $ro, 'gone-rdonly.git dir gone from --purge');
+}
+
 my $test_puh = sub {
         my (@clone_arg) = @_;
         my $x = [qw(-clone --inbox-config=never --manifest= --project-list=