1049 lines · 35.3 KB · Rust
RawDownload
// Copyright 2022-2024 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::error;use std::error::Error as _;use std::io;use std::io::Write as _;use std::iter;use std::process::ExitCode;use std::str;use std::sync::Arc; use itertools::Itertools as _;use jj_lib::absorb::AbsorbError;use jj_lib::backend::BackendError;use jj_lib::config::ConfigFileSaveError;use jj_lib::config::ConfigGetError;use jj_lib::config::ConfigLoadError;use jj_lib::config::ConfigMigrateError;use jj_lib::dsl_util::Diagnostics;use jj_lib::fileset::FilePatternParseError;use jj_lib::fileset::FilesetParseError;use jj_lib::fileset::FilesetParseErrorKind;use jj_lib::fix::FixError;use jj_lib::gitignore::GitIgnoreError;use jj_lib::op_heads_store::OpHeadResolutionError;use jj_lib::op_heads_store::OpHeadsStoreError;use jj_lib::op_store::OpStoreError;use jj_lib::op_walk::OpsetEvaluationError;use jj_lib::op_walk::OpsetResolutionError;use jj_lib::repo::CheckOutCommitError;use jj_lib::repo::EditCommitError;use jj_lib::repo::RepoLoaderError;use jj_lib::repo::RewriteRootCommit;use jj_lib::repo_path::RepoPathBuf;use jj_lib::repo_path::UiPathParseError;use jj_lib::revset;use jj_lib::revset::RevsetEvaluationError;use jj_lib::revset::RevsetParseError;use jj_lib::revset::RevsetParseErrorKind;use jj_lib::revset::RevsetResolutionError;use jj_lib::str_util::StringPatternParseError;use jj_lib::view::RenameWorkspaceError;use jj_lib::working_copy::RecoverWorkspaceError;use jj_lib::working_copy::ResetError;use jj_lib::working_copy::SnapshotError;use jj_lib::working_copy::WorkingCopyStateError;use jj_lib::workspace::WorkspaceInitError;use thiserror::Error; use crate::cli_util::short_operation_hash;use crate::description_util::ParseBulkEditMessageError;use crate::description_util::TempTextEditError;use crate::description_util::TextEditError;use crate::diff_util::DiffRenderError;use crate::formatter::FormatRecorder;use crate::formatter::Formatter;use crate::merge_tools::ConflictResolveError;use crate::merge_tools::DiffEditError;use crate::merge_tools::MergeToolConfigError;use crate::merge_tools::MergeToolPartialResolutionError;use crate::revset_util::BookmarkNameParseError;use crate::revset_util::UserRevsetEvaluationError;use crate::template_parser::TemplateParseError;use crate::template_parser::TemplateParseErrorKind;use crate::ui::Ui; #[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum CommandErrorKind {    User,    Config,    /// Invalid command line. The inner error type may be `clap::Error`.    Cli,    BrokenPipe,    Internal,} #[derive(Clone, Debug)]pub struct CommandError {    pub kind: CommandErrorKind,    pub error: Arc<dyn error::Error + Send + Sync>,    pub hints: Vec<ErrorHint>,} impl CommandError {    pub fn new(        kind: CommandErrorKind,        err: impl Into<Box<dyn error::Error + Send + Sync>>,    ) -> Self {        CommandError {            kind,            error: Arc::from(err.into()),            hints: vec![],        }    }     pub fn with_message(        kind: CommandErrorKind,        message: impl Into<String>,        source: impl Into<Box<dyn error::Error + Send + Sync>>,    ) -> Self {        Self::new(kind, ErrorWithMessage::new(message, source))    }     /// Returns error with the given plain-text `hint` attached.    pub fn hinted(mut self, hint: impl Into<String>) -> Self {        self.add_hint(hint);        self    }     /// Appends plain-text `hint` to the error.    pub fn add_hint(&mut self, hint: impl Into<String>) {        self.hints.push(ErrorHint::PlainText(hint.into()));    }     /// Appends formatted `hint` to the error.    pub fn add_formatted_hint(&mut self, hint: FormatRecorder) {        self.hints.push(ErrorHint::Formatted(hint));    }     /// Constructs formatted hint and appends it to the error.    pub fn add_formatted_hint_with(        &mut self,        write: impl FnOnce(&mut dyn Formatter) -> io::Result<()>,    ) {        let mut formatter = FormatRecorder::new();        write(&mut formatter).expect("write() to FormatRecorder should never fail");        self.add_formatted_hint(formatter);    }     /// Appends 0 or more plain-text `hints` to the error.    pub fn extend_hints(&mut self, hints: impl IntoIterator<Item = String>) {        self.hints            .extend(hints.into_iter().map(ErrorHint::PlainText));    }} #[derive(Clone, Debug)]pub enum ErrorHint {    PlainText(String),    Formatted(FormatRecorder),} /// Wraps error with user-visible message.#[derive(Debug, Error)]#[error("{message}")]struct ErrorWithMessage {    message: String,    source: Box<dyn error::Error + Send + Sync>,} impl ErrorWithMessage {    fn new(        message: impl Into<String>,        source: impl Into<Box<dyn error::Error + Send + Sync>>,    ) -> Self {        ErrorWithMessage {            message: message.into(),            source: source.into(),        }    }} pub fn user_error(err: impl Into<Box<dyn error::Error + Send + Sync>>) -> CommandError {    CommandError::new(CommandErrorKind::User, err)} pub fn user_error_with_hint(    err: impl Into<Box<dyn error::Error + Send + Sync>>,    hint: impl Into<String>,) -> CommandError {    user_error(err).hinted(hint)} pub fn user_error_with_message(    message: impl Into<String>,    source: impl Into<Box<dyn error::Error + Send + Sync>>,) -> CommandError {    CommandError::with_message(CommandErrorKind::User, message, source)} pub fn config_error(err: impl Into<Box<dyn error::Error + Send + Sync>>) -> CommandError {    CommandError::new(CommandErrorKind::Config, err)} pub fn config_error_with_message(    message: impl Into<String>,    source: impl Into<Box<dyn error::Error + Send + Sync>>,) -> CommandError {    CommandError::with_message(CommandErrorKind::Config, message, source)} pub fn cli_error(err: impl Into<Box<dyn error::Error + Send + Sync>>) -> CommandError {    CommandError::new(CommandErrorKind::Cli, err)} pub fn internal_error(err: impl Into<Box<dyn error::Error + Send + Sync>>) -> CommandError {    CommandError::new(CommandErrorKind::Internal, err)} pub fn internal_error_with_message(    message: impl Into<String>,    source: impl Into<Box<dyn error::Error + Send + Sync>>,) -> CommandError {    CommandError::with_message(CommandErrorKind::Internal, message, source)} fn format_similarity_hint<S: AsRef<str>>(candidates: &[S]) -> Option<String> {    match candidates {        [] => None,        names => {            let quoted_names = names.iter().map(|s| format!("`{}`", s.as_ref())).join(", ");            Some(format!("Did you mean {quoted_names}?"))        }    }} impl From<io::Error> for CommandError {    fn from(err: io::Error) -> Self {        let kind = match err.kind() {            io::ErrorKind::BrokenPipe => CommandErrorKind::BrokenPipe,            _ => CommandErrorKind::User,        };        CommandError::new(kind, err)    }} impl From<jj_lib::file_util::PathError> for CommandError {    fn from(err: jj_lib::file_util::PathError) -> Self {        user_error(err)    }} impl From<ConfigFileSaveError> for CommandError {    fn from(err: ConfigFileSaveError) -> Self {        user_error(err)    }} impl From<ConfigGetError> for CommandError {    fn from(err: ConfigGetError) -> Self {        let hint = config_get_error_hint(&err);        let mut cmd_err = config_error(err);        cmd_err.extend_hints(hint);        cmd_err    }} impl From<ConfigLoadError> for CommandError {    fn from(err: ConfigLoadError) -> Self {        let hint = match &err {            ConfigLoadError::Read(_) => None,            ConfigLoadError::Parse { source_path, .. } => source_path                .as_ref()                .map(|path| format!("Check the config file: {}", path.display())),        };        let mut cmd_err = config_error(err);        cmd_err.extend_hints(hint);        cmd_err    }} impl From<ConfigMigrateError> for CommandError {    fn from(err: ConfigMigrateError) -> Self {        let hint = err            .source_path            .as_ref()            .map(|path| format!("Check the config file: {}", path.display()));        let mut cmd_err = config_error(err);        cmd_err.extend_hints(hint);        cmd_err    }} impl From<RewriteRootCommit> for CommandError {    fn from(err: RewriteRootCommit) -> Self {        internal_error_with_message("Attempted to rewrite the root commit", err)    }} impl From<EditCommitError> for CommandError {    fn from(err: EditCommitError) -> Self {        internal_error_with_message("Failed to edit a commit", err)    }} impl From<CheckOutCommitError> for CommandError {    fn from(err: CheckOutCommitError) -> Self {        internal_error_with_message("Failed to check out a commit", err)    }} impl From<RenameWorkspaceError> for CommandError {    fn from(err: RenameWorkspaceError) -> Self {        user_error_with_message("Failed to rename a workspace", err)    }} impl From<BackendError> for CommandError {    fn from(err: BackendError) -> Self {        match &err {            BackendError::Unsupported(_) => user_error(err),            _ => internal_error_with_message("Unexpected error from backend", err),        }    }} impl From<OpHeadsStoreError> for CommandError {    fn from(err: OpHeadsStoreError) -> Self {        internal_error_with_message("Unexpected error from operation heads store", err)    }} impl From<WorkspaceInitError> for CommandError {    fn from(err: WorkspaceInitError) -> Self {        match err {            WorkspaceInitError::DestinationExists(_) => {                user_error("The target repo already exists")            }            WorkspaceInitError::NonUnicodePath => {                user_error("The target repo path contains non-unicode characters")            }            WorkspaceInitError::CheckOutCommit(err) => {                internal_error_with_message("Failed to check out the initial commit", err)            }            WorkspaceInitError::Path(err) => {                internal_error_with_message("Failed to access the repository", err)            }            WorkspaceInitError::OpHeadsStore(err) => {                user_error_with_message("Failed to record initial operation", err)            }            WorkspaceInitError::Backend(err) => {                user_error_with_message("Failed to access the repository", err)            }            WorkspaceInitError::WorkingCopyState(err) => {                internal_error_with_message("Failed to access the repository", err)            }            WorkspaceInitError::SignInit(err) => user_error(err),        }    }} impl From<OpHeadResolutionError> for CommandError {    fn from(err: OpHeadResolutionError) -> Self {        match err {            OpHeadResolutionError::NoHeads => {                internal_error_with_message("Corrupt repository", err)            }        }    }} impl From<OpsetEvaluationError> for CommandError {    fn from(err: OpsetEvaluationError) -> Self {        match err {            OpsetEvaluationError::OpsetResolution(err) => {                let hint = opset_resolution_error_hint(&err);                let mut cmd_err = user_error(err);                cmd_err.extend_hints(hint);                cmd_err            }            OpsetEvaluationError::OpHeadResolution(err) => err.into(),            OpsetEvaluationError::OpHeadsStore(err) => err.into(),            OpsetEvaluationError::OpStore(err) => err.into(),        }    }} impl From<SnapshotError> for CommandError {    fn from(err: SnapshotError) -> Self {        internal_error_with_message("Failed to snapshot the working copy", err)    }} impl From<OpStoreError> for CommandError {    fn from(err: OpStoreError) -> Self {        internal_error_with_message("Failed to load an operation", err)    }} impl From<RepoLoaderError> for CommandError {    fn from(err: RepoLoaderError) -> Self {        internal_error_with_message("Failed to load the repo", err)    }} impl From<ResetError> for CommandError {    fn from(err: ResetError) -> Self {        internal_error_with_message("Failed to reset the working copy", err)    }} impl From<DiffEditError> for CommandError {    fn from(err: DiffEditError) -> Self {        user_error_with_message("Failed to edit diff", err)    }} impl From<DiffRenderError> for CommandError {    fn from(err: DiffRenderError) -> Self {        match err {            DiffRenderError::DiffGenerate(_) => user_error(err),            DiffRenderError::Backend(err) => err.into(),            DiffRenderError::AccessDenied { .. } => user_error(err),            DiffRenderError::InvalidRepoPath(_) => user_error(err),            DiffRenderError::Io(err) => err.into(),        }    }} impl From<ConflictResolveError> for CommandError {    fn from(err: ConflictResolveError) -> Self {        match err {            ConflictResolveError::Backend(err) => err.into(),            ConflictResolveError::Io(err) => err.into(),            _ => user_error_with_message("Failed to resolve conflicts", err),        }    }} impl From<MergeToolPartialResolutionError> for CommandError {    fn from(err: MergeToolPartialResolutionError) -> Self {        user_error(err)    }} impl From<MergeToolConfigError> for CommandError {    fn from(err: MergeToolConfigError) -> Self {        match &err {            MergeToolConfigError::MergeArgsNotConfigured { tool_name } => {                let tool_name = tool_name.clone();                user_error_with_hint(                    err,                    format!(                        "To use `{tool_name}` as a merge tool, the config \                         `merge-tools.{tool_name}.merge-args` must be defined (see docs for \                         details)"                    ),                )            }            _ => user_error_with_message("Failed to load tool configuration", err),        }    }} impl From<TextEditError> for CommandError {    fn from(err: TextEditError) -> Self {        user_error(err)    }} impl From<TempTextEditError> for CommandError {    fn from(err: TempTextEditError) -> Self {        let hint = err.path.as_ref().map(|path| {            let name = err.name.as_deref().unwrap_or("file");            format!("Edited {name} is left in {path}", path = path.display())        });        let mut cmd_err = user_error(err);        cmd_err.extend_hints(hint);        cmd_err    }} #[cfg(feature = "git")]mod git {    use jj_lib::git::GitExportError;    use jj_lib::git::GitFetchError;    use jj_lib::git::GitFetchPrepareError;    use jj_lib::git::GitImportError;    use jj_lib::git::GitPushError;    use jj_lib::git::GitRemoteManagementError;    use jj_lib::git::UnexpectedGitBackendError;     use super::*;     impl From<GitImportError> for CommandError {        fn from(err: GitImportError) -> Self {            let hint = match &err {                GitImportError::MissingHeadTarget { .. }                | GitImportError::MissingRefAncestor { .. } => Some(                    "\Is this Git repository a partial clone (cloned with the --filter argument)?jj currently does not support partial clones. To use jj with this repository, try re-cloning with \                     the full repository contents."                        .to_string(),                ),                GitImportError::InternalBackend(_) => None,                GitImportError::InternalGitError(_) => None,                GitImportError::UnexpectedBackend(_) => None,

Lines 1–500 of 1049.