]> Sergey Matveev's repositories - public-inbox.git/commitdiff
fetch: support --exit-code switch
authorEric Wong <e@80x24.org>
Wed, 15 Sep 2021 21:35:55 +0000 (21:35 +0000)
committerEric Wong <e@80x24.org>
Wed, 15 Sep 2021 23:12:51 +0000 (23:12 +0000)
As noted in the new manpage entry, this is useful for avoiding
public-inbox-index invocations when there's nothing to update.
We use 127 to match "grok-pull", and also because it doesn't
conflict with any of the current curl(1) exit codes.

Documentation/public-inbox-fetch.pod
lib/PublicInbox/Fetch.pm
script/public-inbox-fetch
t/lei-mirror.t
t/v2mirror.t

index 7944fdcdcace3876ba935c22fc909d3f4f7eb0d6..28d5638dd98a8803f822194e014c09663e95f5ec 100644 (file)
@@ -4,7 +4,7 @@ public-inbox-fetch - "git fetch" wrapper for v2 inbox mirrors
 
 =head1 SYNOPSIS
 
-public-inbox-fetch -C INBOX_DIR
+public-inbox-fetch [--exit-code] -C INBOX_DIR
 
 =head1 DESCRIPTION
 
@@ -31,6 +31,15 @@ file to speed up future invocations.
 
 Quiets down progress messages, also passed to L<git-fetch(1)>.
 
+=item --exit-code
+
+Exit with C<127> if no updates are done.  This can be used in
+shell scripts to avoid invoking L<public-inbox-index(1)> when
+there are no updates:
+
+       public-inbox-fetch -q --exit-code && public-inbox-index
+       test $? -eq 0 || exit $?
+
 =item -v
 
 =item --verbose
@@ -45,6 +54,23 @@ Whether to wrap L<git(1)> and L<curl(1)> commands with torsocks.
 
 Default: C<auto>
 
+=back
+
+=head1 EXIT CODES
+
+=over
+
+=item 127
+
+no updates when L</--exit-code> is used above
+
+=back
+
+public-inbox-fetch will also exit with curl L<curl(1)/EXIT CODES>
+as documented in the L<curl(1)> manpage (e.g. C<7> when curl cannot
+reach a host).  Likewise, L<git-fetch(1)> failures are also
+propagated to the user.
+
 =head1 CONTACT
 
 Feedback welcome via plain-text mail to L<mailto:meta@public-inbox.org>
@@ -60,4 +86,4 @@ License: AGPL-3.0+ L<https://www.gnu.org/licenses/agpl-3.0.txt>
 
 =head1 SEE ALSO
 
-L<public-inbox-index(1)>
+L<public-inbox-index(1)>, L<curl(1)>
index 9ea55e9dcf9272d2ddd6e2715433babccc1a80f4..0539fe505e42750de4c6fb483ef936041b6b1fbf 100644 (file)
@@ -75,12 +75,22 @@ sub do_manifest ($$$) {
                my $t1 = $cur->{modified} // next;
                delete($mdiff->{$k}) if $f0 eq $f1 && $t0 == $t1;
        }
-       return unless keys %$mdiff;
+       unless (keys %$mdiff) {
+               $lei->child_error(127 << 8) if $lei->{opt}->{'exit-code'};
+               return;
+       }
        my (undef, $v1_path, @v2_epochs) =
                PublicInbox::LeiMirror::deduce_epochs($mdiff, $ibx_uri->path);
        [ 200, $v1_path, \@v2_epochs, $muri, $ft, $mf ];
 }
 
+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 do_fetch {
        my ($cls, $lei, $cd) = @_;
        my $ibx_ver;
@@ -136,11 +146,14 @@ EOM
        }
        # n.b. this expects all epochs are from the same host
        my $torsocks = $lei->{curl}->torsocks($lei, $muri);
+       my $fp2 = $lei->{opt}->{'exit-code'} ? [] : undef;
+       my $xit = 127;
        for my $d (@git_dir) {
                my $cmd;
                my $opt = {}; # for spawn
                if (-d $d) {
                        $opt->{-C} = $d;
+                       $fp2->[0] = get_fingerprint2($d) if $fp2;
                        $cmd = [ @$torsocks, fetch_cmd($lei, $opt) ];
                } else {
                        my $e_uri = $ibx_uri->clone;
@@ -152,6 +165,7 @@ EOM
                                PublicInbox::LeiMirror::clone_cmd($lei, $opt),
                                $$e_uri, $d];
                        push @new_epoch, substr($epath, 5, -4) + 0;
+                       $xit = 0;
                }
                my $cerr = PublicInbox::LeiMirror::run_reap($lei, $cmd, $opt);
                # do not bail on clone failure if we didn't have a manifest
@@ -159,6 +173,10 @@ EOM
                        $lei->child_error($cerr, "@$cmd failed");
                        return;
                }
+               if ($fp2 && $xit) {
+                       $fp2->[1] = get_fingerprint2($d);
+                       $xit = 0 if $fp2->[0] ne $fp2->[1];
+               }
        }
        for my $i (@new_epoch) { $mg->epoch_cfg_set($i) }
        if ($ft) {
@@ -166,6 +184,7 @@ EOM
                rename($fn, $mf) or die "E: rename($fn, $mf): $!\n";
                $ft->unlink_on_destroy(0);
        }
+       $lei->child_error($xit << 8) if $fp2 && $xit;
 }
 
 1;
index 5d303574c8b140c0a19fed8522393ffe9f7668c9..d7d4ba47ab613254d43e28ec3449bc26a5271452 100755 (executable)
@@ -16,12 +16,13 @@ options:
   --torsocks VAL      whether or not to wrap git and curl commands with
                       torsocks (default: `auto')
                       Must be one of: `auto', `no' or `yes'
+  --exit-code         exit with 127 if no updates
   --verbose | -v      increase verbosity (may be repeated)
     --quiet | -q      increase verbosity (may be repeated)
     -C DIR            chdir to specified directory
 EOF
 GetOptions($opt, qw(help|h quiet|q verbose|v+ C=s@ c=s@
-       no-torsocks torsocks=s)) or die $help;
+       no-torsocks torsocks=s exit-code)) or die $help;
 if ($opt->{help}) { print $help; exit };
 require PublicInbox::Fetch; # loads Admin
 PublicInbox::Admin::do_chdir(delete $opt->{C});
@@ -33,3 +34,4 @@ my $lei = bless {
        0 => *STDIN{GLOB}, 1 => *STDOUT{GLOB}, 2 => *STDERR{GLOB},
 }, 'PublicInbox::LEI';
 PublicInbox::Fetch->do_fetch($lei, '.');
+exit(($lei->{child_error} // 0) >> 8);
index 5238b67cef0d51777dca88fbeee00d75b5d307c1..9fdda5aa213901aceae0430bf36a804129254386 100644 (file)
@@ -111,12 +111,14 @@ SKIP: {
                'all.git alternates created');
        ok(-f "$d/t2/manifest.js.gz", 'manifest saved');
        ok(!-e "$d/t2/mirror.done", 'no leftover mirror.done');
-       ok(run_script([qw(-fetch -C), "$d/t2"], undef, $opt),
+       ok(!run_script([qw(-fetch --exit-code -C), "$d/t2"], undef, $opt),
                '-fetch succeeds w/ manifest.js.gz');
+       is($? >> 8, 127, '--exit-code gave 127');
        unlike($err, qr/git fetch/, 'no fetch done w/ manifest');
        unlink("$d/t2/manifest.js.gz") or xbail "unlink $!";
-       ok(run_script([qw(-fetch -C), "$d/t2"], undef, $opt),
+       ok(!run_script([qw(-fetch --exit-code -C), "$d/t2"], undef, $opt),
                '-fetch succeeds w/o manifest.js.gz');
+       is($? >> 8, 127, '--exit-code gave 127');
        like($err, qr/git fetch/, 'fetch forced w/o manifest');
 
        ok(run_script([qw(-clone -q -C), $d, "$http/t1"], undef, $opt),
@@ -124,13 +126,15 @@ SKIP: {
        ok(-d "$d/t1", 'v1 cloned');
        ok(!-e "$d/t1/mirror.done", 'no leftover file');
        ok(-f "$d/t1/manifest.js.gz", 'manifest saved');
-       ok(run_script([qw(-fetch -C), "$d/t1"], undef, $opt),
+       ok(!run_script([qw(-fetch --exit-code -C), "$d/t1"], undef, $opt),
                'fetching v1 works');
+       is($? >> 8, 127, '--exit-code gave 127');
        unlike($err, qr/git fetch/, 'no fetch done w/ manifest');
        unlink("$d/t1/manifest.js.gz") or xbail "unlink $!";
        my $before = [ glob("$d/t1/*") ];
-       ok(run_script([qw(-fetch -C), "$d/t1"], undef, $opt),
+       ok(!run_script([qw(-fetch --exit-code -C), "$d/t1"], undef, $opt),
                'fetching v1 works w/o manifest.js.gz');
+       is($? >> 8, 127, '--exit-code gave 127');
        unlink("$d/t1/FETCH_HEAD"); # git internal
        like($err, qr/git fetch/, 'no fetch done w/ manifest');
        ok(unlink("$d/t1/manifest.js.gz"), 'manifest created');
index 54ad6945f86be00107eb207050dc9d235279ab94..3df5d05372223243d8f056218477cdd9d1056608 100644 (file)
@@ -98,8 +98,9 @@ $ibx->cleanup;
 my @new_epochs;
 my $fetch_each_epoch = sub {
        my %before = map { $_ => 1 } glob("$tmpdir/m/git/*");
-       run_script([qw(-fetch -q)], undef, {-C => "$tmpdir/m"}) or
+       run_script([qw(-fetch --exit-code -q)], undef, {-C => "$tmpdir/m"}) or
                xbail '-fetch fail';
+       is($?, 0, '--exit-code 0 after fetch updated');
        my @after = grep { !$before{$_} } glob("$tmpdir/m/git/*");
        push @new_epochs, @after;
 };