pub struct ManagedClient<R: ProcessRunner = JobRunner> { /* private fields */ }Expand description
A CliClient wrapper that adds two opt-in concerns the CLI wrappers
(vcs-git, vcs-jj, vcs-github, vcs-gitlab) all share, without touching a
single call site:
- Lock-contention retry (
is_lock_contention) per aRetryPolicy— off by default (RetryPolicy::none); enable withwith_retry. Safe even for mutating commands, since lock contention is a clean pre-execution failure. - Credential injection from an opt-in
CredentialProvider— off by default (no provider); attach one withwith_credentials. When a forge token-env binding is configured (with_token_env), every command run through this client gets the resolved token in that environment variable (e.g.GH_TOKEN). Backends that inject the secret differently (git’scredential.helper) instead callresolve_credentialat the command site. Resolution happens once per call, before the retry loop. Awith_expected_hostbinding travels as the request’s host so a host-keyed provider selects the right instance’s secret; theOk(None)/Errfallback (defer to ambient vs. fail-closed abort) is defined onresolve_credential.
Both default to inert, so a client with neither configured behaves exactly
like a bare CliClient.
Implementations§
Source§impl ManagedClient<JobRunner>
impl ManagedClient<JobRunner>
Sourcepub fn new(program: impl AsRef<OsStr>) -> Self
pub fn new(program: impl AsRef<OsStr>) -> Self
A retrying client driving program on the real job-backed runner (no retry
until with_retry).
Source§impl<R: ProcessRunner> ManagedClient<R>
impl<R: ProcessRunner> ManagedClient<R>
Sourcepub fn with_runner(program: impl AsRef<OsStr>, runner: R) -> Self
pub fn with_runner(program: impl AsRef<OsStr>, runner: R) -> Self
A retrying client driving program on runner — inject a fake in tests.
Sourcepub fn with_retry(self, policy: RetryPolicy) -> Self
pub fn with_retry(self, policy: RetryPolicy) -> Self
Set the lock-contention retry policy (opt-in; default is no retry).
Sourcepub fn retry_policy(&self) -> RetryPolicy
pub fn retry_policy(&self) -> RetryPolicy
The active retry policy.
Sourcepub fn with_credentials(self, provider: Arc<dyn CredentialProvider>) -> Self
pub fn with_credentials(self, provider: Arc<dyn CredentialProvider>) -> Self
Attach a CredentialProvider (opt-in; default is none → ambient auth).
The provider is consulted per operation: automatically when a
with_token_env binding is set, or
on demand via resolve_credential.
Precedence: a resolved token is injected after any
default_env, so the provider wins over a
static default and over the ambient CLI login. Cancellation: a
default_cancel_on token bounds the
spawned process, not provider resolution — if your provider does slow I/O
(a vault lookup), bound it yourself.
Sourcepub fn with_token_env(
self,
service: CredentialService,
var: &'static str,
) -> Self
pub fn with_token_env( self, service: CredentialService, var: &'static str, ) -> Self
Bind the resolved token to an environment variable injected on every
command this client runs (the forge case: GH_TOKEN, GITLAB_TOKEN). The
service tags the CredentialRequest. No effect without a provider.
Sourcepub fn with_expected_host(self, host: impl Into<String>) -> Self
pub fn with_expected_host(self, host: impl Into<String>) -> Self
Bind the remote host this client targets (set by a forge with_host): it
travels as the CredentialRequest’s host whenever the token-env path
resolves a credential, so a host-keyed CredentialProvider returns the
secret for this host and nothing else — one client can’t inject a
neighbouring instance’s token. Without it the request host is unset (the
pre-host-context behaviour). No effect without a provider and a
with_token_env binding.
Sourcepub fn has_credentials(&self) -> bool
pub fn has_credentials(&self) -> bool
Whether a credential provider is configured.
Sourcepub async fn resolve_credential(
&self,
service: CredentialService,
host: Option<&str>,
) -> Result<Option<Credential>>
pub async fn resolve_credential( &self, service: CredentialService, host: Option<&str>, ) -> Result<Option<Credential>>
Resolve a credential for service/host from the configured provider, or
Ok(None) if no provider is set or it defers to ambient auth. Backends
that inject the secret at the command site (git’s credential.helper) call
this directly; the forge token-env path uses it internally.
Fallback policy (identical for read and write operations):
- No provider, or the provider returns
Ok(None)→Ok(None): defer to the CLI’s ambient auth, exactly as if no provider were configured. - A credential whose secret is empty / whitespace-only → treated as
Ok(None)(ambient): injecting an empty token would override the ambient login with nothing instead of deferring to it. - The provider returns
Err→ the error propagates and aborts the operation (fail-closed). A provider that cannot resolve (a vault outage) is never silently downgraded to ambient auth.
Passing the operation’s host is what lets a host-keyed provider return
the secret for that host (or Ok(None) for one it does not handle) — so it
never hands back a neighbouring instance’s token when the host is known, and
an unknown/absent host defers to ambient rather than substituting a default
secret.
Sourcepub fn default_timeout(self, timeout: Duration) -> Self
pub fn default_timeout(self, timeout: Duration) -> Self
Apply a default timeout to every command this client builds.
Sourcepub fn default_inactivity_timeout(self, timeout: Duration) -> Self
pub fn default_inactivity_timeout(self, timeout: Duration) -> Self
Set the resettable output-inactivity window for streamed runs.
The default is disabled (None), preserving the pre-watchdog behaviour.
The window is applied only by run_with_progress
and its per-call budgeted sibling; captured commands keep their existing
absolute-timeout, retry, credential, and cleanup semantics. A successful
read from either output stream resets the window. For jj callers this is
an explicit opt-in: unlike Git’s --progress, jj does not force progress
when stderr is piped, so a configured window is safe only when the selected
jj version/environment emits progress under that transport.
Sourcepub fn default_env(
self,
key: impl AsRef<OsStr>,
value: impl AsRef<OsStr>,
) -> Self
pub fn default_env( self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>, ) -> Self
Set an environment variable on every command this client builds.
Sourcepub fn default_env_remove(self, key: impl AsRef<OsStr>) -> Self
pub fn default_env_remove(self, key: impl AsRef<OsStr>) -> Self
Remove an inherited environment variable on every command this client builds.
Sourcepub fn default_cancel_on(self, token: CancellationToken) -> Self
pub fn default_cancel_on(self, token: CancellationToken) -> Self
Cancel every command this client builds when token fires — and cut a
lock-contention retry backoff short the moment it does, so a cancelled
operation returns promptly instead of sleeping out the remaining delay
before its next attempt. Network fetch/push/clone commands add the
shared FETCH_TIMEOUT_GRACE soft-completion window (and the Windows
console trigger when available); their structured result remains
ErrorReason::Cancelled. The token is applied to the spawned process
(via inner) and observed by the retry loop. Other commands retain their
existing cancellation policy.
Sourcepub fn default_output_budget(self, budget: OutputBudget) -> Self
pub fn default_output_budget(self, budget: OutputBudget) -> Self
Set the default OutputBudget applied to the content verbs this client
builds through run_untrimmed, to a discard verb’s
diagnostics via budget_diagnostics, and to
the output a streamed run retains
(run_with_progress) — off by default
(OutputBudget::unlimited). A single call can override it via
run_untrimmed_within /
run_with_progress_within.
Sourcepub fn output_budget(&self) -> OutputBudget
pub fn output_budget(&self) -> OutputBudget
The active default output budget.
Sourcepub fn budget_diagnostics(&self, cmd: Command) -> Command
pub fn budget_diagnostics(&self, cmd: Command) -> Command
Apply this client’s default budget to cmd as a diagnostic (drop-oldest
tail) bound, for a discard verb that only surfaces its output on failure
(clone/fetch). Caps the retained error/progress buffer without turning a
real failure into ErrorReason::OutputTooLarge — the tail (where a CLI’s fatal
line sits) is preserved, so is_transient_fetch_error /
is_lock_contention still classify it. A no-op when the budget is
unlimited.
This bounds what processkit retains for a captured run. The streamed
twin of the same verbs bounds what the event consumer retains instead,
under the same budget — see
run_with_progress; a streaming clone/fetch
wants both, since a command buffer policy does not bound an event stream.
Sourcepub fn command<I, S>(&self, args: I) -> Command
pub fn command<I, S>(&self, args: I) -> Command
Build a Command for this client’s program (passthrough).
Sourcepub fn command_in<I, S>(&self, dir: &Path, args: I) -> Command
pub fn command_in<I, S>(&self, dir: &Path, args: I) -> Command
Build a Command bound to dir (passthrough).
Sourcepub async fn run(&self, call: impl IntoCommand<R>) -> Result<String>
pub async fn run(&self, call: impl IntoCommand<R>) -> Result<String>
Like CliClient::run, with credential injection and lock-retry.
Sourcepub async fn run_unit(&self, call: impl IntoCommand<R>) -> Result<()>
pub async fn run_unit(&self, call: impl IntoCommand<R>) -> Result<()>
Like CliClient::run_unit, with credential injection and lock-retry.
Sourcepub async fn run_with_progress(
&self,
call: impl IntoCommand<R>,
progress: &mut ProgressCallback<'_>,
) -> Result<()>
pub async fn run_with_progress( &self, call: impl IntoCommand<R>, progress: &mut ProgressCallback<'_>, ) -> Result<()>
Run one command while forwarding live process events to progress.
Credential injection and all command defaults are applied exactly as for
run_unit. Unlike that captured-output path, this is a
deliberately single-attempt lifecycle: neither this client’s optional
lock retry nor a command retry is applied, so the callback observes one
Started … Exited sequence and Exited remains terminal. The returned
error still carries the streamed stdout/stderr for normal processkit
classification.
Output budget: this client’s default OutputBudget
(default_output_budget) bounds the
stdout/stderr this call retains, as a drop-oldest tail — the
streaming counterpart of what
budget_diagnostics applies to a captured
clone/fetch, and the reason a streamed one is memory-bounded by the
same knob rather than growing with the repository. Never fail-loud: a
bounded run still surfaces its real outcome, with the classifiable tail
of its output. Unlimited by default (unchanged behaviour); override for
one call with
run_with_progress_within, whose docs
(and crate::run_with_progress_within’s) define what the ceiling counts.
Sourcepub async fn run_with_progress_within(
&self,
call: impl IntoCommand<R>,
progress: &mut ProgressCallback<'_>,
budget: OutputBudget,
) -> Result<()>
pub async fn run_with_progress_within( &self, call: impl IntoCommand<R>, progress: &mut ProgressCallback<'_>, budget: OutputBudget, ) -> Result<()>
Like run_with_progress, but with an explicit
per-call OutputBudget instead of this client’s default — the
streaming sibling of
run_untrimmed_within, for a call that
wants a tighter tail than the client’s default, or
OutputBudget::unlimited to keep the whole stream for one operation.
See crate::run_with_progress_within for the drop-oldest semantics and
the unit the byte ceiling counts.
Sourcepub async fn output_string(
&self,
call: impl IntoCommand<R>,
) -> Result<ProcessResult<String>>
pub async fn output_string( &self, call: impl IntoCommand<R>, ) -> Result<ProcessResult<String>>
Like CliClient::output_string, with credential injection. No lock-retry:
output_string returns Ok on a non-zero exit (it captures the result), so a
lock failure surfaces as an Ok here, not an Err the retry predicate could
match — route mutations that need lock-retry through
run/run_unit instead.
Sourcepub async fn output_bytes(
&self,
call: impl IntoCommand<R>,
) -> Result<ProcessResult<Vec<u8>>>
pub async fn output_bytes( &self, call: impl IntoCommand<R>, ) -> Result<ProcessResult<Vec<u8>>>
Like CliClient::output_bytes, with credential injection. Captures stdout
as raw bytes, byte-exact — unlike output_string,
which reassembles stdout from decoded lines and so drops a trailing newline.
This is the byte-faithful path run_untrimmed needs.
No lock-retry, for the same reason as output_string: it returns Ok
on a non-zero exit (it captures the result), so a lock failure surfaces as an
Ok here rather than an Err the retry predicate could match.
Sourcepub async fn run_untrimmed(&self, call: impl IntoCommand<R>) -> Result<String>
pub async fn run_untrimmed(&self, call: impl IntoCommand<R>) -> Result<String>
Like run, but returns stdout verbatim — no trim_end.
For content-returning verbs (a file’s bytes at a rev, a diff, a raw
template render) where the trailing newline(s) are part of the value, not
noise: trimming them corrupts a read-modify-write round-trip and desyncs a
diff’s last hunk from its @@ line count. Exit-checked like run; no
lock-retry (a content read is not a mutation).
Routed through output_bytes (raw stdout), not
output_string, so the exact bytes — trailing newline included — survive:
output_string rebuilds stdout from decoded lines and would drop that final
\n. The raw bytes are then decoded with
String::from_utf8_lossy, the same lossy raw-stdout-to-String convention
used elsewhere in this workspace (e.g. vcs-jj).
Output budget: this client’s default OutputBudget
(default_output_budget) is applied as a
fail-loud byte ceiling — a content read past the cap errors with
ErrorReason::OutputTooLarge (carrying the actual and allowed sizes) instead of
buffering an unbounded blob, and a truncated read is never returned as if
complete. Unlimited by default (unchanged behaviour). Override the ceiling
for one call with run_untrimmed_within.
The ceiling rides both captured streams independently: the raw stdout
this verb returns, counted verbatim, and the command’s line-pumped stderr,
counted as raw pipe bytes (terminators included) since processkit 3.0 — so
a command that floods stderr past the cap fails loud here too. See
OutputBudget::bytes for the per-stream unit.
Sourcepub async fn run_untrimmed_within(
&self,
call: impl IntoCommand<R>,
budget: OutputBudget,
) -> Result<String>
pub async fn run_untrimmed_within( &self, call: impl IntoCommand<R>, budget: OutputBudget, ) -> Result<String>
Like run_untrimmed, but with an explicit per-call
OutputBudget instead of this client’s default — the per-call override
used by the *_within content methods (diff_text_within,
show_file_within, pr_diff_within, …) to read a legitimately large
file/diff (a higher ceiling, or OutputBudget::unlimited) or to tighten
the cap for one call.
Sourcepub async fn probe(&self, call: impl IntoCommand<R>) -> Result<bool>
pub async fn probe(&self, call: impl IntoCommand<R>) -> Result<bool>
Like CliClient::probe (zero-or-nonzero exit → bool), with credential
injection and lock-retry.
Sourcepub async fn exit_code(&self, call: impl IntoCommand<R>) -> Result<i32>
pub async fn exit_code(&self, call: impl IntoCommand<R>) -> Result<i32>
Like CliClient::exit_code (the raw exit code; a spawn failure or timeout
still errors), with credential injection and lock-retry.
Sourcepub async fn parse<T>(
&self,
call: impl IntoCommand<R>,
parser: impl FnOnce(&str) -> T + Send,
) -> Result<T>where
T: Send,
pub async fn parse<T>(
&self,
call: impl IntoCommand<R>,
parser: impl FnOnce(&str) -> T + Send,
) -> Result<T>where
T: Send,
Like CliClient::parse (credential injection applied; the FnOnce parser
can’t be re-run, so lock-retry does not — parsing is a read, where lock
contention is not a concern anyway).
Sourcepub async fn parse_bytes<T>(
&self,
call: impl IntoCommand<R>,
parser: impl FnOnce(&[u8]) -> T + Send,
) -> Result<T>where
T: Send,
pub async fn parse_bytes<T>(
&self,
call: impl IntoCommand<R>,
parser: impl FnOnce(&[u8]) -> T + Send,
) -> Result<T>where
T: Send,
Like parse, but hands the parser raw stdout bytes
instead of a lossily-decoded &str. This is the byte-faithful path a parser
needs when a path (or any payload that need not be valid UTF-8) is part
of the output: on Unix a filename can be arbitrary bytes, so decoding it
through String::from_utf8_lossy first would substitute U+FFFD and make
the path unusable to round-trip back into add/commit_paths. Routed
through output_bytes (byte-exact stdout) and
exit-checked like parse (ensure_success); no lock-retry (a
read). Text-only machine output (branch names, hashes, templated rows) should
keep using parse — lossy decoding is acceptable there.
Sourcepub async fn try_parse<T>(
&self,
call: impl IntoCommand<R>,
parser: impl FnOnce(&str) -> Result<T> + Send,
) -> Result<T>where
T: Send,
pub async fn try_parse<T>(
&self,
call: impl IntoCommand<R>,
parser: impl FnOnce(&str) -> Result<T> + Send,
) -> Result<T>where
T: Send,
Like CliClient::try_parse (credential injection applied; FnOnce parser,
and a read, so no lock-retry).