diff --git a/cli/examples/custom-backend/main.rs b/cli/examples/custom-backend/main.rs index 08145d69bd..b636cf17f5 100644 --- a/cli/examples/custom-backend/main.rs +++ b/cli/examples/custom-backend/main.rs @@ -18,7 +18,8 @@ use std::path::Path; use std::time::SystemTime; use async_trait::async_trait; -use jj_cli::cli_util::{CliRunner, CommandError, CommandHelper}; +use jj_cli::cli_util::{CliRunner, CommandHelper}; +use jj_cli::command_error::CommandError; use jj_cli::ui::Ui; use jj_lib::backend::{ Backend, BackendInitError, BackendLoadError, BackendResult, ChangeId, Commit, CommitId, diff --git a/cli/examples/custom-command/main.rs b/cli/examples/custom-command/main.rs index bdcf657491..bf8abba7dc 100644 --- a/cli/examples/custom-command/main.rs +++ b/cli/examples/custom-command/main.rs @@ -14,7 +14,8 @@ use std::io::Write as _; -use jj_cli::cli_util::{CliRunner, CommandError, CommandHelper}; +use jj_cli::cli_util::{CliRunner, CommandHelper}; +use jj_cli::command_error::CommandError; use jj_cli::ui::Ui; #[derive(clap::Parser, Clone, Debug)] diff --git a/cli/examples/custom-global-flag/main.rs b/cli/examples/custom-global-flag/main.rs index c557fe8bb4..50e2153995 100644 --- a/cli/examples/custom-global-flag/main.rs +++ b/cli/examples/custom-global-flag/main.rs @@ -14,7 +14,8 @@ use std::io::Write as _; -use jj_cli::cli_util::{CliRunner, CommandError}; +use jj_cli::cli_util::CliRunner; +use jj_cli::command_error::CommandError; use jj_cli::ui::Ui; #[derive(clap::Args, Clone, Debug)] diff --git a/cli/examples/custom-working-copy/main.rs b/cli/examples/custom-working-copy/main.rs index af9e10b962..40bdcf4b48 100644 --- a/cli/examples/custom-working-copy/main.rs +++ b/cli/examples/custom-working-copy/main.rs @@ -17,7 +17,8 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use itertools::Itertools; -use jj_cli::cli_util::{CliRunner, CommandError, CommandHelper}; +use jj_cli::cli_util::{CliRunner, CommandHelper}; +use jj_cli::command_error::CommandError; use jj_cli::ui::Ui; use jj_lib::backend::{Backend, MergedTreeId}; use jj_lib::commit::Commit; diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index eb49fe6e55..dc5aa065e8 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -25,15 +25,14 @@ use std::rc::Rc; use std::str::FromStr; use std::sync::Arc; use std::time::SystemTime; -use std::{fs, iter, str}; +use std::{fs, str}; use clap::builder::{NonEmptyStringValueParser, TypedValueParser, ValueParserFactory}; use clap::{Arg, ArgAction, ArgMatches, Command, FromArgMatches}; use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; -use jj_lib::backend::{BackendError, ChangeId, CommitId, MergedTreeId}; +use jj_lib::backend::{ChangeId, CommitId, MergedTreeId}; use jj_lib::commit::Commit; -use jj_lib::git::{GitConfigParseError, GitExportError, GitImportError, GitRemoteManagementError}; use jj_lib::git_backend::GitBackend; use jj_lib::gitignore::{GitIgnoreError, GitIgnoreFile}; use jj_lib::hex_util::to_reverse_hex; @@ -41,43 +40,42 @@ use jj_lib::id_prefix::IdPrefixContext; use jj_lib::matchers::{EverythingMatcher, Matcher, PrefixMatcher}; use jj_lib::merged_tree::MergedTree; use jj_lib::object_id::ObjectId; -use jj_lib::op_heads_store::{self, OpHeadResolutionError}; use jj_lib::op_store::{OpStoreError, OperationId, WorkspaceId}; use jj_lib::op_walk::OpsetEvaluationError; use jj_lib::operation::Operation; use jj_lib::repo::{ CheckOutCommitError, EditCommitError, MutableRepo, ReadonlyRepo, Repo, RepoLoader, - RepoLoaderError, RewriteRootCommit, StoreFactories, StoreLoadError, + StoreFactories, StoreLoadError, }; use jj_lib::repo_path::{FsPathParseError, RepoPath, RepoPathBuf}; use jj_lib::revset::{ - DefaultSymbolResolver, Revset, RevsetAliasesMap, RevsetCommitRef, RevsetEvaluationError, - RevsetExpression, RevsetFilterPredicate, RevsetIteratorExt, RevsetParseContext, - RevsetParseError, RevsetParseErrorKind, RevsetResolutionError, RevsetWorkspaceContext, + DefaultSymbolResolver, Revset, RevsetAliasesMap, RevsetCommitRef, RevsetExpression, + RevsetFilterPredicate, RevsetIteratorExt, RevsetParseContext, RevsetParseError, + RevsetWorkspaceContext, }; use jj_lib::rewrite::restore_tree; use jj_lib::settings::{ConfigResultExt as _, UserSettings}; use jj_lib::signing::SignInitError; use jj_lib::str_util::{StringPattern, StringPatternParseError}; use jj_lib::transaction::Transaction; -use jj_lib::tree::TreeMergeError; use jj_lib::view::View; use jj_lib::working_copy::{ - CheckoutStats, LockedWorkingCopy, ResetError, SnapshotError, SnapshotOptions, WorkingCopy, - WorkingCopyFactory, WorkingCopyStateError, + CheckoutStats, LockedWorkingCopy, SnapshotOptions, WorkingCopy, WorkingCopyFactory, }; use jj_lib::workspace::{ - default_working_copy_factories, LockedWorkspace, Workspace, WorkspaceInitError, - WorkspaceLoadError, WorkspaceLoader, + default_working_copy_factories, LockedWorkspace, Workspace, WorkspaceLoadError, WorkspaceLoader, }; -use jj_lib::{dag_walk, file_util, git, op_walk, revset}; +use jj_lib::{dag_walk, file_util, git, op_heads_store, op_walk, revset}; use once_cell::unsync::OnceCell; -use thiserror::Error; use toml_edit; use tracing::instrument; use tracing_chrome::ChromeLayerBuilder; use tracing_subscriber::prelude::*; +use crate::command_error::{ + handle_command_result, internal_error, internal_error_with_message, user_error, + user_error_with_hint, user_error_with_message, CommandError, +}; use crate::commit_templater::CommitTemplateLanguageExtension; use crate::config::{ new_config_path, AnnotatedValue, CommandNameAndArgs, ConfigSource, LayeredConfigs, @@ -86,425 +84,12 @@ use crate::formatter::{FormatRecorder, Formatter, PlainTextFormatter}; use crate::git_util::{ is_colocated_git_workspace, print_failed_git_export, print_git_import_stats, }; -use crate::merge_tools::{ - ConflictResolveError, DiffEditError, DiffEditor, DiffGenerateError, MergeEditor, - MergeToolConfigError, -}; -use crate::template_parser::{TemplateAliasesMap, TemplateParseError, TemplateParseErrorKind}; +use crate::merge_tools::{DiffEditor, MergeEditor, MergeToolConfigError}; +use crate::template_parser::TemplateAliasesMap; use crate::templater::Template; use crate::ui::{ColorChoice, Ui}; use crate::{commit_templater, text_util}; -#[derive(Clone, Debug)] -pub enum CommandError { - UserError { - err: Arc, - hint: Option, - }, - ConfigError(String), - /// Invalid command line - CliError(String), - /// Invalid command line detected by clap - ClapCliError(Arc), - BrokenPipe, - InternalError(Arc), -} - -/// Wraps error with user-visible message. -#[derive(Debug, Error)] -#[error("{message}")] -struct ErrorWithMessage { - message: String, - source: Box, -} - -impl ErrorWithMessage { - fn new( - message: impl Into, - source: impl Into>, - ) -> Self { - ErrorWithMessage { - message: message.into(), - source: source.into(), - } - } -} - -pub fn user_error(err: impl Into>) -> CommandError { - user_error_with_hint_opt(err, None) -} - -pub fn user_error_with_hint( - err: impl Into>, - hint: impl Into, -) -> CommandError { - user_error_with_hint_opt(err, Some(hint.into())) -} - -pub fn user_error_with_message( - message: impl Into, - source: impl Into>, -) -> CommandError { - user_error_with_hint_opt(ErrorWithMessage::new(message, source), None) -} - -pub fn user_error_with_message_and_hint( - message: impl Into, - hint: impl Into, - source: impl Into>, -) -> CommandError { - user_error_with_hint_opt(ErrorWithMessage::new(message, source), Some(hint.into())) -} - -pub fn user_error_with_hint_opt( - err: impl Into>, - hint: Option, -) -> CommandError { - CommandError::UserError { - err: Arc::from(err.into()), - hint, - } -} - -pub fn internal_error(err: impl Into>) -> CommandError { - CommandError::InternalError(Arc::from(err.into())) -} - -pub fn internal_error_with_message( - message: impl Into, - source: impl Into>, -) -> CommandError { - CommandError::InternalError(Arc::new(ErrorWithMessage::new(message, source))) -} - -fn format_similarity_hint>(candidates: &[S]) -> Option { - match candidates { - [] => None, - names => { - let quoted_names = names - .iter() - .map(|s| format!(r#""{}""#, s.as_ref())) - .join(", "); - Some(format!("Did you mean {quoted_names}?")) - } - } -} - -fn print_error_sources(ui: &Ui, source: Option<&dyn std::error::Error>) -> io::Result<()> { - let Some(err) = source else { - return Ok(()); - }; - if err.source().is_none() { - writeln!(ui.stderr(), "Caused by: {err}")?; - } else { - writeln!(ui.stderr(), "Caused by:")?; - for (i, err) in iter::successors(Some(err), |err| err.source()).enumerate() { - writeln!(ui.stderr(), "{n}: {err}", n = i + 1)?; - } - } - Ok(()) -} - -impl From for CommandError { - fn from(err: std::io::Error) -> Self { - if err.kind() == std::io::ErrorKind::BrokenPipe { - CommandError::BrokenPipe - } else { - user_error(err) - } - } -} - -impl From for CommandError { - fn from(err: config::ConfigError) -> Self { - CommandError::ConfigError(err.to_string()) - } -} - -impl From for CommandError { - fn from(err: crate::config::ConfigError) -> Self { - CommandError::ConfigError(err.to_string()) - } -} - -impl From for CommandError { - fn from(err: RewriteRootCommit) -> Self { - internal_error_with_message("Attempted to rewrite the root commit", err) - } -} - -impl From for CommandError { - fn from(err: EditCommitError) -> Self { - internal_error_with_message("Failed to edit a commit", err) - } -} - -impl From for CommandError { - fn from(err: CheckOutCommitError) -> Self { - internal_error_with_message("Failed to check out a commit", err) - } -} - -impl From for CommandError { - fn from(err: BackendError) -> Self { - internal_error_with_message("Unexpected error from backend", err) - } -} - -impl From 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 for CommandError { - fn from(err: OpHeadResolutionError) -> Self { - match err { - OpHeadResolutionError::NoHeads => { - internal_error_with_message("Corrupt repository", err) - } - } - } -} - -impl From for CommandError { - fn from(err: OpsetEvaluationError) -> Self { - match err { - OpsetEvaluationError::OpsetResolution(err) => user_error(err), - OpsetEvaluationError::OpHeadResolution(err) => err.into(), - OpsetEvaluationError::OpStore(err) => err.into(), - } - } -} - -impl From for CommandError { - fn from(err: SnapshotError) -> Self { - match err { - SnapshotError::NewFileTooLarge { .. } => user_error_with_message_and_hint( - "Failed to snapshot the working copy", - r#"Increase the value of the `snapshot.max-new-file-size` config option if you -want this file to be snapshotted. Otherwise add it to your `.gitignore` file."#, - err, - ), - err => internal_error_with_message("Failed to snapshot the working copy", err), - } - } -} - -impl From for CommandError { - fn from(err: TreeMergeError) -> Self { - internal_error_with_message("Merge failed", err) - } -} - -impl From for CommandError { - fn from(err: OpStoreError) -> Self { - internal_error_with_message("Failed to load an operation", err) - } -} - -impl From for CommandError { - fn from(err: RepoLoaderError) -> Self { - internal_error_with_message("Failed to load the repo", err) - } -} - -impl From for CommandError { - fn from(err: ResetError) -> Self { - internal_error_with_message("Failed to reset the working copy", err) - } -} - -impl From for CommandError { - fn from(err: DiffEditError) -> Self { - user_error_with_message("Failed to edit diff", err) - } -} - -impl From for CommandError { - fn from(err: DiffGenerateError) -> Self { - user_error_with_message("Failed to generate diff", err) - } -} - -impl From for CommandError { - fn from(err: ConflictResolveError) -> Self { - user_error_with_message("Failed to resolve conflicts", err) - } -} - -impl From for CommandError { - fn from(err: MergeToolConfigError) -> Self { - user_error_with_message("Failed to load tool configuration", err) - } -} - -impl From for CommandError { - fn from(err: git2::Error) -> Self { - user_error_with_message("Git operation failed", err) - } -} - -impl From for CommandError { - fn from(err: GitImportError) -> Self { - let message = format!("Failed to import refs from underlying Git repo: {err}"); - let hint = match &err { - GitImportError::MissingHeadTarget { .. } - | GitImportError::MissingRefAncestor { .. } => Some( - "\ -Is this Git repository a shallow or partial clone (cloned with the --depth or --filter \ - argument)? -jj currently does not support shallow/partial clones. To use jj with this \ - repository, try -unshallowing the repository (https://stackoverflow.com/q/6802145) or 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, - }; - user_error_with_hint_opt(message, hint) - } -} - -impl From for CommandError { - fn from(err: GitExportError) -> Self { - internal_error_with_message("Failed to export refs to underlying Git repo", err) - } -} - -impl From for CommandError { - fn from(err: GitRemoteManagementError) -> Self { - user_error(err) - } -} - -impl From for CommandError { - fn from(err: RevsetEvaluationError) -> Self { - user_error(err) - } -} - -impl From for CommandError { - fn from(err: RevsetParseError) -> Self { - let err_chain = iter::successors(Some(&err), |e| e.origin()); - let message = err_chain.clone().join("\n"); - // Only for the bottom error, which is usually the root cause - let hint = match err_chain.last().unwrap().kind() { - RevsetParseErrorKind::NotPrefixOperator { - op: _, - similar_op, - description, - } - | RevsetParseErrorKind::NotPostfixOperator { - op: _, - similar_op, - description, - } - | RevsetParseErrorKind::NotInfixOperator { - op: _, - similar_op, - description, - } => Some(format!("Did you mean '{similar_op}' for {description}?")), - RevsetParseErrorKind::NoSuchFunction { - name: _, - candidates, - } => format_similarity_hint(candidates), - _ => None, - }; - user_error_with_hint_opt(format!("Failed to parse revset: {message}"), hint) - } -} - -impl From for CommandError { - fn from(err: RevsetResolutionError) -> Self { - let hint = match &err { - RevsetResolutionError::NoSuchRevision { - name: _, - candidates, - } => format_similarity_hint(candidates), - RevsetResolutionError::EmptyString - | RevsetResolutionError::WorkspaceMissingWorkingCopy { .. } - | RevsetResolutionError::AmbiguousCommitIdPrefix(_) - | RevsetResolutionError::AmbiguousChangeIdPrefix(_) - | RevsetResolutionError::StoreError(_) => None, - }; - user_error_with_hint_opt(err, hint) - } -} - -impl From for CommandError { - fn from(err: TemplateParseError) -> Self { - let err_chain = iter::successors(Some(&err), |e| e.origin()); - let message = err_chain.clone().join("\n"); - // Only for the bottom error, which is usually the root cause - let hint = match err_chain.last().unwrap().kind() { - TemplateParseErrorKind::NoSuchKeyword { candidates, .. } - | TemplateParseErrorKind::NoSuchFunction { candidates, .. } - | TemplateParseErrorKind::NoSuchMethod { candidates, .. } => { - format_similarity_hint(candidates) - } - _ => None, - }; - user_error_with_hint_opt(format!("Failed to parse template: {message}"), hint) - } -} - -impl From for CommandError { - fn from(err: FsPathParseError) -> Self { - user_error(err) - } -} - -impl From for CommandError { - fn from(err: clap::Error) -> Self { - CommandError::ClapCliError(Arc::new(err)) - } -} - -impl From for CommandError { - fn from(err: GitConfigParseError) -> Self { - internal_error_with_message("Failed to parse Git config", err) - } -} - -impl From for CommandError { - fn from(err: WorkingCopyStateError) -> Self { - internal_error_with_message("Failed to access working copy state", err) - } -} - -impl From for CommandError { - fn from(err: GitIgnoreError) -> Self { - user_error_with_message("Failed to process .gitignore.", err) - } -} - #[derive(Clone)] struct ChromeTracingFlushGuard { _inner: Option>, @@ -2761,73 +2346,6 @@ pub fn parse_args( Ok((matches, args)) } -const BROKEN_PIPE_EXIT_CODE: u8 = 3; - -pub fn handle_command_result( - ui: &mut Ui, - result: Result<(), CommandError>, -) -> std::io::Result { - match &result { - Ok(()) => Ok(ExitCode::SUCCESS), - Err(CommandError::UserError { err, hint }) => { - writeln!(ui.error(), "Error: {err}")?; - print_error_sources(ui, err.source())?; - if let Some(hint) = hint { - writeln!(ui.hint(), "Hint: {hint}")?; - } - Ok(ExitCode::from(1)) - } - Err(CommandError::ConfigError(message)) => { - writeln!(ui.error(), "Config error: {message}")?; - writeln!( - ui.hint(), - "For help, see https://github.com/martinvonz/jj/blob/main/docs/config.md." - )?; - Ok(ExitCode::from(1)) - } - Err(CommandError::CliError(message)) => { - writeln!(ui.error(), "Error: {message}")?; - Ok(ExitCode::from(2)) - } - Err(CommandError::ClapCliError(inner)) => { - let clap_str = if ui.color() { - inner.render().ansi().to_string() - } else { - inner.render().to_string() - }; - - match inner.kind() { - clap::error::ErrorKind::DisplayHelp - | clap::error::ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand => { - ui.request_pager() - } - _ => {} - }; - // Definitions for exit codes and streams come from - // https://github.com/clap-rs/clap/blob/master/src/error/mod.rs - match inner.kind() { - clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => { - write!(ui.stdout(), "{clap_str}")?; - Ok(ExitCode::SUCCESS) - } - _ => { - write!(ui.stderr(), "{clap_str}")?; - Ok(ExitCode::from(2)) - } - } - } - Err(CommandError::BrokenPipe) => { - // A broken pipe is not an error, but a signal to exit gracefully. - Ok(ExitCode::from(BROKEN_PIPE_EXIT_CODE)) - } - Err(CommandError::InternalError(err)) => { - writeln!(ui.error(), "Internal error: {err}")?; - print_error_sources(ui, err.source())?; - Ok(ExitCode::from(255)) - } - } -} - /// CLI command builder and runner. #[must_use] pub struct CliRunner { @@ -3044,8 +2562,7 @@ impl CliRunner { let mut ui = Ui::with_config(&layered_configs.merge()) .expect("default config should be valid, env vars are stringly typed"); let result = self.run_internal(&mut ui, layered_configs); - let exit_code = handle_command_result(&mut ui, result) - .unwrap_or_else(|_| ExitCode::from(BROKEN_PIPE_EXIT_CODE)); + let exit_code = handle_command_result(&mut ui, result); ui.finalize_pager(); exit_code } diff --git a/cli/src/command_error.rs b/cli/src/command_error.rs new file mode 100644 index 0000000000..2621fb0ad0 --- /dev/null +++ b/cli/src/command_error.rs @@ -0,0 +1,523 @@ +// 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::io::Write as _; +use std::process::ExitCode; +use std::sync::Arc; +use std::{error, io, iter, str}; + +use itertools::Itertools as _; +use jj_lib::backend::BackendError; +use jj_lib::git::{GitConfigParseError, GitExportError, GitImportError, 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::repo::{CheckOutCommitError, EditCommitError, RepoLoaderError, RewriteRootCommit}; +use jj_lib::repo_path::FsPathParseError; +use jj_lib::revset::{ + RevsetEvaluationError, RevsetParseError, RevsetParseErrorKind, RevsetResolutionError, +}; +use jj_lib::signing::SignInitError; +use jj_lib::tree::TreeMergeError; +use jj_lib::working_copy::{ResetError, SnapshotError, WorkingCopyStateError}; +use jj_lib::workspace::WorkspaceInitError; +use thiserror::Error; + +use crate::merge_tools::{ + ConflictResolveError, DiffEditError, DiffGenerateError, MergeToolConfigError, +}; +use crate::template_parser::{TemplateParseError, TemplateParseErrorKind}; +use crate::ui::Ui; + +#[derive(Clone, Debug)] +pub enum CommandError { + UserError { + err: Arc, + hint: Option, + }, + ConfigError(String), + /// Invalid command line + CliError(String), + /// Invalid command line detected by clap + ClapCliError(Arc), + BrokenPipe, + InternalError(Arc), +} + +/// Wraps error with user-visible message. +#[derive(Debug, Error)] +#[error("{message}")] +struct ErrorWithMessage { + message: String, + source: Box, +} + +impl ErrorWithMessage { + fn new( + message: impl Into, + source: impl Into>, + ) -> Self { + ErrorWithMessage { + message: message.into(), + source: source.into(), + } + } +} + +pub fn user_error(err: impl Into>) -> CommandError { + user_error_with_hint_opt(err, None) +} + +pub fn user_error_with_hint( + err: impl Into>, + hint: impl Into, +) -> CommandError { + user_error_with_hint_opt(err, Some(hint.into())) +} + +pub fn user_error_with_message( + message: impl Into, + source: impl Into>, +) -> CommandError { + user_error_with_hint_opt(ErrorWithMessage::new(message, source), None) +} + +pub fn user_error_with_message_and_hint( + message: impl Into, + hint: impl Into, + source: impl Into>, +) -> CommandError { + user_error_with_hint_opt(ErrorWithMessage::new(message, source), Some(hint.into())) +} + +pub fn user_error_with_hint_opt( + err: impl Into>, + hint: Option, +) -> CommandError { + CommandError::UserError { + err: Arc::from(err.into()), + hint, + } +} + +pub fn internal_error(err: impl Into>) -> CommandError { + CommandError::InternalError(Arc::from(err.into())) +} + +pub fn internal_error_with_message( + message: impl Into, + source: impl Into>, +) -> CommandError { + CommandError::InternalError(Arc::new(ErrorWithMessage::new(message, source))) +} + +fn format_similarity_hint>(candidates: &[S]) -> Option { + 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 for CommandError { + fn from(err: io::Error) -> Self { + if err.kind() == io::ErrorKind::BrokenPipe { + CommandError::BrokenPipe + } else { + user_error(err) + } + } +} + +impl From for CommandError { + fn from(err: config::ConfigError) -> Self { + CommandError::ConfigError(err.to_string()) + } +} + +impl From for CommandError { + fn from(err: crate::config::ConfigError) -> Self { + CommandError::ConfigError(err.to_string()) + } +} + +impl From for CommandError { + fn from(err: RewriteRootCommit) -> Self { + internal_error_with_message("Attempted to rewrite the root commit", err) + } +} + +impl From for CommandError { + fn from(err: EditCommitError) -> Self { + internal_error_with_message("Failed to edit a commit", err) + } +} + +impl From for CommandError { + fn from(err: CheckOutCommitError) -> Self { + internal_error_with_message("Failed to check out a commit", err) + } +} + +impl From for CommandError { + fn from(err: BackendError) -> Self { + internal_error_with_message("Unexpected error from backend", err) + } +} + +impl From 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 for CommandError { + fn from(err: OpHeadResolutionError) -> Self { + match err { + OpHeadResolutionError::NoHeads => { + internal_error_with_message("Corrupt repository", err) + } + } + } +} + +impl From for CommandError { + fn from(err: OpsetEvaluationError) -> Self { + match err { + OpsetEvaluationError::OpsetResolution(err) => user_error(err), + OpsetEvaluationError::OpHeadResolution(err) => err.into(), + OpsetEvaluationError::OpStore(err) => err.into(), + } + } +} + +impl From for CommandError { + fn from(err: SnapshotError) -> Self { + match err { + SnapshotError::NewFileTooLarge { .. } => user_error_with_message_and_hint( + "Failed to snapshot the working copy", + r#"Increase the value of the `snapshot.max-new-file-size` config option if you +want this file to be snapshotted. Otherwise add it to your `.gitignore` file."#, + err, + ), + err => internal_error_with_message("Failed to snapshot the working copy", err), + } + } +} + +impl From for CommandError { + fn from(err: TreeMergeError) -> Self { + internal_error_with_message("Merge failed", err) + } +} + +impl From for CommandError { + fn from(err: OpStoreError) -> Self { + internal_error_with_message("Failed to load an operation", err) + } +} + +impl From for CommandError { + fn from(err: RepoLoaderError) -> Self { + internal_error_with_message("Failed to load the repo", err) + } +} + +impl From for CommandError { + fn from(err: ResetError) -> Self { + internal_error_with_message("Failed to reset the working copy", err) + } +} + +impl From for CommandError { + fn from(err: DiffEditError) -> Self { + user_error_with_message("Failed to edit diff", err) + } +} + +impl From for CommandError { + fn from(err: DiffGenerateError) -> Self { + user_error_with_message("Failed to generate diff", err) + } +} + +impl From for CommandError { + fn from(err: ConflictResolveError) -> Self { + user_error_with_message("Failed to resolve conflicts", err) + } +} + +impl From for CommandError { + fn from(err: MergeToolConfigError) -> Self { + user_error_with_message("Failed to load tool configuration", err) + } +} + +impl From for CommandError { + fn from(err: git2::Error) -> Self { + user_error_with_message("Git operation failed", err) + } +} + +impl From for CommandError { + fn from(err: GitImportError) -> Self { + let message = format!("Failed to import refs from underlying Git repo: {err}"); + let hint = match &err { + GitImportError::MissingHeadTarget { .. } + | GitImportError::MissingRefAncestor { .. } => Some( + "\ +Is this Git repository a shallow or partial clone (cloned with the --depth or --filter \ + argument)? +jj currently does not support shallow/partial clones. To use jj with this \ + repository, try +unshallowing the repository (https://stackoverflow.com/q/6802145) or 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, + }; + user_error_with_hint_opt(message, hint) + } +} + +impl From for CommandError { + fn from(err: GitExportError) -> Self { + internal_error_with_message("Failed to export refs to underlying Git repo", err) + } +} + +impl From for CommandError { + fn from(err: GitRemoteManagementError) -> Self { + user_error(err) + } +} + +impl From for CommandError { + fn from(err: RevsetEvaluationError) -> Self { + user_error(err) + } +} + +impl From for CommandError { + fn from(err: RevsetParseError) -> Self { + let err_chain = iter::successors(Some(&err), |e| e.origin()); + let message = err_chain.clone().join("\n"); + // Only for the bottom error, which is usually the root cause + let hint = match err_chain.last().unwrap().kind() { + RevsetParseErrorKind::NotPrefixOperator { + op: _, + similar_op, + description, + } + | RevsetParseErrorKind::NotPostfixOperator { + op: _, + similar_op, + description, + } + | RevsetParseErrorKind::NotInfixOperator { + op: _, + similar_op, + description, + } => Some(format!("Did you mean '{similar_op}' for {description}?")), + RevsetParseErrorKind::NoSuchFunction { + name: _, + candidates, + } => format_similarity_hint(candidates), + _ => None, + }; + user_error_with_hint_opt(format!("Failed to parse revset: {message}"), hint) + } +} + +impl From for CommandError { + fn from(err: RevsetResolutionError) -> Self { + let hint = match &err { + RevsetResolutionError::NoSuchRevision { + name: _, + candidates, + } => format_similarity_hint(candidates), + RevsetResolutionError::EmptyString + | RevsetResolutionError::WorkspaceMissingWorkingCopy { .. } + | RevsetResolutionError::AmbiguousCommitIdPrefix(_) + | RevsetResolutionError::AmbiguousChangeIdPrefix(_) + | RevsetResolutionError::StoreError(_) => None, + }; + user_error_with_hint_opt(err, hint) + } +} + +impl From for CommandError { + fn from(err: TemplateParseError) -> Self { + let err_chain = iter::successors(Some(&err), |e| e.origin()); + let message = err_chain.clone().join("\n"); + // Only for the bottom error, which is usually the root cause + let hint = match err_chain.last().unwrap().kind() { + TemplateParseErrorKind::NoSuchKeyword { candidates, .. } + | TemplateParseErrorKind::NoSuchFunction { candidates, .. } + | TemplateParseErrorKind::NoSuchMethod { candidates, .. } => { + format_similarity_hint(candidates) + } + _ => None, + }; + user_error_with_hint_opt(format!("Failed to parse template: {message}"), hint) + } +} + +impl From for CommandError { + fn from(err: FsPathParseError) -> Self { + user_error(err) + } +} + +impl From for CommandError { + fn from(err: clap::Error) -> Self { + CommandError::ClapCliError(Arc::new(err)) + } +} + +impl From for CommandError { + fn from(err: GitConfigParseError) -> Self { + internal_error_with_message("Failed to parse Git config", err) + } +} + +impl From for CommandError { + fn from(err: WorkingCopyStateError) -> Self { + internal_error_with_message("Failed to access working copy state", err) + } +} + +impl From for CommandError { + fn from(err: GitIgnoreError) -> Self { + user_error_with_message("Failed to process .gitignore.", err) + } +} + +const BROKEN_PIPE_EXIT_CODE: u8 = 3; + +pub(crate) fn handle_command_result(ui: &mut Ui, result: Result<(), CommandError>) -> ExitCode { + try_handle_command_result(ui, result).unwrap_or_else(|_| ExitCode::from(BROKEN_PIPE_EXIT_CODE)) +} + +fn try_handle_command_result( + ui: &mut Ui, + result: Result<(), CommandError>, +) -> io::Result { + match &result { + Ok(()) => Ok(ExitCode::SUCCESS), + Err(CommandError::UserError { err, hint }) => { + writeln!(ui.error(), "Error: {err}")?; + print_error_sources(ui, err.source())?; + if let Some(hint) = hint { + writeln!(ui.hint(), "Hint: {hint}")?; + } + Ok(ExitCode::from(1)) + } + Err(CommandError::ConfigError(message)) => { + writeln!(ui.error(), "Config error: {message}")?; + writeln!( + ui.hint(), + "For help, see https://github.com/martinvonz/jj/blob/main/docs/config.md." + )?; + Ok(ExitCode::from(1)) + } + Err(CommandError::CliError(message)) => { + writeln!(ui.error(), "Error: {message}")?; + Ok(ExitCode::from(2)) + } + Err(CommandError::ClapCliError(inner)) => { + let clap_str = if ui.color() { + inner.render().ansi().to_string() + } else { + inner.render().to_string() + }; + + match inner.kind() { + clap::error::ErrorKind::DisplayHelp + | clap::error::ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand => { + ui.request_pager() + } + _ => {} + }; + // Definitions for exit codes and streams come from + // https://github.com/clap-rs/clap/blob/master/src/error/mod.rs + match inner.kind() { + clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => { + write!(ui.stdout(), "{clap_str}")?; + Ok(ExitCode::SUCCESS) + } + _ => { + write!(ui.stderr(), "{clap_str}")?; + Ok(ExitCode::from(2)) + } + } + } + Err(CommandError::BrokenPipe) => { + // A broken pipe is not an error, but a signal to exit gracefully. + Ok(ExitCode::from(BROKEN_PIPE_EXIT_CODE)) + } + Err(CommandError::InternalError(err)) => { + writeln!(ui.error(), "Internal error: {err}")?; + print_error_sources(ui, err.source())?; + Ok(ExitCode::from(255)) + } + } +} + +fn print_error_sources(ui: &Ui, source: Option<&dyn error::Error>) -> io::Result<()> { + let Some(err) = source else { + return Ok(()); + }; + if err.source().is_none() { + writeln!(ui.stderr(), "Caused by: {err}")?; + } else { + writeln!(ui.stderr(), "Caused by:")?; + for (i, err) in iter::successors(Some(err), |err| err.source()).enumerate() { + writeln!(ui.stderr(), "{n}: {err}", n = i + 1)?; + } + } + Ok(()) +} diff --git a/cli/src/commands/abandon.rs b/cli/src/commands/abandon.rs index 896e9db498..ee76c0d7db 100644 --- a/cli/src/commands/abandon.rs +++ b/cli/src/commands/abandon.rs @@ -17,9 +17,8 @@ use std::io::Write; use jj_lib::object_id::ObjectId; use tracing::instrument; -use crate::cli_util::{ - resolve_multiple_nonempty_revsets, CommandError, CommandHelper, RevisionArg, -}; +use crate::cli_util::{resolve_multiple_nonempty_revsets, CommandHelper, RevisionArg}; +use crate::command_error::CommandError; use crate::ui::Ui; /// Abandon a revision diff --git a/cli/src/commands/backout.rs b/cli/src/commands/backout.rs index abfd009b1c..1538b848e5 100644 --- a/cli/src/commands/backout.rs +++ b/cli/src/commands/backout.rs @@ -16,7 +16,8 @@ use jj_lib::object_id::ObjectId; use jj_lib::rewrite::back_out_commit; use tracing::instrument; -use crate::cli_util::{CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{CommandHelper, RevisionArg}; +use crate::command_error::CommandError; use crate::ui::Ui; /// Apply the reverse of a revision on top of another revision diff --git a/cli/src/commands/bench.rs b/cli/src/commands/bench.rs index 49b3c6ab7e..865dd8cca6 100644 --- a/cli/src/commands/bench.rs +++ b/cli/src/commands/bench.rs @@ -23,7 +23,8 @@ use criterion::{BatchSize, BenchmarkGroup, BenchmarkId, Criterion}; use jj_lib::object_id::HexPrefix; use jj_lib::repo::Repo; -use crate::cli_util::{CommandError, CommandHelper, WorkspaceCommandHelper}; +use crate::cli_util::{CommandHelper, WorkspaceCommandHelper}; +use crate::command_error::CommandError; use crate::ui::Ui; /// Commands for benchmarking internal operations diff --git a/cli/src/commands/branch.rs b/cli/src/commands/branch.rs index b4b5e82c44..9f936ffeda 100644 --- a/cli/src/commands/branch.rs +++ b/cli/src/commands/branch.rs @@ -28,9 +28,9 @@ use jj_lib::str_util::StringPattern; use jj_lib::view::View; use crate::cli_util::{ - parse_string_pattern, user_error, user_error_with_hint, CommandError, CommandHelper, - RemoteBranchName, RemoteBranchNamePattern, RevisionArg, + parse_string_pattern, CommandHelper, RemoteBranchName, RemoteBranchNamePattern, RevisionArg, }; +use crate::command_error::{user_error, user_error_with_hint, CommandError}; use crate::formatter::Formatter; use crate::ui::Ui; diff --git a/cli/src/commands/cat.rs b/cli/src/commands/cat.rs index 840b6960f5..128ed73a2c 100644 --- a/cli/src/commands/cat.rs +++ b/cli/src/commands/cat.rs @@ -19,7 +19,8 @@ use jj_lib::repo::Repo; use pollster::FutureExt; use tracing::instrument; -use crate::cli_util::{user_error, CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{CommandHelper, RevisionArg}; +use crate::command_error::{user_error, CommandError}; use crate::ui::Ui; /// Print contents of a file in a revision diff --git a/cli/src/commands/checkout.rs b/cli/src/commands/checkout.rs index 7d446faedc..329f186056 100644 --- a/cli/src/commands/checkout.rs +++ b/cli/src/commands/checkout.rs @@ -15,7 +15,8 @@ use jj_lib::object_id::ObjectId; use tracing::instrument; -use crate::cli_util::{join_message_paragraphs, CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{join_message_paragraphs, CommandHelper, RevisionArg}; +use crate::command_error::CommandError; use crate::ui::Ui; /// Create a new, empty change and edit it in the working copy diff --git a/cli/src/commands/chmod.rs b/cli/src/commands/chmod.rs index 6c91a427fe..dfcd0ab1f8 100644 --- a/cli/src/commands/chmod.rs +++ b/cli/src/commands/chmod.rs @@ -18,7 +18,8 @@ use jj_lib::merged_tree::MergedTreeBuilder; use jj_lib::object_id::ObjectId; use tracing::instrument; -use crate::cli_util::{user_error, CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{CommandHelper, RevisionArg}; +use crate::command_error::{user_error, CommandError}; use crate::ui::Ui; #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] diff --git a/cli/src/commands/commit.rs b/cli/src/commands/commit.rs index a6f4906ae0..5fa566afea 100644 --- a/cli/src/commands/commit.rs +++ b/cli/src/commands/commit.rs @@ -17,7 +17,8 @@ use jj_lib::repo::Repo; use jj_lib::rewrite::merge_commit_trees; use tracing::instrument; -use crate::cli_util::{join_message_paragraphs, user_error, CommandError, CommandHelper}; +use crate::cli_util::{join_message_paragraphs, CommandHelper}; +use crate::command_error::{user_error, CommandError}; use crate::description_util::{description_template_for_commit, edit_description}; use crate::ui::Ui; diff --git a/cli/src/commands/config.rs b/cli/src/commands/config.rs index 3ed0efbca5..e39f27e21b 100644 --- a/cli/src/commands/config.rs +++ b/cli/src/commands/config.rs @@ -19,9 +19,10 @@ use itertools::Itertools; use tracing::instrument; use crate::cli_util::{ - get_new_config_file_path, run_ui_editor, serialize_config_value, user_error, - write_config_value_to_file, CommandError, CommandHelper, + get_new_config_file_path, run_ui_editor, serialize_config_value, write_config_value_to_file, + CommandHelper, }; +use crate::command_error::{user_error, CommandError}; use crate::config::{AnnotatedValue, ConfigSource}; use crate::ui::Ui; diff --git a/cli/src/commands/debug.rs b/cli/src/commands/debug.rs index f458b17f18..5224d62fe0 100644 --- a/cli/src/commands/debug.rs +++ b/cli/src/commands/debug.rs @@ -23,7 +23,8 @@ use jj_lib::object_id::ObjectId; use jj_lib::working_copy::WorkingCopy; use jj_lib::{op_walk, revset}; -use crate::cli_util::{internal_error, user_error, CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{CommandHelper, RevisionArg}; +use crate::command_error::{internal_error, user_error, CommandError}; use crate::template_parser; use crate::ui::Ui; diff --git a/cli/src/commands/describe.rs b/cli/src/commands/describe.rs index c6d15eb16b..99b2e0ec8f 100644 --- a/cli/src/commands/describe.rs +++ b/cli/src/commands/describe.rs @@ -17,7 +17,8 @@ use std::io::{self, Read, Write}; use jj_lib::object_id::ObjectId; use tracing::instrument; -use crate::cli_util::{join_message_paragraphs, CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{join_message_paragraphs, CommandHelper, RevisionArg}; +use crate::command_error::CommandError; use crate::description_util::{description_template_for_describe, edit_description}; use crate::ui::Ui; diff --git a/cli/src/commands/diff.rs b/cli/src/commands/diff.rs index 5a072fa012..4677b5071e 100644 --- a/cli/src/commands/diff.rs +++ b/cli/src/commands/diff.rs @@ -15,7 +15,8 @@ use jj_lib::rewrite::merge_commit_trees; use tracing::instrument; -use crate::cli_util::{CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{CommandHelper, RevisionArg}; +use crate::command_error::CommandError; use crate::diff_util::{diff_formats_for, show_diff, DiffFormatArgs}; use crate::ui::Ui; diff --git a/cli/src/commands/diffedit.rs b/cli/src/commands/diffedit.rs index 6adf43f728..1345667c75 100644 --- a/cli/src/commands/diffedit.rs +++ b/cli/src/commands/diffedit.rs @@ -19,7 +19,8 @@ use jj_lib::object_id::ObjectId; use jj_lib::rewrite::merge_commit_trees; use tracing::instrument; -use crate::cli_util::{CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{CommandHelper, RevisionArg}; +use crate::command_error::CommandError; use crate::ui::Ui; /// Touch up the content changes in a revision with a diff editor diff --git a/cli/src/commands/duplicate.rs b/cli/src/commands/duplicate.rs index 703bdd9597..6a3c091668 100644 --- a/cli/src/commands/duplicate.rs +++ b/cli/src/commands/duplicate.rs @@ -20,9 +20,9 @@ use jj_lib::repo::Repo; use tracing::instrument; use crate::cli_util::{ - resolve_multiple_nonempty_revsets, short_commit_hash, user_error, CommandError, CommandHelper, - RevisionArg, + resolve_multiple_nonempty_revsets, short_commit_hash, CommandHelper, RevisionArg, }; +use crate::command_error::{user_error, CommandError}; use crate::ui::Ui; /// Create a new change with the same content as an existing one diff --git a/cli/src/commands/edit.rs b/cli/src/commands/edit.rs index f48aecb048..9a4aac7c65 100644 --- a/cli/src/commands/edit.rs +++ b/cli/src/commands/edit.rs @@ -17,7 +17,8 @@ use std::io::Write; use jj_lib::object_id::ObjectId; use tracing::instrument; -use crate::cli_util::{CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{CommandHelper, RevisionArg}; +use crate::command_error::CommandError; use crate::ui::Ui; /// Edit a commit in the working copy diff --git a/cli/src/commands/files.rs b/cli/src/commands/files.rs index bbc3df4b8f..d058cf8433 100644 --- a/cli/src/commands/files.rs +++ b/cli/src/commands/files.rs @@ -16,7 +16,8 @@ use std::io::Write; use tracing::instrument; -use crate::cli_util::{CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{CommandHelper, RevisionArg}; +use crate::command_error::CommandError; use crate::ui::Ui; /// List files in a revision diff --git a/cli/src/commands/git.rs b/cli/src/commands/git.rs index e0cffbcf08..4eb49ce802 100644 --- a/cli/src/commands/git.rs +++ b/cli/src/commands/git.rs @@ -42,10 +42,13 @@ use maplit::hashset; use crate::cli_util::{ parse_string_pattern, print_trackable_remote_branches, resolve_multiple_nonempty_revsets, - short_change_hash, short_commit_hash, start_repo_transaction, user_error, user_error_with_hint, - user_error_with_hint_opt, user_error_with_message, CommandError, CommandHelper, RevisionArg, + short_change_hash, short_commit_hash, start_repo_transaction, CommandHelper, RevisionArg, WorkspaceCommandHelper, }; +use crate::command_error::{ + user_error, user_error_with_hint, user_error_with_hint_opt, user_error_with_message, + CommandError, +}; use crate::git_util::{ get_git_repo, is_colocated_git_workspace, print_failed_git_export, print_git_import_stats, with_remote_git_callbacks, diff --git a/cli/src/commands/init.rs b/cli/src/commands/init.rs index dcd53dffd6..6f442a3fed 100644 --- a/cli/src/commands/init.rs +++ b/cli/src/commands/init.rs @@ -20,7 +20,8 @@ use jj_lib::workspace::Workspace; use tracing::instrument; use super::git; -use crate::cli_util::{user_error_with_hint, user_error_with_message, CommandError, CommandHelper}; +use crate::cli_util::CommandHelper; +use crate::command_error::{user_error_with_hint, user_error_with_message, CommandError}; use crate::ui::Ui; /// Create a new repo in the given directory diff --git a/cli/src/commands/interdiff.rs b/cli/src/commands/interdiff.rs index b83f5f3b69..f551a87b10 100644 --- a/cli/src/commands/interdiff.rs +++ b/cli/src/commands/interdiff.rs @@ -16,7 +16,8 @@ use clap::ArgGroup; use jj_lib::rewrite::rebase_to_dest_parent; use tracing::instrument; -use crate::cli_util::{CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{CommandHelper, RevisionArg}; +use crate::command_error::CommandError; use crate::diff_util::{self, DiffFormatArgs}; use crate::ui::Ui; diff --git a/cli/src/commands/log.rs b/cli/src/commands/log.rs index 83ea7f2fec..8bf661fff1 100644 --- a/cli/src/commands/log.rs +++ b/cli/src/commands/log.rs @@ -21,7 +21,8 @@ use jj_lib::revset_graph::{ }; use tracing::instrument; -use crate::cli_util::{CommandError, CommandHelper, LogContentFormat, RevisionArg}; +use crate::cli_util::{CommandHelper, LogContentFormat, RevisionArg}; +use crate::command_error::CommandError; use crate::diff_util::{self, DiffFormatArgs}; use crate::graphlog::{get_graphlog, Edge}; use crate::ui::Ui; diff --git a/cli/src/commands/merge.rs b/cli/src/commands/merge.rs index f1e1b7c4cd..13fd00e11f 100644 --- a/cli/src/commands/merge.rs +++ b/cli/src/commands/merge.rs @@ -15,7 +15,8 @@ use tracing::instrument; use super::new; -use crate::cli_util::{CommandError, CommandHelper}; +use crate::cli_util::CommandHelper; +use crate::command_error::CommandError; use crate::ui::Ui; #[instrument(skip_all)] diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index 6acca1c5c8..e307e96583 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -62,7 +62,8 @@ use std::fmt::Debug; use clap::{CommandFactory, FromArgMatches, Subcommand}; use tracing::instrument; -use crate::cli_util::{user_error_with_hint, Args, CommandError, CommandHelper}; +use crate::cli_util::{Args, CommandHelper}; +use crate::command_error::{user_error_with_hint, CommandError}; use crate::ui::Ui; #[derive(clap::Parser, Clone, Debug)] diff --git a/cli/src/commands/move.rs b/cli/src/commands/move.rs index ea47374c9c..bfae6f06c4 100644 --- a/cli/src/commands/move.rs +++ b/cli/src/commands/move.rs @@ -18,7 +18,8 @@ use jj_lib::repo::Repo; use jj_lib::rewrite::merge_commit_trees; use tracing::instrument; -use crate::cli_util::{user_error, CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{CommandHelper, RevisionArg}; +use crate::command_error::{user_error, CommandError}; use crate::description_util::combine_messages; use crate::ui::Ui; diff --git a/cli/src/commands/new.rs b/cli/src/commands/new.rs index e17657996b..17222316ed 100644 --- a/cli/src/commands/new.rs +++ b/cli/src/commands/new.rs @@ -22,9 +22,8 @@ use jj_lib::revset::{RevsetExpression, RevsetIteratorExt}; use jj_lib::rewrite::{merge_commit_trees, rebase_commit}; use tracing::instrument; -use crate::cli_util::{ - self, short_commit_hash, user_error, CommandError, CommandHelper, RevisionArg, -}; +use crate::cli_util::{self, short_commit_hash, CommandHelper, RevisionArg}; +use crate::command_error::{user_error, CommandError}; use crate::ui::Ui; /// Create a new, empty change and (by default) edit it in the working copy diff --git a/cli/src/commands/next.rs b/cli/src/commands/next.rs index ead6ecbf94..d1193e4ee6 100644 --- a/cli/src/commands/next.rs +++ b/cli/src/commands/next.rs @@ -19,9 +19,8 @@ use jj_lib::commit::Commit; use jj_lib::repo::Repo; use jj_lib::revset::{RevsetExpression, RevsetIteratorExt}; -use crate::cli_util::{ - short_commit_hash, user_error, CommandError, CommandHelper, WorkspaceCommandHelper, -}; +use crate::cli_util::{short_commit_hash, CommandHelper, WorkspaceCommandHelper}; +use crate::command_error::{user_error, CommandError}; use crate::ui::Ui; /// Move the current working copy commit to the next child revision in the diff --git a/cli/src/commands/obslog.rs b/cli/src/commands/obslog.rs index 5994b8fe28..069ef8508e 100644 --- a/cli/src/commands/obslog.rs +++ b/cli/src/commands/obslog.rs @@ -18,9 +18,8 @@ use jj_lib::matchers::EverythingMatcher; use jj_lib::rewrite::rebase_to_dest_parent; use tracing::instrument; -use crate::cli_util::{ - CommandError, CommandHelper, LogContentFormat, RevisionArg, WorkspaceCommandHelper, -}; +use crate::cli_util::{CommandHelper, LogContentFormat, RevisionArg, WorkspaceCommandHelper}; +use crate::command_error::CommandError; use crate::diff_util::{self, DiffFormat, DiffFormatArgs}; use crate::formatter::Formatter; use crate::graphlog::{get_graphlog, Edge}; diff --git a/cli/src/commands/operation.rs b/cli/src/commands/operation.rs index f5cf527814..88190f17eb 100644 --- a/cli/src/commands/operation.rs +++ b/cli/src/commands/operation.rs @@ -23,10 +23,8 @@ use jj_lib::op_walk; use jj_lib::operation::Operation; use jj_lib::repo::Repo; -use crate::cli_util::{ - short_operation_hash, user_error, user_error_with_hint, CommandError, CommandHelper, - LogContentFormat, -}; +use crate::cli_util::{short_operation_hash, CommandHelper, LogContentFormat}; +use crate::command_error::{user_error, user_error_with_hint, CommandError}; use crate::graphlog::{get_graphlog, Edge}; use crate::operation_templater; use crate::templater::Template as _; diff --git a/cli/src/commands/prev.rs b/cli/src/commands/prev.rs index 2da2fadf2e..58af0e1ff9 100644 --- a/cli/src/commands/prev.rs +++ b/cli/src/commands/prev.rs @@ -16,7 +16,8 @@ use itertools::Itertools; use jj_lib::repo::Repo; use jj_lib::revset::{RevsetExpression, RevsetIteratorExt}; -use crate::cli_util::{short_commit_hash, user_error, CommandError, CommandHelper}; +use crate::cli_util::{short_commit_hash, CommandHelper}; +use crate::command_error::{user_error, CommandError}; use crate::commands::next::choose_commit; use crate::ui::Ui; diff --git a/cli/src/commands/rebase.rs b/cli/src/commands/rebase.rs index abe6c81525..0e9534e1e9 100644 --- a/cli/src/commands/rebase.rs +++ b/cli/src/commands/rebase.rs @@ -29,9 +29,10 @@ use jj_lib::settings::UserSettings; use tracing::instrument; use crate::cli_util::{ - self, resolve_multiple_nonempty_revsets_default_single, short_commit_hash, user_error, - CommandError, CommandHelper, RevisionArg, WorkspaceCommandHelper, + self, resolve_multiple_nonempty_revsets_default_single, short_commit_hash, CommandHelper, + RevisionArg, WorkspaceCommandHelper, }; +use crate::command_error::{user_error, CommandError}; use crate::ui::Ui; /// Move revisions to different parent(s) diff --git a/cli/src/commands/resolve.rs b/cli/src/commands/resolve.rs index 0e5370cc75..3512e01893 100644 --- a/cli/src/commands/resolve.rs +++ b/cli/src/commands/resolve.rs @@ -22,7 +22,8 @@ use jj_lib::object_id::ObjectId; use jj_lib::repo_path::RepoPathBuf; use tracing::instrument; -use crate::cli_util::{CommandError, CommandHelper, WorkspaceCommandHelper}; +use crate::cli_util::{CommandHelper, WorkspaceCommandHelper}; +use crate::command_error::CommandError; use crate::formatter::Formatter; use crate::ui::Ui; diff --git a/cli/src/commands/restore.rs b/cli/src/commands/restore.rs index f9d11bdb6f..5c71005f17 100644 --- a/cli/src/commands/restore.rs +++ b/cli/src/commands/restore.rs @@ -18,7 +18,8 @@ use jj_lib::object_id::ObjectId; use jj_lib::rewrite::{merge_commit_trees, restore_tree}; use tracing::instrument; -use crate::cli_util::{user_error, CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{CommandHelper, RevisionArg}; +use crate::command_error::{user_error, CommandError}; use crate::ui::Ui; /// Restore paths from another revision diff --git a/cli/src/commands/root.rs b/cli/src/commands/root.rs index 54794f7a26..c3231dd0db 100644 --- a/cli/src/commands/root.rs +++ b/cli/src/commands/root.rs @@ -14,7 +14,8 @@ use tracing::instrument; -use crate::cli_util::{CommandError, CommandHelper}; +use crate::cli_util::CommandHelper; +use crate::command_error::CommandError; use crate::commands::workspace; use crate::ui::Ui; diff --git a/cli/src/commands/run.rs b/cli/src/commands/run.rs index 82f28c5aac..0dae7b3c99 100644 --- a/cli/src/commands/run.rs +++ b/cli/src/commands/run.rs @@ -14,9 +14,8 @@ //! This file contains the internal implementation of `run`. -use crate::cli_util::{ - resolve_multiple_nonempty_revsets, user_error, CommandError, CommandHelper, RevisionArg, -}; +use crate::cli_util::{resolve_multiple_nonempty_revsets, CommandHelper, RevisionArg}; +use crate::command_error::{user_error, CommandError}; use crate::ui::Ui; /// Run a command across a set of revisions. diff --git a/cli/src/commands/show.rs b/cli/src/commands/show.rs index 082d8c64f0..19e276a142 100644 --- a/cli/src/commands/show.rs +++ b/cli/src/commands/show.rs @@ -15,7 +15,8 @@ use jj_lib::matchers::EverythingMatcher; use tracing::instrument; -use crate::cli_util::{CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{CommandHelper, RevisionArg}; +use crate::command_error::CommandError; use crate::diff_util::{self, DiffFormatArgs}; use crate::ui::Ui; diff --git a/cli/src/commands/sparse.rs b/cli/src/commands/sparse.rs index bf313789f2..fddac7bade 100644 --- a/cli/src/commands/sparse.rs +++ b/cli/src/commands/sparse.rs @@ -24,9 +24,8 @@ use jj_lib::repo_path::RepoPathBuf; use jj_lib::settings::UserSettings; use tracing::instrument; -use crate::cli_util::{ - edit_temp_file, internal_error_with_message, print_checkout_stats, CommandError, CommandHelper, -}; +use crate::cli_util::{edit_temp_file, print_checkout_stats, CommandHelper}; +use crate::command_error::{internal_error_with_message, CommandError}; use crate::ui::Ui; /// Manage which paths from the working-copy commit are present in the working diff --git a/cli/src/commands/split.rs b/cli/src/commands/split.rs index ff3d918629..21c8a091f2 100644 --- a/cli/src/commands/split.rs +++ b/cli/src/commands/split.rs @@ -18,7 +18,8 @@ use jj_lib::repo::Repo; use jj_lib::rewrite::merge_commit_trees; use tracing::instrument; -use crate::cli_util::{CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{CommandHelper, RevisionArg}; +use crate::command_error::CommandError; use crate::description_util::{description_template_for_commit, edit_description}; use crate::ui::Ui; diff --git a/cli/src/commands/squash.rs b/cli/src/commands/squash.rs index b66e4c7724..1098741e40 100644 --- a/cli/src/commands/squash.rs +++ b/cli/src/commands/squash.rs @@ -17,7 +17,8 @@ use jj_lib::object_id::ObjectId; use jj_lib::revset; use tracing::instrument; -use crate::cli_util::{self, user_error, CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{self, CommandHelper, RevisionArg}; +use crate::command_error::{user_error, CommandError}; use crate::description_util::combine_messages; use crate::ui::Ui; diff --git a/cli/src/commands/status.rs b/cli/src/commands/status.rs index 32bfd644f1..07fa3224c0 100644 --- a/cli/src/commands/status.rs +++ b/cli/src/commands/status.rs @@ -19,7 +19,8 @@ use jj_lib::rewrite::merge_commit_trees; use tracing::instrument; use super::resolve; -use crate::cli_util::{CommandError, CommandHelper}; +use crate::cli_util::CommandHelper; +use crate::command_error::CommandError; use crate::diff_util; use crate::ui::Ui; diff --git a/cli/src/commands/tag.rs b/cli/src/commands/tag.rs index 1dd7afc6a7..6103f0bd8f 100644 --- a/cli/src/commands/tag.rs +++ b/cli/src/commands/tag.rs @@ -14,7 +14,8 @@ use jj_lib::str_util::StringPattern; -use crate::cli_util::{parse_string_pattern, CommandError, CommandHelper}; +use crate::cli_util::{parse_string_pattern, CommandHelper}; +use crate::command_error::CommandError; use crate::ui::Ui; /// Manage tags. diff --git a/cli/src/commands/unsquash.rs b/cli/src/commands/unsquash.rs index db70767729..5dc10452b2 100644 --- a/cli/src/commands/unsquash.rs +++ b/cli/src/commands/unsquash.rs @@ -17,7 +17,8 @@ use jj_lib::object_id::ObjectId; use jj_lib::rewrite::merge_commit_trees; use tracing::instrument; -use crate::cli_util::{user_error, CommandError, CommandHelper, RevisionArg}; +use crate::cli_util::{CommandHelper, RevisionArg}; +use crate::command_error::{user_error, CommandError}; use crate::description_util::combine_messages; use crate::ui::Ui; diff --git a/cli/src/commands/untrack.rs b/cli/src/commands/untrack.rs index a7c809195f..207d5a3126 100644 --- a/cli/src/commands/untrack.rs +++ b/cli/src/commands/untrack.rs @@ -21,7 +21,8 @@ use jj_lib::repo::Repo; use jj_lib::working_copy::SnapshotOptions; use tracing::instrument; -use crate::cli_util::{user_error_with_hint, CommandError, CommandHelper}; +use crate::cli_util::CommandHelper; +use crate::command_error::{user_error_with_hint, CommandError}; use crate::ui::Ui; /// Stop tracking specified paths in the working copy diff --git a/cli/src/commands/util.rs b/cli/src/commands/util.rs index d986f52559..2ec9f0c3c8 100644 --- a/cli/src/commands/util.rs +++ b/cli/src/commands/util.rs @@ -20,7 +20,8 @@ use clap::{Command, Subcommand}; use jj_lib::repo::Repo; use tracing::instrument; -use crate::cli_util::{user_error, CommandError, CommandHelper}; +use crate::cli_util::CommandHelper; +use crate::command_error::{user_error, CommandError}; use crate::ui::Ui; /// Infrequently used commands such as for generating shell completions diff --git a/cli/src/commands/version.rs b/cli/src/commands/version.rs index f182d561d7..9981138b6e 100644 --- a/cli/src/commands/version.rs +++ b/cli/src/commands/version.rs @@ -16,7 +16,8 @@ use std::io::Write; use tracing::instrument; -use crate::cli_util::{CommandError, CommandHelper}; +use crate::cli_util::CommandHelper; +use crate::command_error::CommandError; use crate::ui::Ui; /// Display version information diff --git a/cli/src/commands/workspace.rs b/cli/src/commands/workspace.rs index c5f7662130..3701df5a8f 100644 --- a/cli/src/commands/workspace.rs +++ b/cli/src/commands/workspace.rs @@ -29,10 +29,10 @@ use jj_lib::workspace::Workspace; use tracing::instrument; use crate::cli_util::{ - self, check_stale_working_copy, internal_error_with_message, print_checkout_stats, - short_commit_hash, user_error, CommandError, CommandHelper, RevisionArg, WorkingCopyFreshness, - WorkspaceCommandHelper, + self, check_stale_working_copy, print_checkout_stats, short_commit_hash, CommandHelper, + RevisionArg, WorkingCopyFreshness, WorkspaceCommandHelper, }; +use crate::command_error::{internal_error_with_message, user_error, CommandError}; use crate::ui::Ui; /// Commands for working with workspaces diff --git a/cli/src/description_util.rs b/cli/src/description_util.rs index 745ab53ba0..90a9cb95b1 100644 --- a/cli/src/description_util.rs +++ b/cli/src/description_util.rs @@ -5,7 +5,8 @@ use jj_lib::merged_tree::MergedTree; use jj_lib::repo::ReadonlyRepo; use jj_lib::settings::UserSettings; -use crate::cli_util::{edit_temp_file, CommandError, WorkspaceCommandHelper}; +use crate::cli_util::{edit_temp_file, WorkspaceCommandHelper}; +use crate::command_error::CommandError; use crate::diff_util::{self, DiffFormat}; use crate::formatter::PlainTextFormatter; use crate::text_util; diff --git a/cli/src/diff_util.rs b/cli/src/diff_util.rs index 6f52ad294a..7c35265b81 100644 --- a/cli/src/diff_util.rs +++ b/cli/src/diff_util.rs @@ -37,7 +37,8 @@ use pollster::FutureExt; use tracing::instrument; use unicode_width::UnicodeWidthStr as _; -use crate::cli_util::{CommandError, WorkspaceCommandHelper}; +use crate::cli_util::WorkspaceCommandHelper; +use crate::command_error::CommandError; use crate::config::CommandNameAndArgs; use crate::formatter::Formatter; use crate::merge_tools::{self, ExternalMergeTool}; diff --git a/cli/src/git_util.rs b/cli/src/git_util.rs index 0aeb4ec00f..a19b160ebf 100644 --- a/cli/src/git_util.rs +++ b/cli/src/git_util.rs @@ -30,7 +30,7 @@ use jj_lib::store::Store; use jj_lib::workspace::Workspace; use unicode_width::UnicodeWidthStr; -use crate::cli_util::{user_error, CommandError}; +use crate::command_error::{user_error, CommandError}; use crate::formatter::Formatter; use crate::progress::Progress; use crate::ui::Ui; diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 32bb24a92d..4fc40c934c 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -16,6 +16,7 @@ pub mod cleanup_guard; pub mod cli_util; +pub mod command_error; pub mod commands; pub mod commit_templater; pub mod config; diff --git a/cli/src/ui.rs b/cli/src/ui.rs index d7d19a4a4d..3f857bdc32 100644 --- a/cli/src/ui.rs +++ b/cli/src/ui.rs @@ -21,7 +21,7 @@ use std::{env, fmt, io, mem}; use minus::Pager as MinusPager; use tracing::instrument; -use crate::cli_util::CommandError; +use crate::command_error::CommandError; use crate::config::CommandNameAndArgs; use crate::formatter::{Formatter, FormatterFactory, LabeledWriter};