templater: add "parent_commit_ids" keyword

A list type isn't so useful without a map operation, but List<CommitId>
is at least printable. Maybe we can experiment with it to craft a map
operation.

If a map operation is introduced, this keyword might be replaced with
"parents.map(|commit| commit.commit_id)", where parents is of List<Commit>
type, and the .map() method will probably return List<Template>.
This commit is contained in:
Yuya Nishihara 2023-03-06 21:02:57 +09:00
parent 0b27f8371a
commit 4984e611f4
4 changed files with 72 additions and 1 deletions

View file

@ -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<CommitId>`
* `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.

View file

@ -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<dyn TemplateProperty<Commit, Output = Vec<CommitOrChangeId<'repo>>> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::CommitOrChangeIdList(property)
}
fn wrap_shortest_id_prefix(
&self,
property: Box<dyn TemplateProperty<Commit, Output = ShortestIdPrefix> + 'repo>,
@ -88,6 +98,9 @@ impl<'repo> CommitTemplateLanguage<'repo, '_> {
enum CommitTemplatePropertyKind<'repo> {
Core(CoreTemplatePropertyKind<'repo, Commit>),
CommitOrChangeId(Box<dyn TemplateProperty<Commit, Output = CommitOrChangeId<'repo>> + 'repo>),
CommitOrChangeIdList(
Box<dyn TemplateProperty<Commit, Output = Vec<CommitOrChangeId<'repo>>> + 'repo>,
),
ShortestIdPrefix(Box<dyn TemplateProperty<Commit, Output = ShortestIdPrefix> + 'repo>),
}
@ -95,6 +108,7 @@ impl<'repo> IntoTemplateProperty<'repo, Commit> for CommitTemplatePropertyKind<'
fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<Commit, Output = bool> + '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<String>
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<Branch>?
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<NameRef>?
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<NameRef>?
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<CommitOrChangeId<'_>> {
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<Commit, Output = CommitOrChangeId<'repo>> + 'repo,

View file

@ -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<L::Context, Output = Vec<P>> + 'a,
function: &FunctionCallNode,
) -> TemplateParseResult<L::Property> {
// TODO: .join(separator), .map(), ...
Err(TemplateParseError::no_such_method("List", function))
}
fn build_global_function<'a, L: TemplateLanguage<'a>>(
language: &L,
function: &FunctionCallNode,

View file

@ -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();