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