From 4b993c665b24a6f0f8db985845e33f970defeb54 Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Thu, 25 Apr 2024 18:05:23 +0900 Subject: [PATCH] templater: expose RefTarget methods through RefName type I considered adding RefTarget template type, but some of the methods naturally fit to RefName. For example, a conflicted branch name is decorated as "??", so it makes sense to add branch.conflict() instead of branch.target().conflict(). I'm not pretty sure how many RefName methods we'll need to add to port the current branch listing, but there will be .tracked(), .tracking_local_present(), .ahead_by(), and .behind_by(). --- cli/src/commit_templater.rs | 56 +++++++++++++++++++++++++++++ cli/tests/test_tag_command.rs | 67 ++++++++++++++++++++++++++--------- docs/templates.md | 8 +++++ 3 files changed, 115 insertions(+), 16 deletions(-) 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