diff --git a/cli/src/commit_templater.rs b/cli/src/commit_templater.rs index e289bb0ea1..1222f6cfc2 100644 --- a/cli/src/commit_templater.rs +++ b/cli/src/commit_templater.rs @@ -757,6 +757,10 @@ impl RefName { self.remote.is_some() } + fn is_present(&self) -> bool { + self.target.is_present() + } + /// Whether the ref target has conflicts. fn has_conflict(&self) -> bool { self.target.has_conflict() @@ -805,6 +809,58 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Re Ok(L::wrap_string(out_property)) }, ); + map.insert( + "present", + |_language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = self_property.map(|ref_name| ref_name.is_present()); + Ok(L::wrap_boolean(out_property)) + }, + ); + map.insert( + "conflict", + |_language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = self_property.map(|ref_name| ref_name.has_conflict()); + Ok(L::wrap_boolean(out_property)) + }, + ); + map.insert( + "normal_target", + |language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let repo = language.repo; + let out_property = self_property.and_then(|ref_name| { + let maybe_id = ref_name.target.as_normal(); + Ok(maybe_id.map(|id| repo.store().get_commit(id)).transpose()?) + }); + Ok(L::wrap_commit_opt(out_property)) + }, + ); + map.insert( + "removed_targets", + |language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let repo = language.repo; + let out_property = self_property.and_then(|ref_name| { + let ids = ref_name.target.removed_ids(); + Ok(ids.map(|id| repo.store().get_commit(id)).try_collect()?) + }); + Ok(L::wrap_commit_list(out_property)) + }, + ); + map.insert( + "added_targets", + |language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let repo = language.repo; + let out_property = self_property.and_then(|ref_name| { + let ids = ref_name.target.added_ids(); + Ok(ids.map(|id| repo.store().get_commit(id)).try_collect()?) + }); + Ok(L::wrap_commit_list(out_property)) + }, + ); map } diff --git a/cli/tests/test_tag_command.rs b/cli/tests/test_tag_command.rs index b50de037ea..237b36d880 100644 --- a/cli/tests/test_tag_command.rs +++ b/cli/tests/test_tag_command.rs @@ -35,44 +35,79 @@ fn test_tag_list() { test_env.jj_cmd_ok(&repo_path, &["branch", "create", "branch1"]); test_env.jj_cmd_ok(&repo_path, &["new", "root()", "-mcommit2"]); test_env.jj_cmd_ok(&repo_path, &["branch", "create", "branch2"]); + test_env.jj_cmd_ok(&repo_path, &["new", "root()", "-mcommit3"]); + test_env.jj_cmd_ok(&repo_path, &["branch", "create", "branch3"]); test_env.jj_cmd_ok(&repo_path, &["git", "export"]); copy_ref("refs/heads/branch1", "refs/tags/test_tag"); copy_ref("refs/heads/branch2", "refs/tags/test_tag2"); + copy_ref("refs/heads/branch1", "refs/tags/conflicted_tag"); test_env.jj_cmd_ok(&repo_path, &["git", "import"]); + copy_ref("refs/heads/branch2", "refs/tags/conflicted_tag"); + test_env.jj_cmd_ok(&repo_path, &["git", "import"]); + copy_ref("refs/heads/branch3", "refs/tags/conflicted_tag"); + test_env.jj_cmd_ok(&repo_path, &["git", "import", "--at-op=@-"]); + test_env.jj_cmd_ok(&repo_path, &["status"]); // resolve concurrent ops insta::assert_snapshot!( test_env.jj_cmd_success(&repo_path, &["tag", "list"]), @r###" - test_tag - test_tag2 - "###); + conflicted_tag + test_tag + test_tag2 + "###); - insta::assert_snapshot!( - test_env.jj_cmd_success(&repo_path, &["tag", "list", "--color=always"]), - @r###" - test_tag - test_tag2 - "###); + insta::assert_snapshot!( + test_env.jj_cmd_success(&repo_path, &["tag", "list", "--color=always"]), + @r###" + conflicted_tag + test_tag + test_tag2 + "###); // Test pattern matching. insta::assert_snapshot!( test_env.jj_cmd_success(&repo_path, &["tag", "list", "test_tag2"]), @r###" - test_tag2 - "###); + test_tag2 + "###); insta::assert_snapshot!( test_env.jj_cmd_success(&repo_path, &["tag", "list", "glob:test_tag?"]), @r###" - test_tag2 - "###); + test_tag2 + "###); - let template = r#"'name: ' ++ name ++ "\n""#; + let template = r#" + concat( + "[" ++ name ++ "]\n", + separate(" ", "present:", present) ++ "\n", + separate(" ", "conflict:", conflict) ++ "\n", + separate(" ", "normal_target:", normal_target.description().first_line()) ++ "\n", + separate(" ", "removed_targets:", removed_targets.map(|c| c.description().first_line())) ++ "\n", + separate(" ", "added_targets:", added_targets.map(|c| c.description().first_line())) ++ "\n", + ) + "#; insta::assert_snapshot!( test_env.jj_cmd_success(&repo_path, &["tag", "list", "-T", template]), @r###" - name: test_tag - name: test_tag2 + [conflicted_tag] + present: true + conflict: true + normal_target: + removed_targets: commit1 + added_targets: commit2 commit3 + [test_tag] + present: true + conflict: false + normal_target: commit1 + removed_targets: + added_targets: commit1 + [test_tag2] + present: true + conflict: false + normal_target: commit2 + removed_targets: + added_targets: commit2 "###); } diff --git a/docs/templates.md b/docs/templates.md index fe82eaf527..fe446db88e 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -155,6 +155,14 @@ The following methods are defined. * `.name() -> String`: Local branch or tag name. * `.remote() -> String`: Remote name or empty if this is a local ref. +* `.present() -> Boolean`: True if the ref points to any commit. +* `.conflict() -> Boolean`: True if [the branch or tag is + conflicted](branches.md#conflicts). +* `.normal_target() -> Option`: Target commit if the ref is not + conflicted and points to a commit. +* `.removed_targets() -> List`: Old target commits if conflicted. +* `.added_targets() -> List`: New target commits. The list usually + contains one "normal" target. ### ShortestIdPrefix type