From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: X-Spam-Status: No, score=-4.0 required=3.0 tests=ALL_TRUSTED,BAYES_00 shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id CB5BE1F8C8 for ; Wed, 15 Sep 2021 21:35:59 +0000 (UTC) From: Eric Wong To: meta@public-inbox.org Subject: [PATCH 1/5] fetch: support --exit-code switch Date: Wed, 15 Sep 2021 21:35:55 +0000 Message-Id: <20210915213559.21757-2-e@80x24.org> In-Reply-To: <20210915213559.21757-1-e@80x24.org> References: <20210915213559.21757-1-e@80x24.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: 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 | 30 ++++++++++++++++++++++++++-- lib/PublicInbox/Fetch.pm | 21 ++++++++++++++++++- script/public-inbox-fetch | 4 +++- t/lei-mirror.t | 12 +++++++---- t/v2mirror.t | 3 ++- 5 files changed, 61 insertions(+), 9 deletions(-) diff --git a/Documentation/public-inbox-fetch.pod b/Documentation/public-inbox-fetch.pod index 7944fdcd..28d5638d 100644 --- a/Documentation/public-inbox-fetch.pod +++ b/Documentation/public-inbox-fetch.pod @@ -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. +=item --exit-code + +Exit with C<127> if no updates are done. This can be used in +shell scripts to avoid invoking L 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 and L commands with torsocks. Default: C +=back + +=head1 EXIT CODES + +=over + +=item 127 + +no updates when L is used above + +=back + +public-inbox-fetch will also exit with curl L +as documented in the L manpage (e.g. C<7> when curl cannot +reach a host). Likewise, L failures are also +propagated to the user. + =head1 CONTACT Feedback welcome via plain-text mail to L @@ -60,4 +86,4 @@ License: AGPL-3.0+ L =head1 SEE ALSO -L +L, L diff --git a/lib/PublicInbox/Fetch.pm b/lib/PublicInbox/Fetch.pm index 9ea55e9d..0539fe50 100644 --- a/lib/PublicInbox/Fetch.pm +++ b/lib/PublicInbox/Fetch.pm @@ -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; diff --git a/script/public-inbox-fetch b/script/public-inbox-fetch index 5d303574..d7d4ba47 100755 --- a/script/public-inbox-fetch +++ b/script/public-inbox-fetch @@ -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); diff --git a/t/lei-mirror.t b/t/lei-mirror.t index 5238b67c..9fdda5aa 100644 --- a/t/lei-mirror.t +++ b/t/lei-mirror.t @@ -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'); diff --git a/t/v2mirror.t b/t/v2mirror.t index 54ad6945..3df5d053 100644 --- a/t/v2mirror.t +++ b/t/v2mirror.t @@ -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; };