use std::path::PathBuf;
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
NotARepository(PathBuf),
BareRepository(PathBuf),
WorktreeNotFound(PathBuf),
Io(std::io::Error),
Vcs(processkit::Error),
Rollback(vcs_jj::Rollback),
Unsupported(String),
}
impl Error {
pub fn is_merge_conflict(&self) -> bool {
matches!(self, Error::Vcs(e) if vcs_cli_support::is_merge_conflict(e))
}
pub fn is_nothing_to_commit(&self) -> bool {
matches!(self, Error::Vcs(e) if vcs_cli_support::is_nothing_to_commit(e))
}
pub fn is_transient_fetch_error(&self) -> bool {
matches!(self, Error::Vcs(e) if vcs_cli_support::is_transient_fetch_error(e))
}
pub fn is_transient(&self) -> bool {
matches!(self, Error::Vcs(e) if e.is_transient())
}
pub fn is_not_found(&self) -> bool {
matches!(self, Error::Vcs(e) if e.is_not_found())
}
pub fn is_invalid_input(&self) -> bool {
match self {
Error::Io(e) => e.kind() == std::io::ErrorKind::InvalidInput,
Error::Vcs(e) => vcs_cli_support::is_invalid_input(e),
_ => false,
}
}
pub fn is_resource_not_found(&self) -> bool {
matches!(self, Error::WorktreeNotFound(_))
}
pub fn is_unsupported(&self) -> bool {
matches!(self, Error::Unsupported(_))
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::NotARepository(p) => {
write!(
f,
"no git or jj repository found at or above {}",
p.display()
)
}
Error::BareRepository(p) => {
write!(f, "bare git repositories are unsupported ({})", p.display())
}
Error::WorktreeNotFound(p) => {
write!(f, "no worktree found at {}", p.display())
}
Error::Io(e) => write!(f, "{e}"),
Error::Vcs(e) => write!(f, "{e}"),
Error::Rollback(r) => {
write!(f, "operation rollback did not complete cleanly: {r}")
}
Error::Unsupported(what) => write!(f, "unsupported operation: {what}"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Io(e) => Some(e),
Error::Vcs(e) => Some(e),
Error::Rollback(r) => r.failure().map(|e| e as &(dyn std::error::Error + 'static)),
_ => None,
}
}
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::Io(e)
}
}
impl From<processkit::Error> for Error {
fn from(e: processkit::Error) -> Self {
Error::Vcs(e)
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_transient_delegates_to_processkit_and_excludes_facade_variants() {
let interrupted = Error::Vcs(processkit::Error::spawn(
"git",
std::io::Error::from(std::io::ErrorKind::Interrupted),
));
assert!(interrupted.is_transient());
let missing = Error::Vcs(processkit::Error::spawn(
"git",
std::io::Error::from(std::io::ErrorKind::NotFound),
));
assert!(!missing.is_transient());
assert!(!Error::Io(std::io::Error::from(std::io::ErrorKind::Interrupted)).is_transient());
assert!(!Error::NotARepository("/x".into()).is_transient());
}
#[test]
fn is_not_found_only_for_a_missing_binary() {
let not_found = Error::Vcs(processkit::Error::not_found("jj", None));
assert!(not_found.is_not_found());
let exit = Error::Vcs(processkit::Error::exit(
"git",
1,
"",
"fatal: not a git repository",
));
assert!(!exit.is_not_found());
assert!(!Error::NotARepository("/x".into()).is_not_found());
}
#[test]
fn is_invalid_input_for_guard_rejections_and_facade_input_errors() {
let guarded = Error::Vcs(processkit::Error::spawn(
"git",
std::io::Error::new(std::io::ErrorKind::InvalidInput, "flag-like"),
));
assert!(guarded.is_invalid_input());
assert!(
Error::Io(std::io::Error::from(std::io::ErrorKind::InvalidInput)).is_invalid_input()
);
assert!(
!Error::Vcs(processkit::Error::spawn(
"git",
std::io::Error::from(std::io::ErrorKind::NotFound),
))
.is_invalid_input()
);
assert!(!Error::NotARepository("/x".into()).is_invalid_input());
assert!(!Error::Io(std::io::Error::other("disk full")).is_invalid_input());
}
#[test]
fn is_unsupported_only_for_the_unsupported_variant() {
let unsupported = Error::Unsupported("continue during a bisect".into());
assert!(unsupported.is_unsupported());
assert!(unsupported.to_string().contains("bisect"));
assert!(!unsupported.is_invalid_input());
assert!(
!Error::Io(std::io::Error::from(std::io::ErrorKind::InvalidInput)).is_unsupported()
);
assert!(!Error::NotARepository("/x".into()).is_unsupported());
}
#[test]
fn is_resource_not_found_only_for_a_worktree_lookup() {
assert!(Error::WorktreeNotFound("/wt".into()).is_resource_not_found());
let missing_bin = Error::Vcs(processkit::Error::not_found("jj", None));
assert!(missing_bin.is_not_found() && !missing_bin.is_resource_not_found());
assert!(!Error::NotARepository("/x".into()).is_resource_not_found());
}
}