Skip to main content

vcs_core/
error.rs

1//! The facade's error type: a thin wrapper that adds repo-detection failures on
2//! top of the underlying [`processkit::Error`] the per-tool clients return.
3
4use std::path::PathBuf;
5
6/// An error from a [`Repo`](crate::Repo) operation.
7#[derive(Debug)]
8#[non_exhaustive]
9pub enum Error {
10    /// [`Repo::open`](crate::Repo::open) found no `.git`/`.jj` from the start dir
11    /// up to the filesystem root.
12    NotARepository(PathBuf),
13    /// A worktree/workspace lookup by path matched no attached worktree.
14    WorktreeNotFound(PathBuf),
15    /// A filesystem operation failed (e.g. removing a workspace directory).
16    Io(std::io::Error),
17    /// An underlying `vcs-git` / `vcs-jj` (i.e. `processkit`) error.
18    Vcs(processkit::Error),
19}
20
21impl Error {
22    /// Whether this wraps a merge/rebase **conflict** from the backend — so a
23    /// caller can branch on "conflict, resolve it" vs. a hard failure without
24    /// matching on [`processkit::Error`] internals. (Recognises git's conflict
25    /// markers; jj surfaces conflicts as state, not errors — see
26    /// [`Repo::in_progress_state`](crate::Repo::in_progress_state).)
27    ///
28    /// Named to match the wrapper classifiers
29    /// ([`vcs_cli_support::is_merge_conflict`]) — one name per concept across the
30    /// workspace.
31    pub fn is_merge_conflict(&self) -> bool {
32        matches!(self, Error::Vcs(e) if vcs_cli_support::is_merge_conflict(e))
33    }
34
35    /// Whether this is a benign "nothing to commit" — an empty commit attempt the
36    /// caller likely wants to treat as a no-op.
37    pub fn is_nothing_to_commit(&self) -> bool {
38        matches!(self, Error::Vcs(e) if vcs_cli_support::is_nothing_to_commit(e))
39    }
40
41    /// Whether this is a **transient** fetch/network failure worth retrying
42    /// (DNS, connection reset, timeout). The underlying clients already retry
43    /// their own fetches; this is for retrying higher-level flows.
44    pub fn is_transient_fetch_error(&self) -> bool {
45        matches!(self, Error::Vcs(e) if vcs_cli_support::is_transient_fetch_error(e))
46    }
47}
48
49impl std::fmt::Display for Error {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        match self {
52            Error::NotARepository(p) => {
53                write!(
54                    f,
55                    "no git or jj repository found at or above {}",
56                    p.display()
57                )
58            }
59            Error::WorktreeNotFound(p) => {
60                write!(f, "no worktree found at {}", p.display())
61            }
62            Error::Io(e) => write!(f, "{e}"),
63            Error::Vcs(e) => write!(f, "{e}"),
64        }
65    }
66}
67
68impl std::error::Error for Error {
69    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
70        match self {
71            Error::Io(e) => Some(e),
72            Error::Vcs(e) => Some(e),
73            _ => None,
74        }
75    }
76}
77
78impl From<std::io::Error> for Error {
79    fn from(e: std::io::Error) -> Self {
80        Error::Io(e)
81    }
82}
83
84impl From<processkit::Error> for Error {
85    fn from(e: processkit::Error) -> Self {
86        Error::Vcs(e)
87    }
88}
89
90/// `Result` specialised to the facade [`Error`].
91pub type Result<T> = std::result::Result<T, Error>;