pub(crate) mod audit;
pub(crate) mod compact;
pub(crate) mod fetch;
pub mod gc;
pub(crate) mod git;
pub(crate) mod keys;
pub(crate) mod list;
pub(crate) mod manifest;
pub(crate) mod pack;
pub(crate) mod push;
pub mod read;
pub(crate) mod retry;
pub(crate) mod schema;
pub use read::{PackIndexCache, read_blob};
#[derive(Debug, thiserror::Error)]
pub enum PackchainError {
#[error("packchain schema version {found} unsupported (this build reads v{expected})")]
UnsupportedSchemaVersion {
found: u32,
expected: u32,
},
#[error("invalid 40-hex sha `{found}`: must be 40 lowercase hex characters")]
InvalidSha {
found: String,
},
#[error("packchain schema parse error: {0}")]
ParseJson(#[from] serde_json::Error),
#[error("invalid path: {} (not valid UTF-8)", String::from_utf8_lossy(bytes))]
InvalidPath {
bytes: Vec<u8>,
},
#[error("packchain git error: {0}")]
Git(#[from] crate::git::GitError),
#[error("cannot push from a shallow clone: rev-walk crosses a shallow boundary")]
ShallowPushRejected,
#[error("chain.json absent for {ref_name}; the branch is unknown to the bucket")]
ChainAbsent {
ref_name: String,
},
#[error("packchain: chain.json references missing pack at {key}")]
PackMissing {
key: String,
},
#[error("packchain: baseline bundle missing at {key}")]
BaselineMissing {
key: String,
},
#[error("pack content SHA unavailable: {0}")]
PackTrailer(String),
#[error("pack build error: {0}")]
PackBuild(String),
#[error("pack index write error: {0}")]
PackIndexWrite(Box<gix_pack::bundle::write::Error>),
#[error("packchain object-store error: {0}")]
Store(#[from] crate::object_store::ObjectStoreError),
#[error("packchain I/O error: {0}")]
Io(#[from] std::io::Error),
#[error(
"read_blob requires the packchain engine; this remote uses `{found}` — \
check the URL's `?engine=` parameter or the bucket's `FORMAT` key"
)]
WrongEngine {
found: crate::url::StorageEngine,
},
#[error("path-index.json absent for {ref_name}; the branch's path map is unavailable")]
PathIndexAbsent {
ref_name: String,
},
#[error("path `{path}` not found in {ref_name}")]
PathNotFound {
ref_name: String,
path: String,
},
#[error("malformed path `{path}`: {reason}")]
MalformedPath {
path: String,
reason: &'static str,
},
#[error("path `{path}` resolves to a directory, not a file")]
PathNotABlob {
path: String,
},
#[error("blob {sha} for path `{path}` not present in any chain pack")]
BlobNotInChain {
sha: String,
path: String,
},
#[error("malformed pack entry at offset {offset}: {reason}")]
MalformedPackEntry {
offset: u64,
reason: String,
},
#[error("zlib decompression failure for entry at offset {offset}")]
Decompress {
offset: u64,
},
#[error("pack delta chain exceeds maximum depth ({max})")]
DeltaTooDeep {
max: u32,
},
#[error("malformed delta payload: {reason}")]
MalformedDelta {
reason: &'static str,
},
#[error("invalid ref name `{name}`")]
InvalidRefName {
name: String,
},
#[error("tree {oid} forms a cycle in the path-index walk")]
TreeCycle {
oid: String,
},
#[error(
"transient chain/path-index mismatch for {ref_name}: \
chain.tip = {chain_tip}, path_index.tip = {path_index_tip}; retry"
)]
TransientChainPathIndexMismatch {
ref_name: String,
chain_tip: String,
path_index_tip: String,
},
#[error(
"read_blob exhausted {attempts} retries against concurrent GC: \
last missing pack `{last_missing_key}`; retry the call"
)]
ConcurrentGcRetriesExhausted {
last_missing_key: String,
attempts: u32,
},
}
impl From<gix_pack::bundle::write::Error> for PackchainError {
fn from(value: gix_pack::bundle::write::Error) -> Self {
Self::PackIndexWrite(Box::new(value))
}
}
impl From<tokio::task::JoinError> for PackchainError {
fn from(value: tokio::task::JoinError) -> Self {
Self::Io(std::io::Error::other(value.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unsupported_schema_version_renders_both_versions() {
let err = PackchainError::UnsupportedSchemaVersion {
found: 2,
expected: 1,
};
assert_eq!(
err.to_string(),
"packchain schema version 2 unsupported (this build reads v1)"
);
}
#[test]
fn shallow_push_rejected_includes_actionable_wording() {
let err = PackchainError::ShallowPushRejected;
let msg = err.to_string();
assert!(
msg.contains("shallow clone"),
"shallow rejection wording must mention shallow clone: {msg}",
);
}
}