Skip to main content

rtb_forge/git/
error.rs

1//! `RepoError` — the wrapped error model for the git-ops slice.
2//!
3//! Per v0.5 scope A8 (resolution 2026-05-12), backend errors (`gix`
4//! errors and `git` subprocess failures) are wrapped, not leaked, so
5//! the public API stays stable across backend swaps. Each operation
6//! stringifies its backend error into the `cause` field of its own
7//! variant at the call site.
8//! Tests assert on variant shape (`matches!(err, RepoError::OpenFailed
9//! { .. })`) rather than backend internals.
10//!
11//! See §3.4 of spec 0023 for the full variant table:
12//! <https://gitlab.com/phpboyscout/rust-tool-base/-/wikis/specs/0023-v0.5-scope>
13
14use std::path::PathBuf;
15
16use miette::Diagnostic;
17use thiserror::Error;
18
19/// Errors surfaced by every method on [`crate::git::Repo`].
20///
21/// Variant shape is part of the public API; backend internals are not.
22/// See the module docs for the rationale.
23#[derive(Debug, Error, Diagnostic)]
24#[non_exhaustive]
25pub enum RepoError {
26    /// Filesystem-path failure (missing parent, permission denied,
27    /// symlink loop). Path carried for diagnostics.
28    #[error("io error at `{path}`: {source}")]
29    #[diagnostic(code(rtb_forge::git::io))]
30    Io {
31        /// The path the operation tried to use.
32        path: PathBuf,
33        /// Underlying I/O failure.
34        #[source]
35        source: std::io::Error,
36    },
37
38    /// `Repo::open` could not load the repository at the given path.
39    /// Backend-specific reason in `cause` (stringified).
40    #[error("could not open repository at `{path}`: {cause}")]
41    #[diagnostic(code(rtb_forge::git::open_failed))]
42    OpenFailed {
43        /// The path that failed to open.
44        path: PathBuf,
45        /// Stringified backend error.
46        cause: String,
47    },
48
49    /// `Repo::init` could not create a repository at the given path.
50    #[error("could not init repository at `{path}`: {cause}")]
51    #[diagnostic(code(rtb_forge::git::init_failed))]
52    InitFailed {
53        /// The path where init was attempted.
54        path: PathBuf,
55        /// Stringified backend error.
56        cause: String,
57    },
58
59    /// Clone setup or transport failure (URL parse, refs negotiation,
60    /// network). Surfaces in `Repo::clone` (v0.5 commit 3).
61    #[error("could not clone `{url}`: {cause}")]
62    #[diagnostic(code(rtb_forge::git::clone_failed))]
63    CloneFailed {
64        /// The URL being cloned from.
65        url: String,
66        /// Stringified backend error.
67        cause: String,
68    },
69
70    /// Fetch transport / refs failure. Surfaces in `Repo::fetch`
71    /// (v0.5 commit 4).
72    #[error("could not fetch from `{remote}`: {cause}")]
73    #[diagnostic(code(rtb_forge::git::fetch_failed))]
74    FetchFailed {
75        /// The remote name (typically `origin`).
76        remote: String,
77        /// Stringified backend error.
78        cause: String,
79    },
80
81    /// Checkout could not switch to `revspec`. Surfaces in
82    /// `Repo::checkout` (v0.5 commit 4).
83    #[error("could not check out `{revspec}`: {cause}")]
84    #[diagnostic(code(rtb_forge::git::checkout_failed))]
85    CheckoutFailed {
86        /// The revspec the caller asked for.
87        revspec: String,
88        /// Stringified backend error.
89        cause: String,
90    },
91
92    /// Commit creation / staging failure. Surfaces in `Repo::commit`
93    /// (v0.5 commit 3).
94    #[error("could not create commit: {cause}")]
95    #[diagnostic(code(rtb_forge::git::commit_failed))]
96    CommitFailed {
97        /// Stringified backend error.
98        cause: String,
99    },
100
101    /// Status walk failure. Surfaces in `Repo::status` when gix
102    /// cannot iterate the worktree (e.g. permission errors).
103    #[error("could not compute status: {cause}")]
104    #[diagnostic(code(rtb_forge::git::status_failed))]
105    StatusFailed {
106        /// Stringified backend error.
107        cause: String,
108    },
109
110    /// Commit-graph walk failure. Surfaces in `Repo::walk` for
111    /// failures that aren't [`RepoError::RevspecNotFound`] (gix
112    /// internal error, object-store I/O, unsupported revspec kind).
113    #[error("could not walk commits: {cause}")]
114    #[diagnostic(code(rtb_forge::git::walk_failed))]
115    WalkFailed {
116        /// Stringified backend error.
117        cause: String,
118    },
119
120    /// Tree-diff failure. Surfaces in `Repo::diff` for failures
121    /// that aren't [`RepoError::RevspecNotFound`] (gix internal
122    /// error reading trees, blob diffing).
123    #[error("could not diff trees: {cause}")]
124    #[diagnostic(code(rtb_forge::git::diff_failed))]
125    DiffFailed {
126        /// Stringified backend error.
127        cause: String,
128    },
129
130    /// Push transport / refs failure. Surfaces in `Repo::push` when
131    /// `git push` cannot be run or exits non-zero.
132    #[error("could not push `{refspec}` to `{remote}`: {cause}")]
133    #[diagnostic(code(rtb_forge::git::push_failed))]
134    PushFailed {
135        /// The remote name.
136        remote: String,
137        /// The refspec being pushed.
138        refspec: String,
139        /// Stringified backend error.
140        cause: String,
141    },
142
143    /// Caller-facing error for revspecs (e.g. `HEAD~999`, a missing
144    /// tag) that don't resolve to a known object.
145    #[error("revspec `{revspec}` not found")]
146    #[diagnostic(code(rtb_forge::git::revspec_not_found))]
147    RevspecNotFound {
148        /// The revspec the caller asked for.
149        revspec: String,
150    },
151
152    /// Dirty-working-tree guard tripped. Listed paths are dirty.
153    /// Operations affected: `checkout`, path-based `commit`.
154    #[error("working tree is dirty: {} path(s) need attention", paths.len())]
155    #[diagnostic(code(rtb_forge::git::dirty_working_tree))]
156    DirtyWorkingTree {
157        /// The dirty paths discovered.
158        paths: Vec<PathBuf>,
159    },
160
161    /// Credential resolution failed. Wraps `rtb-credentials`'s
162    /// existing error — not a backend leak because `rtb-credentials`
163    /// is part of the framework's stable public surface.
164    #[error("credential resolution failed: {0}")]
165    #[diagnostic(code(rtb_forge::git::auth))]
166    Auth(
167        #[from]
168        #[source]
169        rtb_credentials::CredentialError,
170    ),
171}