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 #[error(transparent)]
54 #[diagnostic(forward(0))]
55 Stack(#[from] StackError),
56}
57
58#[derive(Error, Diagnostic, Debug)]
60pub enum RepoError {
61 #[error("Not a bare repository at {0}")]
62 #[diagnostic(
63 code(workon::repo::not_bare),
64 help("Workon commands must be run in bare repositories")
65 )]
66 NotBare(String),
67}
68
69#[derive(Error, Diagnostic, Debug)]
71pub enum WorktreeError {
72 #[error("Invalid .git file format")]
73 #[diagnostic(
74 code(workon::worktree::invalid_git_file),
75 help("The .git file should contain 'gitdir: <path>' pointing to the git directory")
76 )]
77 InvalidGitFile,
78
79 #[error("Could not find worktree '{0}'")]
80 #[diagnostic(
81 code(workon::worktree::not_found),
82 help("Use 'git workon list' to see available worktrees")
83 )]
84 NotFound(String),
85
86 #[error("Not in a worktree directory")]
87 #[diagnostic(
88 code(workon::worktree::not_in_worktree),
89 help("Run this command from within a worktree directory")
90 )]
91 NotInWorktree,
92
93 #[error("Could not determine branch target")]
94 #[diagnostic(
95 code(workon::worktree::no_branch_target),
96 help("The branch may be in an invalid state")
97 )]
98 NoBranchTarget,
99
100 #[error("Could not get current branch target")]
101 #[diagnostic(code(workon::worktree::no_current_branch_target))]
102 NoCurrentBranchTarget,
103
104 #[error("Could not get local branch target")]
105 #[diagnostic(code(workon::worktree::no_local_branch_target))]
106 NoLocalBranchTarget,
107
108 #[error("Worktree path has no parent directory")]
109 #[diagnostic(
110 code(workon::worktree::no_parent),
111 help("Cannot create parent directories for worktree path")
112 )]
113 NoParent,
114
115 #[error("Invalid worktree name: contains invalid UTF-8")]
116 #[diagnostic(
117 code(workon::worktree::invalid_name),
118 help("Worktree names must be valid UTF-8 strings")
119 )]
120 InvalidName,
121
122 #[error("Expected an empty index!")]
123 #[diagnostic(code(workon::worktree::non_empty_index))]
124 NonEmptyIndex,
125
126 #[error("Worktree '{to}' already exists")]
127 #[diagnostic(
128 code(workon::worktree::target_exists),
129 help("Choose a different name or remove the existing worktree first")
130 )]
131 TargetExists { to: String },
132
133 #[error("Cannot move detached HEAD worktree")]
134 #[diagnostic(
135 code(workon::worktree::move_detached),
136 help("Detached HEAD worktrees have no branch to rename")
137 )]
138 CannotMoveDetached,
139
140 #[error("Branch '{0}' is protected and cannot be renamed")]
141 #[diagnostic(
142 code(workon::worktree::protected_branch_move),
143 help("Protected branches are configured in workon.pruneProtectedBranches. Use --force to override.")
144 )]
145 ProtectedBranchMove(String),
146
147 #[error("Worktree is dirty (uncommitted changes)")]
148 #[diagnostic(
149 code(workon::worktree::dirty_worktree),
150 help("Commit or stash changes, or use --force to override")
151 )]
152 DirtyWorktree,
153
154 #[error("Worktree has unpushed commits")]
155 #[diagnostic(
156 code(workon::worktree::unpushed_commits),
157 help("Push commits first, or use --force to override")
158 )]
159 UnpushedCommits,
160}
161
162#[derive(Error, Diagnostic, Debug)]
164pub enum ConfigError {
165 #[error("Invalid PR format: '{format}' - {reason}")]
166 #[diagnostic(
167 code(workon::config::invalid_pr_format),
168 help("Valid placeholders: {{number}}, {{title}}, {{author}}, {{branch}}")
169 )]
170 InvalidPrFormat { format: String, reason: String },
171
172 #[error("Config entry has no value")]
173 #[diagnostic(code(workon::config::no_value))]
174 NoValue,
175}
176
177#[derive(Error, Diagnostic, Debug)]
179pub enum DefaultBranchError {
180 #[error("Could not determine default branch for remote {remote:?}")]
181 #[diagnostic(
182 code(workon::default_branch::no_remote_default),
183 help("The remote may not have a default branch configured")
184 )]
185 NoRemoteDefault { remote: Option<String> },
186
187 #[error("Remote is not connected")]
188 #[diagnostic(
189 code(workon::default_branch::not_connected),
190 help("Failed to establish connection to remote repository")
191 )]
192 NotConnected,
193
194 #[error("Could not determine default branch: neither 'main' nor 'master' exist, and init.defaultBranch is not configured")]
195 #[diagnostic(
196 code(workon::default_branch::no_default_branch),
197 help("Set init.defaultBranch in your git config, or create a 'main' or 'master' branch")
198 )]
199 NoDefaultBranch,
200}
201
202#[derive(Error, Diagnostic, Debug)]
204pub enum StackError {
205 #[error("Stack model '{model}' is not yet supported")]
206 #[diagnostic(
207 code(workon::stack::unsupported_model),
208 help(
209 "Only 'graphite' is implemented in this version. \
210 Support for branchless, sapling, and spr is planned."
211 )
212 )]
213 UnsupportedModel { model: String },
214
215 #[error("Unknown stack model '{value}'")]
216 #[diagnostic(
217 code(workon::stack::unknown_model),
218 help("Valid values: graphite, none, auto")
219 )]
220 UnknownModel { value: String },
221
222 #[error("Worktree granularity 'diff' is not yet implemented")]
223 #[diagnostic(
224 code(workon::stack::unsupported_granularity),
225 help(
226 "Only 'stack' (one worktree per stack) is supported in this version. \
227 'diff' (one worktree per branch) is planned."
228 )
229 )]
230 UnsupportedGranularity,
231
232 #[error("Unknown worktree granularity '{value}'")]
233 #[diagnostic(code(workon::stack::unknown_granularity), help("Valid values: stack"))]
234 UnknownGranularity { value: String },
235
236 #[error("Graphite CLI ('gt') is not installed or not in PATH")]
237 #[diagnostic(
238 code(workon::stack::gt_not_installed),
239 help(
240 "Install Graphite: https://graphite.dev/cli \
241 Or set workon.stackModel = none to disable stack support."
242 )
243 )]
244 GtNotInstalled,
245
246 #[error("Graphite command failed: {stderr}")]
247 #[diagnostic(code(workon::stack::gt_command_failed))]
248 GtCommandFailed { stderr: String },
249
250 #[error("Failed to parse Graphite metadata: {message}")]
251 #[diagnostic(code(workon::stack::gt_parse_failed))]
252 GtParseFailed { message: String },
253
254 #[error("Repository is not Graphite-managed (no .graphite_repo_config)")]
255 #[diagnostic(
256 code(workon::stack::not_a_graphite_repo),
257 help("Run 'gt init' in this repository, or unset workon.stackModel.")
258 )]
259 NotAGraphiteRepo,
260}
261
262#[derive(Error, Diagnostic, Debug)]
264pub enum PrError {
265 #[error("Invalid PR reference: {input}")]
266 #[diagnostic(
267 code(workon::pr::invalid_reference),
268 help("Use formats like #123, pr-123, or https://github.com/owner/repo/pull/123")
269 )]
270 InvalidReference { input: String },
271
272 #[error("PR #{number} not found on remote {remote}")]
273 #[diagnostic(
274 code(workon::pr::not_found),
275 help("Verify the PR number exists and you have access to the repository")
276 )]
277 PrNotFound { number: u32, remote: String },
278
279 #[error("No git remote configured")]
280 #[diagnostic(
281 code(workon::pr::no_remote),
282 help("Add a remote with: git remote add origin <url>")
283 )]
284 NoRemoteConfigured,
285
286 #[error("Failed to fetch PR refs from {remote}: {message}")]
287 #[diagnostic(
288 code(workon::pr::fetch_failed),
289 help("Check your network connection and repository access")
290 )]
291 FetchFailed { remote: String, message: String },
292
293 #[error("gh CLI is not installed or not in PATH")]
294 #[diagnostic(
295 code(workon::pr::gh_not_installed),
296 help("Install gh CLI: https://cli.github.com/")
297 )]
298 GhNotInstalled,
299
300 #[error("Failed to fetch PR metadata from gh: {message}")]
301 #[diagnostic(
302 code(workon::pr::gh_fetch_failed),
303 help("Check your network connection and GitHub authentication (gh auth status)")
304 )]
305 GhFetchFailed { message: String },
306
307 #[error("Invalid JSON output from gh CLI: {message}")]
308 #[diagnostic(
309 code(workon::pr::gh_json_parse_failed),
310 help("This may indicate a gh CLI version incompatibility")
311 )]
312 GhJsonParseFailed { message: String },
313
314 #[error("Fork repository missing owner information")]
315 #[diagnostic(
316 code(workon::pr::missing_fork_owner),
317 help("This PR may be from a deleted fork")
318 )]
319 MissingForkOwner,
320}
321
322#[derive(Error, Diagnostic, Debug)]
324pub enum CopyError {
325 #[error("Invalid glob pattern '{pattern}'")]
326 #[diagnostic(
327 code(workon::copy::invalid_glob_pattern),
328 help("Check glob pattern syntax: *, **, ?, [...]")
329 )]
330 InvalidGlobPattern {
331 pattern: String,
332 #[source]
333 source: glob::PatternError,
334 },
335
336 #[error("Path is not valid UTF-8: {}", path.display())]
337 #[diagnostic(code(workon::copy::invalid_path))]
338 InvalidPath { path: PathBuf },
339
340 #[error("Failed to read glob entry")]
341 #[diagnostic(code(workon::copy::glob_error))]
342 GlobEntry(#[from] glob::GlobError),
343
344 #[error("Failed to copy '{}' to '{}'", src.display(), dest.display())]
345 #[diagnostic(code(workon::copy::copy_failed))]
346 CopyFailed {
347 src: PathBuf,
348 dest: PathBuf,
349 #[source]
350 source: std::io::Error,
351 },
352
353 #[error("Failed to open repository at '{}'", path.display())]
354 #[diagnostic(
355 code(workon::copy::repo_open_error),
356 help("Ensure the path is a valid git repository")
357 )]
358 RepoOpen {
359 path: PathBuf,
360 #[source]
361 source: git2::Error,
362 },
363}