Skip to main content

grit_lib/refs/store/
error.rs

1//! Errors for [`super::RefStore`] operations.
2
3use std::fmt;
4use std::sync::Arc;
5
6use crate::git_dir::GitDirPathOperation;
7use crate::refs::RefnameUnavailable;
8
9use super::{Expected, RawRef};
10
11/// Failure modes for reference store reads and transactions.
12#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
13#[non_exhaustive]
14pub enum RefStoreError {
15    /// Another prepared transaction holds the store lock.
16    #[error("cannot lock ref '{name}': lock held")]
17    LockHeld {
18        /// Ref name associated with the active lock (often the first update name).
19        name: String,
20    },
21
22    /// Compare-and-swap or existence check failed for an update.
23    #[error("ref '{name}' did not match expected state")]
24    ExpectedMismatch {
25        /// Ref that failed the check.
26        name: String,
27        /// What the caller required.
28        expected: Expected,
29        /// What was observed (`None` when the ref is absent).
30        actual: Option<RawRef>,
31    },
32
33    /// A ref name cannot be created because of a directory/file conflict.
34    #[error("cannot lock ref: {}", reason.lock_message_suffix())]
35    NameUnavailable {
36        /// Structured conflict reason (Git `refs_verify_refname_available` style).
37        reason: RefnameUnavailable,
38    },
39
40    /// The same ref name appears more than once in one transaction.
41    #[error("multiple updates for ref '{name}' not allowed")]
42    DuplicateUpdate {
43        /// Duplicate ref name.
44        name: String,
45    },
46
47    /// Symbolic reference resolution exceeded [`crate::refs::SYMREF_MAXDEPTH`].
48    #[error("symbolic ref loop or chain too deep")]
49    SymrefLoop,
50
51    /// The ref (or the target at the end of its symbolic chain) does not exist.
52    #[error("ref not found: {name}")]
53    NotFound {
54        /// Name of the missing ref: the requested name, or the dangling symbolic target.
55        name: String,
56    },
57
58    /// Stored data for a ref or reflog is malformed.
59    #[error("corrupt ref store: {0}")]
60    Corrupt(String),
61
62    /// Filesystem I/O on a git-directory path during ref store work.
63    #[error("{}", crate::git_dir::io_error::format_git_dir_io_message(.path, *operation, &Self::git_dir_io_source(*.os_error)))]
64    GitDirPathIo {
65        /// Display path such as `.git/refs/heads/main` or `.git/worktrees/wt/HEAD.lock`.
66        path: String,
67        /// What the operation was trying to do when I/O failed.
68        operation: GitDirPathOperation,
69        /// Raw OS error code from the failing I/O call.
70        os_error: i32,
71    },
72
73    /// Ref name is not safe to store (traversal, illegal component, etc.).
74    ///
75    /// Every backend checks update names and both sides of reflog and case-only renames
76    /// in [`super::RefStore::prepare`], before taking locks.
77    #[error("refusing to update ref with bad name '{name}'")]
78    InvalidRefName {
79        /// The rejected ref name.
80        name: String,
81    },
82
83    /// An update would store the all-zero (null) object id as a direct ref value.
84    ///
85    /// The null id names no object, so Git treats such a ref as broken. Delete a ref with
86    /// `new_value: None` instead. Reflog-only updates
87    /// ([`super::RefUpdateFlags::log_only`]) may still carry the null id.
88    #[error("refusing to point ref '{name}' at the null object id")]
89    NullOid {
90        /// The ref the update targeted.
91        name: String,
92    },
93
94    /// Reflog identity or message would forge or corrupt a log line.
95    #[error(transparent)]
96    InvalidReflog(#[from] crate::ident::SignatureError),
97
98    /// A custom backend's own storage failed (database, network, …).
99    ///
100    /// Built-in backends never return this; their I/O failures stay typed elsewhere.
101    #[error(transparent)]
102    Backend(BackendError),
103}
104
105impl RefStoreError {
106    pub(crate) fn git_dir_path_io(
107        path: String,
108        operation: GitDirPathOperation,
109        source: std::io::Error,
110    ) -> Self {
111        Self::GitDirPathIo {
112            path,
113            operation,
114            os_error: source.raw_os_error().unwrap_or(0),
115        }
116    }
117
118    pub(crate) fn git_dir_io_source(os_error: i32) -> std::io::Error {
119        if os_error == 0 {
120            std::io::Error::from(std::io::ErrorKind::Other)
121        } else {
122            std::io::Error::from_raw_os_error(os_error)
123        }
124    }
125
126    pub(crate) fn not_found(name: &str) -> Self {
127        Self::NotFound {
128            name: name.to_owned(),
129        }
130    }
131
132    /// Wrap a custom backend's storage error as [`Self::Backend`].
133    pub fn backend(err: impl std::error::Error + Send + Sync + 'static) -> Self {
134        Self::Backend(BackendError::new(err))
135    }
136}
137
138/// A custom ref backend's storage error, carried by [`RefStoreError::Backend`].
139///
140/// Cheap to clone (the error is shared). Two values are equal only when they share the same
141/// underlying error. Use [`Self::downcast_ref`] to recover the backend's concrete type.
142#[derive(Clone)]
143pub struct BackendError(Arc<dyn std::error::Error + Send + Sync + 'static>);
144
145impl BackendError {
146    /// Wrap `err`.
147    pub fn new(err: impl std::error::Error + Send + Sync + 'static) -> Self {
148        Self(Arc::new(err))
149    }
150
151    /// Borrow the wrapped error.
152    #[must_use]
153    pub fn get_ref(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
154        self.0.as_ref()
155    }
156
157    /// The wrapped error as `E`, when it has that type.
158    #[must_use]
159    pub fn downcast_ref<E: std::error::Error + 'static>(&self) -> Option<&E> {
160        self.0.downcast_ref::<E>()
161    }
162}
163
164impl fmt::Debug for BackendError {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        fmt::Debug::fmt(&self.0, f)
167    }
168}
169
170impl fmt::Display for BackendError {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        fmt::Display::fmt(&self.0, f)
173    }
174}
175
176impl std::error::Error for BackendError {
177    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
178        self.0.source()
179    }
180}
181
182impl PartialEq for BackendError {
183    fn eq(&self, other: &Self) -> bool {
184        Arc::ptr_eq(&self.0, &other.0)
185    }
186}
187
188impl Eq for BackendError {}
189
190impl From<BackendError> for RefStoreError {
191    fn from(err: BackendError) -> Self {
192        Self::Backend(err)
193    }
194}
195
196impl From<RefnameUnavailable> for RefStoreError {
197    fn from(reason: RefnameUnavailable) -> Self {
198        Self::NameUnavailable { reason }
199    }
200}