#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum RetentionError {
#[error("malformed retention input")]
Malformed(#[source] Box<dyn std::error::Error + Send + Sync>),
#[error("reading pseudoref at {path}")]
PseudorefIo {
path: std::path::PathBuf,
#[source]
source: std::io::Error,
},
#[error("reading retention input")]
Read(#[source] Box<dyn std::error::Error + Send + Sync>),
#[error("gc.recentObjectsHook is unsupported by retention planning")]
RecentObjectsHook,
#[error("precious-object repository forbids deletion planning")]
PreciousObjects,
#[error("partial clone has an incomplete local object inventory")]
PartialClone,
#[error("aggregate reflog byte limit")]
AggregateReflogByteLimit,
#[error("aggregate reflog record limit")]
AggregateReflogRecordLimit,
#[error("index entry limit")]
IndexEntryLimit,
#[error("aggregate root limit")]
AggregateRootLimit,
#[error("symbolic reference cycle")]
SymbolicReferenceCycle,
#[error("symbolic reference depth limit")]
SymbolicReferenceDepthLimit,
#[error("object directory entry limit")]
ObjectDirectoryEntryLimit,
#[error("non-directory object entry")]
NonDirectoryObjectEntry,
#[error("pack entry limit")]
PackEntryLimit,
#[error("non-UTF-8 object fanout")]
NonUtf8ObjectFanout,
#[error("unknown object directory entry")]
UnknownObjectDirectoryEntry,
#[error("loose object entry limit")]
LooseObjectEntryLimit,
#[error("non-file loose object")]
NonFileLooseObject,
#[error("non-UTF-8 loose object")]
NonUtf8LooseObject,
#[error("reachable object limit")]
ReachableObjectLimit,
#[error("reachable byte limit")]
ReachableByteLimit,
#[error("reachable edge limit")]
ReachableEdgeLimit,
#[error("pack directory entry limit")]
PackDirectoryEntryLimit,
#[error("missing pinned plan store")]
MissingPinnedPlanStore,
#[error("non-directory object fanout")]
NonDirectoryObjectFanout,
#[error("reflog byte limit")]
ReflogByteLimit,
#[error("repository layout changed")]
RepositoryLayoutChanged,
#[error("retention inputs changed")]
RetentionInputsChanged,
#[error("retention output changed")]
RetentionOutputChanged,
#[error("non-file retention input target")]
NonFileRetentionInputTarget,
#[error("input entry limit")]
InputEntryLimit,
#[error("non-file retention input")]
NonFileRetentionInput,
#[error("input byte limit")]
InputByteLimit,
#[error("invalid caller head")]
InvalidCallerHead,
#[error("caller head limit")]
CallerHeadLimit,
#[error("protected pack has no readable index")]
MissingProtectedIndex,
#[error("missing protected object")]
MissingProtectedObject,
#[error("protected packed object limit")]
ProtectedPackedObjectLimit,
#[error("retention scan cancelled")]
Cancelled,
#[error("input entry overflow")]
InputEntryOverflow,
#[error("input byte overflow")]
InputByteOverflow,
#[error("reachable byte overflow")]
ReachableByteOverflow,
#[error("reflog disappeared during discovery: {0:?}")]
ReflogDisappeared(crate::refs::RefName),
#[error("reflog inventory is incomplete: {0:?}")]
ReflogLimit(&'static str),
#[error("reflog records are incomplete")]
ReflogIncomplete,
#[error("pseudoref exceeds its byte bound: {0:?}")]
PseudorefByteLimit(std::path::PathBuf),
#[error("pseudoref cannot be interpreted: {0:?}")]
MalformedPseudoref(std::path::PathBuf),
#[error("pack entry is not a file: {0:?}")]
NonFilePackEntry(std::path::PathBuf),
#[error("reachable object is missing: {0}")]
MissingObject(crate::ObjectId),
#[error("reachable object has the wrong kind: {0}")]
WrongObjectKind(crate::ObjectId),
#[error("object contains a null edge: {0}")]
NullEdge(crate::ObjectId),
#[error("graph edges disagree about object kind: {0}")]
ConflictingObjectKind(crate::ObjectId),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum RetentionErrorKind {
Limit,
Unsupported,
Malformed,
Changed,
Cancelled,
Read,
}
impl RetentionError {
pub fn kind(&self) -> RetentionErrorKind {
match self {
Self::AggregateReflogByteLimit
| Self::AggregateReflogRecordLimit
| Self::IndexEntryLimit
| Self::AggregateRootLimit
| Self::SymbolicReferenceDepthLimit
| Self::ObjectDirectoryEntryLimit
| Self::PackEntryLimit
| Self::LooseObjectEntryLimit
| Self::ReachableObjectLimit
| Self::ReachableByteLimit
| Self::ReachableEdgeLimit
| Self::PackDirectoryEntryLimit
| Self::ReflogByteLimit
| Self::InputEntryLimit
| Self::InputByteLimit
| Self::CallerHeadLimit
| Self::ProtectedPackedObjectLimit
| Self::InputEntryOverflow
| Self::InputByteOverflow
| Self::ReachableByteOverflow
| Self::ReflogLimit(..)
| Self::PseudorefByteLimit(..) => RetentionErrorKind::Limit,
Self::RecentObjectsHook | Self::PreciousObjects | Self::PartialClone => {
RetentionErrorKind::Unsupported
}
Self::RepositoryLayoutChanged
| Self::RetentionInputsChanged
| Self::RetentionOutputChanged
| Self::ReflogDisappeared(..) => RetentionErrorKind::Changed,
Self::SymbolicReferenceCycle
| Self::NonDirectoryObjectEntry
| Self::NonUtf8ObjectFanout
| Self::UnknownObjectDirectoryEntry
| Self::NonFileLooseObject
| Self::NonUtf8LooseObject
| Self::MissingPinnedPlanStore
| Self::NonDirectoryObjectFanout
| Self::NonFileRetentionInputTarget
| Self::NonFileRetentionInput
| Self::InvalidCallerHead
| Self::MissingProtectedIndex
| Self::MissingProtectedObject
| Self::ReflogIncomplete
| Self::MalformedPseudoref(..)
| Self::NonFilePackEntry(..)
| Self::MissingObject(..)
| Self::WrongObjectKind(..)
| Self::NullEdge(..)
| Self::ConflictingObjectKind(..) => RetentionErrorKind::Malformed,
Self::Cancelled => RetentionErrorKind::Cancelled,
Self::Malformed(..) => RetentionErrorKind::Malformed,
Self::PseudorefIo { .. } => RetentionErrorKind::Read,
Self::Read(..) => RetentionErrorKind::Read,
}
}
pub(super) fn reflog(log: crate::refs::ImportedReflog) -> Self {
match log.into_end() {
crate::refs::ReflogReadEnd::Eof => Self::ReflogIncomplete,
crate::refs::ReflogReadEnd::Limit(bound) => Self::ReflogLimit(bound),
crate::refs::ReflogReadEnd::Cancelled => Self::Cancelled,
crate::refs::ReflogReadEnd::Io(error) => Self::read(error),
}
}
pub(super) fn malformed(error: impl std::error::Error + Send + Sync + 'static) -> Self {
Self::Malformed(Box::new(error))
}
pub(super) fn from_prune(error: super::PruneCause) -> Self {
match error {
super::PruneCause::Incomplete(error) => error,
super::PruneCause::Cancelled => Self::Cancelled,
super::PruneCause::Io(error) => Self::read(error),
error => Self::read(error),
}
}
pub(super) fn read(error: impl std::error::Error + Send + Sync + 'static) -> Self {
Self::Read(Box::new(error))
}
}
#[cfg(test)]
mod tests {
use std::error::Error;
use super::RetentionError;
#[test]
fn prune_inventory_conditions_keep_their_category() {
let error = RetentionError::from_prune(super::super::PruneCause::Incomplete(
RetentionError::LooseObjectEntryLimit,
));
assert!(matches!(error, RetentionError::LooseObjectEntryLimit));
assert_eq!(error.kind(), super::RetentionErrorKind::Limit);
let cancelled = RetentionError::from_prune(super::super::PruneCause::Cancelled);
assert_eq!(cancelled.kind(), super::RetentionErrorKind::Cancelled);
}
#[test]
fn read_preserves_source_type() {
let error = RetentionError::read(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"denied",
));
let source = error
.source()
.unwrap()
.downcast_ref::<std::io::Error>()
.unwrap();
assert_eq!(source.kind(), std::io::ErrorKind::PermissionDenied);
assert_eq!(error.to_string(), "reading retention input");
}
}