1use std::path::PathBuf;
2
3use miette::Diagnostic;
4use thiserror::Error;
5
6pub type Result<T> = std::result::Result<T, WorkonError>;
8
9#[derive(Error, Diagnostic, Debug)]
11pub enum WorkonError {
12 #[error(transparent)]
14 #[diagnostic(code(workon::git_error))]
15 Git(#[from] git2::Error),
16
17 #[error(transparent)]
19 #[diagnostic(code(workon::io_error))]
20 Io(#[from] std::io::Error),
21
22 #[error(transparent)]
24 #[diagnostic(forward(0))]
25 Repo(#[from] RepoError),
26
27 #[error(transparent)]
29 #[diagnostic(forward(0))]
30 Worktree(#[from] WorktreeError),
31
32 #[error(transparent)]
34 #[diagnostic(forward(0))]
35 Config(#[from] ConfigError),
36
37 #[error(transparent)]
39 #[diagnostic(forward(0))]
40 DefaultBranch(#[from] DefaultBranchError),
41
42 #[error(transparent)]
44 #[diagnostic(forward(0))]
45 Pr(#[from] PrError),
46
47 #[error(transparent)]
49 #[diagnostic(forward(0))]
50 Copy(#[from] CopyError),
51}
52
53#[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#[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#[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#[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#[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#[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}