mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-14 16:37:42 +00:00
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.
This commit is contained in:
parent
33911712a2
commit
8235e458ed
4 changed files with 128 additions and 69 deletions
|
@ -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 <RE
|
|||
&self,
|
||||
revision_str: &str,
|
||||
) -> Result<Rc<RevsetExpression>, 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<RevsetExpression>,
|
||||
expression: Rc<RevsetExpression>,
|
||||
) -> Result<Box<dyn Revset + 'repo>, 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 <RE
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn revset_symbol_resolver(&self) -> Result<DefaultSymbolResolver<'_>, CommandError> {
|
||||
let id_prefix_context = self.id_prefix_context()?;
|
||||
let commit_id_resolver: revset::PrefixResolver<CommitId> =
|
||||
Box::new(|repo, prefix| id_prefix_context.resolve_commit_prefix(repo, prefix));
|
||||
let change_id_resolver: revset::PrefixResolver<Vec<CommitId>> =
|
||||
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 <RE
|
|||
.map(|commit| commit.id().clone())
|
||||
.collect(),
|
||||
);
|
||||
let (params, immutable_heads_str) = self
|
||||
.revset_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 = self.parse_revset(immutable_heads_str)?;
|
||||
let immutable_revset = immutable_heads_revset
|
||||
.ancestors()
|
||||
.union(&RevsetExpression::commit(
|
||||
self.repo().store().root_commit_id().clone(),
|
||||
));
|
||||
let immutable_revset = revset_util::parse_immutable_expression(
|
||||
self.repo().as_ref(),
|
||||
&self.revset_parse_context(),
|
||||
)?;
|
||||
let revset = self.evaluate_revset(to_rewrite_revset.intersection(&immutable_revset))?;
|
||||
if let Some(commit) = revset.iter().commits(self.repo().store()).next() {
|
||||
let commit = commit?;
|
||||
|
@ -1634,33 +1608,6 @@ pub fn resolve_all_revs(
|
|||
}
|
||||
}
|
||||
|
||||
fn load_revset_aliases(
|
||||
ui: &Ui,
|
||||
layered_configs: &LayeredConfigs,
|
||||
) -> Result<RevsetAliasesMap, CommandError> {
|
||||
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,
|
||||
|
|
|
@ -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:#?}")?;
|
||||
|
|
|
@ -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;
|
||||
|
|
110
cli/src/revset_util.rs
Normal file
110
cli/src/revset_util.rs
Normal file
|
@ -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<RevsetAliasesMap, CommandError> {
|
||||
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<Rc<RevsetExpression>, 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<RevsetExpression>,
|
||||
) -> Result<Box<dyn Revset + 'a>, 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<CommitId> =
|
||||
Box::new(|repo, prefix| id_prefix_context.resolve_commit_prefix(repo, prefix));
|
||||
let change_id_resolver: revset::PrefixResolver<Vec<CommitId>> =
|
||||
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<Rc<RevsetExpression>, 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(),
|
||||
)))
|
||||
}
|
Loading…
Reference in a new issue