Skip to content

RepoError variants

Every Repo method returns Result<_, RepoError>. The enum is #[non_exhaustive], so match with a _ arm.

What can I match on?

The variant shape is the public contract. The cause string is not: it carries whatever gix or git stderr said, and it changes when the backend changes. Assert on matches!(err, RepoError::OpenFailed { .. }), never on the text.

RepoError does not implement Clone, unlike ProviderError. It holds a live std::io::Error in the Io variant and a CredentialError in Auth.

Which method produces which variant?

Variant Produced by
Io reserved; no method in the crate constructs it today
OpenFailed open
InitFailed init
CloneFailed clone
FetchFailed fetch
CheckoutFailed checkout
CommitFailed commit
StatusFailed status
WalkFailed walk, and blame
DiffFailed diff
PushFailed push
RevspecNotFound walk, diff, blame, checkout
DirtyWorkingTree checkout
Auth clone, fetch, push
PushUnsupported never

OpenFailed / InitFailed

could not open repository at `{path}`: {cause}
could not init repository at `{path}`: {cause}

Codes rtb_forge::git::open_failed and rtb_forge::git::init_failed. path is the path you passed. cause is gix's stringified error, or a spawn_blocking join error: … string if the background task itself failed.

OpenFailed is what you get for a directory that is not a repository, and for a repository whose .git cannot be read.

CloneFailed

could not clone `{url}`: {cause}

Code rtb_forge::git::clone_failed. The cause prefix tells you how far it got:

Prefix Stage
prepare: … gix could not parse the URL or prepare the destination
fetch: … gix refs negotiation or object download failed
checkout: … gix could not write the working tree
spawn \git clone`: …| thegit` binary could not be launched (authenticated clones only)
git clone: … git clone exited non-zero; the rest is its stderr
open post-clone: … the clone succeeded but the result could not be opened

FetchFailed / PushFailed

could not fetch from `{remote}`: {cause}
could not push `{refspec}` to `{remote}`: {cause}

Codes rtb_forge::git::fetch_failed and rtb_forge::git::push_failed. cause is either spawn \git …`: orgit …: `.

PushFailed's API documentation claims the variant is "only reachable when the git2-fallback Cargo feature is enabled". That is stale: there is no such feature, and this is the ordinary failure variant for Repo::push.

When a spawn_blocking join fails, remote and refspec are the empty string rather than the values you passed — the owned copies were moved into the failed task. Do not rely on those fields being populated in every case.

CheckoutFailed

could not check out `{revspec}`: {cause}

Code rtb_forge::git::checkout_failed. Reached when git checkout exits non-zero for a reason that does not look like a missing revspec — see RevspecNotFound below for how that call is made.

CommitFailed

could not create commit: {cause}

Code rtb_forge::git::commit_failed. There is no path or message field; everything is in cause:

cause Meaning
no paths supplied — commit requires at least one path to stage you passed an empty slice
spawn \git add`: …| thegit` binary is missing or not executable
git add: … git add rejected a path
git commit: … nothing staged, no configured identity, a hook refused, …
git rev-parse: … the commit was created but its OID could not be read
oid utf-8: … git rev-parse produced non-UTF-8 output

Note the ordering: a git rev-parse failure means the commit did happen. Do not treat CommitFailed as proof the repository is unchanged.

StatusFailed / WalkFailed / DiffFailed

could not compute status: {cause}
could not walk commits: {cause}
could not diff trees: {cause}

Codes rtb_forge::git::status_failed, rtb_forge::git::walk_failed, rtb_forge::git::diff_failed.

WalkFailed covers more than its name suggests. It is produced by:

  • walk for an unsupported revspec kind — unsupported revspec kind: ….
  • walk for object-store failures partway through, delivered as an Err item on the stream rather than from the walk() call itself.
  • blame for every failure that is not a missing revspec — resource-cache setup, commit lookup, gix-blame internals. There is no BlameFailed variant.

RevspecNotFound

revspec `{revspec}` not found

Code rtb_forge::git::revspec_not_found. Produced by walk, diff, blame and checkout.

The revspec field is not always a bare revspec:

  • From blame, a missing file is reported as RevspecNotFound with a revspec of the form <file path> at <revspec>. There is no separate file-not-found variant, so you cannot distinguish "bad revision" from "file absent at that revision" by variant alone.
  • From checkout, the classification comes from matching git's stderr against did not match any, unknown revision, not a valid object name and pathspec. Under a non-English locale those phrases do not appear and the same situation arrives as CheckoutFailed instead.

DirtyWorkingTree

working tree is dirty: {n} path(s) need attention

Code rtb_forge::git::dirty_working_tree. Carries paths: Vec<PathBuf> — the staged bucket first, then the unstaged one.

Only checkout with force = false produces it. Because RepoStatus::staged is always empty, the staged half contributes nothing and the list is effectively the unstaged paths.

Auth

credential resolution failed: {0}

Code rtb_forge::git::auth. Wraps rtb_credentials::CredentialError as a #[source], so the underlying reason — env var unset, keychain locked, literal refused under CI=true — is available through std::error::Error::source.

This variant means the credential could not be resolved locally. It does not mean the remote rejected it: a token the server refuses produces CloneFailed / FetchFailed / PushFailed carrying git's stderr.

PushUnsupported

push is not supported without the `git2-fallback` Cargo feature

Code rtb_forge::git::push_unsupported, with help text suggesting you enable git2-fallback.

Nothing constructs this variant. It is a leftover from a design where push would have needed libgit2. Push works on any build with the git feature, and there is no git2-fallback feature to enable. The variant remains only because removing it from a #[non_exhaustive] public enum would be a breaking change.

If you are handling it in a match arm, that arm is dead code.