cli: add convenient wrapper for user revset evaluation

Many callers of resolve_revset() and evaluate_revset() will be migrated to
this wrapper. "single" and "default_single" APIs won't be replaced because
they require more contexts to construct error messages.

id_prefix_context() now uses bare revset::parse() to avoid dependency cycle.
This commit is contained in:
Yuya Nishihara 2024-03-30 23:59:49 +09:00
parent 75e938c6b8
commit 690270670e
9 changed files with 103 additions and 43 deletions

View file

@ -53,7 +53,7 @@ use jj_lib::repo::{
use jj_lib::repo_path::{FsPathParseError, RepoPath, RepoPathBuf};
use jj_lib::revset::{
Revset, RevsetAliasesMap, RevsetCommitRef, RevsetExpression, RevsetFilterPredicate,
RevsetIteratorExt, RevsetParseContext, RevsetParseError, RevsetWorkspaceContext,
RevsetIteratorExt, RevsetParseContext, RevsetWorkspaceContext,
};
use jj_lib::rewrite::restore_tree;
use jj_lib::settings::{ConfigResultExt as _, UserSettings};
@ -88,6 +88,7 @@ use crate::git_util::{
};
use crate::merge_tools::{DiffEditor, MergeEditor, MergeToolConfigError};
use crate::operation_templater::OperationTemplateLanguageExtension;
use crate::revset_util::RevsetExpressionEvaluator;
use crate::template_builder::TemplateLanguage;
use crate::template_parser::TemplateAliasesMap;
use crate::templater::{PropertyPlaceholder, TemplateRenderer};
@ -754,9 +755,10 @@ impl WorkspaceCommandHelper {
/// Resolve a revset any number of revisions (including 0).
pub fn resolve_revset(&self, revision_str: &str) -> Result<Vec<Commit>, CommandError> {
let revset_expression = self.parse_revset(revision_str)?;
let revset = self.evaluate_revset(revset_expression)?;
Ok(revset.iter().commits(self.repo().store()).try_collect()?)
Ok(self
.parse_revset(revision_str)?
.evaluate_to_commits()?
.try_collect()?)
}
/// Resolve a revset any number of revisions (including 0), but require the
@ -781,8 +783,7 @@ impl WorkspaceCommandHelper {
should_hint_about_all_prefix: bool,
) -> Result<Commit, CommandError> {
let revset_expression = self.parse_revset(revision_str)?;
let revset = self.evaluate_revset(revset_expression.clone())?;
let mut iter = revset.iter().commits(self.repo().store()).fuse();
let mut iter = revset_expression.evaluate_to_commits()?.fuse();
match (iter.next(), iter.next()) {
(Some(commit), None) => Ok(commit?),
(None, _) => Err(user_error(format!(
@ -821,7 +822,7 @@ impl WorkspaceCommandHelper {
`jj abandon -r <REVISION>`.",
);
} else if let RevsetExpression::CommitRef(RevsetCommitRef::Symbol(branch_name)) =
revset_expression.as_ref()
revset_expression.expression().as_ref()
{
// Separate hint if there's a conflicted branch
cmd_err.add_formatted_hint_with(|formatter| {
@ -860,30 +861,41 @@ impl WorkspaceCommandHelper {
pub fn parse_revset(
&self,
revision_str: &str,
) -> Result<Rc<RevsetExpression>, RevsetParseError> {
revset::parse(revision_str, &self.revset_parse_context())
) -> Result<RevsetExpressionEvaluator<'_>, CommandError> {
let expression = revset::parse(revision_str, &self.revset_parse_context())?;
self.attach_revset_evaluator(expression)
}
/// Parses the given revset expressions and concatenates them all.
pub fn parse_union_revsets(
&self,
revision_args: &[RevisionArg],
) -> Result<Rc<RevsetExpression>, RevsetParseError> {
) -> Result<RevsetExpressionEvaluator<'_>, CommandError> {
let context = self.revset_parse_context();
let expressions: Vec<_> = revision_args
.iter()
.map(|s| revset::parse(s, &context))
.try_collect()?;
Ok(RevsetExpression::union_all(&expressions))
let expression = RevsetExpression::union_all(&expressions);
self.attach_revset_evaluator(expression)
}
pub fn evaluate_revset<'repo>(
&'repo self,
expression: Rc<RevsetExpression>,
) -> Result<Box<dyn Revset + 'repo>, CommandError> {
let repo = self.repo().as_ref();
let symbol_resolver = revset_util::default_symbol_resolver(repo, self.id_prefix_context()?);
Ok(revset_util::evaluate(repo, &symbol_resolver, expression)?)
Ok(self.attach_revset_evaluator(expression)?.evaluate()?)
}
fn attach_revset_evaluator(
&self,
expression: Rc<RevsetExpression>,
) -> Result<RevsetExpressionEvaluator<'_>, CommandError> {
Ok(RevsetExpressionEvaluator::new(
self.repo().as_ref(),
self.id_prefix_context()?,
expression,
))
}
pub(crate) fn revset_parse_context(&self) -> RevsetParseContext {
@ -908,9 +920,10 @@ impl WorkspaceCommandHelper {
.get_string("revsets.short-prefixes")
.unwrap_or_else(|_| self.settings.default_revset());
if !revset_string.is_empty() {
let disambiguation_revset = self.parse_revset(&revset_string).map_err(|err| {
config_error_with_message("Invalid `revsets.short-prefixes`", err)
})?;
let disambiguation_revset =
revset::parse(&revset_string, &self.revset_parse_context()).map_err(|err| {
config_error_with_message("Invalid `revsets.short-prefixes`", err)
})?;
context = context.disambiguate_within(revset::optimize(disambiguation_revset));
}
Ok(context)

View file

@ -16,8 +16,6 @@ use std::io::Write;
use itertools::Itertools as _;
use jj_lib::object_id::ObjectId;
use jj_lib::repo::Repo as _;
use jj_lib::revset::RevsetIteratorExt as _;
use tracing::instrument;
use crate::cli_util::{CommandHelper, RevisionArg};
@ -52,12 +50,10 @@ pub(crate) fn cmd_abandon(
args: &AbandonArgs,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let to_abandon: Vec<_> = {
let repo = workspace_command.repo();
let expression = workspace_command.parse_union_revsets(&args.revisions)?;
let revset = workspace_command.evaluate_revset(expression)?;
revset.iter().commits(repo.store()).try_collect()?
};
let to_abandon: Vec<_> = workspace_command
.parse_union_revsets(&args.revisions)?
.evaluate_to_commits()?
.try_collect()?;
if to_abandon.is_empty() {
writeln!(ui.stderr(), "No revisions to abandon.")?;
return Ok(());

View file

@ -204,7 +204,7 @@ fn bench_revset<M: Measurement>(
revset: &str,
) -> Result<(), CommandError> {
writeln!(ui.stderr(), "----------Testing revset: {revset}----------")?;
let expression = revset::optimize(workspace_command.parse_revset(revset)?);
let expression = revset::optimize(workspace_command.parse_revset(revset)?.expression().clone());
// Time both evaluation and iteration.
let routine = |workspace_command: &WorkspaceCommandHelper, expression: Rc<RevsetExpression>| {
// Evaluate the expression without parsing/evaluating short-prefixes.

View file

@ -624,7 +624,10 @@ fn cmd_branch_list(
}
if !args.revisions.is_empty() {
// Match against local targets only, which is consistent with "jj git push".
let filter_expression = workspace_command.parse_union_revsets(&args.revisions)?;
let filter_expression = workspace_command
.parse_union_revsets(&args.revisions)?
.expression()
.clone();
// Intersects with the set of local branch targets to minimize the lookup space.
let revset_expression = RevsetExpression::branches(StringPattern::everything())
.intersection(&filter_expression);

View file

@ -42,11 +42,10 @@ pub(crate) fn cmd_duplicate(
args: &DuplicateArgs,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let to_duplicate: Vec<CommitId> = {
let expression = workspace_command.parse_union_revsets(&args.revisions)?;
let revset = workspace_command.evaluate_revset(expression)?;
revset.iter().collect() // in reverse topological order
};
let to_duplicate: Vec<CommitId> = workspace_command
.parse_union_revsets(&args.revisions)?
.evaluate_to_commit_ids()?
.collect(); // in reverse topological order
if to_duplicate.is_empty() {
writeln!(ui.stderr(), "No revisions to duplicate.")?;
return Ok(());

View file

@ -1205,6 +1205,7 @@ fn find_branches_targeted_by_revisions<'a>(
for rev_str in revisions {
let expression = workspace_command
.parse_revset(rev_str)?
.expression()
.intersection(&RevsetExpression::branches(StringPattern::everything()));
let revset = workspace_command.evaluate_revset(expression)?;
let mut commit_ids = revset.iter().peekable();

View file

@ -81,7 +81,9 @@ pub(crate) fn cmd_log(
workspace_command.parse_revset(&command.settings().default_revset())?
} else {
workspace_command.parse_union_revsets(&args.revisions)?
};
}
.expression()
.clone();
if !args.paths.is_empty() {
let repo_paths: Vec<_> = args
.paths

View file

@ -15,8 +15,6 @@
//! This file contains the internal implementation of `run`.
use itertools::Itertools as _;
use jj_lib::repo::Repo as _;
use jj_lib::revset::RevsetIteratorExt as _;
use crate::cli_util::{CommandHelper, RevisionArg};
use crate::command_error::{user_error, CommandError};
@ -52,12 +50,10 @@ pub struct RunArgs {
pub fn cmd_run(ui: &mut Ui, command: &CommandHelper, args: &RunArgs) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?;
let _resolved_commits: Vec<_> = {
let repo = workspace_command.repo();
let expression = workspace_command.parse_union_revsets(&args.revisions)?;
let revset = workspace_command.evaluate_revset(expression)?;
revset.iter().commits(repo.store()).try_collect()?
};
let _resolved_commits: Vec<_> = workspace_command
.parse_union_revsets(&args.revisions)?
.evaluate_to_commits()?
.try_collect()?;
// Jobs are resolved in this order:
// 1. Commandline argument iff > 0.
// 2. the amount of cores available.

View file

@ -17,12 +17,13 @@
use std::rc::Rc;
use itertools::Itertools as _;
use jj_lib::backend::CommitId;
use jj_lib::backend::{BackendResult, CommitId};
use jj_lib::commit::Commit;
use jj_lib::id_prefix::IdPrefixContext;
use jj_lib::repo::Repo;
use jj_lib::revset::{
self, DefaultSymbolResolver, Revset, RevsetAliasesMap, RevsetEvaluationError, RevsetExpression,
RevsetParseContext, RevsetParseError, RevsetResolutionError,
RevsetIteratorExt as _, RevsetParseContext, RevsetParseError, RevsetResolutionError,
};
use jj_lib::settings::ConfigResultExt as _;
use thiserror::Error;
@ -41,6 +42,55 @@ pub enum UserRevsetEvaluationError {
Evaluation(RevsetEvaluationError),
}
/// Wrapper around `RevsetExpression` to provide convenient methods.
pub struct RevsetExpressionEvaluator<'repo> {
repo: &'repo dyn Repo,
id_prefix_context: &'repo IdPrefixContext,
expression: Rc<RevsetExpression>,
}
impl<'repo> RevsetExpressionEvaluator<'repo> {
pub fn new(
repo: &'repo dyn Repo,
id_prefix_context: &'repo IdPrefixContext,
expression: Rc<RevsetExpression>,
) -> Self {
RevsetExpressionEvaluator {
repo,
id_prefix_context,
expression,
}
}
/// Returns the underlying expression.
pub fn expression(&self) -> &Rc<RevsetExpression> {
&self.expression
}
/// Evaluates the expression.
pub fn evaluate(&self) -> Result<Box<dyn Revset + 'repo>, UserRevsetEvaluationError> {
let symbol_resolver = default_symbol_resolver(self.repo, self.id_prefix_context);
evaluate(self.repo, &symbol_resolver, self.expression.clone())
}
/// Evaluates the expression to an iterator over commit ids. Entries are
/// sorted in reverse topological order.
pub fn evaluate_to_commit_ids(
&self,
) -> Result<Box<dyn Iterator<Item = CommitId> + 'repo>, UserRevsetEvaluationError> {
Ok(self.evaluate()?.iter())
}
/// Evaluates the expression to an iterator over commit objects. Entries are
/// sorted in reverse topological order.
pub fn evaluate_to_commits(
&self,
) -> Result<impl Iterator<Item = BackendResult<Commit>> + 'repo, UserRevsetEvaluationError>
{
Ok(self.evaluate()?.iter().commits(self.repo.store()))
}
}
pub fn load_revset_aliases(
ui: &Ui,
layered_configs: &LayeredConfigs,