From 8235e458ed1eb2dfa9c6bf238f08e0071862cc64 Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Wed, 13 Mar 2024 15:46:50 +0900 Subject: [PATCH] cli: extract primitives for user revset parsing and evaluation Some of them will be called directly from the commit templater which shouldn't know WorkspaceCommandHelper. All parameters are passed as function arguments instead of having a nicer wrapper struct. That's because some resources (e.g. repo and id prefix context) are also used for different purposes, and it seemed uneasy to introduce high-level abstraction satisfying all the use cases. --- cli/src/cli_util.rs | 81 +++++----------------------- cli/src/commands/debug.rs | 5 +- cli/src/lib.rs | 1 + cli/src/revset_util.rs | 110 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 128 insertions(+), 69 deletions(-) create mode 100644 cli/src/revset_util.rs diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index ae6968ad1..889042438 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -52,9 +52,8 @@ use jj_lib::repo::{ }; use jj_lib::repo_path::{FsPathParseError, RepoPath, RepoPathBuf}; use jj_lib::revset::{ - DefaultSymbolResolver, Revset, RevsetAliasesMap, RevsetCommitRef, RevsetExpression, - RevsetFilterPredicate, RevsetIteratorExt, RevsetParseContext, RevsetParseError, - RevsetWorkspaceContext, + Revset, RevsetAliasesMap, RevsetCommitRef, RevsetExpression, RevsetFilterPredicate, + RevsetIteratorExt, RevsetParseContext, RevsetParseError, RevsetWorkspaceContext, }; use jj_lib::rewrite::restore_tree; use jj_lib::settings::{ConfigResultExt as _, UserSettings}; @@ -68,7 +67,7 @@ use jj_lib::working_copy::{ use jj_lib::workspace::{ default_working_copy_factories, LockedWorkspace, Workspace, WorkspaceLoadError, WorkspaceLoader, }; -use jj_lib::{dag_walk, file_util, git, op_heads_store, op_walk, revset}; +use jj_lib::{dag_walk, file_util, git, op_heads_store, op_walk}; use once_cell::unsync::OnceCell; use tracing::instrument; use tracing_chrome::ChromeLayerBuilder; @@ -91,7 +90,7 @@ use crate::template_builder::TemplateLanguage; use crate::template_parser::TemplateAliasesMap; use crate::templater::Template; use crate::ui::{ColorChoice, Ui}; -use crate::{template_builder, text_util}; +use crate::{revset_util, template_builder, text_util}; #[derive(Clone)] struct ChromeTracingFlushGuard { @@ -406,7 +405,7 @@ impl WorkspaceCommandHelper { let settings = command.settings.clone(); let commit_summary_template_text = settings.config().get_string("templates.commit_summary")?; - let revset_aliases_map = load_revset_aliases(ui, &command.layered_configs)?; + let revset_aliases_map = revset_util::load_revset_aliases(ui, &command.layered_configs)?; let template_aliases_map = command.load_template_aliases(ui)?; let loaded_at_head = command.global_args.at_operation == "@"; let may_update_working_copy = loaded_at_head && !command.global_args.ignore_working_copy; @@ -824,18 +823,16 @@ Set which revision the branch points to with `jj branch set {branch_name} -r Result, RevsetParseError> { - let expression = revset::parse(revision_str, &self.revset_parse_context())?; - Ok(revset::optimize(expression)) + revset_util::parse(revision_str, &self.revset_parse_context()) } pub fn evaluate_revset<'repo>( &'repo self, - revset_expression: Rc, + expression: Rc, ) -> Result, CommandError> { - let symbol_resolver = self.revset_symbol_resolver()?; - let revset_expression = - revset_expression.resolve_user_expression(self.repo().as_ref(), &symbol_resolver)?; - Ok(revset_expression.evaluate(self.repo().as_ref())?) + let repo = self.repo().as_ref(); + let symbol_resolver = revset_util::default_symbol_resolver(repo, self.id_prefix_context()?); + revset_util::evaluate(repo, &symbol_resolver, expression) } pub(crate) fn revset_parse_context(&self) -> RevsetParseContext { @@ -851,18 +848,6 @@ Set which revision the branch points to with `jj branch set {branch_name} -r Result, CommandError> { - let id_prefix_context = self.id_prefix_context()?; - let commit_id_resolver: revset::PrefixResolver = - Box::new(|repo, prefix| id_prefix_context.resolve_commit_prefix(repo, prefix)); - let change_id_resolver: revset::PrefixResolver> = - Box::new(|repo, prefix| id_prefix_context.resolve_change_prefix(repo, prefix)); - let symbol_resolver = DefaultSymbolResolver::new(self.repo().as_ref()) - .with_commit_id_resolver(commit_id_resolver) - .with_change_id_resolver(change_id_resolver); - Ok(symbol_resolver) - } - pub fn id_prefix_context(&self) -> Result<&IdPrefixContext, CommandError> { self.user_repo.id_prefix_context.get_or_try_init(|| { let mut context: IdPrefixContext = IdPrefixContext::default(); @@ -948,21 +933,10 @@ Set which revision the branch points to with `jj branch set {branch_name} -r Result { - const TABLE_KEY: &str = "revset-aliases"; - let mut aliases_map = RevsetAliasesMap::new(); - // Load from all config layers in order. 'f(x)' in default layer should be - // overridden by 'f(a)' in user. - for (_, config) in layered_configs.sources() { - let table = if let Some(table) = config.get_table(TABLE_KEY).optional()? { - table - } else { - continue; - }; - for (decl, value) in table.into_iter().sorted_by(|a, b| a.0.cmp(&b.0)) { - let r = value - .into_string() - .map_err(|e| e.to_string()) - .and_then(|v| aliases_map.insert(&decl, v).map_err(|e| e.to_string())); - if let Err(s) = r { - writeln!(ui.warning(), r#"Failed to load "{TABLE_KEY}.{decl}": {s}"#)?; - } - } - } - Ok(aliases_map) -} - pub fn resolve_multiple_nonempty_revsets( revision_args: &[RevisionArg], workspace_command: &WorkspaceCommandHelper, diff --git a/cli/src/commands/debug.rs b/cli/src/commands/debug.rs index 5224d62fe..47519aae5 100644 --- a/cli/src/commands/debug.rs +++ b/cli/src/commands/debug.rs @@ -25,8 +25,8 @@ use jj_lib::{op_walk, revset}; use crate::cli_util::{CommandHelper, RevisionArg}; use crate::command_error::{internal_error, user_error, CommandError}; -use crate::template_parser; use crate::ui::Ui; +use crate::{revset_util, template_parser}; /// Low-level commands not intended for users #[derive(Subcommand, Clone, Debug)] @@ -143,7 +143,8 @@ fn cmd_debug_revset( writeln!(ui.stdout(), "{expression:#?}")?; writeln!(ui.stdout())?; - let symbol_resolver = workspace_command.revset_symbol_resolver()?; + let symbol_resolver = + revset_util::default_symbol_resolver(repo, workspace_command.id_prefix_context()?); let expression = expression.resolve_user_expression(repo, &symbol_resolver)?; writeln!(ui.stdout(), "-- Resolved:")?; writeln!(ui.stdout(), "{expression:#?}")?; diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 00cbdfaab..6e60b293a 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -29,6 +29,7 @@ pub mod graphlog; pub mod merge_tools; pub mod operation_templater; mod progress; +pub mod revset_util; pub mod template_builder; pub mod template_parser; pub mod templater; diff --git a/cli/src/revset_util.rs b/cli/src/revset_util.rs new file mode 100644 index 000000000..a5ce91767 --- /dev/null +++ b/cli/src/revset_util.rs @@ -0,0 +1,110 @@ +// 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. + +//! Utility for parsing and evaluating user-provided revset expressions. + +use std::rc::Rc; + +use itertools::Itertools as _; +use jj_lib::backend::CommitId; +use jj_lib::id_prefix::IdPrefixContext; +use jj_lib::repo::Repo; +use jj_lib::revset::{ + self, DefaultSymbolResolver, Revset, RevsetAliasesMap, RevsetExpression, RevsetParseContext, + RevsetParseError, +}; +use jj_lib::settings::ConfigResultExt as _; + +use crate::command_error::{user_error, CommandError}; +use crate::config::LayeredConfigs; +use crate::ui::Ui; + +pub fn load_revset_aliases( + ui: &Ui, + layered_configs: &LayeredConfigs, +) -> Result { + const TABLE_KEY: &str = "revset-aliases"; + let mut aliases_map = RevsetAliasesMap::new(); + // Load from all config layers in order. 'f(x)' in default layer should be + // overridden by 'f(a)' in user. + for (_, config) in layered_configs.sources() { + let table = if let Some(table) = config.get_table(TABLE_KEY).optional()? { + table + } else { + continue; + }; + for (decl, value) in table.into_iter().sorted_by(|a, b| a.0.cmp(&b.0)) { + let r = value + .into_string() + .map_err(|e| e.to_string()) + .and_then(|v| aliases_map.insert(&decl, v).map_err(|e| e.to_string())); + if let Err(s) = r { + writeln!(ui.warning(), r#"Failed to load "{TABLE_KEY}.{decl}": {s}"#)?; + } + } + } + Ok(aliases_map) +} + +pub fn parse( + revision_str: &str, + context: &RevsetParseContext, +) -> Result, RevsetParseError> { + let expression = revset::parse(revision_str, context)?; + Ok(revset::optimize(expression)) +} + +pub fn evaluate<'a>( + repo: &'a dyn Repo, + symbol_resolver: &DefaultSymbolResolver, + expression: Rc, +) -> Result, CommandError> { + let resolved = expression.resolve_user_expression(repo, symbol_resolver)?; + Ok(resolved.evaluate(repo)?) +} + +/// Wraps the given `IdPrefixContext` in `SymbolResolver` to be passed in to +/// `evaluate()`. +pub fn default_symbol_resolver<'a>( + repo: &'a dyn Repo, + id_prefix_context: &'a IdPrefixContext, +) -> DefaultSymbolResolver<'a> { + let commit_id_resolver: revset::PrefixResolver = + Box::new(|repo, prefix| id_prefix_context.resolve_commit_prefix(repo, prefix)); + let change_id_resolver: revset::PrefixResolver> = + Box::new(|repo, prefix| id_prefix_context.resolve_change_prefix(repo, prefix)); + DefaultSymbolResolver::new(repo) + .with_commit_id_resolver(commit_id_resolver) + .with_change_id_resolver(change_id_resolver) +} + +/// Parses user-configured expression defining the immutable set. +pub fn parse_immutable_expression( + repo: &dyn Repo, + context: &RevsetParseContext, +) -> Result, CommandError> { + let (params, immutable_heads_str) = + context.aliases_map.get_function("immutable_heads").unwrap(); + if !params.is_empty() { + return Err(user_error( + r#"The `revset-aliases.immutable_heads()` function must be declared without arguments."#, + )); + } + let immutable_heads_revset = parse(immutable_heads_str, context)?; + Ok(immutable_heads_revset + .ancestors() + .union(&RevsetExpression::commit( + repo.store().root_commit_id().clone(), + ))) +}