1635 lines · 61.5 KB · Rust
RawDownload
// Copyright 2020-2023 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. use std::any::Any;use std::cmp::max;use std::collections::HashMap;use std::io;use std::rc::Rc; use futures::stream::BoxStream;use itertools::Itertools as _;use jj_lib::backend::BackendResult;use jj_lib::backend::ChangeId;use jj_lib::backend::CommitId;use jj_lib::commit::Commit;use jj_lib::copies::CopiesTreeDiffEntry;use jj_lib::copies::CopyRecords;use jj_lib::extensions_map::ExtensionsMap;use jj_lib::fileset;use jj_lib::fileset::FilesetDiagnostics;use jj_lib::fileset::FilesetExpression;use jj_lib::id_prefix::IdPrefixContext;use jj_lib::id_prefix::IdPrefixIndex;use jj_lib::matchers::Matcher;use jj_lib::merged_tree::MergedTree;use jj_lib::object_id::ObjectId as _;use jj_lib::op_store::RefTarget;use jj_lib::op_store::RemoteRef;use jj_lib::op_store::WorkspaceId;use jj_lib::repo::Repo;use jj_lib::repo_path::RepoPathUiConverter;use jj_lib::revset;use jj_lib::revset::Revset;use jj_lib::revset::RevsetContainingFn;use jj_lib::revset::RevsetDiagnostics;use jj_lib::revset::RevsetModifier;use jj_lib::revset::RevsetParseContext;use jj_lib::revset::UserRevsetExpression;use jj_lib::store::Store;use once_cell::unsync::OnceCell; use crate::diff_util;use crate::formatter::Formatter;use crate::revset_util;use crate::template_builder;use crate::template_builder::merge_fn_map;use crate::template_builder::BuildContext;use crate::template_builder::CoreTemplateBuildFnTable;use crate::template_builder::CoreTemplatePropertyKind;use crate::template_builder::IntoTemplateProperty;use crate::template_builder::TemplateBuildMethodFnMap;use crate::template_builder::TemplateLanguage;use crate::template_parser;use crate::template_parser::ExpressionNode;use crate::template_parser::FunctionCallNode;use crate::template_parser::TemplateDiagnostics;use crate::template_parser::TemplateParseError;use crate::template_parser::TemplateParseResult;use crate::templater;use crate::templater::PlainTextFormattedProperty;use crate::templater::SizeHint;use crate::templater::Template;use crate::templater::TemplateFormatter;use crate::templater::TemplateProperty;use crate::templater::TemplatePropertyError;use crate::templater::TemplatePropertyExt as _;use crate::text_util; pub trait CommitTemplateLanguageExtension {    fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo>;     fn build_cache_extensions(&self, extensions: &mut ExtensionsMap);} pub struct CommitTemplateLanguage<'repo> {    repo: &'repo dyn Repo,    path_converter: &'repo RepoPathUiConverter,    workspace_id: WorkspaceId,    // RevsetParseContext doesn't borrow a repo, but we'll need 'repo lifetime    // anyway to capture it to evaluate dynamically-constructed user expression    // such as `revset("ancestors(" ++ commit_id ++ ")")`.    // TODO: Maybe refactor context structs? RepoPathUiConverter and WorkspaceId    // are contained in RevsetParseContext for example.    revset_parse_context: RevsetParseContext<'repo>,    id_prefix_context: &'repo IdPrefixContext,    immutable_expression: Rc<UserRevsetExpression>,    build_fn_table: CommitTemplateBuildFnTable<'repo>,    keyword_cache: CommitKeywordCache<'repo>,    cache_extensions: ExtensionsMap,} impl<'repo> CommitTemplateLanguage<'repo> {    /// Sets up environment where commit template will be transformed to    /// evaluation tree.    pub fn new(        repo: &'repo dyn Repo,        path_converter: &'repo RepoPathUiConverter,        workspace_id: &WorkspaceId,        revset_parse_context: RevsetParseContext<'repo>,        id_prefix_context: &'repo IdPrefixContext,        immutable_expression: Rc<UserRevsetExpression>,        extensions: &[impl AsRef<dyn CommitTemplateLanguageExtension>],    ) -> Self {        let mut build_fn_table = CommitTemplateBuildFnTable::builtin();        let mut cache_extensions = ExtensionsMap::empty();         for extension in extensions {            build_fn_table.merge(extension.as_ref().build_fn_table());            extension                .as_ref()                .build_cache_extensions(&mut cache_extensions);        }         CommitTemplateLanguage {            repo,            path_converter,            workspace_id: workspace_id.clone(),            revset_parse_context,            id_prefix_context,            immutable_expression,            build_fn_table,            keyword_cache: CommitKeywordCache::default(),            cache_extensions,        }    }} impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> {    type Property = CommitTemplatePropertyKind<'repo>;     template_builder::impl_core_wrap_property_fns!('repo, CommitTemplatePropertyKind::Core);     fn build_function(        &self,        diagnostics: &mut TemplateDiagnostics,        build_ctx: &BuildContext<Self::Property>,        function: &FunctionCallNode,    ) -> TemplateParseResult<Self::Property> {        let table = &self.build_fn_table.core;        table.build_function(self, diagnostics, build_ctx, function)    }     fn build_method(        &self,        diagnostics: &mut TemplateDiagnostics,        build_ctx: &BuildContext<Self::Property>,        property: Self::Property,        function: &FunctionCallNode,    ) -> TemplateParseResult<Self::Property> {        let type_name = property.type_name();        match property {            CommitTemplatePropertyKind::Core(property) => {                let table = &self.build_fn_table.core;                table.build_method(self, diagnostics, build_ctx, property, function)            }            CommitTemplatePropertyKind::Commit(property) => {                let table = &self.build_fn_table.commit_methods;                let build = template_parser::lookup_method(type_name, table, function)?;                build(self, diagnostics, build_ctx, property, function)            }            CommitTemplatePropertyKind::CommitOpt(property) => {                let type_name = "Commit";                let table = &self.build_fn_table.commit_methods;                let build = template_parser::lookup_method(type_name, table, function)?;                let inner_property = property.try_unwrap(type_name);                build(                    self,                    diagnostics,                    build_ctx,                    Box::new(inner_property),                    function,                )            }            CommitTemplatePropertyKind::CommitList(property) => {                // TODO: migrate to table?                template_builder::build_unformattable_list_method(                    self,                    diagnostics,                    build_ctx,                    property,                    function,                    Self::wrap_commit,                )            }            CommitTemplatePropertyKind::RefName(property) => {                let table = &self.build_fn_table.ref_name_methods;                let build = template_parser::lookup_method(type_name, table, function)?;                build(self, diagnostics, build_ctx, property, function)            }            CommitTemplatePropertyKind::RefNameOpt(property) => {                let type_name = "RefName";                let table = &self.build_fn_table.ref_name_methods;                let build = template_parser::lookup_method(type_name, table, function)?;                let inner_property = property.try_unwrap(type_name);                build(                    self,                    diagnostics,                    build_ctx,                    Box::new(inner_property),                    function,                )            }            CommitTemplatePropertyKind::RefNameList(property) => {                // TODO: migrate to table?                template_builder::build_formattable_list_method(                    self,                    diagnostics,                    build_ctx,                    property,                    function,                    Self::wrap_ref_name,                )            }            CommitTemplatePropertyKind::CommitOrChangeId(property) => {                let table = &self.build_fn_table.commit_or_change_id_methods;                let build = template_parser::lookup_method(type_name, table, function)?;                build(self, diagnostics, build_ctx, property, function)            }            CommitTemplatePropertyKind::ShortestIdPrefix(property) => {                let table = &self.build_fn_table.shortest_id_prefix_methods;                let build = template_parser::lookup_method(type_name, table, function)?;                build(self, diagnostics, build_ctx, property, function)            }            CommitTemplatePropertyKind::TreeDiff(property) => {                let table = &self.build_fn_table.tree_diff_methods;                let build = template_parser::lookup_method(type_name, table, function)?;                build(self, diagnostics, build_ctx, property, function)            }        }    }} // If we need to add multiple languages that support Commit types, this can be// turned into a trait which extends TemplateLanguage.impl<'repo> CommitTemplateLanguage<'repo> {    pub fn repo(&self) -> &'repo dyn Repo {        self.repo    }     pub fn workspace_id(&self) -> &WorkspaceId {        &self.workspace_id    }     pub fn keyword_cache(&self) -> &CommitKeywordCache<'repo> {        &self.keyword_cache    }     pub fn cache_extension<T: Any>(&self) -> Option<&T> {        self.cache_extensions.get::<T>()    }     pub fn wrap_commit(        property: impl TemplateProperty<Output = Commit> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::Commit(Box::new(property))    }     pub fn wrap_commit_opt(        property: impl TemplateProperty<Output = Option<Commit>> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::CommitOpt(Box::new(property))    }     pub fn wrap_commit_list(        property: impl TemplateProperty<Output = Vec<Commit>> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::CommitList(Box::new(property))    }     pub fn wrap_ref_name(        property: impl TemplateProperty<Output = Rc<RefName>> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::RefName(Box::new(property))    }     pub fn wrap_ref_name_opt(        property: impl TemplateProperty<Output = Option<Rc<RefName>>> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::RefNameOpt(Box::new(property))    }     pub fn wrap_ref_name_list(        property: impl TemplateProperty<Output = Vec<Rc<RefName>>> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::RefNameList(Box::new(property))    }     pub fn wrap_commit_or_change_id(        property: impl TemplateProperty<Output = CommitOrChangeId> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::CommitOrChangeId(Box::new(property))    }     pub fn wrap_shortest_id_prefix(        property: impl TemplateProperty<Output = ShortestIdPrefix> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::ShortestIdPrefix(Box::new(property))    }     pub fn wrap_tree_diff(        property: impl TemplateProperty<Output = TreeDiff> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::TreeDiff(Box::new(property))    }} pub enum CommitTemplatePropertyKind<'repo> {    Core(CoreTemplatePropertyKind<'repo>),    Commit(Box<dyn TemplateProperty<Output = Commit> + 'repo>),    CommitOpt(Box<dyn TemplateProperty<Output = Option<Commit>> + 'repo>),    CommitList(Box<dyn TemplateProperty<Output = Vec<Commit>> + 'repo>),    RefName(Box<dyn TemplateProperty<Output = Rc<RefName>> + 'repo>),    RefNameOpt(Box<dyn TemplateProperty<Output = Option<Rc<RefName>>> + 'repo>),    RefNameList(Box<dyn TemplateProperty<Output = Vec<Rc<RefName>>> + 'repo>),    CommitOrChangeId(Box<dyn TemplateProperty<Output = CommitOrChangeId> + 'repo>),    ShortestIdPrefix(Box<dyn TemplateProperty<Output = ShortestIdPrefix> + 'repo>),    TreeDiff(Box<dyn TemplateProperty<Output = TreeDiff> + 'repo>),} impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> {    fn type_name(&self) -> &'static str {        match self {            CommitTemplatePropertyKind::Core(property) => property.type_name(),            CommitTemplatePropertyKind::Commit(_) => "Commit",            CommitTemplatePropertyKind::CommitOpt(_) => "Option<Commit>",            CommitTemplatePropertyKind::CommitList(_) => "List<Commit>",            CommitTemplatePropertyKind::RefName(_) => "RefName",            CommitTemplatePropertyKind::RefNameOpt(_) => "Option<RefName>",            CommitTemplatePropertyKind::RefNameList(_) => "List<RefName>",            CommitTemplatePropertyKind::CommitOrChangeId(_) => "CommitOrChangeId",            CommitTemplatePropertyKind::ShortestIdPrefix(_) => "ShortestIdPrefix",            CommitTemplatePropertyKind::TreeDiff(_) => "TreeDiff",        }    }     fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<Output = bool> + 'repo>> {        match self {            CommitTemplatePropertyKind::Core(property) => property.try_into_boolean(),            CommitTemplatePropertyKind::Commit(_) => None,            CommitTemplatePropertyKind::CommitOpt(property) => {                Some(Box::new(property.map(|opt| opt.is_some())))            }            CommitTemplatePropertyKind::CommitList(property) => {                Some(Box::new(property.map(|l| !l.is_empty())))            }            CommitTemplatePropertyKind::RefName(_) => None,            CommitTemplatePropertyKind::RefNameOpt(property) => {                Some(Box::new(property.map(|opt| opt.is_some())))            }            CommitTemplatePropertyKind::RefNameList(property) => {                Some(Box::new(property.map(|l| !l.is_empty())))            }            CommitTemplatePropertyKind::CommitOrChangeId(_) => None,            CommitTemplatePropertyKind::ShortestIdPrefix(_) => None,            // TODO: boolean cast could be implemented, but explicit            // diff.empty() method might be better.            CommitTemplatePropertyKind::TreeDiff(_) => None,        }    }     fn try_into_integer(self) -> Option<Box<dyn TemplateProperty<Output = i64> + 'repo>> {        match self {            CommitTemplatePropertyKind::Core(property) => property.try_into_integer(),            _ => None,        }    }     fn try_into_plain_text(self) -> Option<Box<dyn TemplateProperty<Output = String> + 'repo>> {        match self {            CommitTemplatePropertyKind::Core(property) => property.try_into_plain_text(),            _ => {                let template = self.try_into_template()?;                Some(Box::new(PlainTextFormattedProperty::new(template)))            }        }    }     fn try_into_template(self) -> Option<Box<dyn Template + 'repo>> {        match self {            CommitTemplatePropertyKind::Core(property) => property.try_into_template(),            CommitTemplatePropertyKind::Commit(_) => None,            CommitTemplatePropertyKind::CommitOpt(_) => None,            CommitTemplatePropertyKind::CommitList(_) => None,            CommitTemplatePropertyKind::RefName(property) => Some(property.into_template()),            CommitTemplatePropertyKind::RefNameOpt(property) => Some(property.into_template()),            CommitTemplatePropertyKind::RefNameList(property) => Some(property.into_template()),            CommitTemplatePropertyKind::CommitOrChangeId(property) => {                Some(property.into_template())            }            CommitTemplatePropertyKind::ShortestIdPrefix(property) => {                Some(property.into_template())            }            CommitTemplatePropertyKind::TreeDiff(_) => None,        }    }     fn try_into_eq(self, other: Self) -> Option<Box<dyn TemplateProperty<Output = bool> + 'repo>> {        match (self, other) {            (CommitTemplatePropertyKind::Core(lhs), CommitTemplatePropertyKind::Core(rhs)) => {                lhs.try_into_eq(rhs)            }            (CommitTemplatePropertyKind::Core(_), _) => None,            (CommitTemplatePropertyKind::Commit(_), _) => None,            (CommitTemplatePropertyKind::CommitOpt(_), _) => None,            (CommitTemplatePropertyKind::CommitList(_), _) => None,            (CommitTemplatePropertyKind::RefName(_), _) => None,            (CommitTemplatePropertyKind::RefNameOpt(_), _) => None,            (CommitTemplatePropertyKind::RefNameList(_), _) => None,            (CommitTemplatePropertyKind::CommitOrChangeId(_), _) => None,            (CommitTemplatePropertyKind::ShortestIdPrefix(_), _) => None,            (CommitTemplatePropertyKind::TreeDiff(_), _) => None,        }    }} /// Table of functions that translate method call node of self type `T`.pub type CommitTemplateBuildMethodFnMap<'repo, T> =    TemplateBuildMethodFnMap<'repo, CommitTemplateLanguage<'repo>, T>; /// Symbol table of methods available in the commit template.pub struct CommitTemplateBuildFnTable<'repo> {    pub core: CoreTemplateBuildFnTable<'repo, CommitTemplateLanguage<'repo>>,    pub commit_methods: CommitTemplateBuildMethodFnMap<'repo, Commit>,    pub ref_name_methods: CommitTemplateBuildMethodFnMap<'repo, Rc<RefName>>,    pub commit_or_change_id_methods: CommitTemplateBuildMethodFnMap<'repo, CommitOrChangeId>,    pub shortest_id_prefix_methods: CommitTemplateBuildMethodFnMap<'repo, ShortestIdPrefix>,    pub tree_diff_methods: CommitTemplateBuildMethodFnMap<'repo, TreeDiff>,} impl<'repo> CommitTemplateBuildFnTable<'repo> {    /// Creates new symbol table containing the builtin methods.    fn builtin() -> Self {        CommitTemplateBuildFnTable {            core: CoreTemplateBuildFnTable::builtin(),            commit_methods: builtin_commit_methods(),            ref_name_methods: builtin_ref_name_methods(),            commit_or_change_id_methods: builtin_commit_or_change_id_methods(),            shortest_id_prefix_methods: builtin_shortest_id_prefix_methods(),            tree_diff_methods: builtin_tree_diff_methods(),        }    }     pub fn empty() -> Self {        CommitTemplateBuildFnTable {            core: CoreTemplateBuildFnTable::empty(),            commit_methods: HashMap::new(),            ref_name_methods: HashMap::new(),            commit_or_change_id_methods: HashMap::new(),            shortest_id_prefix_methods: HashMap::new(),            tree_diff_methods: HashMap::new(),        }    }     fn merge(&mut self, extension: CommitTemplateBuildFnTable<'repo>) {        let CommitTemplateBuildFnTable {            core,            commit_methods,            ref_name_methods,            commit_or_change_id_methods,            shortest_id_prefix_methods,            tree_diff_methods,        } = extension;         self.core.merge(core);        merge_fn_map(&mut self.commit_methods, commit_methods);        merge_fn_map(&mut self.ref_name_methods, ref_name_methods);        merge_fn_map(            &mut self.commit_or_change_id_methods,            commit_or_change_id_methods,        );        merge_fn_map(            &mut self.shortest_id_prefix_methods,            shortest_id_prefix_methods,        );        merge_fn_map(&mut self.tree_diff_methods, tree_diff_methods);    }} #[derive(Default)]pub struct CommitKeywordCache<'repo> {    // Build index lazily, and Rc to get away from &self lifetime.    bookmarks_index: OnceCell<Rc<RefNamesIndex>>,    tags_index: OnceCell<Rc<RefNamesIndex>>,    git_refs_index: OnceCell<Rc<RefNamesIndex>>,    is_immutable_fn: OnceCell<Rc<RevsetContainingFn<'repo>>>,} impl<'repo> CommitKeywordCache<'repo> {    pub fn bookmarks_index(&self, repo: &dyn Repo) -> &Rc<RefNamesIndex> {

Lines 1–500 of 1635.