1use std::path::PathBuf;
2use std::{fmt, io};
3
4use git2::Error as GitError;
5
6use crate::FormatterError;
7use crate::path::{PathError, PathSelector, RepositoryPath};
8
9#[derive(Debug, thiserror::Error)]
11pub enum FilesError {
12 #[error("cannot open the Git repository at {}", path.display())]
14 Open {
15 path: PathBuf,
17 #[source]
19 source: GitError,
20 },
21 #[error("Git repository has no worktree")]
23 NoWorktree,
24 #[error(transparent)]
26 Path(#[from] PathError),
27 #[error("invalid path selector {selector:?}")]
29 InvalidSelector {
30 selector: PathSelector,
32 },
33 #[error("conflicting managed policy for `{path}` at priority {priority}")]
35 ManagedConflict {
36 path: RepositoryPath,
38 priority: u32,
40 },
41 #[error(
44 "`{first}` and `{second}` differ only by case, which collides on case-insensitive \
45 filesystems"
46 )]
47 CaseInsensitivePath {
48 first: RepositoryPath,
50 second: RepositoryPath,
52 },
53 #[error("invalid document {path}")]
55 Document {
56 path: RepositoryPath,
58 #[source]
60 source: ephemeris_documents::DocumentError,
61 },
62 #[error("cannot read the Git index")]
64 ReadIndex {
65 #[source]
67 source: GitError,
68 },
69 #[error("cannot read the Git working tree status")]
71 ReadStatus {
72 #[source]
74 source: GitError,
75 },
76 #[error("cannot write the Git index")]
78 WriteIndex {
79 #[source]
81 source: GitError,
82 },
83 #[error("cannot stage `{path}`")]
85 Stage {
86 path: RepositoryPath,
88 #[source]
90 source: GitError,
91 },
92 #[error(
95 "unsafe change to `{path}`: current worktree state is not recoverable from HEAD or index"
96 )]
97 Unsafe {
98 path: RepositoryPath,
100 },
101 #[error(
103 "tracked files in managed paths are not generated by Ephemeris: {}. Delete them or \
104 declare the path unmanaged through ephemeris.paths",
105 PathList(paths)
106 )]
107 Foreign {
108 paths: Vec<RepositoryPath>,
110 },
111 #[error("`{path}` changed since the plan was made")]
113 Stale {
114 path: RepositoryPath,
116 },
117 #[error("a snapshot plan cannot be applied to a worktree")]
119 SnapshotPlan,
120 #[error("duplicate path `{0}` in planned updates")]
122 DuplicatePath(RepositoryPath),
123 #[error(transparent)]
125 Formatter(#[from] FormatterError),
126}
127
128struct PathList<'a>(&'a [RepositoryPath]);
130
131impl fmt::Display for PathList<'_> {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 for (index, path) in self.0.iter().enumerate() {
134 if index > 0 {
135 f.write_str(", ")?;
136 }
137 write!(f, "`{path}`")?;
138 }
139 Ok(())
140 }
141}
142
143pub(crate) trait IoResultExt<T> {
145 fn at_path(self, path: impl Into<PathBuf>) -> Result<T, FilesError>;
147}
148
149impl<T> IoResultExt<T> for io::Result<T> {
150 fn at_path(self, path: impl Into<PathBuf>) -> Result<T, FilesError> {
151 self.map_err(|source| {
152 PathError::Io {
153 path: path.into(),
154 source,
155 }
156 .into()
157 })
158 }
159}