// 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::path::Path;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::MergedTreeValue;use jj_lib::backend::MergedTreeValueExt as _;use jj_lib::backend::Timestamp;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::conflicts::ConflictMaterializeOptions;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::fileset::FilesetParseContext;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::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::revset;use jj_lib::revset::Revset;use jj_lib::revset::RevsetContainingFn;use jj_lib::revset::RevsetDiagnostics;use jj_lib::revset::RevsetParseContext;use jj_lib::revset::RevsetParseError;use jj_lib::revset::RevsetStreamExt as _;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::template_parser;use jj_lib::template_parser::ExpressionNode;use jj_lib::template_parser::FunctionCallNode;use jj_lib::template_parser::TemplateDiagnostics;use jj_lib::template_parser::TemplateParseError;use jj_lib::template_parser::TemplateParseResult;use jj_lib::trailer;use jj_lib::trailer::Trailer;use jj_lib::ui_path::RepoPathUiConverter;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::DiffStatOptions;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::revset_util::UserRevsetEvaluationError;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::TemplateBuildFunctionFnMap;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::templater;use crate::templater::BoxedAnyProperty;use crate::templater::BoxedSerializeProperty;use crate::templater::BoxedTemplateProperty;use crate::templater::Literal;use crate::templater::NoTemplateBooleanCast;use crate::templater::SizeHint;use crate::templater::Template;use crate::templater::TemplateFormatter;use crate::templater::TemplatePropertyError;use crate::templater::TemplatePropertyExt as _;use crate::templater::list_to_boolean;use crate::templater::option_to_boolean; 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 in a way that // Revset/FilesetParseContext are constructed from a common resource? // 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, 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, } } fn conflict_materialize_options(&self) -> ConflictMaterializeOptions { ConflictMaterializeOptions { marker_style: self.conflict_marker_style, marker_len: None, merge: self.repo.store().merge_options().clone(), } } fn fileset_parse_context(&self) -> FilesetParseContext<'repo> { FilesetParseContext { aliases_map: self.revset_parse_context.fileset_aliases_map, path_converter: self.path_converter, } }} impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> { type Property = CommitTemplatePropertyKind<'repo>; fn settings(&self) -> &UserSettings { self.repo.base_repo().settings() } fn current_dir(&self) -> &Path { let RepoPathUiConverter::Fs { cwd, base: _ } = self.path_converter; cwd } 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 inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(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 inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(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 inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(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) => { // Forward to String methods, but the type_name is "RefSymbol" 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 inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(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 inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(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::TreeDiffEntryOpt(property) => { let inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(diagnostics, build_ctx, inner_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::TreeEntryOpt(property) => { let inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(diagnostics, build_ctx, inner_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::TreeValue(property) => { let table = &self.build_fn_table.tree_value_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::TreeValueOpt(property) => { let inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(diagnostics, build_ctx, inner_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::DiffStatEntryOpt(property) => { let inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(diagnostics, build_ctx, inner_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::CryptographicSignature(property) => { let table = &self.build_fn_table.cryptographic_signature_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::CryptographicSignatureOpt(property) => { let inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(diagnostics, build_ctx, inner_property, function) } CommitTemplatePropertyKind::AnnotationLine(property) => { 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::TrailerOpt(property) => { let inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(diagnostics, build_ctx, inner_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 { &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()) }} impl NoTemplateBooleanCast for Commit {}impl NoTemplateBooleanCast for CommitEvolutionEntry {}impl NoTemplateBooleanCast for Rc<CommitRef> {}impl NoTemplateBooleanCast for WorkspaceRef {}impl NoTemplateBooleanCast for RefSymbolBuf {}impl NoTemplateBooleanCast for RepoPathBuf {}impl NoTemplateBooleanCast for ChangeId {}impl NoTemplateBooleanCast for CommitId {}impl NoTemplateBooleanCast for ShortestIdPrefix {}impl NoTemplateBooleanCast for TreeDiff {}impl NoTemplateBooleanCast for TreeDiffEntry {}impl NoTemplateBooleanCast for TreeEntry {}impl NoTemplateBooleanCast for DiffStatsFormatted<'_> {}impl NoTemplateBooleanCast for DiffStatEntry {}impl NoTemplateBooleanCast for CryptographicSignature {}impl NoTemplateBooleanCast for AnnotationLine {}impl NoTemplateBooleanCast for Trailer {} 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>),Lines 1–500 of 3629. 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::path::Path; 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::MergedTreeValue; use jj_lib::backend::MergedTreeValueExt as _; use jj_lib::backend::Timestamp; 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::conflicts::ConflictMaterializeOptions; 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::fileset::FilesetParseContext; 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::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::revset; use jj_lib::revset::Revset; use jj_lib::revset::RevsetContainingFn; use jj_lib::revset::RevsetDiagnostics; use jj_lib::revset::RevsetParseContext; use jj_lib::revset::RevsetParseError; use jj_lib::revset::RevsetStreamExt as _; 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::template_parser; use jj_lib::template_parser::ExpressionNode; use jj_lib::template_parser::FunctionCallNode; use jj_lib::template_parser::TemplateDiagnostics; use jj_lib::template_parser::TemplateParseError; use jj_lib::template_parser::TemplateParseResult; use jj_lib::trailer; use jj_lib::trailer::Trailer; use jj_lib::ui_path::RepoPathUiConverter; 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::DiffStatOptions; 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::revset_util::UserRevsetEvaluationError; 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::TemplateBuildFunctionFnMap; 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::templater; use crate::templater::BoxedAnyProperty; use crate::templater::BoxedSerializeProperty; use crate::templater::BoxedTemplateProperty; use crate::templater::Literal; use crate::templater::NoTemplateBooleanCast; use crate::templater::SizeHint; use crate::templater::Template; use crate::templater::TemplateFormatter; use crate::templater::TemplatePropertyError; use crate::templater::TemplatePropertyExt as _; use crate::templater::list_to_boolean; use crate::templater::option_to_boolean; 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 in a way that // Revset/FilesetParseContext are constructed from a common resource? // 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, 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, } } fn conflict_materialize_options(&self) -> ConflictMaterializeOptions { ConflictMaterializeOptions { marker_style: self.conflict_marker_style, marker_len: None, merge: self.repo.store().merge_options().clone(), } } fn fileset_parse_context(&self) -> FilesetParseContext<'repo> { FilesetParseContext { aliases_map: self.revset_parse_context.fileset_aliases_map, path_converter: self.path_converter, } } } impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> { type Property = CommitTemplatePropertyKind<'repo>; fn settings(&self) -> &UserSettings { self.repo.base_repo().settings() } fn current_dir(&self) -> &Path { let RepoPathUiConverter::Fs { cwd, base: _ } = self.path_converter; cwd } 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 inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(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 inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(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 inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(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) => { // Forward to String methods, but the type_name is "RefSymbol" 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 inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(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 inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(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::TreeDiffEntryOpt(property) => { let inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(diagnostics, build_ctx, inner_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::TreeEntryOpt(property) => { let inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(diagnostics, build_ctx, inner_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::TreeValue(property) => { let table = &self.build_fn_table.tree_value_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::TreeValueOpt(property) => { let inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(diagnostics, build_ctx, inner_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::DiffStatEntryOpt(property) => { let inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(diagnostics, build_ctx, inner_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::CryptographicSignature(property) => { let table = &self.build_fn_table.cryptographic_signature_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(self, diagnostics, build_ctx, property, function) } CommitTemplatePropertyKind::CryptographicSignatureOpt(property) => { let inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(diagnostics, build_ctx, inner_property, function) } CommitTemplatePropertyKind::AnnotationLine(property) => { 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::TrailerOpt(property) => { let inner_property = property.try_unwrap(type_name).into_dyn_wrapped(); self.build_method(diagnostics, build_ctx, inner_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 { &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()) } } impl NoTemplateBooleanCast for Commit {} impl NoTemplateBooleanCast for CommitEvolutionEntry {} impl NoTemplateBooleanCast for Rc<CommitRef> {} impl NoTemplateBooleanCast for WorkspaceRef {} impl NoTemplateBooleanCast for RefSymbolBuf {} impl NoTemplateBooleanCast for RepoPathBuf {} impl NoTemplateBooleanCast for ChangeId {} impl NoTemplateBooleanCast for CommitId {} impl NoTemplateBooleanCast for ShortestIdPrefix {} impl NoTemplateBooleanCast for TreeDiff {} impl NoTemplateBooleanCast for TreeDiffEntry {} impl NoTemplateBooleanCast for TreeEntry {} impl NoTemplateBooleanCast for DiffStatsFormatted<'_> {} impl NoTemplateBooleanCast for DiffStatEntry {} impl NoTemplateBooleanCast for CryptographicSignature {} impl NoTemplateBooleanCast for AnnotationLine {} impl NoTemplateBooleanCast for Trailer {} 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>),