863 lines · 32.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::cmp::max;use std::collections::HashMap;use std::io;use std::rc::Rc; use itertools::Itertools as _;use jj_lib::backend::{ChangeId, CommitId};use jj_lib::commit::Commit;use jj_lib::hex_util::to_reverse_hex;use jj_lib::id_prefix::IdPrefixContext;use jj_lib::object_id::ObjectId as _;use jj_lib::op_store::{RefTarget, WorkspaceId};use jj_lib::repo::Repo;use jj_lib::{git, rewrite};use once_cell::unsync::OnceCell; use crate::formatter::Formatter;use crate::template_builder::{    self, merge_fn_map, BuildContext, CoreTemplateBuildFnTable, CoreTemplatePropertyKind,    IntoTemplateProperty, TemplateBuildMethodFnMap, TemplateLanguage,};use crate::template_parser::{self, FunctionCallNode, TemplateParseResult};use crate::templater::{    self, IntoTemplate, PlainTextFormattedProperty, Template, TemplateFunction, TemplateProperty,    TemplatePropertyFn,};use crate::text_util; pub trait CommitTemplateLanguageExtension {    fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo>;} pub struct CommitTemplateLanguage<'repo> {    repo: &'repo dyn Repo,    workspace_id: WorkspaceId,    id_prefix_context: &'repo IdPrefixContext,    build_fn_table: CommitTemplateBuildFnTable<'repo>,    keyword_cache: CommitKeywordCache,} impl<'repo> CommitTemplateLanguage<'repo> {    /// Sets up environment where commit template will be transformed to    /// evaluation tree.    pub fn new(        repo: &'repo dyn Repo,        workspace_id: &WorkspaceId,        id_prefix_context: &'repo IdPrefixContext,        extension: Option<&dyn CommitTemplateLanguageExtension>,    ) -> Self {        let mut build_fn_table = CommitTemplateBuildFnTable::builtin();        if let Some(extension) = extension {            build_fn_table.merge(extension.build_fn_table());        }        CommitTemplateLanguage {            repo,            workspace_id: workspace_id.clone(),            id_prefix_context,            build_fn_table,            keyword_cache: CommitKeywordCache::default(),        }    }} impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> {    type Context = Commit;    type Property = CommitTemplatePropertyKind<'repo>;     template_builder::impl_core_wrap_property_fns!('repo, CommitTemplatePropertyKind::Core);     fn build_self(&self) -> Self::Property {        // Commit object is lightweight (a few Arc + CommitId)        self.wrap_commit(TemplatePropertyFn(|commit: &Commit| Ok(commit.clone())))    }     fn build_method(        &self,        build_ctx: &BuildContext<Self::Property>,        property: Self::Property,        function: &FunctionCallNode,    ) -> TemplateParseResult<Self::Property> {        match property {            CommitTemplatePropertyKind::Core(property) => {                let table = &self.build_fn_table.core;                table.build_method(self, build_ctx, property, function)            }            CommitTemplatePropertyKind::Commit(property) => {                let table = &self.build_fn_table.commit_methods;                let build = template_parser::lookup_method("Commit", table, function)?;                build(self, build_ctx, property, function)            }            CommitTemplatePropertyKind::CommitList(property) => {                // TODO: migrate to table?                template_builder::build_unformattable_list_method(                    self,                    build_ctx,                    property,                    function,                    |item| self.wrap_commit(item),                )            }            CommitTemplatePropertyKind::RefName(property) => {                let table = &self.build_fn_table.ref_name_methods;                let build = template_parser::lookup_method("RefName", table, function)?;                build(self, build_ctx, property, function)            }            CommitTemplatePropertyKind::RefNameList(property) => {                // TODO: migrate to table?                template_builder::build_formattable_list_method(                    self,                    build_ctx,                    property,                    function,                    |item| self.wrap_ref_name(item),                )            }            CommitTemplatePropertyKind::CommitOrChangeId(property) => {                let table = &self.build_fn_table.commit_or_change_id_methods;                let build = template_parser::lookup_method("CommitOrChangeId", table, function)?;                build(self, build_ctx, property, function)            }            CommitTemplatePropertyKind::ShortestIdPrefix(property) => {                let table = &self.build_fn_table.shortest_id_prefix_methods;                let build = template_parser::lookup_method("ShortestIdPrefix", table, function)?;                build(self, 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) -> &dyn Repo {        self.repo    }     pub fn workspace_id(&self) -> &WorkspaceId {        &self.workspace_id    }     pub fn keyword_cache(&self) -> &CommitKeywordCache {        &self.keyword_cache    }     pub fn wrap_commit(        &self,        property: impl TemplateProperty<Commit, Output = Commit> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::Commit(Box::new(property))    }     pub fn wrap_commit_list(        &self,        property: impl TemplateProperty<Commit, Output = Vec<Commit>> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::CommitList(Box::new(property))    }     pub fn wrap_ref_name(        &self,        property: impl TemplateProperty<Commit, Output = RefName> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::RefName(Box::new(property))    }     pub fn wrap_ref_name_list(        &self,        property: impl TemplateProperty<Commit, Output = Vec<RefName>> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::RefNameList(Box::new(property))    }     pub fn wrap_commit_or_change_id(        &self,        property: impl TemplateProperty<Commit, Output = CommitOrChangeId> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::CommitOrChangeId(Box::new(property))    }     pub fn wrap_shortest_id_prefix(        &self,        property: impl TemplateProperty<Commit, Output = ShortestIdPrefix> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::ShortestIdPrefix(Box::new(property))    }} pub enum CommitTemplatePropertyKind<'repo> {    Core(CoreTemplatePropertyKind<'repo, Commit>),    Commit(Box<dyn TemplateProperty<Commit, Output = Commit> + 'repo>),    CommitList(Box<dyn TemplateProperty<Commit, Output = Vec<Commit>> + 'repo>),    RefName(Box<dyn TemplateProperty<Commit, Output = RefName> + 'repo>),    RefNameList(Box<dyn TemplateProperty<Commit, Output = Vec<RefName>> + 'repo>),    CommitOrChangeId(Box<dyn TemplateProperty<Commit, Output = CommitOrChangeId> + 'repo>),    ShortestIdPrefix(Box<dyn TemplateProperty<Commit, Output = ShortestIdPrefix> + 'repo>),} impl<'repo> IntoTemplateProperty<'repo, Commit> for CommitTemplatePropertyKind<'repo> {    fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<Commit, Output = bool> + 'repo>> {        match self {            CommitTemplatePropertyKind::Core(property) => property.try_into_boolean(),            CommitTemplatePropertyKind::Commit(_) => None,            CommitTemplatePropertyKind::CommitList(property) => {                Some(Box::new(TemplateFunction::new(property, |l| {                    Ok(!l.is_empty())                })))            }            CommitTemplatePropertyKind::RefName(_) => None,            CommitTemplatePropertyKind::RefNameList(property) => {                Some(Box::new(TemplateFunction::new(property, |l| {                    Ok(!l.is_empty())                })))            }            CommitTemplatePropertyKind::CommitOrChangeId(_) => None,            CommitTemplatePropertyKind::ShortestIdPrefix(_) => None,        }    }     fn try_into_integer(self) -> Option<Box<dyn TemplateProperty<Commit, Output = i64> + 'repo>> {        match self {            CommitTemplatePropertyKind::Core(property) => property.try_into_integer(),            _ => None,        }    }     fn try_into_plain_text(        self,    ) -> Option<Box<dyn TemplateProperty<Commit, 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<Commit> + 'repo>> {        match self {            CommitTemplatePropertyKind::Core(property) => property.try_into_template(),            CommitTemplatePropertyKind::Commit(_) => None,            CommitTemplatePropertyKind::CommitList(_) => None,            CommitTemplatePropertyKind::RefName(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())            }        }    }} /// 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, RefName>,    pub commit_or_change_id_methods: CommitTemplateBuildMethodFnMap<'repo, CommitOrChangeId>,    pub shortest_id_prefix_methods: CommitTemplateBuildMethodFnMap<'repo, ShortestIdPrefix>,} 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(),        }    }     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(),        }    }     fn merge(&mut self, extension: CommitTemplateBuildFnTable<'repo>) {        let CommitTemplateBuildFnTable {            core,            commit_methods,            ref_name_methods,            commit_or_change_id_methods,            shortest_id_prefix_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,        );    }} #[derive(Debug, Default)]pub struct CommitKeywordCache {    // Build index lazily, and Rc to get away from &self lifetime.    branches_index: OnceCell<Rc<RefNamesIndex>>,    tags_index: OnceCell<Rc<RefNamesIndex>>,    git_refs_index: OnceCell<Rc<RefNamesIndex>>,} impl CommitKeywordCache {    pub fn branches_index(&self, repo: &dyn Repo) -> &Rc<RefNamesIndex> {        self.branches_index            .get_or_init(|| Rc::new(build_branches_index(repo)))    }     pub fn tags_index(&self, repo: &dyn Repo) -> &Rc<RefNamesIndex> {        self.tags_index            .get_or_init(|| Rc::new(build_ref_names_index(repo.view().tags())))    }     pub fn git_refs_index(&self, repo: &dyn Repo) -> &Rc<RefNamesIndex> {        self.git_refs_index            .get_or_init(|| Rc::new(build_ref_names_index(repo.view().git_refs())))    }} fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Commit> {    // Not using maplit::hashmap!{} or custom declarative macro here because    // code completion inside macro is quite restricted.    let mut map = CommitTemplateBuildMethodFnMap::<Commit>::new();    map.insert(        "description",        |language, _build_ctx, self_property, function| {            template_parser::expect_no_arguments(function)?;            let out_property = TemplateFunction::new(self_property, |commit| {                Ok(text_util::complete_newline(commit.description()))            });            Ok(language.wrap_string(out_property))        },    );    map.insert(        "change_id",        |language, _build_ctx, self_property, function| {            template_parser::expect_no_arguments(function)?;            let out_property = TemplateFunction::new(self_property, |commit| {                Ok(CommitOrChangeId::Change(commit.change_id().to_owned()))            });            Ok(language.wrap_commit_or_change_id(out_property))        },    );    map.insert(        "commit_id",        |language, _build_ctx, self_property, function| {            template_parser::expect_no_arguments(function)?;            let out_property = TemplateFunction::new(self_property, |commit| {                Ok(CommitOrChangeId::Commit(commit.id().to_owned()))            });            Ok(language.wrap_commit_or_change_id(out_property))        },    );    map.insert(        "parents",        |language, _build_ctx, self_property, function| {            template_parser::expect_no_arguments(function)?;            let out_property = TemplateFunction::new(self_property, |commit| Ok(commit.parents()));            Ok(language.wrap_commit_list(out_property))        },    );    map.insert("author", |language, _build_ctx, self_property, function| {        template_parser::expect_no_arguments(function)?;        let out_property =            TemplateFunction::new(self_property, |commit| Ok(commit.author().clone()));        Ok(language.wrap_signature(out_property))    });    map.insert(        "committer",        |language, _build_ctx, self_property, function| {            template_parser::expect_no_arguments(function)?;            let out_property =                TemplateFunction::new(self_property, |commit| Ok(commit.committer().clone()));            Ok(language.wrap_signature(out_property))        },    );    map.insert(        "working_copies",        |language, _build_ctx, self_property, function| {            template_parser::expect_no_arguments(function)?;            let repo = language.repo;            let out_property = TemplateFunction::new(self_property, |commit| {                Ok(extract_working_copies(repo, &commit))            });            Ok(language.wrap_string(out_property))        },    );    map.insert(        "current_working_copy",        |language, _build_ctx, self_property, function| {            template_parser::expect_no_arguments(function)?;            let repo = language.repo;            let workspace_id = language.workspace_id.clone();            let out_property = TemplateFunction::new(self_property, move |commit| {                Ok(Some(commit.id()) == repo.view().get_wc_commit_id(&workspace_id))            });            Ok(language.wrap_boolean(out_property))        },    );    map.insert(        "branches",        |language, _build_ctx, self_property, function| {            template_parser::expect_no_arguments(function)?;            let index = language.keyword_cache.branches_index(language.repo).clone();            let out_property = TemplateFunction::new(self_property, move |commit| {                Ok(index                    .get(commit.id())                    .iter()                    .filter(|ref_name| ref_name.is_local() || !ref_name.synced)                    .cloned()                    .collect())            });            Ok(language.wrap_ref_name_list(out_property))        },    );    map.insert(        "local_branches",        |language, _build_ctx, self_property, function| {            template_parser::expect_no_arguments(function)?;            let index = language.keyword_cache.branches_index(language.repo).clone();            let out_property = TemplateFunction::new(self_property, move |commit| {                Ok(index                    .get(commit.id())                    .iter()                    .filter(|ref_name| ref_name.is_local())                    .cloned()                    .collect())            });            Ok(language.wrap_ref_name_list(out_property))        },    );    map.insert(        "remote_branches",        |language, _build_ctx, self_property, function| {            template_parser::expect_no_arguments(function)?;            let index = language.keyword_cache.branches_index(language.repo).clone();            let out_property = TemplateFunction::new(self_property, move |commit| {                Ok(index                    .get(commit.id())                    .iter()                    .filter(|ref_name| ref_name.is_remote())                    .cloned()                    .collect())            });            Ok(language.wrap_ref_name_list(out_property))        },    );    map.insert("tags", |language, _build_ctx, self_property, function| {        template_parser::expect_no_arguments(function)?;        let index = language.keyword_cache.tags_index(language.repo).clone();        let out_property = TemplateFunction::new(self_property, move |commit| {            Ok(index.get(commit.id()).to_vec())        });        Ok(language.wrap_ref_name_list(out_property))    });    map.insert(        "git_refs",        |language, _build_ctx, self_property, function| {            template_parser::expect_no_arguments(function)?;            let index = language.keyword_cache.git_refs_index(language.repo).clone();            let out_property = TemplateFunction::new(self_property, move |commit| {                Ok(index.get(commit.id()).to_vec())            });            Ok(language.wrap_ref_name_list(out_property))        },    );    map.insert(        "git_head",

Lines 1–500 of 863.