2369 lines · 91.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::cmp::Ordering;use std::collections::HashMap;use std::io;use std::rc::Rc; use bstr::BString;use futures::stream::BoxStream;use futures::StreamExt as _;use futures::TryStreamExt as _;use itertools::Itertools as _;use jj_lib::backend::BackendResult;use jj_lib::backend::ChangeId;use jj_lib::backend::CommitId;use jj_lib::backend::TreeValue;use jj_lib::commit::Commit;use jj_lib::conflicts;use jj_lib::conflicts::ConflictMarkerStyle;use jj_lib::copies::CopiesTreeDiffEntry;use jj_lib::copies::CopiesTreeDiffEntryPath;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::merge::MergedTreeValue;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::ref_name::WorkspaceName;use jj_lib::ref_name::WorkspaceNameBuf;use jj_lib::repo::Repo;use jj_lib::repo_path::RepoPathBuf;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::settings::UserSettings;use jj_lib::signing::SigStatus;use jj_lib::signing::SignError;use jj_lib::signing::SignResult;use jj_lib::signing::Verification;use jj_lib::store::Store;use jj_lib::trailer;use jj_lib::trailer::Trailer;use once_cell::unsync::OnceCell;use pollster::FutureExt as _; use crate::diff_util;use crate::diff_util::DiffStats;use crate::formatter::Formatter;use crate::revset_util;use crate::template_builder;use crate::template_builder::expect_plain_text_expression;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_name: WorkspaceNameBuf,    // 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    // WorkspaceName are contained in RevsetParseContext for example.    revset_parse_context: RevsetParseContext<'repo>,    id_prefix_context: &'repo IdPrefixContext,    immutable_expression: Rc<UserRevsetExpression>,    conflict_marker_style: ConflictMarkerStyle,    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.    #[expect(clippy::too_many_arguments)]    pub fn new(        repo: &'repo dyn Repo,        path_converter: &'repo RepoPathUiConverter,        workspace_name: &WorkspaceName,        revset_parse_context: RevsetParseContext<'repo>,        id_prefix_context: &'repo IdPrefixContext,        immutable_expression: Rc<UserRevsetExpression>,        conflict_marker_style: ConflictMarkerStyle,        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_name: workspace_name.to_owned(),            revset_parse_context,            id_prefix_context,            immutable_expression,            conflict_marker_style,            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 settings(&self) -> &UserSettings {        self.repo.base_repo().settings()    }     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,                    Self::wrap_commit_list,                )            }            CommitTemplatePropertyKind::CommitRef(property) => {                let table = &self.build_fn_table.commit_ref_methods;                let build = template_parser::lookup_method(type_name, table, function)?;                build(self, diagnostics, build_ctx, property, function)            }            CommitTemplatePropertyKind::CommitRefOpt(property) => {                let type_name = "CommitRef";                let table = &self.build_fn_table.commit_ref_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::CommitRefList(property) => {                // TODO: migrate to table?                template_builder::build_formattable_list_method(                    self,                    diagnostics,                    build_ctx,                    property,                    function,                    Self::wrap_commit_ref,                    Self::wrap_commit_ref_list,                )            }            CommitTemplatePropertyKind::RepoPath(property) => {                let table = &self.build_fn_table.repo_path_methods;                let build = template_parser::lookup_method(type_name, table, function)?;                build(self, diagnostics, build_ctx, property, function)            }            CommitTemplatePropertyKind::RepoPathOpt(property) => {                let type_name = "RepoPath";                let table = &self.build_fn_table.repo_path_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::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)            }            CommitTemplatePropertyKind::TreeDiffEntry(property) => {                let table = &self.build_fn_table.tree_diff_entry_methods;                let build = template_parser::lookup_method(type_name, table, function)?;                build(self, diagnostics, build_ctx, property, function)            }            CommitTemplatePropertyKind::TreeDiffEntryList(property) => {                // TODO: migrate to table?                template_builder::build_unformattable_list_method(                    self,                    diagnostics,                    build_ctx,                    property,                    function,                    Self::wrap_tree_diff_entry,                    Self::wrap_tree_diff_entry_list,                )            }            CommitTemplatePropertyKind::TreeEntry(property) => {                let table = &self.build_fn_table.tree_entry_methods;                let build = template_parser::lookup_method(type_name, table, function)?;                build(self, diagnostics, build_ctx, property, function)            }            CommitTemplatePropertyKind::DiffStats(property) => {                let table = &self.build_fn_table.diff_stats_methods;                let build = template_parser::lookup_method(type_name, table, function)?;                // Strip off formatting parameters which are needed only for the                // default template output.                let property = Box::new(property.map(|formatted| formatted.stats));                build(self, diagnostics, build_ctx, property, function)            }            CommitTemplatePropertyKind::CryptographicSignatureOpt(property) => {                let type_name = "CryptographicSignature";                let table = &self.build_fn_table.cryptographic_signature_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::AnnotationLine(property) => {                let type_name = "AnnotationLine";                let table = &self.build_fn_table.annotation_line_methods;                let build = template_parser::lookup_method(type_name, table, function)?;                build(self, diagnostics, build_ctx, property, function)            }            CommitTemplatePropertyKind::Trailer(property) => {                let table = &self.build_fn_table.trailer_methods;                let build = template_parser::lookup_method(type_name, table, function)?;                build(self, diagnostics, build_ctx, property, function)            }            CommitTemplatePropertyKind::TrailerList(property) => {                // TODO: migrate to table?                if function.name == "contains_key" {                    let [key_node] = function.expect_exact_arguments()?;                    let key_property =                        expect_plain_text_expression(self, diagnostics, build_ctx, key_node)?;                    let out_property = (property, key_property)                        .map(|(trailers, key)| trailers.iter().any(|t| t.key == key));                    Ok(Self::wrap_boolean(out_property))                } else {                    template_builder::build_formattable_list_method(                        self,                        diagnostics,                        build_ctx,                        property,                        function,                        Self::wrap_trailer,                        Self::wrap_trailer_list,                    )                }            }        }    }} // 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_name(&self) -> &WorkspaceName {        &self.workspace_name    }     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_commit_ref(        property: impl TemplateProperty<Output = Rc<CommitRef>> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::CommitRef(Box::new(property))    }     pub fn wrap_commit_ref_opt(        property: impl TemplateProperty<Output = Option<Rc<CommitRef>>> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::CommitRefOpt(Box::new(property))    }     pub fn wrap_commit_ref_list(        property: impl TemplateProperty<Output = Vec<Rc<CommitRef>>> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::CommitRefList(Box::new(property))    }     pub fn wrap_repo_path(        property: impl TemplateProperty<Output = RepoPathBuf> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::RepoPath(Box::new(property))    }     pub fn wrap_repo_path_opt(        property: impl TemplateProperty<Output = Option<RepoPathBuf>> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::RepoPathOpt(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 fn wrap_tree_diff_entry(        property: impl TemplateProperty<Output = TreeDiffEntry> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::TreeDiffEntry(Box::new(property))    }     pub fn wrap_tree_diff_entry_list(        property: impl TemplateProperty<Output = Vec<TreeDiffEntry>> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::TreeDiffEntryList(Box::new(property))    }     pub fn wrap_tree_entry(        property: impl TemplateProperty<Output = TreeEntry> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::TreeEntry(Box::new(property))    }     pub fn wrap_diff_stats(        property: impl TemplateProperty<Output = DiffStatsFormatted<'repo>> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::DiffStats(Box::new(property))    }     fn wrap_cryptographic_signature_opt(        property: impl TemplateProperty<Output = Option<CryptographicSignature>> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::CryptographicSignatureOpt(Box::new(property))    }     pub fn wrap_annotation_line(        property: impl TemplateProperty<Output = AnnotationLine> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::AnnotationLine(Box::new(property))    }     pub fn wrap_trailer(        property: impl TemplateProperty<Output = Trailer> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::Trailer(Box::new(property))    }     pub fn wrap_trailer_list(        property: impl TemplateProperty<Output = Vec<Trailer>> + 'repo,    ) -> CommitTemplatePropertyKind<'repo> {        CommitTemplatePropertyKind::TrailerList(Box::new(property))    }}

Lines 1–500 of 2369.