Skip to main content

workon/
error.rs

1use std::path::PathBuf;
2
3use miette::Diagnostic;
4use thiserror::Error;
5
6/// Result type alias using WorkonError
7pub type Result<T> = std::result::Result<T, WorkonError>;
8
9/// Main error type for the workon library
10#[derive(Error, Diagnostic, Debug)]
11pub enum WorkonError {
12    /// Git operation failed
13    #[error(transparent)]
14    #[diagnostic(code(workon::git_error))]
15    Git(#[from] git2::Error),
16
17    /// I/O operation failed
18    #[error(transparent)]
19    #[diagnostic(code(workon::io_error))]
20    Io(#[from] std::io::Error),
21
22    /// Repository-related errors
23    #[error(transparent)]
24    #[diagnostic(forward(0))]
25    Repo(#[from] RepoError),
26
27    /// Worktree-related errors
28    #[error(transparent)]
29    #[diagnostic(forward(0))]
30    Worktree(#[from] WorktreeError),
31
32    /// Configuration-related errors
33    #[error(transparent)]
34    #[diagnostic(forward(0))]
35    Config(#[from] ConfigError),
36
37    /// Default branch detection errors
38    #[error(transparent)]
39    #[diagnostic(forward(0))]
40    DefaultBranch(#[from] DefaultBranchError),
41
42    /// Pull request-related errors
43    #[error(transparent)]
44    #[diagnostic(forward(0))]
45    Pr(#[from] PrError),
46
47    /// File copy errors
48    #[error(transparent)]
49    #[diagnostic(forward(0))]
50    Copy(#[from] CopyError),
51}
52
53/// Repository-specific errors
54#[derive(Error, Diagnostic, Debug)]
55pub enum RepoError {
56    #[error("Not a bare repository at {0}")]
57    #[diagnostic(
58        code(workon::repo::not_bare),
59        help("Workon commands must be run in bare repositories")
60    )]
61    NotBare(String),
62}
63
64/// Worktree-specific errors
65#[derive(Error, Diagnostic, Debug)]
66pub enum WorktreeError {
67    #[error("Invalid .git file format")]
68    #[diagnostic(
69        code(workon::worktree::invalid_git_file),
70        help("The .git file should contain 'gitdir: <path>' pointing to the git directory")
71    )]
72    InvalidGitFile,
73
74    #[error("Could not find worktree '{0}'")]
75    #[diagnostic(
76        code(workon::worktree::not_found),
77        help("Use 'git workon list' to see available worktrees")
78    )]
79    NotFound(String),
80
81    #[error("Not in a worktree directory")]
82    #[diagnostic(
83        code(workon::worktree::not_in_worktree),
84        help("Run this command from within a worktree directory")
85    )]
86    NotInWorktree,
87
88    #[error("Could not determine branch target")]
89    #[diagnostic(
90        code(workon::worktree::no_branch_target),
91        help("The branch may be in an invalid state")
92    )]
93    NoBranchTarget,
94
95    #[error("Could not get current branch target")]
96    #[diagnostic(code(workon::worktree::no_current_branch_target))]
97    NoCurrentBranchTarget,
98
99    #[error("Could not get local branch target")]
100    #[diagnostic(code(workon::worktree::no_local_branch_target))]
101    NoLocalBranchTarget,
102
103    #[error("Worktree path has no parent directory")]
104    #[diagnostic(
105        code(workon::worktree::no_parent),
106        help("Cannot create parent directories for worktree path")
107    )]
108    NoParent,
109
110    #[error("Invalid worktree name: contains invalid UTF-8")]
111    #[diagnostic(
112        code(workon::worktree::invalid_name),
113        help("Worktree names must be valid UTF-8 strings")
114    )]
115    InvalidName,
116
117    #[error("Expected an empty index!")]
118    #[diagnostic(code(workon::worktree::non_empty_index))]
119    NonEmptyIndex,
120
121    #[error("Worktree '{to}' already exists")]
122    #[diagnostic(
123        code(workon::worktree::target_exists),
124        help("Choose a different name or remove the existing worktree first")
125    )]
126    TargetExists { to: String },
127
128    #[error("Cannot move detached HEAD worktree")]
129    #[diagnostic(
130        code(workon::worktree::move_detached),
131        help("Detached HEAD worktrees have no branch to rename")
132    )]
133    CannotMoveDetached,
134
135    #[error("Branch '{0}' is protected and cannot be renamed")]
136    #[diagnostic(
137        code(workon::worktree::protected_branch_move),
138        help("Protected branches are configured in workon.pruneProtectedBranches. Use --force to override.")
139    )]
140    ProtectedBranchMove(String),
141
142    #[error("Worktree is dirty (uncommitted changes)")]
143    #[diagnostic(
144        code(workon::worktree::dirty_worktree),
145        help("Commit or stash changes, or use --force to override")
146    )]
147    DirtyWorktree,
148
149    #[error("Worktree has unpushed commits")]
150    #[diagnostic(
151        code(workon::worktree::unpushed_commits),
152        help("Push commits first, or use --force to override")
153    )]
154    UnpushedCommits,
155}
156
157/// Configuration-related errors
158#[derive(Error, Diagnostic, Debug)]
159pub enum ConfigError {
160    #[error("Invalid PR format: '{format}' - {reason}")]
161    #[diagnostic(
162        code(workon::config::invalid_pr_format),
163        help("Valid placeholders: {{number}}, {{title}}, {{author}}, {{branch}}")
164    )]
165    InvalidPrFormat { format: String, reason: String },
166
167    #[error("Config entry has no value")]
168    #[diagnostic(code(workon::config::no_value))]
169    NoValue,
170}
171
172/// Default branch detection errors
173#[derive(Error, Diagnostic, Debug)]
174pub enum DefaultBranchError {
175    #[error("Could not determine default branch for remote {remote:?}")]
176    #[diagnostic(
177        code(workon::default_branch::no_remote_default),
178        help("The remote may not have a default branch configured")
179    )]
180    NoRemoteDefault { remote: Option<String> },
181
182    #[error("Remote is not connected")]
183    #[diagnostic(
184        code(workon::default_branch::not_connected),
185        help("Failed to establish connection to remote repository")
186    )]
187    NotConnected,
188
189    #[error("Could not determine default branch: neither 'main' nor 'master' exist, and init.defaultBranch is not configured")]
190    #[diagnostic(
191        code(workon::default_branch::no_default_branch),
192        help("Set init.defaultBranch in your git config, or create a 'main' or 'master' branch")
193    )]
194    NoDefaultBranch,
195}
196
197/// Pull request-related errors
198#[derive(Error, Diagnostic, Debug)]
199pub enum PrError {
200    #[error("Invalid PR reference: {input}")]
201    #[diagnostic(
202        code(workon::pr::invalid_reference),
203        help("Use formats like #123, pr-123, or https://github.com/owner/repo/pull/123")
204    )]
205    InvalidReference { input: String },
206
207    #[error("PR #{number} not found on remote {remote}")]
208    #[diagnostic(
209        code(workon::pr::not_found),
210        help("Verify the PR number exists and you have access to the repository")
211    )]
212    PrNotFound { number: u32, remote: String },
213
214    #[error("No git remote configured")]
215    #[diagnostic(
216        code(workon::pr::no_remote),
217        help("Add a remote with: git remote add origin <url>")
218    )]
219    NoRemoteConfigured,
220
221    #[error("Failed to fetch PR refs from {remote}: {message}")]
222    #[diagnostic(
223        code(workon::pr::fetch_failed),
224        help("Check your network connection and repository access")
225    )]
226    FetchFailed { remote: String, message: String },
227
228    #[error("gh CLI is not installed or not in PATH")]
229    #[diagnostic(
230        code(workon::pr::gh_not_installed),
231        help("Install gh CLI: https://cli.github.com/")
232    )]
233    GhNotInstalled,
234
235    #[error("Failed to fetch PR metadata from gh: {message}")]
236    #[diagnostic(
237        code(workon::pr::gh_fetch_failed),
238        help("Check your network connection and GitHub authentication (gh auth status)")
239    )]
240    GhFetchFailed { message: String },
241
242    #[error("Invalid JSON output from gh CLI: {message}")]
243    #[diagnostic(
244        code(workon::pr::gh_json_parse_failed),
245        help("This may indicate a gh CLI version incompatibility")
246    )]
247    GhJsonParseFailed { message: String },
248
249    #[error("Fork repository missing owner information")]
250    #[diagnostic(
251        code(workon::pr::missing_fork_owner),
252        help("This PR may be from a deleted fork")
253    )]
254    MissingForkOwner,
255}
256
257/// File copy errors
258#[derive(Error, Diagnostic, Debug)]
259pub enum CopyError {
260    #[error("Invalid glob pattern '{pattern}'")]
261    #[diagnostic(
262        code(workon::copy::invalid_glob_pattern),
263        help("Check glob pattern syntax: *, **, ?, [...]")
264    )]
265    InvalidGlobPattern {
266        pattern: String,
267        #[source]
268        source: glob::PatternError,
269    },
270
271    #[error("Path is not valid UTF-8: {}", path.display())]
272    #[diagnostic(code(workon::copy::invalid_path))]
273    InvalidPath { path: PathBuf },
274
275    #[error("Failed to read glob entry")]
276    #[diagnostic(code(workon::copy::glob_error))]
277    GlobEntry(#[from] glob::GlobError),
278
279    #[error("Failed to copy '{}' to '{}'", src.display(), dest.display())]
280    #[diagnostic(code(workon::copy::copy_failed))]
281    CopyFailed {
282        src: PathBuf,
283        dest: PathBuf,
284        #[source]
285        source: std::io::Error,
286    },
287
288    #[error("Failed to open repository at '{}'", path.display())]
289    #[diagnostic(
290        code(workon::copy::repo_open_error),
291        help("Ensure the path is a valid git repository")
292    )]
293    RepoOpen {
294        path: PathBuf,
295        #[source]
296        source: git2::Error,
297    },
298}