App-karr
view release on metacpan or search on metacpan
lib/App/karr/Role/BoardDiscovery.pm view on Meta::CPAN
print STDERR
"Note: refs/karr/config is missing, so this board is half-initialized:\n"
. "$held under refs/karr/, and the name, statuses and defaults shown are\n"
. "karr's own, not the board's. Run 'karr init' to complete it -- it keeps\n"
. "what is already there.\n";
return 1;
}
# Nothing here at all -- which is what a fresh clone looks like, and what a
# repository that never had a board looks like, and the two want opposite
# things from the reader. Ask the remote before refusing (#173).
return $self->require_local_board(%args) if $self->_autofetch_board;
my $advice = $store->git->has_remote
? "'git clone' does not fetch refs/karr/*, so a fresh clone starts out like\n"
. "this. Run 'karr sync' to fetch the board, or 'karr init' to start one here.\n"
: "Run 'karr init' to create one.\n";
die "No karr board in this repository: nothing is stored under refs/karr/.\n"
. "This is not an empty board -- nothing was read here at all.\n"
. $advice
. ( defined $args{hint} ? $args{hint} : '' );
}
# The fetch require_local_board tries before it refuses (#173). True when the
# board is now here, false when it is not -- and either nothing at all or
# exactly one line on STDERR, never STDOUT, because the --json and --compact
# output this must not disturb is what the agents it exists for parse.
#
# `git clone` does not carry refs/karr/*, so every fresh clone starts out
# holding no board while the whole board sits on its remote. The refusal that
# used to be the only answer here was accurate and its advice was right, and it
# still cost a second command to act on advice karr could act on itself: the
# remote is configured, and `git ls-remote origin refs/karr/*` answers in
# milliseconds. Agents took it hardest -- they read "no board" and moved on.
#
# Four things have to be true, and each of them is a case this must not touch:
#
# KARR_NO_AUTO_FETCH unset. An opt-OUT, deliberately: an opt-in would be
# found only by people who already know their board is on the remote --
# precisely the ones who do not need it, since the state it repairs is
# invisible to everyone else. It firing unwanted costs one bounded round
# trip; it not existing costs a reader who concludes the board is gone.
#
# A remote. Without one there is nothing to ask, and `karr init` really is
# the answer.
#
# No unpublished deletions (App::karr::Git/has_pending_deletes). A `karr
# destroy` whose push did not land leaves exactly this state -- no local
# board, the whole board still on the remote -- and it must not be answered
# by fetching the board back. The refs/karr-remote mirror already decides
# that correctly on its own: a ref the mirror holds and the board no longer
# does reads as this clone's own deletion rather than as something
# unfetched, so reconciliation keeps it deleted (measured: with the mirror
# in place the pull adopts nothing). This check is the cheap half -- it
# costs no round trip per read -- and it is the only guard left where the
# mirror is empty but the deletions are unpublished, e.g. a board fetched
# into refs/karr by hand. A destroy that did land leaves neither tombstones
# nor a remote board, so the question below refuses it instead.
#
# The remote actually advertises refs/karr/* (App::karr::Git/remote_has_board
# -- bounded and never prompting, so an unreachable or silent remote costs
# seconds, not the command). A remote without a board leaves the refusal
# exactly as it was: there `karr init` is right.
#
# Only then does it pull, through the ordinary App::karr::Git/pull -- the same
# reconciliation `karr sync --pull` runs, with the same guards, on a board that
# has nothing local to lose. Nothing is pushed: this is a read.
sub _autofetch_board {
my ($self) = @_;
return 0 if $ENV{KARR_NO_AUTO_FETCH};
my $store = $self->store;
my $git = $store->git;
return 0 unless $git->has_remote;
return 0 if $git->has_pending_deletes;
my $advertised = $git->remote_has_board;
if ( !defined $advertised ) {
# The probe could not ask. Say so in one line -- the reader has just
# waited for it, and the refusal that follows would otherwise look
# instant and offline -- then leave the refusal to speak for itself.
my $why = $git->last_error // 'unknown error';
$why =~ s/\s*\n.*//s;
print STDERR "karr: could not ask the remote whether it has the board: $why\n";
return 0;
}
return 0 unless $advertised;
# pull dies on the board-identity and wholesale-wipe guards and on a ref
# it could not apply. None of the three can fire on a board with no local
# refs, but this is not the place to be the second thing that knows that:
# an unasked fetch must not turn a read's refusal into someone else's
# exception.
my $ok = eval { $git->pull };
unless ($ok) {
my $why = $@ || $git->last_error || 'unknown error';
$why =~ s/\s*\n.*//s;
print STDERR "karr: could not fetch the board from the remote: $why\n";
return 0;
}
# The board can still be gone -- destroyed between the probe and the pull.
# Then this fetched nothing and the refusal below is the right answer.
return 0 unless $store->has_board_refs;
print STDERR
"karr: fetched the board from the remote ('git clone' does not carry refs/karr/*).\n";
return 1;
}
# How many task refs the repository holds. Through a list, because
# list_task_refs returns through sort and would answer the number of arguments
# sort was handed if it were called in scalar context.
sub _task_refs_held {
my ($self) = @_;
my @ids = $self->store->git->list_task_refs;
return scalar @ids;
}
1;
__END__
( run in 1.310 second using v1.01-cache-2.11-cpan-aadc1410aed )