rtb-forge 0.8.1

Release-provider abstractions and backends (GitHub, GitLab, Bitbucket, Gitea, Codeberg, Direct) plus an async git-operations Repo built on gix. Part of the phpboyscout Rust toolkit.
Documentation
---
title: RepoError variants
description: Every git-operation failure variant, its miette diagnostic code, which Repo method produces it, and what the cause field contains.
tags: [reference, errors, git]
---

# `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` |

## `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\`: …` | the `git` 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 …\`: <os error>` or `git …: <stderr>`.

When a `spawn_blocking` join fails, `remote` and `refspec` are the **empty
string** rather than the values you passed, because 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\`: …` | the `git` 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, with `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](05-repo-operations.md#repostatusstaged-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.