diff --git a/docs/templates.md b/docs/templates.md index 4432dbb1f..efb8a6cb3 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -18,6 +18,7 @@ The following keywords can be used in `jj log`/`jj obslog` templates. * `description: String` * `change_id: ChangeId` * `commit_id: CommitId` +* `parent_commit_ids: List` * `author: Signature` * `committer: Signature` * `working_copies: String`: For multi-workspace repository, indicate @@ -83,6 +84,10 @@ The following methods are defined. No methods are defined. +### List type + +No methods are defined. + ### OperationId type The following methods are defined. diff --git a/src/commit_templater.rs b/src/commit_templater.rs index 4da790e42..41041591f 100644 --- a/src/commit_templater.rs +++ b/src/commit_templater.rs @@ -30,7 +30,7 @@ use crate::template_parser::{ TemplateLanguage, TemplateParseError, TemplateParseResult, }; use crate::templater::{ - IntoTemplate, PlainTextFormattedProperty, Template, TemplateProperty, TemplatePropertyFn, + self, IntoTemplate, PlainTextFormattedProperty, Template, TemplateProperty, TemplatePropertyFn, }; struct CommitTemplateLanguage<'repo, 'b> { @@ -60,6 +60,9 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo, '_> { CommitTemplatePropertyKind::CommitOrChangeId(property) => { build_commit_or_change_id_method(self, property, function) } + CommitTemplatePropertyKind::CommitOrChangeIdList(property) => { + template_parser::build_list_method(self, property, function) + } CommitTemplatePropertyKind::ShortestIdPrefix(property) => { build_shortest_id_prefix_method(self, property, function) } @@ -77,6 +80,13 @@ impl<'repo> CommitTemplateLanguage<'repo, '_> { CommitTemplatePropertyKind::CommitOrChangeId(property) } + fn wrap_commit_or_change_id_list( + &self, + property: Box>> + 'repo>, + ) -> CommitTemplatePropertyKind<'repo> { + CommitTemplatePropertyKind::CommitOrChangeIdList(property) + } + fn wrap_shortest_id_prefix( &self, property: Box + 'repo>, @@ -88,6 +98,9 @@ impl<'repo> CommitTemplateLanguage<'repo, '_> { enum CommitTemplatePropertyKind<'repo> { Core(CoreTemplatePropertyKind<'repo, Commit>), CommitOrChangeId(Box> + 'repo>), + CommitOrChangeIdList( + Box>> + 'repo>, + ), ShortestIdPrefix(Box + 'repo>), } @@ -95,6 +108,7 @@ impl<'repo> IntoTemplateProperty<'repo, Commit> for CommitTemplatePropertyKind<' fn try_into_boolean(self) -> Option + 'repo>> { match self { CommitTemplatePropertyKind::Core(property) => property.try_into_boolean(), + // TODO: should we allow implicit cast of List type? _ => None, } } @@ -119,6 +133,7 @@ impl<'repo> IntoTemplate<'repo, Commit> for CommitTemplatePropertyKind<'repo> { match self { CommitTemplatePropertyKind::Core(property) => property.into_template(), CommitTemplatePropertyKind::CommitOrChangeId(property) => property.into_template(), + CommitTemplatePropertyKind::CommitOrChangeIdList(property) => property.into_template(), CommitTemplatePropertyKind::ShortestIdPrefix(property) => property.into_template(), } } @@ -152,6 +167,13 @@ fn build_commit_keyword<'repo>( "commit_id" => language.wrap_commit_or_change_id(wrap_fn(move |commit| { CommitOrChangeId::new(repo, IdKind::Commit(commit.id().to_owned())) })), + "parent_commit_ids" => language.wrap_commit_or_change_id_list(wrap_fn(move |commit| { + commit + .parent_ids() + .iter() + .map(|id| CommitOrChangeId::new(repo, IdKind::Commit(id.to_owned()))) + .collect() + })), "author" => language.wrap_signature(wrap_fn(|commit| commit.author().clone())), "committer" => language.wrap_signature(wrap_fn(|commit| commit.committer().clone())), "working_copies" => language.wrap_string(wrap_repo_fn(repo, extract_working_copies)), @@ -179,6 +201,7 @@ fn build_commit_keyword<'repo>( Ok(property) } +// TODO: return Vec fn extract_working_copies(repo: &dyn Repo, commit: &Commit) -> String { let wc_commit_ids = repo.view().wc_commit_ids(); if wc_commit_ids.len() <= 1 { @@ -193,6 +216,7 @@ fn extract_working_copies(repo: &dyn Repo, commit: &Commit) -> String { names.join(" ") } +// TODO: return Vec? fn extract_branches(repo: &dyn Repo, commit: &Commit) -> String { let mut names = vec![]; for (branch_name, branch_target) in repo.view().branches() { @@ -225,6 +249,7 @@ fn extract_branches(repo: &dyn Repo, commit: &Commit) -> String { names.join(" ") } +// TODO: return Vec? fn extract_tags(repo: &dyn Repo, commit: &Commit) -> String { let mut names = vec![]; for (tag_name, target) in repo.view().tags() { @@ -239,6 +264,7 @@ fn extract_tags(repo: &dyn Repo, commit: &Commit) -> String { names.join(" ") } +// TODO: return Vec? fn extract_git_refs(repo: &dyn Repo, commit: &Commit) -> String { // TODO: We should keep a map from commit to ref names so we don't have to walk // all refs here. @@ -255,6 +281,7 @@ fn extract_git_refs(repo: &dyn Repo, commit: &Commit) -> String { names.join(" ") } +// TODO: return NameRef? fn extract_git_head(repo: &dyn Repo, commit: &Commit) -> String { match repo.view().git_head() { Some(ref_target) if ref_target.has_add(commit.id()) => { @@ -322,6 +349,12 @@ impl Template<()> for CommitOrChangeId<'_> { } } +impl Template<()> for Vec> { + fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> { + templater::format_joined(&(), formatter, self, " ") + } +} + fn build_commit_or_change_id_method<'repo>( language: &CommitTemplateLanguage<'repo, '_>, self_property: impl TemplateProperty> + 'repo, diff --git a/src/template_parser.rs b/src/template_parser.rs index b1ca1af85..206703593 100644 --- a/src/template_parser.rs +++ b/src/template_parser.rs @@ -1040,6 +1040,15 @@ fn build_timestamp_range_method<'a, L: TemplateLanguage<'a>>( Ok(property) } +pub fn build_list_method<'a, L: TemplateLanguage<'a>, P>( + _language: &L, + _self_property: impl TemplateProperty> + 'a, + function: &FunctionCallNode, +) -> TemplateParseResult { + // TODO: .join(separator), .map(), ... + Err(TemplateParseError::no_such_method("List", function)) +} + fn build_global_function<'a, L: TemplateLanguage<'a>>( language: &L, function: &FunctionCallNode, diff --git a/tests/test_commit_template.rs b/tests/test_commit_template.rs index b9c39a729..1a45013aa 100644 --- a/tests/test_commit_template.rs +++ b/tests/test_commit_template.rs @@ -17,6 +17,30 @@ use regex::Regex; pub mod common; +#[test] +fn test_log_parent_commit_ids() { + let test_env = TestEnvironment::default(); + test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]); + let repo_path = test_env.env_root().join("repo"); + + test_env.jj_cmd_success(&repo_path, &["new"]); + test_env.jj_cmd_success(&repo_path, &["new", "@-"]); + test_env.jj_cmd_success(&repo_path, &["new", "@", "@-"]); + + let template = r#"commit_id ++ "\nP: " ++ parent_commit_ids ++ "\n""#; + let stdout = test_env.jj_cmd_success(&repo_path, &["log", "-T", template]); + insta::assert_snapshot!(stdout, @r###" + @ c067170d4ca1bc6162b64f7550617ec809647f84 + ├─╮ P: 4db490c88528133d579540b6900b8098f0c17701 230dd059e1b059aefc0da06a2e5a7dbf22362f22 + o │ 4db490c88528133d579540b6900b8098f0c17701 + ├─╯ P: 230dd059e1b059aefc0da06a2e5a7dbf22362f22 + o 230dd059e1b059aefc0da06a2e5a7dbf22362f22 + │ P: 0000000000000000000000000000000000000000 + o 0000000000000000000000000000000000000000 + P: + "###); +} + #[test] fn test_log_author_timestamp() { let test_env = TestEnvironment::default();