// 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. //! Template environment for `jj log`, `jj evolog` and similar. use std::any::Any;use std::cmp::Ordering;use std::cmp::max;use std::collections::HashMap;use std::fmt;use std::fmt::Display;use std::io;use std::rc::Rc;use std::sync::Arc; use bstr::BString;use futures::StreamExt as _;use futures::TryStreamExt as _;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::backend::TreeValue;use jj_lib::commit::Commit;use jj_lib::conflict_labels::ConflictLabels;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::evolution::CommitEvolutionEntry;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::index::IndexResult;use jj_lib::matchers::Matcher;use jj_lib::merge::Diff;use jj_lib::merge::MergedTreeValue;use jj_lib::merged_tree::MergedTree;use jj_lib::object_id::ObjectId as _;use jj_lib::op_store::LocalRemoteRefTarget;use jj_lib::op_store::OperationId;use jj_lib::op_store::RefTarget;use jj_lib::op_store::RemoteRef;use jj_lib::ref_name::RefName;use jj_lib::ref_name::WorkspaceName;use jj_lib::ref_name::WorkspaceNameBuf;use jj_lib::repo::Repo;use jj_lib::repo::RepoLoader;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::rewrite::rebase_to_dest_parent;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 serde::Serialize as _; use crate::diff_util;use crate::diff_util::DiffStatEntry;use crate::diff_util::DiffStats;use crate::formatter::Formatter;use crate::operation_templater;use crate::operation_templater::OperationTemplateBuildFnTable;use crate::operation_templater::OperationTemplateEnvironment;use crate::operation_templater::OperationTemplatePropertyKind;use crate::operation_templater::OperationTemplatePropertyVar;use crate::revset_util;use crate::template_builder;use crate::template_builder::BuildContext;use crate::template_builder::CoreTemplateBuildFnTable;use crate::template_builder::CoreTemplatePropertyKind;use crate::template_builder::CoreTemplatePropertyVar;use crate::template_builder::TemplateBuildMethodFnMap;use crate::template_builder::TemplateLanguage;use crate::template_builder::expect_stringify_expression;use crate::template_builder::merge_fn_map;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::BoxedSerializeProperty;use crate::templater::BoxedTemplateProperty;use crate::templater::ListTemplate;use crate::templater::PlainTextFormattedProperty;use crate::templater::SizeHint;use crate::templater::Template;use crate::templater::TemplateFormatter;use crate::templater::TemplatePropertyError;use crate::templater::TemplatePropertyExt as _; pub trait CommitTemplateLanguageExtension { fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo>; fn build_cache_extensions(&self, extensions: &mut ExtensionsMap);} /// Template environment for `jj log` and `jj evolog`.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: Arc<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: Arc<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>; 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::Operation(property) => { let table = &self.build_fn_table.operation; 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).into_dyn(); build(self, diagnostics, build_ctx, inner_property, function) } CommitTemplatePropertyKind::CommitList(property) => { let table = &self.build_fn_table.commit_list_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::CommitEvolutionEntry(property) => { let table = &self.build_fn_table.commit_evolution_entry_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } 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).into_dyn(); build(self, diagnostics, build_ctx, inner_property, function) } CommitTemplatePropertyKind::CommitRefList(property) => { let table = &self.build_fn_table.commit_ref_list_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::WorkspaceRef(property) => { let table = &self.build_fn_table.workspace_ref_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::WorkspaceRefOpt(property) => { let type_name = "WorkspaceRef"; let table = &self.build_fn_table.workspace_ref_methods; let build = template_parser::lookup_method(type_name, table, function)?; let inner_property = property.try_unwrap(type_name).into_dyn(); build(self, diagnostics, build_ctx, inner_property, function) } CommitTemplatePropertyKind::WorkspaceRefList(property) => { let table = &self.build_fn_table.workspace_ref_list_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::RefSymbol(property) => { let table = &self.build_fn_table.core.string_methods; let build = template_parser::lookup_method(type_name, table, function)?; let inner_property = property.map(|RefSymbolBuf(s)| s).into_dyn(); build(self, diagnostics, build_ctx, inner_property, function) } CommitTemplatePropertyKind::RefSymbolOpt(property) => { let type_name = "RefSymbol"; let table = &self.build_fn_table.core.string_methods; let build = template_parser::lookup_method(type_name, table, function)?; let inner_property = property .try_unwrap(type_name) .map(|RefSymbolBuf(s)| s) .into_dyn(); build(self, diagnostics, build_ctx, inner_property, function) } 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).into_dyn(); build(self, diagnostics, build_ctx, inner_property, function) } CommitTemplatePropertyKind::ChangeId(property) => { let table = &self.build_fn_table.change_id_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::CommitId(property) => { let table = &self.build_fn_table.commit_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) => { let table = &self.build_fn_table.tree_diff_entry_list_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } 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::TreeEntryList(property) => { let table = &self.build_fn_table.tree_entry_list_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 = property.map(|formatted| formatted.stats).into_dyn(); build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::DiffStatEntry(property) => { let table = &self.build_fn_table.diff_stat_entry_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::DiffStatEntryList(property) => { let table = &self.build_fn_table.diff_stat_entry_list_methods; let build = template_parser::lookup_method(type_name, table, function)?; 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).into_dyn(); build(self, diagnostics, build_ctx, 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) => { let table = &self.build_fn_table.trailer_list_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_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>() }} impl OperationTemplateEnvironment for CommitTemplateLanguage<'_> { fn repo_loader(&self) -> &RepoLoader { self.repo.base_repo().loader() } fn current_op_id(&self) -> Option<&OperationId> { // TODO: Maybe return None if the repo is a MutableRepo? Some(self.repo.base_repo().op_id()) }} pub enum CommitTemplatePropertyKind<'repo> { Core(CoreTemplatePropertyKind<'repo>), Operation(OperationTemplatePropertyKind<'repo>), Commit(BoxedTemplateProperty<'repo, Commit>), CommitOpt(BoxedTemplateProperty<'repo, Option<Commit>>), CommitList(BoxedTemplateProperty<'repo, Vec<Commit>>), CommitEvolutionEntry(BoxedTemplateProperty<'repo, CommitEvolutionEntry>), CommitRef(BoxedTemplateProperty<'repo, Rc<CommitRef>>), CommitRefOpt(BoxedTemplateProperty<'repo, Option<Rc<CommitRef>>>), CommitRefList(BoxedTemplateProperty<'repo, Vec<Rc<CommitRef>>>), WorkspaceRef(BoxedTemplateProperty<'repo, WorkspaceRef>), WorkspaceRefOpt(BoxedTemplateProperty<'repo, Option<WorkspaceRef>>), WorkspaceRefList(BoxedTemplateProperty<'repo, Vec<WorkspaceRef>>), RefSymbol(BoxedTemplateProperty<'repo, RefSymbolBuf>), RefSymbolOpt(BoxedTemplateProperty<'repo, Option<RefSymbolBuf>>), RepoPath(BoxedTemplateProperty<'repo, RepoPathBuf>), RepoPathOpt(BoxedTemplateProperty<'repo, Option<RepoPathBuf>>), ChangeId(BoxedTemplateProperty<'repo, ChangeId>), CommitId(BoxedTemplateProperty<'repo, CommitId>), ShortestIdPrefix(BoxedTemplateProperty<'repo, ShortestIdPrefix>), TreeDiff(BoxedTemplateProperty<'repo, TreeDiff>), TreeDiffEntry(BoxedTemplateProperty<'repo, TreeDiffEntry>), TreeDiffEntryList(BoxedTemplateProperty<'repo, Vec<TreeDiffEntry>>), TreeEntry(BoxedTemplateProperty<'repo, TreeEntry>), TreeEntryList(BoxedTemplateProperty<'repo, Vec<TreeEntry>>), DiffStats(BoxedTemplateProperty<'repo, DiffStatsFormatted<'repo>>), DiffStatEntry(BoxedTemplateProperty<'repo, DiffStatEntry>), DiffStatEntryList(BoxedTemplateProperty<'repo, Vec<DiffStatEntry>>), CryptographicSignatureOpt(BoxedTemplateProperty<'repo, Option<CryptographicSignature>>), AnnotationLine(BoxedTemplateProperty<'repo, AnnotationLine>), Trailer(BoxedTemplateProperty<'repo, Trailer>), TrailerList(BoxedTemplateProperty<'repo, Vec<Trailer>>),} template_builder::impl_core_property_wrappers!(<'repo> CommitTemplatePropertyKind<'repo> => Core);operation_templater::impl_operation_property_wrappers!(<'repo> CommitTemplatePropertyKind<'repo> => Operation);template_builder::impl_property_wrappers!(<'repo> CommitTemplatePropertyKind<'repo> { Commit(Commit), CommitOpt(Option<Commit>), CommitList(Vec<Commit>), CommitEvolutionEntry(CommitEvolutionEntry), CommitRef(Rc<CommitRef>), CommitRefOpt(Option<Rc<CommitRef>>), CommitRefList(Vec<Rc<CommitRef>>), WorkspaceRef(WorkspaceRef), WorkspaceRefOpt(Option<WorkspaceRef>), WorkspaceRefList(Vec<WorkspaceRef>), RefSymbol(RefSymbolBuf), RefSymbolOpt(Option<RefSymbolBuf>), RepoPath(RepoPathBuf), RepoPathOpt(Option<RepoPathBuf>), ChangeId(ChangeId), CommitId(CommitId), ShortestIdPrefix(ShortestIdPrefix), TreeDiff(TreeDiff), TreeDiffEntry(TreeDiffEntry), TreeDiffEntryList(Vec<TreeDiffEntry>), TreeEntry(TreeEntry), TreeEntryList(Vec<TreeEntry>), DiffStats(DiffStatsFormatted<'repo>), DiffStatEntry(DiffStatEntry), DiffStatEntryList(Vec<DiffStatEntry>), CryptographicSignatureOpt(Option<CryptographicSignature>), AnnotationLine(AnnotationLine), Trailer(Trailer), TrailerList(Vec<Trailer>),}); impl<'repo> CoreTemplatePropertyVar<'repo> for CommitTemplatePropertyKind<'repo> { fn wrap_template(template: Box<dyn Template + 'repo>) -> Self { Self::Core(CoreTemplatePropertyKind::wrap_template(template)) } fn wrap_list_template(template: Box<dyn ListTemplate + 'repo>) -> Self { Self::Core(CoreTemplatePropertyKind::wrap_list_template(template)) } fn type_name(&self) -> &'static str { match self { Self::Core(property) => property.type_name(),Lines 1–500 of 3259. Load more
// 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. //! Template environment for `jj log`, `jj evolog` and similar. use std::any::Any; use std::cmp::Ordering; use std::cmp::max; use std::collections::HashMap; use std::fmt; use std::fmt::Display; use std::io; use std::rc::Rc; use std::sync::Arc; use bstr::BString; use futures::StreamExt as _; use futures::TryStreamExt as _; 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::backend::TreeValue; use jj_lib::commit::Commit; use jj_lib::conflict_labels::ConflictLabels; 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::evolution::CommitEvolutionEntry; 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::index::IndexResult; use jj_lib::matchers::Matcher; use jj_lib::merge::Diff; use jj_lib::merge::MergedTreeValue; use jj_lib::merged_tree::MergedTree; use jj_lib::object_id::ObjectId as _; use jj_lib::op_store::LocalRemoteRefTarget; use jj_lib::op_store::OperationId; use jj_lib::op_store::RefTarget; use jj_lib::op_store::RemoteRef; use jj_lib::ref_name::RefName; use jj_lib::ref_name::WorkspaceName; use jj_lib::ref_name::WorkspaceNameBuf; use jj_lib::repo::Repo; use jj_lib::repo::RepoLoader; 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::rewrite::rebase_to_dest_parent; 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 serde::Serialize as _; use crate::diff_util; use crate::diff_util::DiffStatEntry; use crate::diff_util::DiffStats; use crate::formatter::Formatter; use crate::operation_templater; use crate::operation_templater::OperationTemplateBuildFnTable; use crate::operation_templater::OperationTemplateEnvironment; use crate::operation_templater::OperationTemplatePropertyKind; use crate::operation_templater::OperationTemplatePropertyVar; use crate::revset_util; use crate::template_builder; use crate::template_builder::BuildContext; use crate::template_builder::CoreTemplateBuildFnTable; use crate::template_builder::CoreTemplatePropertyKind; use crate::template_builder::CoreTemplatePropertyVar; use crate::template_builder::TemplateBuildMethodFnMap; use crate::template_builder::TemplateLanguage; use crate::template_builder::expect_stringify_expression; use crate::template_builder::merge_fn_map; 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::BoxedSerializeProperty; use crate::templater::BoxedTemplateProperty; use crate::templater::ListTemplate; use crate::templater::PlainTextFormattedProperty; use crate::templater::SizeHint; use crate::templater::Template; use crate::templater::TemplateFormatter; use crate::templater::TemplatePropertyError; use crate::templater::TemplatePropertyExt as _; pub trait CommitTemplateLanguageExtension { fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo>; fn build_cache_extensions(&self, extensions: &mut ExtensionsMap); } /// Template environment for `jj log` and `jj evolog`. 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: Arc<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: Arc<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>; 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::Operation(property) => { let table = &self.build_fn_table.operation; 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).into_dyn(); build(self, diagnostics, build_ctx, inner_property, function) } CommitTemplatePropertyKind::CommitList(property) => { let table = &self.build_fn_table.commit_list_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::CommitEvolutionEntry(property) => { let table = &self.build_fn_table.commit_evolution_entry_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } 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).into_dyn(); build(self, diagnostics, build_ctx, inner_property, function) } CommitTemplatePropertyKind::CommitRefList(property) => { let table = &self.build_fn_table.commit_ref_list_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::WorkspaceRef(property) => { let table = &self.build_fn_table.workspace_ref_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::WorkspaceRefOpt(property) => { let type_name = "WorkspaceRef"; let table = &self.build_fn_table.workspace_ref_methods; let build = template_parser::lookup_method(type_name, table, function)?; let inner_property = property.try_unwrap(type_name).into_dyn(); build(self, diagnostics, build_ctx, inner_property, function) } CommitTemplatePropertyKind::WorkspaceRefList(property) => { let table = &self.build_fn_table.workspace_ref_list_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::RefSymbol(property) => { let table = &self.build_fn_table.core.string_methods; let build = template_parser::lookup_method(type_name, table, function)?; let inner_property = property.map(|RefSymbolBuf(s)| s).into_dyn(); build(self, diagnostics, build_ctx, inner_property, function) } CommitTemplatePropertyKind::RefSymbolOpt(property) => { let type_name = "RefSymbol"; let table = &self.build_fn_table.core.string_methods; let build = template_parser::lookup_method(type_name, table, function)?; let inner_property = property .try_unwrap(type_name) .map(|RefSymbolBuf(s)| s) .into_dyn(); build(self, diagnostics, build_ctx, inner_property, function) } 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).into_dyn(); build(self, diagnostics, build_ctx, inner_property, function) } CommitTemplatePropertyKind::ChangeId(property) => { let table = &self.build_fn_table.change_id_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::CommitId(property) => { let table = &self.build_fn_table.commit_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) => { let table = &self.build_fn_table.tree_diff_entry_list_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } 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::TreeEntryList(property) => { let table = &self.build_fn_table.tree_entry_list_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 = property.map(|formatted| formatted.stats).into_dyn(); build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::DiffStatEntry(property) => { let table = &self.build_fn_table.diff_stat_entry_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::DiffStatEntryList(property) => { let table = &self.build_fn_table.diff_stat_entry_list_methods; let build = template_parser::lookup_method(type_name, table, function)?; 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).into_dyn(); build(self, diagnostics, build_ctx, 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) => { let table = &self.build_fn_table.trailer_list_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_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>() } } impl OperationTemplateEnvironment for CommitTemplateLanguage<'_> { fn repo_loader(&self) -> &RepoLoader { self.repo.base_repo().loader() } fn current_op_id(&self) -> Option<&OperationId> { // TODO: Maybe return None if the repo is a MutableRepo? Some(self.repo.base_repo().op_id()) } } pub enum CommitTemplatePropertyKind<'repo> { Core(CoreTemplatePropertyKind<'repo>), Operation(OperationTemplatePropertyKind<'repo>), Commit(BoxedTemplateProperty<'repo, Commit>), CommitOpt(BoxedTemplateProperty<'repo, Option<Commit>>), CommitList(BoxedTemplateProperty<'repo, Vec<Commit>>), CommitEvolutionEntry(BoxedTemplateProperty<'repo, CommitEvolutionEntry>), CommitRef(BoxedTemplateProperty<'repo, Rc<CommitRef>>), CommitRefOpt(BoxedTemplateProperty<'repo, Option<Rc<CommitRef>>>), CommitRefList(BoxedTemplateProperty<'repo, Vec<Rc<CommitRef>>>), WorkspaceRef(BoxedTemplateProperty<'repo, WorkspaceRef>), WorkspaceRefOpt(BoxedTemplateProperty<'repo, Option<WorkspaceRef>>), WorkspaceRefList(BoxedTemplateProperty<'repo, Vec<WorkspaceRef>>), RefSymbol(BoxedTemplateProperty<'repo, RefSymbolBuf>), RefSymbolOpt(BoxedTemplateProperty<'repo, Option<RefSymbolBuf>>), RepoPath(BoxedTemplateProperty<'repo, RepoPathBuf>), RepoPathOpt(BoxedTemplateProperty<'repo, Option<RepoPathBuf>>), ChangeId(BoxedTemplateProperty<'repo, ChangeId>), CommitId(BoxedTemplateProperty<'repo, CommitId>), ShortestIdPrefix(BoxedTemplateProperty<'repo, ShortestIdPrefix>), TreeDiff(BoxedTemplateProperty<'repo, TreeDiff>), TreeDiffEntry(BoxedTemplateProperty<'repo, TreeDiffEntry>), TreeDiffEntryList(BoxedTemplateProperty<'repo, Vec<TreeDiffEntry>>), TreeEntry(BoxedTemplateProperty<'repo, TreeEntry>), TreeEntryList(BoxedTemplateProperty<'repo, Vec<TreeEntry>>), DiffStats(BoxedTemplateProperty<'repo, DiffStatsFormatted<'repo>>), DiffStatEntry(BoxedTemplateProperty<'repo, DiffStatEntry>), DiffStatEntryList(BoxedTemplateProperty<'repo, Vec<DiffStatEntry>>), CryptographicSignatureOpt(BoxedTemplateProperty<'repo, Option<CryptographicSignature>>), AnnotationLine(BoxedTemplateProperty<'repo, AnnotationLine>), Trailer(BoxedTemplateProperty<'repo, Trailer>), TrailerList(BoxedTemplateProperty<'repo, Vec<Trailer>>), } template_builder::impl_core_property_wrappers!(<'repo> CommitTemplatePropertyKind<'repo> => Core); operation_templater::impl_operation_property_wrappers!(<'repo> CommitTemplatePropertyKind<'repo> => Operation); template_builder::impl_property_wrappers!(<'repo> CommitTemplatePropertyKind<'repo> { Commit(Commit), CommitOpt(Option<Commit>), CommitList(Vec<Commit>), CommitEvolutionEntry(CommitEvolutionEntry), CommitRef(Rc<CommitRef>), CommitRefOpt(Option<Rc<CommitRef>>), CommitRefList(Vec<Rc<CommitRef>>), WorkspaceRef(WorkspaceRef), WorkspaceRefOpt(Option<WorkspaceRef>), WorkspaceRefList(Vec<WorkspaceRef>), RefSymbol(RefSymbolBuf), RefSymbolOpt(Option<RefSymbolBuf>), RepoPath(RepoPathBuf), RepoPathOpt(Option<RepoPathBuf>), ChangeId(ChangeId), CommitId(CommitId), ShortestIdPrefix(ShortestIdPrefix), TreeDiff(TreeDiff), TreeDiffEntry(TreeDiffEntry), TreeDiffEntryList(Vec<TreeDiffEntry>), TreeEntry(TreeEntry), TreeEntryList(Vec<TreeEntry>), DiffStats(DiffStatsFormatted<'repo>), DiffStatEntry(DiffStatEntry), DiffStatEntryList(Vec<DiffStatEntry>), CryptographicSignatureOpt(Option<CryptographicSignature>), AnnotationLine(AnnotationLine), Trailer(Trailer), TrailerList(Vec<Trailer>), }); impl<'repo> CoreTemplatePropertyVar<'repo> for CommitTemplatePropertyKind<'repo> { fn wrap_template(template: Box<dyn Template + 'repo>) -> Self { Self::Core(CoreTemplatePropertyKind::wrap_template(template)) } fn wrap_list_template(template: Box<dyn ListTemplate + 'repo>) -> Self { Self::Core(CoreTemplatePropertyKind::wrap_list_template(template)) } fn type_name(&self) -> &'static str { match self { Self::Core(property) => property.type_name(),