NAME
App::karr::Role::SyncLifecycle - Role providing sync lifecycle with retry and guard insurance
VERSION
version 0.600
DESCRIPTION
This role provides sync_before and sync_after methods that wrap Git pull and push operations with retry logic. sync_before creates a App::karr::SyncGuard and retains it on the object as insurance: if the command body dies or croaks before sync_after runs, the guard's DESTROY pushes with 3 retries. Because the guard is held by the role (not by the caller), commands may call both methods in void context; sync_after neutralises the guard so it never pushes twice.
Holding the guard on the command object is also why the CLI cannot rely on DESTROY alone: MooX::Cmd's command chain keeps that object alive past bin/karr's error handler, so on the die path the guard is only reaped in global destruction, where pushing is forbidden. bin/karr therefore drains "flush_armed" in App::karr::SyncGuard from an END block.
Commands that compose this role must also have a store attribute (provided by App::karr::Role::BoardDiscovery) with a git accessor.
"sync_pull_foundation" and "sync_push_foundation" are the same two halves for refs/karr-foundation/* (#190). They share the retry loop rather than copying it -- there is exactly one in this file, and every attempt count, retry banner and --quiet rule is decided in it.
METHODS
sync_pull
$self->sync_pull; # the ordinary guarded pull
$self->sync_pull( accept_wipe => 1 ); # karr sync --prune
$self->sync_pull( accept_foreign => 1 ); # karr sync --accept-foreign-board
Pulls refs from remote with up to 3 attempts, and nothing else. Output is retry-only: the first attempt is silent, retries are announced from attempt 2 ("Pull retry 2 of 3..."), and errors always reach STDERR. --quiet additionally suppresses the retry announcements but never the errors. Each distinct error is shown once: neither a repeat of the previous attempt's error nor the message that ends the command prints it again.
Any named arguments are handed to "pull" in App::karr::Git unchanged, on every attempt. That is how karr sync's two safety valves reach the pull -- and they are passed only when the caller passes them, so a command that calls this (or "sync_before") bare still gets the wholesale-wipe and board-identity refusals (#82, #95). Those refusals die out of "pull" in App::karr::Git rather than returning false, so they end the pull on the first attempt instead of being retried: they are the remote's state, not a transport failure.
This is "sync_before" without the App::karr::SyncGuard, for the one caller that pulls and is not going to push -- karr sync --pull, where an armed guard would be a push at process teardown that the flag exists to prevent. Everything that pulls in order to write calls sync_before.
sync_before
$self->sync_before;
$self->sync_before( accept_wipe => 1 ); # options reach Git::pull
"sync_pull" plus the insurance: it runs the same retrying pull, forwarding any named arguments to "pull" in App::karr::Git, and then creates a App::karr::SyncGuard, retains it on the object (so it outlives the call and covers the command body), and also returns it for callers that want to manage it explicitly. sync_after clears it on a successful push.
sync_after
$self->sync_after; # push with up to 3 attempts
Pushes refs to remote with up to 3 attempts, using the same retry-only output convention as "sync_before" (silent first attempt, retries announced from attempt 2, errors always on STDERR, --quiet silencing only the announcements). It marks the retained guard done and clears it on both outcomes: after a successful push there is nothing left to insure, and after a failed one the guard's three attempts have just been spent, so re-running them from "flush_armed" in App::karr::SyncGuard would only repeat the failure.
A push the remote rejected per ref (a pre-receive hook, a protected ref) is not retried at all: the connection worked and the far side gave its answer, which the error message carries ref by ref. A rejection that is only contention -- two pushes racing for the same ref, see "push_contention" in App::karr::Git -- is retried like any other transient failure, because the same refspec lands on the next attempt.
sync_pull_foundation
$self->sync_pull_foundation;
"sync_pull" for refs/karr-foundation/* -- karr-foundation's shared chain, run logs and question mailbox (App::karr::Foundation::ChainStore). Same three attempts, same retry-only output, same --quiet contract, because it is the same loop; only what it calls differs ("pull_foundation" in App::karr::Git).
The terminal message is its own, and that is the point: this half runs behind the board's, so "Nothing was changed" would be a lie about a board that has just been synced. There are no accept_wipe/accept_foreign options to forward, because the fleet namespace has neither guard -- see "pull_foundation" in App::karr::Git.
karr sync is the only caller. The implicit sync every writing command makes ("sync_before", "sync_after") stays board-only: a stale chain costs a planning round, a stale board costs correctness, and nothing outside the fleet should pay for a namespace it does not have.
sync_push_foundation
$self->sync_push_foundation;
"sync_after" for refs/karr-foundation/*, minus the App::karr::SyncGuard: the guard insures a command body that wrote board refs and died, and nothing in a command body writes this namespace. The rejection verdict is the same one "sync_after" uses, contention included, since it is the same remote answering.
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/karr/issues.
IRC
Join #langertha on irc.perl.org or message Getty directly.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <getty@cpan.org>
COPYRIGHT AND LICENSE
This software is Copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)