856 lines · 28.4 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::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::backend::BackendError;use jj_lib::dsl_util::Diagnostics;use jj_lib::fileset::FilePatternParseError;use jj_lib::fileset::FilesetParseError;use jj_lib::fileset::FilesetParseErrorKind;use jj_lib::git::GitConfigParseError;use jj_lib::git::GitExportError;use jj_lib::git::GitImportError;use jj_lib::git::GitRemoteManagementError;use jj_lib::gitignore::GitIgnoreError;use jj_lib::op_heads_store::OpHeadResolutionError;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::RevsetEvaluationError;use jj_lib::revset::RevsetParseError;use jj_lib::revset::RevsetParseErrorKind;use jj_lib::revset::RevsetResolutionError;use jj_lib::signing::SignInitError;use jj_lib::str_util::StringPatternParseError;use jj_lib::view::RenameWorkspaceError;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::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::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!(r#""{}""#, 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<config::ConfigError> for CommandError {    fn from(err: config::ConfigError) -> Self {        config_error(err)    }} impl From<crate::config::ConfigError> for CommandError {    fn from(err: crate::config::ConfigError) -> Self {        config_error(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<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::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 @ SignInitError::UnknownBackend(_)) => user_error(err),            WorkspaceInitError::SignInit(err) => internal_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::OpStore(err) => err.into(),        }    }} impl From<SnapshotError> for CommandError {    fn from(err: SnapshotError) -> Self {        match err {            SnapshotError::NewFileTooLarge {                path,                size,                max_size,            } => {                // if the size difference is < 1KiB, then show exact bytes.                // otherwise, show in human-readable form; this avoids weird cases                // where a file is 400 bytes too large but the error says something                // like '1.0MiB, maximum size allowed is ~1.0MiB'                let size_diff = size.0 - max_size.0;                let err_str = if size_diff <= 1024 {                    format!(                        "it is {} bytes too large; the maximum size allowed is {} bytes ({}).",                        size_diff, max_size.0, max_size,                    )                } else {                    format!("it is {size}; the maximum size allowed is ~{max_size}.")                };                 user_error(format!(                    "Failed to snapshot the working copy\nThe file '{}' is too large to be \                     snapshotted: {}",                    path.display(),                    err_str,                ))                .hinted(format!(                    "This is to prevent large files from being added on accident. You can fix \                     this error by:  - Adding the file to `.gitignore`  - Run `jj config set --repo snapshot.max-new-file-size {}`    This will increase the maximum file size allowed for new files, in this repository only.  - Run `jj --config-toml 'snapshot.max-new-file-size={}' st`    This will increase the maximum file size allowed for new files, for this command only.",                    size.0, size.0                ))            }            err => 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 {        user_error_with_message("Failed to resolve conflicts", 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<git2::Error> for CommandError {    fn from(err: git2::Error) -> Self {        user_error_with_message("Git operation failed", err)    }} 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::RemoteReservedForLocalGitRepo => {                Some("Run `jj git remote rename` to give different name.".to_string())            }            GitImportError::InternalBackend(_) => None,            GitImportError::InternalGitError(_) => None,            GitImportError::UnexpectedBackend => None,        };        let mut cmd_err =            user_error_with_message("Failed to import refs from underlying Git repo", err);        cmd_err.extend_hints(hint);        cmd_err    }} impl From<GitExportError> for CommandError {    fn from(err: GitExportError) -> Self {        internal_error_with_message("Failed to export refs to underlying Git repo", err)    }} impl From<GitRemoteManagementError> for CommandError {    fn from(err: GitRemoteManagementError) -> Self {        user_error(err)    }} impl From<RevsetEvaluationError> for CommandError {    fn from(err: RevsetEvaluationError) -> Self {        user_error(err)    }} impl From<FilesetParseError> for CommandError {    fn from(err: FilesetParseError) -> Self {        let hint = fileset_parse_error_hint(&err);        let mut cmd_err =            user_error_with_message(format!("Failed to parse fileset: {}", err.kind()), err);        cmd_err.extend_hints(hint);        cmd_err    }} impl From<RevsetParseError> for CommandError {

Lines 1–500 of 856.