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(), + ))) +}