Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display commit signatures #4853

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,9 @@ Thanks to the people who made this release happen!
* New function `working_copies()` for revsets to show the working copy commits
of all workspaces.

* Add templater support for rendering commit signatures and added new builtin templates which
show commit signatures.

### Fixed bugs

None.
Expand Down
166 changes: 166 additions & 0 deletions cli/src/commit_templater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ use jj_lib::revset::RevsetDiagnostics;
use jj_lib::revset::RevsetModifier;
use jj_lib::revset::RevsetParseContext;
use jj_lib::revset::UserRevsetExpression;
use jj_lib::signing::SigStatus;
use jj_lib::signing::SignError;
use jj_lib::signing::Verification;
use jj_lib::store::Store;
use once_cell::unsync::OnceCell;

Expand All @@ -72,6 +75,7 @@ use crate::templater::PlainTextFormattedProperty;
use crate::templater::SizeHint;
use crate::templater::Template;
use crate::templater::TemplateFormatter;
use crate::templater::TemplateFunction;
use crate::templater::TemplateProperty;
use crate::templater::TemplatePropertyError;
use crate::templater::TemplatePropertyExt as _;
Expand Down Expand Up @@ -237,6 +241,11 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> {
let build = template_parser::lookup_method(type_name, table, function)?;
build(self, diagnostics, build_ctx, property, function)
}
CommitTemplatePropertyKind::CommitSignature(property) => {
let table = &self.build_fn_table.build_commit_signature_method;
let build = template_parser::lookup_method("CommitSignature", table, function)?;
build(self, diagnostics, build_ctx, property, function)
}
}
}
}
Expand Down Expand Up @@ -313,6 +322,13 @@ impl<'repo> CommitTemplateLanguage<'repo> {
) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::TreeDiff(Box::new(property))
}

fn wrap_commit_signature(
&self,
property: impl TemplateProperty<Output = CommitSignature> + 'repo,
) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::CommitSignature(Box::new(property))
}
}

pub enum CommitTemplatePropertyKind<'repo> {
Expand All @@ -326,6 +342,7 @@ pub enum CommitTemplatePropertyKind<'repo> {
CommitOrChangeId(Box<dyn TemplateProperty<Output = CommitOrChangeId> + 'repo>),
ShortestIdPrefix(Box<dyn TemplateProperty<Output = ShortestIdPrefix> + 'repo>),
TreeDiff(Box<dyn TemplateProperty<Output = TreeDiff> + 'repo>),
CommitSignature(Box<dyn TemplateProperty<Output = CommitSignature> + 'repo>),
}

impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> {
Expand All @@ -341,6 +358,7 @@ impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::CommitOrChangeId(_) => "CommitOrChangeId",
CommitTemplatePropertyKind::ShortestIdPrefix(_) => "ShortestIdPrefix",
CommitTemplatePropertyKind::TreeDiff(_) => "TreeDiff",
CommitTemplatePropertyKind::CommitSignature(_) => "CommitSignature",
}
}

Expand All @@ -366,6 +384,7 @@ impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> {
// TODO: boolean cast could be implemented, but explicit
// diff.empty() method might be better.
CommitTemplatePropertyKind::TreeDiff(_) => None,
CommitTemplatePropertyKind::CommitSignature(_) => None,
}
}

Expand Down Expand Up @@ -402,6 +421,7 @@ impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> {
Some(property.into_template())
}
CommitTemplatePropertyKind::TreeDiff(_) => None,
CommitTemplatePropertyKind::CommitSignature(_) => None,
}
}

Expand All @@ -420,6 +440,7 @@ impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> {
(CommitTemplatePropertyKind::CommitOrChangeId(_), _) => None,
(CommitTemplatePropertyKind::ShortestIdPrefix(_), _) => None,
(CommitTemplatePropertyKind::TreeDiff(_), _) => None,
(CommitTemplatePropertyKind::CommitSignature(_), _) => None,
}
}
}
Expand All @@ -436,6 +457,7 @@ pub struct CommitTemplateBuildFnTable<'repo> {
pub commit_or_change_id_methods: CommitTemplateBuildMethodFnMap<'repo, CommitOrChangeId>,
pub shortest_id_prefix_methods: CommitTemplateBuildMethodFnMap<'repo, ShortestIdPrefix>,
pub tree_diff_methods: CommitTemplateBuildMethodFnMap<'repo, TreeDiff>,
pub build_commit_signature_method: CommitTemplateBuildMethodFnMap<'repo, CommitSignature>,
}

impl<'repo> CommitTemplateBuildFnTable<'repo> {
Expand All @@ -448,6 +470,7 @@ impl<'repo> CommitTemplateBuildFnTable<'repo> {
commit_or_change_id_methods: builtin_commit_or_change_id_methods(),
shortest_id_prefix_methods: builtin_shortest_id_prefix_methods(),
tree_diff_methods: builtin_tree_diff_methods(),
build_commit_signature_method: build_commit_signature_method(),
}
}

Expand All @@ -459,6 +482,7 @@ impl<'repo> CommitTemplateBuildFnTable<'repo> {
commit_or_change_id_methods: HashMap::new(),
shortest_id_prefix_methods: HashMap::new(),
tree_diff_methods: HashMap::new(),
build_commit_signature_method: HashMap::new(),
}
}

Expand All @@ -470,6 +494,7 @@ impl<'repo> CommitTemplateBuildFnTable<'repo> {
commit_or_change_id_methods,
shortest_id_prefix_methods,
tree_diff_methods,
build_commit_signature_method,
} = extension;

self.core.merge(core);
Expand All @@ -484,6 +509,10 @@ impl<'repo> CommitTemplateBuildFnTable<'repo> {
shortest_id_prefix_methods,
);
merge_fn_map(&mut self.tree_diff_methods, tree_diff_methods);
merge_fn_map(
&mut self.build_commit_signature_method,
build_commit_signature_method,
);
}
}

Expand Down Expand Up @@ -594,6 +623,22 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
Ok(L::wrap_boolean(out_property))
},
);
map.insert(
"signature",
|language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
Ok(
language.wrap_commit_signature(TemplateFunction::new(self_property, |commit| {
match commit.verification() {
Ok(Some(v)) => Ok(CommitSignature::Present(v)),
Err(SignError::InvalidSignatureFormat) => Ok(CommitSignature::Invalid),
Ok(None) => Ok(CommitSignature::Absent),
Err(e) => Err(e.into()),
}
})),
)
},
);
map.insert(
"working_copies",
|language, _diagnostics, _build_ctx, self_property, function| {
Expand Down Expand Up @@ -1633,3 +1678,124 @@ fn builtin_tree_diff_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, T
// TODO: add files() or map() to support custom summary-like formatting?
map
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CommitSignature {
Present(Verification),
Absent,
Invalid,
}

impl CommitSignature {
pub fn is_present(&self) -> bool {
matches!(self, CommitSignature::Present(_))
}

pub fn is_invalid(&self) -> bool {
matches!(self, CommitSignature::Invalid)
}

pub fn is(&self, status: SigStatus) -> bool {
match self {
CommitSignature::Present(v) => v.status == status,
_ => false,
}
}

pub fn key(self) -> String {
match self {
CommitSignature::Present(v) => v.key.unwrap_or_default(),
_ => Default::default(),
}
}

pub fn display(self) -> String {
match self {
CommitSignature::Present(v) => v.display.unwrap_or_default(),
_ => Default::default(),
}
}

pub fn backend(self) -> String {
match self {
CommitSignature::Present(v) => v.backend().unwrap_or_default().to_owned(),
_ => Default::default(),
}
}
}

fn build_commit_signature_method<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, CommitSignature>
{
type L<'repo> = CommitTemplateLanguage<'repo>;
// Not using maplit::hashmap!{} or custom declarative macro here because
// code completion inside macro is quite restricted.
let mut map = CommitTemplateBuildMethodFnMap::<CommitSignature>::new();
map.insert(
"present",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = TemplateFunction::new(self_property, |sig| Ok(sig.is_present()));
Ok(L::wrap_boolean(out_property))
},
);
map.insert(
"good",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property =
TemplateFunction::new(self_property, |sig| Ok(sig.is(SigStatus::Good)));
Ok(L::wrap_boolean(out_property))
},
);
map.insert(
"unknown",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property =
TemplateFunction::new(self_property, |sig| Ok(sig.is(SigStatus::Unknown)));
Ok(L::wrap_boolean(out_property))
},
);
map.insert(
"bad",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property =
TemplateFunction::new(self_property, |sig| Ok(sig.is(SigStatus::Bad)));
Ok(L::wrap_boolean(out_property))
},
);
map.insert(
"invalid",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = TemplateFunction::new(self_property, |sig| Ok(sig.is_invalid()));
Ok(L::wrap_boolean(out_property))
},
);
map.insert(
"key",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = TemplateFunction::new(self_property, |sig| Ok(sig.key()));
Ok(L::wrap_string(out_property))
},
);
map.insert(
"display",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = TemplateFunction::new(self_property, |sig| Ok(sig.display()));
Ok(L::wrap_string(out_property))
},
);
map.insert(
"backend",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = TemplateFunction::new(self_property, |sig| Ok(sig.backend()));
Ok(L::wrap_string(out_property))
},
);
map
}
7 changes: 7 additions & 0 deletions cli/src/config/colors.toml
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,10 @@
"node current_operation" = { fg = "green", bold = true }
"node immutable" = { fg = "bright cyan", bold = true }
"node conflict" = { fg = "red", bold = true }

"signature good" = "green"
"signature unknown" = "bright black"
"signature bad" = "red"
"signature invalid" = "yellow"
"signature key" = "blue"
"signature display" = "yellow"
58 changes: 58 additions & 0 deletions cli/src/config/templates.toml
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,61 @@ coalesce(
"o",
)
'''

'builtin_log_detailed_with_sig' = '''
concat(
"Commit ID: " ++ commit_id ++ "\n",
"Change ID: " ++ change_id ++ "\n",
surround("Bookmarks: ", "\n", separate(" ", local_bookmarks, remote_bookmarks)),
surround("Tags: ", "\n", tags),
"Author: " ++ format_detailed_signature(author) ++ "\n",
"Committer: " ++ format_detailed_signature(committer) ++ "\n",
builtin_sig_detailed,
"\n",
indent(" ", if(description, description, description_placeholder ++ "\n")),
"\n",
)
'''

builtin_sig_status = '''
if(signature.present(),
label("signature",
concat(
"[",
label("status",
if(signature.good(), label("good", "✓︎"),
if(signature.unknown(), label("unknown", "?"),
if(signature.bad(), label("bad", "x"),
if(signature.invalid(), label("invalid", "x"))
)
)
)
),
"]"
)
)
)
'''

builtin_sig_detailed = '''
if(signature.present(),
concat(
"Signature: ",
label("signature",
if(signature.good(), label("good", "Good"),
if(signature.unknown(), label("unknown", "Unknown"),
if(signature.bad(), label("bad", "Bad"),
if(signature.invalid(), label("invalid", "Invalid"))
)
)
)
),
if(signature.backend(), " " ++ label("backend", signature.backend()) ++ " signature"),
if(signature.display(),
". By " ++ label("display", signature.display()) ++ if(signature.key(), " (key " ++ label("key", signature.key()) ++ ")"),
if(signature.key(), ". Key " ++ label("key", signature.key()))
),
"\n"
)
)
'''
1 change: 1 addition & 0 deletions cli/tests/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ mod test_revset_output;
mod test_root;
mod test_shell_completion;
mod test_show_command;
mod test_signature_templates;
mod test_simplify_parents_command;
mod test_sparse_command;
mod test_split_command;
Expand Down
3 changes: 3 additions & 0 deletions cli/tests/test_evolog_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,13 +336,16 @@ fn test_evolog_with_no_template() {
- builtin_log_compact
- builtin_log_compact_full_description
- builtin_log_detailed
- builtin_log_detailed_with_sig
- builtin_log_node
- builtin_log_node_ascii
- builtin_log_oneline
- builtin_op_log_comfortable
- builtin_op_log_compact
- builtin_op_log_node
- builtin_op_log_node_ascii
- builtin_sig_detailed
- builtin_sig_status
- commit_summary_separator
- description_placeholder
- email_placeholder
Expand Down
3 changes: 3 additions & 0 deletions cli/tests/test_log_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,16 @@ fn test_log_with_no_template() {
- builtin_log_compact
- builtin_log_compact_full_description
- builtin_log_detailed
- builtin_log_detailed_with_sig
- builtin_log_node
- builtin_log_node_ascii
- builtin_log_oneline
- builtin_op_log_comfortable
- builtin_op_log_compact
- builtin_op_log_node
- builtin_op_log_node_ascii
- builtin_sig_detailed
- builtin_sig_status
- commit_summary_separator
- description_placeholder
- email_placeholder
Expand Down
Loading