diff --git a/CHANGELOG.md b/CHANGELOG.md index fee9f39a90..dc394a59aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking changes +* The `git_head` template keyword now returns an optional value instead of a + list of 0 or 1 element. + ### New features * Config now supports rgb hex colors (in the form `#rrggbb`) wherever existing color names are supported. diff --git a/cli/src/commit_templater.rs b/cli/src/commit_templater.rs index 66887dd4e8..fb1581b3c9 100644 --- a/cli/src/commit_templater.rs +++ b/cli/src/commit_templater.rs @@ -149,6 +149,14 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> { let build = template_parser::lookup_method("RefName", table, function)?; build(self, build_ctx, property, function) } + CommitTemplatePropertyKind::RefNameOpt(property) => { + let table = &self.build_fn_table.ref_name_methods; + let build = template_parser::lookup_method("RefName", table, function)?; + let inner_property = property.and_then(|opt| { + opt.ok_or_else(|| TemplatePropertyError("No RefName available".into())) + }); + build(self, build_ctx, Box::new(inner_property), function) + } CommitTemplatePropertyKind::RefNameList(property) => { // TODO: migrate to table? template_builder::build_formattable_list_method( @@ -216,6 +224,12 @@ impl<'repo> CommitTemplateLanguage<'repo> { CommitTemplatePropertyKind::RefName(Box::new(property)) } + pub fn wrap_ref_name_opt( + property: impl TemplateProperty> + 'repo, + ) -> CommitTemplatePropertyKind<'repo> { + CommitTemplatePropertyKind::RefNameOpt(Box::new(property)) + } + pub fn wrap_ref_name_list( property: impl TemplateProperty> + 'repo, ) -> CommitTemplatePropertyKind<'repo> { @@ -241,6 +255,7 @@ pub enum CommitTemplatePropertyKind<'repo> { CommitOpt(Box> + 'repo>), CommitList(Box> + 'repo>), RefName(Box + 'repo>), + RefNameOpt(Box> + 'repo>), RefNameList(Box> + 'repo>), CommitOrChangeId(Box + 'repo>), ShortestIdPrefix(Box + 'repo>), @@ -258,6 +273,9 @@ impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> { Some(Box::new(property.map(|l| !l.is_empty()))) } CommitTemplatePropertyKind::RefName(_) => None, + CommitTemplatePropertyKind::RefNameOpt(property) => { + Some(Box::new(property.map(|opt| opt.is_some()))) + } CommitTemplatePropertyKind::RefNameList(property) => { Some(Box::new(property.map(|l| !l.is_empty()))) } @@ -290,6 +308,7 @@ impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::CommitOpt(_) => None, CommitTemplatePropertyKind::CommitList(_) => None, CommitTemplatePropertyKind::RefName(property) => Some(property.into_template()), + CommitTemplatePropertyKind::RefNameOpt(property) => Some(property.into_template()), CommitTemplatePropertyKind::RefNameList(property) => Some(property.into_template()), CommitTemplatePropertyKind::CommitOrChangeId(property) => { Some(property.into_template()) @@ -530,7 +549,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm template_parser::expect_no_arguments(function)?; let repo = language.repo; let out_property = self_property.map(|commit| extract_git_head(repo, &commit)); - Ok(L::wrap_ref_name_list(out_property)) + Ok(L::wrap_ref_name_opt(out_property)) }, ); map.insert( @@ -764,20 +783,16 @@ fn build_ref_names_index<'a>( index } -// TODO: maybe add option or nullable type? -fn extract_git_head(repo: &dyn Repo, commit: &Commit) -> Vec { +fn extract_git_head(repo: &dyn Repo, commit: &Commit) -> Option { let target = repo.view().git_head(); - if target.added_ids().contains(commit.id()) { - let ref_name = RefName { + target.added_ids().contains(commit.id()).then(|| { + RefName { name: "HEAD".to_owned(), remote: Some(git::REMOTE_NAME_FOR_LOCAL_GIT_REPO.to_owned()), conflict: target.has_conflict(), synced: false, // has no local counterpart - }; - vec![ref_name] - } else { - vec![] - } + } + }) } #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/cli/src/templater.rs b/cli/src/templater.rs index 168b4031e4..d5268f4773 100644 --- a/cli/src/templater.rs +++ b/cli/src/templater.rs @@ -55,6 +55,14 @@ impl Template for Box { } } +// All optional printable types should be printable, and it's unlikely to +// implement different formatting per type. +impl Template for Option { + fn format(&self, formatter: &mut dyn Formatter) -> io::Result<()> { + self.as_ref().map_or(Ok(()), |t| t.format(formatter)) + } +} + impl Template for Signature { fn format(&self, formatter: &mut dyn Formatter) -> io::Result<()> { write!(formatter.labeled("name"), "{}", self.name)?; diff --git a/cli/tests/test_commit_template.rs b/cli/tests/test_commit_template.rs index dd653a7d53..5dfa90dcc2 100644 --- a/cli/tests/test_commit_template.rs +++ b/cli/tests/test_commit_template.rs @@ -494,6 +494,20 @@ fn test_log_git_head() { test_env.jj_cmd_ok(&repo_path, &["new", "-m=initial"]); std::fs::write(repo_path.join("file"), "foo\n").unwrap(); + + let template = r#" + separate(", ", + if(git_head, "name: " ++ git_head.name()), + "remote: " ++ git_head.remote(), + ) ++ "\n" + "#; + let stdout = test_env.jj_cmd_success(&repo_path, &["log", "-T", template]); + insta::assert_snapshot!(stdout, @r###" + @ remote: + ◉ name: HEAD, remote: git + ◉ remote: + "###); + let stdout = test_env.jj_cmd_success(&repo_path, &["log", "--color=always"]); insta::assert_snapshot!(stdout, @r###" @ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 50aaf475 diff --git a/docs/templates.md b/docs/templates.md index bb5bace3c3..5ab5472d71 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -81,7 +81,7 @@ This type cannot be printed. The following methods are defined. * `remote_branches() -> List`: All remote branches pointing to the commit. * `tags() -> List` * `git_refs() -> List` -* `git_head() -> List` +* `git_head() -> Option` * `divergent() -> Boolean`: True if the commit's change id corresponds to multiple visible commits. * `hidden() -> Boolean`: True if the commit is not visible (a.k.a. abandoned).