diff --git a/cli/src/commit_templater.rs b/cli/src/commit_templater.rs index a00656261a..cbf9191b32 100644 --- a/cli/src/commit_templater.rs +++ b/cli/src/commit_templater.rs @@ -430,6 +430,7 @@ pub struct CommitKeywordCache<'repo> { tags_index: OnceCell>, git_refs_index: OnceCell>, is_immutable_fn: OnceCell>>, + is_private_fn: OnceCell>>, } impl<'repo> CommitKeywordCache<'repo> { @@ -458,6 +459,17 @@ impl<'repo> CommitKeywordCache<'repo> { Ok(revset.containing_fn().into()) }) } + + pub fn is_private_fn( + &self, + language: &CommitTemplateLanguage<'repo>, + span: pest::Span<'_>, + ) -> TemplateParseResult<&Rc>> { + self.is_private_fn.get_or_try_init(|| { + let revset = evaluate_private_revset(language, span)?; + Ok(revset.containing_fn().into()) + }) + } } fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Commit> { @@ -650,6 +662,18 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm Ok(L::wrap_boolean(out_property)) }, ); + map.insert( + "private", + |language, _build_ctx, self_property, function| { + function.expect_no_arguments()?; + let is_private = language + .keyword_cache + .is_private_fn(language, function.name_span)? + .clone(); + let out_property = self_property.map(move |commit| is_private(commit.id())); + Ok(L::wrap_boolean(out_property)) + }, + ); map.insert( "contained_in", |language, _build_ctx, self_property, function| { @@ -762,6 +786,18 @@ fn evaluate_immutable_revset<'repo>( evaluate_revset_expression(language, span, expression) } +fn evaluate_private_revset<'repo>( + language: &CommitTemplateLanguage<'repo>, + span: pest::Span<'_>, +) -> Result, TemplateParseError> { + let expression = revset_util::parse_private_expression(&language.revset_parse_context) + .map_err(|err| { + TemplateParseError::expression("Failed to parse revset", span).with_source(err) + })?; + + evaluate_revset_expression(language, span, expression) +} + fn evaluate_user_revset<'repo>( language: &CommitTemplateLanguage<'repo>, span: pest::Span<'_>, diff --git a/cli/src/config/templates.toml b/cli/src/config/templates.toml index bc8d0c0888..d85a3679c9 100644 --- a/cli/src/config/templates.toml +++ b/cli/src/config/templates.toml @@ -211,11 +211,13 @@ coalesce( if(current_working_copy, "working_copy"), if(immutable, "immutable"), if(conflict, "conflict"), + if(private, "private"), ), coalesce( if(current_working_copy, "@"), if(immutable, "◆"), if(conflict, "×"), + if(private, "◌"), "○", ) ) @@ -230,11 +232,13 @@ coalesce( if(current_working_copy, "working_copy"), if(immutable, "immutable"), if(conflict, "conflict"), + if(private, "private"), ), coalesce( if(current_working_copy, "@"), if(immutable, "+"), if(conflict, "x"), + if(private, "#"), "o", ) ) diff --git a/cli/tests/test_log_command.rs b/cli/tests/test_log_command.rs index 15b1061152..6a046e4790 100644 --- a/cli/tests/test_log_command.rs +++ b/cli/tests/test_log_command.rs @@ -1541,3 +1541,66 @@ fn test_log_with_custom_symbols() { ^ "###); } + +#[test] +fn test_log_with_private_commits() { + // Test that elided commits are shown as synthetic nodes. + let test_env = TestEnvironment::default(); + test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); + let repo_path = test_env.env_root().join("repo"); + + test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "initial"]); + test_env.jj_cmd_ok(&repo_path, &["new", "-m", "main branch 1"]); + test_env.jj_cmd_ok(&repo_path, &["new", "-m", "main branch 2"]); + test_env.jj_cmd_ok(&repo_path, &["new", "@--", "-m", "side branch 1"]); + test_env.jj_cmd_ok(&repo_path, &["new", "-m", "private 1"]); + test_env.jj_cmd_ok( + &repo_path, + &["new", "-m", "merge", r#"description("main branch 2")"#, "@"], + ); + + test_env.add_config(r#"revset-aliases."private_roots()" = "description(glob:'private*')""#); + + let get_log = |revs: &str| -> String { + test_env.jj_cmd_success( + &repo_path, + &["log", "-T", r#"description ++ "\n""#, "-r", revs], + ) + }; + + // Simple test with showing default and elided nodes. + test_env.add_config("ui.graph.style = 'curved'"); + insta::assert_snapshot!(get_log(".."), @r###" + @ merge + ├─╮ + │ ◌ private 1 + │ │ + │ ○ side branch 1 + │ │ + ○ │ main branch 2 + │ │ + ○ │ main branch 1 + ├─╯ + ○ initial + │ + ~ + "###); + + // Simple test with showing default and elided nodes, ascii style. + test_env.add_config("ui.graph.style = 'ascii'"); + insta::assert_snapshot!(get_log(".."), @r###" + @ merge + |\ + | # private 1 + | | + | o side branch 1 + | | + o | main branch 2 + | | + o | main branch 1 + |/ + o initial + | + ~ + "###); +}