mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-31 00:12:06 +00:00
cli: preserve source object of internal error
I'm going to rewrite the error output to print source chain one by one. The "{message}: {err}" pattern is wrapped in an error struct. InternalError variant could be a { message, source } pair, but not all errors need an additional message. This will also apply to UserError.
This commit is contained in:
parent
8f118074fe
commit
a278059c64
4 changed files with 86 additions and 62 deletions
|
@ -71,6 +71,7 @@ use jj_lib::workspace::{
|
|||
};
|
||||
use jj_lib::{dag_walk, file_util, git, op_walk, revset};
|
||||
use once_cell::unsync::OnceCell;
|
||||
use thiserror::Error;
|
||||
use toml_edit;
|
||||
use tracing::instrument;
|
||||
use tracing_chrome::ChromeLayerBuilder;
|
||||
|
@ -99,7 +100,27 @@ pub enum CommandError {
|
|||
/// Invalid command line detected by clap
|
||||
ClapCliError(Arc<clap::Error>),
|
||||
BrokenPipe,
|
||||
InternalError(String),
|
||||
InternalError(Arc<dyn std::error::Error + Send + Sync>),
|
||||
}
|
||||
|
||||
/// Wraps error with user-visible message.
|
||||
#[derive(Debug, Error)]
|
||||
#[error("{message}: {source}")]
|
||||
struct ErrorWithMessage {
|
||||
message: String,
|
||||
source: Box<dyn std::error::Error + Send + Sync>,
|
||||
}
|
||||
|
||||
impl ErrorWithMessage {
|
||||
fn new(
|
||||
message: impl Into<String>,
|
||||
source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
) -> Self {
|
||||
ErrorWithMessage {
|
||||
message: message.into(),
|
||||
source: source.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn user_error(message: impl Into<String>) -> CommandError {
|
||||
|
@ -115,6 +136,17 @@ pub fn user_error_with_hint(message: impl Into<String>, hint: impl Into<String>)
|
|||
}
|
||||
}
|
||||
|
||||
pub fn internal_error(err: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> CommandError {
|
||||
CommandError::InternalError(Arc::from(err.into()))
|
||||
}
|
||||
|
||||
pub fn internal_error_with_message(
|
||||
message: impl Into<String>,
|
||||
source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
) -> CommandError {
|
||||
CommandError::InternalError(Arc::new(ErrorWithMessage::new(message, source)))
|
||||
}
|
||||
|
||||
fn format_similarity_hint<S: AsRef<str>>(candidates: &[S]) -> Option<String> {
|
||||
match candidates {
|
||||
[] => None,
|
||||
|
@ -153,19 +185,19 @@ impl From<crate::config::ConfigError> for CommandError {
|
|||
|
||||
impl From<RewriteRootCommit> for CommandError {
|
||||
fn from(err: RewriteRootCommit) -> Self {
|
||||
CommandError::InternalError(format!("Attempted to rewrite the root commit: {err}"))
|
||||
internal_error_with_message("Attempted to rewrite the root commit", err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EditCommitError> for CommandError {
|
||||
fn from(err: EditCommitError) -> Self {
|
||||
CommandError::InternalError(format!("Failed to edit a commit: {err}"))
|
||||
internal_error_with_message("Failed to edit a commit", err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CheckOutCommitError> for CommandError {
|
||||
fn from(err: CheckOutCommitError) -> Self {
|
||||
CommandError::InternalError(format!("Failed to check out a commit: {err}"))
|
||||
internal_error_with_message("Failed to check out a commit", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,11 +216,11 @@ impl From<WorkspaceInitError> for CommandError {
|
|||
WorkspaceInitError::NonUnicodePath => {
|
||||
user_error("The target repo path contains non-unicode characters")
|
||||
}
|
||||
WorkspaceInitError::CheckOutCommit(err) => CommandError::InternalError(format!(
|
||||
"Failed to check out the initial commit: {err}"
|
||||
)),
|
||||
WorkspaceInitError::CheckOutCommit(err) => {
|
||||
internal_error_with_message("Failed to check out the initial commit", err)
|
||||
}
|
||||
WorkspaceInitError::Path(err) => {
|
||||
CommandError::InternalError(format!("Failed to access the repository: {err}"))
|
||||
internal_error_with_message("Failed to access the repository", err)
|
||||
}
|
||||
WorkspaceInitError::PathNotFound(path) => {
|
||||
user_error(format!("{} doesn't exist", path.display()))
|
||||
|
@ -197,12 +229,12 @@ impl From<WorkspaceInitError> for CommandError {
|
|||
user_error(format!("Failed to access the repository: {err}"))
|
||||
}
|
||||
WorkspaceInitError::WorkingCopyState(err) => {
|
||||
CommandError::InternalError(format!("Failed to access the repository: {err}"))
|
||||
internal_error_with_message("Failed to access the repository", err)
|
||||
}
|
||||
WorkspaceInitError::SignInit(err @ SignInitError::UnknownBackend(_)) => {
|
||||
user_error(format!("{err}"))
|
||||
}
|
||||
WorkspaceInitError::SignInit(err) => CommandError::InternalError(format!("{err}")),
|
||||
WorkspaceInitError::SignInit(err) => internal_error(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -210,9 +242,9 @@ impl From<WorkspaceInitError> for CommandError {
|
|||
impl From<OpHeadResolutionError> for CommandError {
|
||||
fn from(err: OpHeadResolutionError) -> Self {
|
||||
match err {
|
||||
OpHeadResolutionError::NoHeads => CommandError::InternalError(
|
||||
"Corrupt repository: there are no operations".to_string(),
|
||||
),
|
||||
OpHeadResolutionError::NoHeads => {
|
||||
internal_error_with_message("Corrupt repository", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -235,34 +267,32 @@ impl From<SnapshotError> for CommandError {
|
|||
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 => {
|
||||
CommandError::InternalError(format!("Failed to snapshot the working copy: {err}"))
|
||||
}
|
||||
err => internal_error_with_message("Failed to snapshot the working copy", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TreeMergeError> for CommandError {
|
||||
fn from(err: TreeMergeError) -> Self {
|
||||
CommandError::InternalError(format!("Merge failed: {err}"))
|
||||
internal_error_with_message("Merge failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OpStoreError> for CommandError {
|
||||
fn from(err: OpStoreError) -> Self {
|
||||
CommandError::InternalError(format!("Failed to load an operation: {err}"))
|
||||
internal_error_with_message("Failed to load an operation", err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RepoLoaderError> for CommandError {
|
||||
fn from(err: RepoLoaderError) -> Self {
|
||||
CommandError::InternalError(format!("Failed to load the repo: {err}"))
|
||||
internal_error_with_message("Failed to load the repo", err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResetError> for CommandError {
|
||||
fn from(_: ResetError) -> Self {
|
||||
CommandError::InternalError("Failed to reset the working copy".to_string())
|
||||
fn from(err: ResetError) -> Self {
|
||||
internal_error_with_message("Failed to reset the working copy", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -318,9 +348,7 @@ repository contents."
|
|||
|
||||
impl From<GitExportError> for CommandError {
|
||||
fn from(err: GitExportError) -> Self {
|
||||
CommandError::InternalError(format!(
|
||||
"Failed to export refs to underlying Git repo: {err}"
|
||||
))
|
||||
internal_error_with_message("Failed to export refs to underlying Git repo", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -406,13 +434,13 @@ impl From<clap::Error> for CommandError {
|
|||
|
||||
impl From<GitConfigParseError> for CommandError {
|
||||
fn from(err: GitConfigParseError) -> Self {
|
||||
CommandError::InternalError(format!("Failed to parse Git config: {err} "))
|
||||
internal_error_with_message("Failed to parse Git config", err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WorkingCopyStateError> for CommandError {
|
||||
fn from(err: WorkingCopyStateError) -> Self {
|
||||
CommandError::InternalError(format!("Failed to access working copy state: {err}"))
|
||||
internal_error_with_message("Failed to access working copy state", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -497,9 +525,7 @@ impl TracingSubscription {
|
|||
.with_default_directive(tracing::metadata::LevelFilter::DEBUG.into())
|
||||
.from_env_lossy()
|
||||
})
|
||||
.map_err(|err| {
|
||||
CommandError::InternalError(format!("failed to enable verbose logging: {err}"))
|
||||
})?;
|
||||
.map_err(|err| internal_error_with_message("failed to enable verbose logging", err))?;
|
||||
tracing::info!("verbose logging enabled");
|
||||
Ok(())
|
||||
}
|
||||
|
@ -702,7 +728,7 @@ impl CommandHelper {
|
|||
let op_id = workspace.working_copy().operation_id();
|
||||
let op_data = op_store
|
||||
.read_operation(op_id)
|
||||
.map_err(|e| CommandError::InternalError(format!("Failed to read operation: {e}")))?;
|
||||
.map_err(|e| internal_error_with_message("Failed to read operation", e))?;
|
||||
let operation = Operation::new(op_store.clone(), op_id.clone(), op_data);
|
||||
let repo = workspace.repo_loader().load_at(&operation)?;
|
||||
self.for_loaded_repo(ui, workspace, repo)
|
||||
|
@ -1405,7 +1431,7 @@ See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-workin
|
|||
));
|
||||
}
|
||||
WorkingCopyFreshness::SiblingOperation => {
|
||||
return Err(CommandError::InternalError(format!(
|
||||
return Err(internal_error(format!(
|
||||
"The repo was loaded at operation {}, which seems to be a sibling of the \
|
||||
working copy's operation {}",
|
||||
short_operation_hash(repo.op_id()),
|
||||
|
@ -1837,19 +1863,18 @@ jj init --git-repo=.",
|
|||
WorkspaceLoadError::Path(e) => user_error(format!("{}: {}", e, e.error)),
|
||||
WorkspaceLoadError::NonUnicodePath => user_error(err.to_string()),
|
||||
WorkspaceLoadError::StoreLoadError(err @ StoreLoadError::UnsupportedType { .. }) => {
|
||||
CommandError::InternalError(format!(
|
||||
"This version of the jj binary doesn't support this type of repo: {err}"
|
||||
))
|
||||
internal_error_with_message(
|
||||
"This version of the jj binary doesn't support this type of repo",
|
||||
err,
|
||||
)
|
||||
}
|
||||
WorkspaceLoadError::StoreLoadError(
|
||||
err @ (StoreLoadError::ReadError { .. } | StoreLoadError::Backend(_)),
|
||||
) => CommandError::InternalError(format!(
|
||||
"The repository appears broken or inaccessible: {err}"
|
||||
)),
|
||||
) => internal_error_with_message("The repository appears broken or inaccessible", err),
|
||||
WorkspaceLoadError::StoreLoadError(StoreLoadError::Signing(
|
||||
err @ SignInitError::UnknownBackend(_),
|
||||
)) => user_error(format!("{err}")),
|
||||
WorkspaceLoadError::StoreLoadError(err) => CommandError::InternalError(format!("{err}")),
|
||||
WorkspaceLoadError::StoreLoadError(err) => internal_error(err),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2104,11 +2129,10 @@ pub fn update_working_copy(
|
|||
let stats = workspace
|
||||
.check_out(repo.op_id().clone(), old_tree_id.as_ref(), new_commit)
|
||||
.map_err(|err| {
|
||||
CommandError::InternalError(format!(
|
||||
"Failed to check out commit {}: {}",
|
||||
new_commit.id().hex(),
|
||||
err
|
||||
))
|
||||
internal_error_with_message(
|
||||
format!("Failed to check out commit {}", new_commit.id().hex()),
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
Some(stats)
|
||||
} else {
|
||||
|
@ -2821,8 +2845,8 @@ pub fn handle_command_result(
|
|||
// A broken pipe is not an error, but a signal to exit gracefully.
|
||||
Ok(ExitCode::from(BROKEN_PIPE_EXIT_CODE))
|
||||
}
|
||||
Err(CommandError::InternalError(message)) => {
|
||||
writeln!(ui.error(), "Internal error: {message}")?;
|
||||
Err(CommandError::InternalError(err)) => {
|
||||
writeln!(ui.error(), "Internal error: {err}")?;
|
||||
Ok(ExitCode::from(255))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ use jj_lib::object_id::ObjectId;
|
|||
use jj_lib::working_copy::WorkingCopy;
|
||||
use jj_lib::{op_walk, revset};
|
||||
|
||||
use crate::cli_util::{user_error, CommandError, CommandHelper, RevisionArg};
|
||||
use crate::cli_util::{internal_error, user_error, CommandError, CommandHelper, RevisionArg};
|
||||
use crate::template_parser;
|
||||
use crate::ui::Ui;
|
||||
|
||||
|
@ -205,7 +205,7 @@ fn cmd_debug_index(
|
|||
let index_store = repo_loader.index_store();
|
||||
let index = index_store
|
||||
.get_index_at_op(&op, repo_loader.store())
|
||||
.map_err(|err| CommandError::InternalError(err.to_string()))?;
|
||||
.map_err(internal_error)?;
|
||||
if let Some(default_index) = index.as_any().downcast_ref::<DefaultReadonlyIndex>() {
|
||||
let stats = default_index.as_composite().stats();
|
||||
writeln!(ui.stdout(), "Number of commits: {}", stats.num_commits)?;
|
||||
|
@ -244,12 +244,10 @@ fn cmd_debug_reindex(
|
|||
let op = op_walk::resolve_op_for_load(repo_loader, &command.global_args().at_operation)?;
|
||||
let index_store = repo_loader.index_store();
|
||||
if let Some(default_index_store) = index_store.as_any().downcast_ref::<DefaultIndexStore>() {
|
||||
default_index_store
|
||||
.reinit()
|
||||
.map_err(|err| CommandError::InternalError(err.to_string()))?;
|
||||
default_index_store.reinit().map_err(internal_error)?;
|
||||
let default_index = default_index_store
|
||||
.build_index_at_operation(&op, repo_loader.store())
|
||||
.map_err(|err| CommandError::InternalError(err.to_string()))?;
|
||||
.map_err(internal_error)?;
|
||||
writeln!(
|
||||
ui.stderr(),
|
||||
"Finished indexing {:?} commits.",
|
||||
|
|
|
@ -24,7 +24,9 @@ use jj_lib::repo_path::RepoPathBuf;
|
|||
use jj_lib::settings::UserSettings;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::cli_util::{edit_temp_file, print_checkout_stats, CommandError, CommandHelper};
|
||||
use crate::cli_util::{
|
||||
edit_temp_file, internal_error_with_message, print_checkout_stats, CommandError, CommandHelper,
|
||||
};
|
||||
use crate::ui::Ui;
|
||||
|
||||
/// Manage which paths from the working-copy commit are present in the working
|
||||
|
@ -147,9 +149,7 @@ fn cmd_sparse_set(
|
|||
let stats = locked_ws
|
||||
.locked_wc()
|
||||
.set_sparse_patterns(new_patterns)
|
||||
.map_err(|err| {
|
||||
CommandError::InternalError(format!("Failed to update working copy paths: {err}"))
|
||||
})?;
|
||||
.map_err(|err| internal_error_with_message("Failed to update working copy paths", err))?;
|
||||
let operation_id = locked_ws.locked_wc().old_operation_id().clone();
|
||||
locked_ws.finish(operation_id)?;
|
||||
print_checkout_stats(ui, stats, &wc_commit)?;
|
||||
|
|
|
@ -27,8 +27,8 @@ use jj_lib::workspace::Workspace;
|
|||
use tracing::instrument;
|
||||
|
||||
use crate::cli_util::{
|
||||
self, check_stale_working_copy, print_checkout_stats, user_error, CommandError, CommandHelper,
|
||||
RevisionArg, WorkspaceCommandHelper,
|
||||
self, check_stale_working_copy, internal_error_with_message, print_checkout_stats, user_error,
|
||||
CommandError, CommandHelper, RevisionArg, WorkspaceCommandHelper,
|
||||
};
|
||||
use crate::ui::Ui;
|
||||
|
||||
|
@ -315,11 +315,13 @@ fn cmd_workspace_update_stale(
|
|||
.locked_wc()
|
||||
.check_out(&desired_wc_commit)
|
||||
.map_err(|err| {
|
||||
CommandError::InternalError(format!(
|
||||
"Failed to check out commit {}: {}",
|
||||
desired_wc_commit.id().hex(),
|
||||
err
|
||||
))
|
||||
internal_error_with_message(
|
||||
format!(
|
||||
"Failed to check out commit {}",
|
||||
desired_wc_commit.id().hex()
|
||||
),
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
locked_ws.finish(repo.op_id().clone())?;
|
||||
write!(ui.stderr(), "Working copy now at: ")?;
|
||||
|
|
Loading…
Reference in a new issue