2024-03-13 06:46:50 +00:00
|
|
|
// 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.
|
|
|
|
|
2024-03-13 11:08:25 +00:00
|
|
|
use std::iter;
|
2024-03-13 06:46:50 +00:00
|
|
|
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::{
|
2024-03-13 07:13:48 +00:00
|
|
|
self, DefaultSymbolResolver, Revset, RevsetAliasesMap, RevsetEvaluationError, RevsetExpression,
|
|
|
|
RevsetParseContext, RevsetParseError, RevsetResolutionError,
|
2024-03-13 06:46:50 +00:00
|
|
|
};
|
|
|
|
use jj_lib::settings::ConfigResultExt as _;
|
2024-03-13 07:13:48 +00:00
|
|
|
use thiserror::Error;
|
2024-03-13 06:46:50 +00:00
|
|
|
|
|
|
|
use crate::command_error::{user_error, CommandError};
|
|
|
|
use crate::config::LayeredConfigs;
|
|
|
|
use crate::ui::Ui;
|
|
|
|
|
2024-03-14 02:59:34 +00:00
|
|
|
const BUILTIN_IMMUTABLE_HEADS: &str = "immutable_heads";
|
|
|
|
|
2024-03-13 07:13:48 +00:00
|
|
|
#[derive(Debug, Error)]
|
|
|
|
pub enum UserRevsetEvaluationError {
|
|
|
|
#[error(transparent)]
|
|
|
|
Resolution(RevsetResolutionError),
|
|
|
|
#[error(transparent)]
|
|
|
|
Evaluation(RevsetEvaluationError),
|
|
|
|
}
|
|
|
|
|
2024-03-13 06:46:50 +00:00
|
|
|
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}"#)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-03-14 02:59:34 +00:00
|
|
|
|
|
|
|
// TODO: If we add support for function overloading (#2966), this check can
|
|
|
|
// be removed.
|
|
|
|
let (params, _) = aliases_map.get_function(BUILTIN_IMMUTABLE_HEADS).unwrap();
|
|
|
|
if !params.is_empty() {
|
|
|
|
return Err(user_error(format!(
|
|
|
|
"The `revset-aliases.{name}()` function must be declared without arguments",
|
|
|
|
name = BUILTIN_IMMUTABLE_HEADS
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
|
2024-03-13 06:46:50 +00:00
|
|
|
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>,
|
2024-03-13 07:13:48 +00:00
|
|
|
) -> Result<Box<dyn Revset + 'a>, UserRevsetEvaluationError> {
|
|
|
|
let resolved = expression
|
|
|
|
.resolve_user_expression(repo, symbol_resolver)
|
|
|
|
.map_err(UserRevsetEvaluationError::Resolution)?;
|
|
|
|
resolved
|
|
|
|
.evaluate(repo)
|
|
|
|
.map_err(UserRevsetEvaluationError::Evaluation)
|
2024-03-13 06:46:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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,
|
2024-03-14 02:59:34 +00:00
|
|
|
) -> Result<Rc<RevsetExpression>, RevsetParseError> {
|
|
|
|
let (params, immutable_heads_str) = context
|
|
|
|
.aliases_map
|
|
|
|
.get_function(BUILTIN_IMMUTABLE_HEADS)
|
|
|
|
.unwrap();
|
|
|
|
assert!(
|
|
|
|
params.is_empty(),
|
|
|
|
"invalid declaration should have been rejected by load_revset_aliases()"
|
|
|
|
);
|
2024-03-13 06:46:50 +00:00
|
|
|
let immutable_heads_revset = parse(immutable_heads_str, context)?;
|
|
|
|
Ok(immutable_heads_revset
|
|
|
|
.ancestors()
|
|
|
|
.union(&RevsetExpression::commit(
|
|
|
|
repo.store().root_commit_id().clone(),
|
|
|
|
)))
|
|
|
|
}
|
2024-03-13 11:08:25 +00:00
|
|
|
|
|
|
|
pub fn format_parse_error(err: &RevsetParseError) -> String {
|
|
|
|
let message = iter::successors(Some(err), |e| e.origin()).join("\n");
|
|
|
|
format!("Failed to parse revset: {message}")
|
|
|
|
}
|