user/dev discussion of public-inbox itself
 help / color / mirror / code / Atom feed
From: Eric Wong <e@80x24.org>
To: meta@public-inbox.org
Subject: [PATCH 1/5] fetch: support --exit-code switch
Date: Wed, 15 Sep 2021 21:35:55 +0000	[thread overview]
Message-ID: <20210915213559.21757-2-e@80x24.org> (raw)
In-Reply-To: <20210915213559.21757-1-e@80x24.org>

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<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)>
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;
 };

  reply	other threads:[~2021-09-15 21:35 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-09-15 21:35 [PATCH 0/5] make(1) inspired things Eric Wong
2021-09-15 21:35 ` Eric Wong [this message]
2021-09-15 21:35 ` [PATCH 2/5] TODO: update some items Eric Wong
2021-09-15 21:35 ` [PATCH 3/5] clone|fetch|--mirror: add convenience $INBOX_DIR/Makefile Eric Wong
2021-09-15 22:45   ` Kyle Meyer
2021-09-15 23:15     ` Eric Wong
2021-09-15 21:35 ` [PATCH 4/5] fetch|clone|--mirror: shorten paths for progress output Eric Wong
2021-09-15 21:35 ` [PATCH 5/5] support -C (chdir) for most non-daemon commands Eric Wong

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://public-inbox.org/README

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210915213559.21757-2-e@80x24.org \
    --to=e@80x24.org \
    --cc=meta@public-inbox.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://80x24.org/public-inbox.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).