revset: resolve file path at parse() stage

Baby step towards embedding matcher in RevsetExpression. If we had a fileset
language or regex pattern, we would probably want to parse it at this stage
so the syntax error can be reported without evaluation.
This commit is contained in:
Yuya Nishihara 2022-11-01 16:44:06 +09:00
parent 78c0cf81bf
commit fba6741c23

View file

@ -46,10 +46,6 @@ pub enum RevsetError {
AmbiguousCommitIdPrefix(String), AmbiguousCommitIdPrefix(String),
#[error("Change id prefix \"{0}\" is ambiguous")] #[error("Change id prefix \"{0}\" is ambiguous")]
AmbiguousChangeIdPrefix(String), AmbiguousChangeIdPrefix(String),
#[error("Invalid file pattern: {0}")]
FsPathParseError(#[from] FsPathParseError),
#[error("Cannot resolve file pattern without workspace")]
FsPathWithoutWorkspace,
#[error("Unexpected error from store: {0}")] #[error("Unexpected error from store: {0}")]
StoreError(#[from] BackendError), StoreError(#[from] BackendError),
} }
@ -203,6 +199,10 @@ pub enum RevsetParseError {
NoSuchFunction(String), NoSuchFunction(String),
#[error("Invalid arguments to revset function \"{name}\": {message}")] #[error("Invalid arguments to revset function \"{name}\": {message}")]
InvalidFunctionArguments { name: String, message: String }, InvalidFunctionArguments { name: String, message: String },
#[error("Invalid file pattern: {0}")]
FsPathParseError(#[from] FsPathParseError),
#[error("Cannot resolve file pattern without workspace")]
FsPathWithoutWorkspace,
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
@ -211,7 +211,7 @@ pub enum RevsetFilterPredicate {
Description(String), Description(String),
Author(String), // Matches against both name and email Author(String), // Matches against both name and email
Committer(String), // Matches against both name and email Committer(String), // Matches against both name and email
File(String), File(RepoPath),
} }
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
@ -395,7 +395,7 @@ impl RevsetExpression {
} }
/// Commits in `self` modifying the paths specified by the `pattern`. /// Commits in `self` modifying the paths specified by the `pattern`.
pub fn with_file(self: &Rc<RevsetExpression>, pattern: String) -> Rc<RevsetExpression> { pub fn with_file(self: &Rc<RevsetExpression>, pattern: RepoPath) -> Rc<RevsetExpression> {
Rc::new(RevsetExpression::Filter { Rc::new(RevsetExpression::Filter {
candidates: self.clone(), candidates: self.clone(),
predicate: RevsetFilterPredicate::File(pattern), predicate: RevsetFilterPredicate::File(pattern),
@ -786,7 +786,7 @@ fn parse_function_expression(
}) })
} }
} }
"description" | "author" | "committer" | "file" => { "description" | "author" | "committer" => {
if arg_count != 1 { if arg_count != 1 {
return Err(RevsetParseError::InvalidFunctionArguments { return Err(RevsetParseError::InvalidFunctionArguments {
name, name,
@ -802,12 +802,29 @@ fn parse_function_expression(
"description" => Ok(candidates.with_description(needle)), "description" => Ok(candidates.with_description(needle)),
"author" => Ok(candidates.with_author(needle)), "author" => Ok(candidates.with_author(needle)),
"committer" => Ok(candidates.with_committer(needle)), "committer" => Ok(candidates.with_committer(needle)),
"file" => Ok(candidates.with_file(needle)),
_ => { _ => {
panic!("unexpected function name: {}", name) panic!("unexpected function name: {}", name)
} }
} }
} }
"file" => {
if arg_count != 1 {
return Err(RevsetParseError::InvalidFunctionArguments {
name,
message: "Expected 1 argument".to_string(),
});
}
if let Some(ctx) = workspace_ctx {
let needle = parse_function_argument_to_string(
&name,
argument_pairs.next().unwrap().into_inner(),
)?;
let path = RepoPath::parse_fs_path(ctx.cwd, ctx.workspace_root, &needle)?;
Ok(RevsetExpression::all().with_file(path))
} else {
Err(RevsetParseError::FsPathWithoutWorkspace)
}
}
_ => Err(RevsetParseError::NoSuchFunction(name)), _ => Err(RevsetParseError::NoSuchFunction(name)),
} }
} }
@ -1565,14 +1582,10 @@ pub fn evaluate_expression<'repo>(
})) }))
} }
RevsetFilterPredicate::File(pattern) => { RevsetFilterPredicate::File(pattern) => {
if let Some(ctx) = workspace_ctx { // TODO: Add support for globs and other formats
// TODO: Add support for globs and other formats let matcher: Box<dyn Matcher> =
let path = RepoPath::parse_fs_path(ctx.cwd, ctx.workspace_root, pattern)?; Box::new(PrefixMatcher::new(std::slice::from_ref(pattern)));
let matcher: Box<dyn Matcher> = Box::new(PrefixMatcher::new(&[path])); Ok(filter_by_diff(repo, matcher, candidates))
Ok(filter_by_diff(repo, matcher, candidates))
} else {
Err(RevsetError::FsPathWithoutWorkspace)
}
} }
} }
} }
@ -1645,7 +1658,13 @@ mod tests {
use super::*; use super::*;
fn parse(revset_str: &str) -> Result<Rc<RevsetExpression>, RevsetParseError> { fn parse(revset_str: &str) -> Result<Rc<RevsetExpression>, RevsetParseError> {
super::parse(revset_str, None) // Set up pseudo context to resolve file(path)
let workspace_ctx = RevsetWorkspaceContext {
cwd: Path::new("/"),
workspace_id: &WorkspaceId::default(),
workspace_root: Path::new("/"),
};
super::parse(revset_str, Some(&workspace_ctx))
} }
#[test] #[test]
@ -1733,10 +1752,10 @@ mod tests {
}) })
); );
assert_eq!( assert_eq!(
foo_symbol.with_file("pattern".to_string()), foo_symbol.with_file(RepoPath::from_internal_string("pattern")),
Rc::new(RevsetExpression::Filter { Rc::new(RevsetExpression::Filter {
candidates: foo_symbol.clone(), candidates: foo_symbol.clone(),
predicate: RevsetFilterPredicate::File("pattern".to_string()), predicate: RevsetFilterPredicate::File(RepoPath::from_internal_string("pattern")),
}) })
); );
assert_eq!( assert_eq!(