ok/jj
1
0
Fork 0
forked from mirrors/jj

op_walk: add error types for fake "opset" expression

This removes CommandError dependency from these resolution functions. We might
want to refactor the error types again if we introduce a real "opset" evaluator.

The error message for unresolved op heads now includes "@" instead of the whole
expression.
This commit is contained in:
Yuya Nishihara 2023-12-30 22:56:50 +09:00
parent 94fc32ab47
commit e4460d5386
3 changed files with 67 additions and 28 deletions

View file

@ -44,6 +44,7 @@ use jj_lib::matchers::{EverythingMatcher, Matcher, PrefixMatcher};
use jj_lib::merged_tree::MergedTree; use jj_lib::merged_tree::MergedTree;
use jj_lib::op_heads_store::{self, OpHeadResolutionError, OpHeadsStore}; use jj_lib::op_heads_store::{self, OpHeadResolutionError, OpHeadsStore};
use jj_lib::op_store::{OpStore, OpStoreError, OperationId, WorkspaceId}; use jj_lib::op_store::{OpStore, OpStoreError, OperationId, WorkspaceId};
use jj_lib::op_walk::{OpsetEvaluationError, OpsetResolutionError};
use jj_lib::operation::Operation; use jj_lib::operation::Operation;
use jj_lib::repo::{ use jj_lib::repo::{
CheckOutCommitError, EditCommitError, MutableRepo, ReadonlyRepo, Repo, RepoLoader, CheckOutCommitError, EditCommitError, MutableRepo, ReadonlyRepo, Repo, RepoLoader,
@ -213,6 +214,16 @@ impl From<OpHeadResolutionError> for CommandError {
} }
} }
impl From<OpsetEvaluationError> for CommandError {
fn from(err: OpsetEvaluationError) -> Self {
match err {
OpsetEvaluationError::OpsetResolution(err) => user_error(err.to_string()),
OpsetEvaluationError::OpHeadResolution(err) => err.into(),
OpsetEvaluationError::OpStore(err) => err.into(),
}
}
}
impl From<SnapshotError> for CommandError { impl From<SnapshotError> for CommandError {
fn from(err: SnapshotError) -> Self { fn from(err: SnapshotError) -> Self {
match err { match err {
@ -648,11 +659,12 @@ impl CommandHelper {
}, },
) )
} else { } else {
resolve_op_for_load( let operation = resolve_op_for_load(
repo_loader.op_store(), repo_loader.op_store(),
repo_loader.op_heads_store(), repo_loader.op_heads_store(),
&self.global_args.at_operation, &self.global_args.at_operation,
) )?;
Ok(operation)
} }
} }
@ -962,7 +974,7 @@ impl WorkspaceCommandHelper {
git_ignores git_ignores
} }
pub fn resolve_single_op(&self, op_str: &str) -> Result<Operation, CommandError> { pub fn resolve_single_op(&self, op_str: &str) -> Result<Operation, OpsetEvaluationError> {
// When resolving the "@" operation in a `ReadonlyRepo`, we resolve it to the // When resolving the "@" operation in a `ReadonlyRepo`, we resolve it to the
// operation the repo was loaded at. // operation the repo was loaded at.
resolve_single_op( resolve_single_op(
@ -2011,12 +2023,10 @@ pub fn resolve_op_for_load(
op_store: &Arc<dyn OpStore>, op_store: &Arc<dyn OpStore>,
op_heads_store: &Arc<dyn OpHeadsStore>, op_heads_store: &Arc<dyn OpHeadsStore>,
op_str: &str, op_str: &str,
) -> Result<Operation, CommandError> { ) -> Result<Operation, OpsetEvaluationError> {
let get_current_op = || { let get_current_op = || {
op_heads_store::resolve_op_heads(op_heads_store.as_ref(), op_store, |_| { op_heads_store::resolve_op_heads(op_heads_store.as_ref(), op_store, |_| {
Err(user_error(format!( Err(OpsetResolutionError::MultipleOperations("@".to_owned()).into())
r#"The "{op_str}" expression resolved to more than one operation"#
)))
}) })
}; };
resolve_single_op(op_store, op_heads_store, get_current_op, op_str) resolve_single_op(op_store, op_heads_store, get_current_op, op_str)
@ -2025,9 +2035,9 @@ pub fn resolve_op_for_load(
fn resolve_single_op( fn resolve_single_op(
op_store: &Arc<dyn OpStore>, op_store: &Arc<dyn OpStore>,
op_heads_store: &Arc<dyn OpHeadsStore>, op_heads_store: &Arc<dyn OpHeadsStore>,
get_current_op: impl FnOnce() -> Result<Operation, CommandError>, get_current_op: impl FnOnce() -> Result<Operation, OpsetEvaluationError>,
op_str: &str, op_str: &str,
) -> Result<Operation, CommandError> { ) -> Result<Operation, OpsetEvaluationError> {
let op_symbol = op_str.trim_end_matches('-'); let op_symbol = op_str.trim_end_matches('-');
let op_postfix = &op_str[op_symbol.len()..]; let op_postfix = &op_str[op_symbol.len()..];
let mut operation = match op_symbol { let mut operation = match op_symbol {
@ -2037,14 +2047,10 @@ fn resolve_single_op(
for _ in op_postfix.chars() { for _ in op_postfix.chars() {
let mut parent_ops = operation.parents(); let mut parent_ops = operation.parents();
let Some(op) = parent_ops.next().transpose()? else { let Some(op) = parent_ops.next().transpose()? else {
return Err(user_error(format!( return Err(OpsetResolutionError::EmptyOperations(op_str.to_owned()).into());
r#"The "{op_str}" expression resolved to no operations"#
)));
}; };
if parent_ops.next().is_some() { if parent_ops.next().is_some() {
return Err(user_error(format!( return Err(OpsetResolutionError::MultipleOperations(op_str.to_owned()).into());
r#"The "{op_str}" expression resolved to more than one operation"#
)));
} }
drop(parent_ops); drop(parent_ops);
operation = op; operation = op;
@ -2091,11 +2097,9 @@ fn resolve_single_op_from_store(
op_store: &Arc<dyn OpStore>, op_store: &Arc<dyn OpStore>,
op_heads_store: &Arc<dyn OpHeadsStore>, op_heads_store: &Arc<dyn OpHeadsStore>,
op_str: &str, op_str: &str,
) -> Result<Operation, CommandError> { ) -> Result<Operation, OpsetEvaluationError> {
if op_str.is_empty() || !op_str.as_bytes().iter().all(|b| b.is_ascii_hexdigit()) { if op_str.is_empty() || !op_str.as_bytes().iter().all(|b| b.is_ascii_hexdigit()) {
return Err(user_error(format!( return Err(OpsetResolutionError::InvalidIdPrefix(op_str.to_owned()).into());
"Operation ID \"{op_str}\" is not a valid hexadecimal prefix"
)));
} }
if let Ok(binary_op_id) = hex::decode(op_str) { if let Ok(binary_op_id) = hex::decode(op_str) {
let op_id = OperationId::new(binary_op_id); let op_id = OperationId::new(binary_op_id);
@ -2107,9 +2111,7 @@ fn resolve_single_op_from_store(
// Fall through // Fall through
} }
Err(err) => { Err(err) => {
return Err(CommandError::InternalError(format!( return Err(OpsetEvaluationError::OpStore(err));
"Failed to read operation: {err}"
)));
} }
} }
} }
@ -2120,13 +2122,11 @@ fn resolve_single_op_from_store(
} }
} }
if matches.is_empty() { if matches.is_empty() {
Err(user_error(format!("No operation ID matching \"{op_str}\""))) Err(OpsetResolutionError::NoSuchOperation(op_str.to_owned()).into())
} else if matches.len() == 1 { } else if matches.len() == 1 {
Ok(matches.pop().unwrap()) Ok(matches.pop().unwrap())
} else { } else {
Err(user_error(format!( Err(OpsetResolutionError::AmbiguousIdPrefix(op_str.to_owned()).into())
"Operation ID prefix \"{op_str}\" is ambiguous"
)))
} }
} }

View file

@ -110,7 +110,7 @@ fn test_op_log() {
], ],
); );
insta::assert_snapshot!(test_env.jj_cmd_failure(&repo_path, &["log", "--at-op", "@-"]), @r###" insta::assert_snapshot!(test_env.jj_cmd_failure(&repo_path, &["log", "--at-op", "@-"]), @r###"
Error: The "@-" expression resolved to more than one operation Error: The "@" expression resolved to more than one operation
"###); "###);
test_env.jj_cmd_ok(&repo_path, &["st"]); test_env.jj_cmd_ok(&repo_path, &["st"]);
insta::assert_snapshot!(test_env.jj_cmd_failure(&repo_path, &["log", "--at-op", "@-"]), @r###" insta::assert_snapshot!(test_env.jj_cmd_failure(&repo_path, &["log", "--at-op", "@-"]), @r###"

View file

@ -17,11 +17,50 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use itertools::Itertools as _; use itertools::Itertools as _;
use thiserror::Error;
use crate::dag_walk; use crate::dag_walk;
use crate::op_store::OpStoreResult; use crate::op_heads_store::OpHeadResolutionError;
use crate::op_store::{OpStoreError, OpStoreResult};
use crate::operation::Operation; use crate::operation::Operation;
/// Error that may occur during evaluation of operation set expression.
#[derive(Debug, Error)]
pub enum OpsetEvaluationError {
/// Failed to resolve operation set expression.
#[error(transparent)]
OpsetResolution(#[from] OpsetResolutionError),
/// Failed to resolve the current operation heads.
#[error(transparent)]
OpHeadResolution(#[from] OpHeadResolutionError),
/// Failed to access operation object.
#[error(transparent)]
OpStore(#[from] OpStoreError),
}
/// Error that may occur during parsing and resolution of operation set
/// expression.
#[derive(Debug, Error)]
pub enum OpsetResolutionError {
// TODO: Maybe empty/multiple operations should be allowed, and rejected by
// caller as needed.
/// Expression resolved to multiple operations.
#[error(r#"The "{0}" expression resolved to more than one operation"#)]
MultipleOperations(String),
/// Expression resolved to no operations.
#[error(r#"The "{0}" expression resolved to no operations"#)]
EmptyOperations(String),
/// Invalid symbol as an operation ID.
#[error(r#"Operation ID "{0}" is not a valid hexadecimal prefix"#)]
InvalidIdPrefix(String),
/// Operation ID not found.
#[error(r#"No operation ID matching "{0}""#)]
NoSuchOperation(String),
/// Operation ID prefix matches multiple operations.
#[error(r#"Operation ID prefix "{0}" is ambiguous"#)]
AmbiguousIdPrefix(String),
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct OperationByEndTime(Operation); struct OperationByEndTime(Operation);