diff --git a/cli/examples/custom-commit-templater/main.rs b/cli/examples/custom-commit-templater/main.rs index edb78fd843..e3ae01a664 100644 --- a/cli/examples/custom-commit-templater/main.rs +++ b/cli/examples/custom-commit-templater/main.rs @@ -17,19 +17,24 @@ use jj_cli::commit_templater::{CommitTemplateBuildFnTable, CommitTemplateLanguag use jj_cli::template_builder::TemplateLanguage; use jj_cli::template_parser::{self, TemplateParseError}; use jj_cli::templater::{TemplateFunction, TemplatePropertyError}; +use jj_lib::backend::CommitId; use jj_lib::commit::Commit; +use jj_lib::extensions_map::ExtensionsMap; use jj_lib::object_id::ObjectId; +use jj_lib::repo::Repo; +use jj_lib::revset::RevsetExpression; +use once_cell::sync::OnceCell; struct HexCounter; -fn num_digits_in_id(commit: Commit) -> Result { +fn num_digits_in_id(id: &CommitId) -> i64 { let mut count = 0; - for ch in commit.id().hex().chars() { + for ch in id.hex().chars() { if ch.is_ascii_digit() { count += 1; } } - Ok(count) + count } fn num_char_in_id(commit: Commit, ch_match: char) -> Result { @@ -42,14 +47,57 @@ fn num_char_in_id(commit: Commit, ch_match: char) -> Result, +} + +impl MostDigitsInId { + fn new() -> Self { + Self { + count: OnceCell::new(), + } + } + + fn count(&self, repo: &dyn Repo) -> i64 { + *self.count.get_or_init(|| { + RevsetExpression::all() + .evaluate_programmatic(repo) + .unwrap() + .iter() + .map(|id| num_digits_in_id(&id)) + .max() + .unwrap_or(0) + }) + } +} + impl CommitTemplateLanguageExtension for HexCounter { fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo> { let mut table = CommitTemplateBuildFnTable::empty(); + table.commit_methods.insert( + "has_most_digits", + |language, _build_context, property, call| { + template_parser::expect_no_arguments(call)?; + let most_digits = language + .cache_extension::() + .unwrap() + .count(language.repo()); + Ok( + language.wrap_boolean(TemplateFunction::new(property, move |commit| { + Ok(num_digits_in_id(commit.id()) == most_digits) + })), + ) + }, + ); table.commit_methods.insert( "num_digits_in_id", |language, _build_context, property, call| { template_parser::expect_no_arguments(call)?; - Ok(language.wrap_integer(TemplateFunction::new(property, num_digits_in_id))) + Ok( + language.wrap_integer(TemplateFunction::new(property, |commit| { + Ok(num_digits_in_id(commit.id())) + })), + ) }, ); table.commit_methods.insert( @@ -78,6 +126,10 @@ impl CommitTemplateLanguageExtension for HexCounter { table } + + fn build_cache_extensions(&self, extensions: &mut ExtensionsMap) { + extensions.insert(MostDigitsInId::new()); + } } fn main() -> std::process::ExitCode { diff --git a/cli/src/commit_templater.rs b/cli/src/commit_templater.rs index e45759dc69..60a0a6fdd7 100644 --- a/cli/src/commit_templater.rs +++ b/cli/src/commit_templater.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::any::Any; use std::cmp::max; use std::collections::HashMap; use std::io; @@ -20,6 +21,7 @@ use std::rc::Rc; use itertools::Itertools as _; use jj_lib::backend::{ChangeId, CommitId}; use jj_lib::commit::Commit; +use jj_lib::extensions_map::ExtensionsMap; use jj_lib::hex_util::to_reverse_hex; use jj_lib::id_prefix::IdPrefixContext; use jj_lib::object_id::ObjectId as _; @@ -42,6 +44,8 @@ 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> { @@ -50,6 +54,7 @@ pub struct CommitTemplateLanguage<'repo> { id_prefix_context: &'repo IdPrefixContext, build_fn_table: CommitTemplateBuildFnTable<'repo>, keyword_cache: CommitKeywordCache, + cache_extensions: ExtensionsMap, } impl<'repo> CommitTemplateLanguage<'repo> { @@ -62,15 +67,22 @@ impl<'repo> CommitTemplateLanguage<'repo> { extension: Option<&dyn CommitTemplateLanguageExtension>, ) -> Self { let mut build_fn_table = CommitTemplateBuildFnTable::builtin(); + let mut cache_extensions = ExtensionsMap::empty(); + + // TODO: Extension methods should be refactored to be plural, to support + // multiple extensions in a dynamic load environment if let Some(extension) = extension { build_fn_table.merge(extension.build_fn_table()); + extension.build_cache_extensions(&mut cache_extensions); } + CommitTemplateLanguage { repo, workspace_id: workspace_id.clone(), id_prefix_context, build_fn_table, keyword_cache: CommitKeywordCache::default(), + cache_extensions, } } } @@ -156,6 +168,10 @@ impl<'repo> CommitTemplateLanguage<'repo> { &self.keyword_cache } + pub fn cache_extension(&self) -> Option<&T> { + self.cache_extensions.get::() + } + pub fn wrap_commit( &self, property: impl TemplateProperty + 'repo,