Skip to content

Commit b000597

Browse files
necauquapylbrecht
authored andcommitted
sign: Add templater methods to show signature info
Add it to default templates as well, it only shows anything when there's a signature. --- I also had to fix an issue in `TestSignatureBackend::sign()`. The following test was failing: ``` ---- test_signature_templates::test_signature_templates stdout ---- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Snapshot Summary ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Snapshot: signature_templates Source: cli/tests/test_signature_templates.rs:28 ──────────────────────────────────────────────────────────────────────────────── Expression: stdout ──────────────────────────────────────────────────────────────────────────────── -old snapshot +new results ────────────┬─────────────────────────────────────────────────────────────────── 0 0 │ @ Commit ID: 05ac066d05701071af20e77506a0f2195194cbc9 1 1 │ │ Change ID: qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu 2 2 │ │ Author: Test User <[email protected]> (2001-02-03 08:05:07) 3 3 │ │ Committer: Test User <[email protected]> (2001-02-03 08:05:07) 4 │-│ Signature: Good test signature 4 │+│ Signature: Bad test signature 5 5 │ │ 6 6 │ │ (no description set) 7 7 │ │ 8 8 │ ◆ Commit ID: 0000000000000000000000000000000000000000 ────────────┴─────────────────────────────────────────────────────────────────── ``` Print debugging revealed that the signature was bad, because of a missing trailing `\n` in `TestSignatureBackend::sign()`. ```diff diff --git a/lib/src/test_signing_backend.rs b/lib/src/test_signing_backend.rs index d47fef1..0ba249e358 100644 --- a/lib/src/test_signing_backend.rs +++ b/lib/src/test_signing_backend.rs @@ -59,6 +59,8 @@ let key = (!key.is_empty()).then_some(std::str::from_utf8(key).unwrap().to_owned()); let sig = self.sign(data, key.as_deref())?; + dbg!(&std::str::from_utf8(&signature).unwrap()); + dbg!(&std::str::from_utf8(&sig).unwrap()); if sig == signature { Ok(Verification::new( SigStatus::Good, ``` ``` [lib/src/test_signing_backend.rs:62:9] &std::str::from_utf8(&signature).unwrap() = \"--- JJ-TEST-SIGNATURE ---\\nKEY: \\n5300977ff3ecda4555bd86d383b070afac7b7459c07f762af918943975394a8261d244629e430c8554258904f16dd9c18d737f8969f2e7d849246db0d93cc004\\n\" [lib/src/test_signing_backend.rs:63:9] &std::str::from_utf8(&sig).unwrap() = \"--- JJ-TEST-SIGNATURE ---\\nKEY: \\n5300977ff3ecda4555bd86d383b070afac7b7459c07f762af918943975394a8261d244629e430c8554258904f16dd9c18d737f8969f2e7d849246db0d93cc004\" ``` I still have no idea, where in the call chain that trailing `\n` is added to `signature`. I tried to retrace `signature`'s steps. However, it seemed to be returned from `TestSignatureBackend::sign()`, which was even more mind-boggling to me since `sig` is also returned from `TestSignatureBackend::sign()`. How can they be different?
1 parent e0a46fa commit b000597

19 files changed

+446
-35
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,9 @@ Thanks to the people who made this release happen!
936936
* New function `working_copies()` for revsets to show the working copy commits
937937
of all workspaces.
938938

939+
* Add templater support for rendering commit signatures and added new builtin templates which
940+
show commit signatures.
941+
939942
### Fixed bugs
940943

941944
None.

cli/src/commit_templater.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ use jj_lib::revset::RevsetDiagnostics;
4747
use jj_lib::revset::RevsetModifier;
4848
use jj_lib::revset::RevsetParseContext;
4949
use jj_lib::revset::UserRevsetExpression;
50+
use jj_lib::signing::SigStatus;
51+
use jj_lib::signing::SignError;
52+
use jj_lib::signing::Verification;
5053
use jj_lib::store::Store;
5154
use once_cell::unsync::OnceCell;
5255

@@ -72,6 +75,7 @@ use crate::templater::PlainTextFormattedProperty;
7275
use crate::templater::SizeHint;
7376
use crate::templater::Template;
7477
use crate::templater::TemplateFormatter;
78+
use crate::templater::TemplateFunction;
7579
use crate::templater::TemplateProperty;
7680
use crate::templater::TemplatePropertyError;
7781
use crate::templater::TemplatePropertyExt as _;
@@ -237,6 +241,11 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> {
237241
let build = template_parser::lookup_method(type_name, table, function)?;
238242
build(self, diagnostics, build_ctx, property, function)
239243
}
244+
CommitTemplatePropertyKind::CommitSignature(property) => {
245+
let table = &self.build_fn_table.build_commit_signature_method;
246+
let build = template_parser::lookup_method("CommitSignature", table, function)?;
247+
build(self, diagnostics, build_ctx, property, function)
248+
}
240249
}
241250
}
242251
}
@@ -313,6 +322,13 @@ impl<'repo> CommitTemplateLanguage<'repo> {
313322
) -> CommitTemplatePropertyKind<'repo> {
314323
CommitTemplatePropertyKind::TreeDiff(Box::new(property))
315324
}
325+
326+
fn wrap_commit_signature(
327+
&self,
328+
property: impl TemplateProperty<Output = CommitSignature> + 'repo,
329+
) -> CommitTemplatePropertyKind<'repo> {
330+
CommitTemplatePropertyKind::CommitSignature(Box::new(property))
331+
}
316332
}
317333

318334
pub enum CommitTemplatePropertyKind<'repo> {
@@ -326,6 +342,7 @@ pub enum CommitTemplatePropertyKind<'repo> {
326342
CommitOrChangeId(Box<dyn TemplateProperty<Output = CommitOrChangeId> + 'repo>),
327343
ShortestIdPrefix(Box<dyn TemplateProperty<Output = ShortestIdPrefix> + 'repo>),
328344
TreeDiff(Box<dyn TemplateProperty<Output = TreeDiff> + 'repo>),
345+
CommitSignature(Box<dyn TemplateProperty<Output = CommitSignature> + 'repo>),
329346
}
330347

331348
impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> {
@@ -341,6 +358,7 @@ impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> {
341358
CommitTemplatePropertyKind::CommitOrChangeId(_) => "CommitOrChangeId",
342359
CommitTemplatePropertyKind::ShortestIdPrefix(_) => "ShortestIdPrefix",
343360
CommitTemplatePropertyKind::TreeDiff(_) => "TreeDiff",
361+
CommitTemplatePropertyKind::CommitSignature(_) => "CommitSignature",
344362
}
345363
}
346364

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

@@ -402,6 +421,7 @@ impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> {
402421
Some(property.into_template())
403422
}
404423
CommitTemplatePropertyKind::TreeDiff(_) => None,
424+
CommitTemplatePropertyKind::CommitSignature(_) => None,
405425
}
406426
}
407427

@@ -420,6 +440,7 @@ impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> {
420440
(CommitTemplatePropertyKind::CommitOrChangeId(_), _) => None,
421441
(CommitTemplatePropertyKind::ShortestIdPrefix(_), _) => None,
422442
(CommitTemplatePropertyKind::TreeDiff(_), _) => None,
443+
(CommitTemplatePropertyKind::CommitSignature(_), _) => None,
423444
}
424445
}
425446
}
@@ -436,6 +457,7 @@ pub struct CommitTemplateBuildFnTable<'repo> {
436457
pub commit_or_change_id_methods: CommitTemplateBuildMethodFnMap<'repo, CommitOrChangeId>,
437458
pub shortest_id_prefix_methods: CommitTemplateBuildMethodFnMap<'repo, ShortestIdPrefix>,
438459
pub tree_diff_methods: CommitTemplateBuildMethodFnMap<'repo, TreeDiff>,
460+
pub build_commit_signature_method: CommitTemplateBuildMethodFnMap<'repo, CommitSignature>,
439461
}
440462

441463
impl<'repo> CommitTemplateBuildFnTable<'repo> {
@@ -448,6 +470,7 @@ impl<'repo> CommitTemplateBuildFnTable<'repo> {
448470
commit_or_change_id_methods: builtin_commit_or_change_id_methods(),
449471
shortest_id_prefix_methods: builtin_shortest_id_prefix_methods(),
450472
tree_diff_methods: builtin_tree_diff_methods(),
473+
build_commit_signature_method: build_commit_signature_method(),
451474
}
452475
}
453476

@@ -459,6 +482,7 @@ impl<'repo> CommitTemplateBuildFnTable<'repo> {
459482
commit_or_change_id_methods: HashMap::new(),
460483
shortest_id_prefix_methods: HashMap::new(),
461484
tree_diff_methods: HashMap::new(),
485+
build_commit_signature_method: HashMap::new(),
462486
}
463487
}
464488

@@ -470,6 +494,7 @@ impl<'repo> CommitTemplateBuildFnTable<'repo> {
470494
commit_or_change_id_methods,
471495
shortest_id_prefix_methods,
472496
tree_diff_methods,
497+
build_commit_signature_method,
473498
} = extension;
474499

475500
self.core.merge(core);
@@ -484,6 +509,10 @@ impl<'repo> CommitTemplateBuildFnTable<'repo> {
484509
shortest_id_prefix_methods,
485510
);
486511
merge_fn_map(&mut self.tree_diff_methods, tree_diff_methods);
512+
merge_fn_map(
513+
&mut self.build_commit_signature_method,
514+
build_commit_signature_method,
515+
);
487516
}
488517
}
489518

@@ -594,6 +623,22 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
594623
Ok(L::wrap_boolean(out_property))
595624
},
596625
);
626+
map.insert(
627+
"signature",
628+
|language, _diagnostics, _build_ctx, self_property, function| {
629+
function.expect_no_arguments()?;
630+
Ok(
631+
language.wrap_commit_signature(TemplateFunction::new(self_property, |commit| {
632+
match commit.verification() {
633+
Ok(Some(v)) => Ok(CommitSignature::Present(v)),
634+
Err(SignError::InvalidSignatureFormat) => Ok(CommitSignature::Invalid),
635+
Ok(None) => Ok(CommitSignature::Absent),
636+
Err(e) => Err(e.into()),
637+
}
638+
})),
639+
)
640+
},
641+
);
597642
map.insert(
598643
"working_copies",
599644
|language, _diagnostics, _build_ctx, self_property, function| {
@@ -1633,3 +1678,124 @@ fn builtin_tree_diff_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, T
16331678
// TODO: add files() or map() to support custom summary-like formatting?
16341679
map
16351680
}
1681+
1682+
#[derive(Clone, Debug, Eq, PartialEq)]
1683+
pub enum CommitSignature {
1684+
Present(Verification),
1685+
Absent,
1686+
Invalid,
1687+
}
1688+
1689+
impl CommitSignature {
1690+
pub fn is_present(&self) -> bool {
1691+
matches!(self, CommitSignature::Present(_))
1692+
}
1693+
1694+
pub fn is_invalid(&self) -> bool {
1695+
matches!(self, CommitSignature::Invalid)
1696+
}
1697+
1698+
pub fn is(&self, status: SigStatus) -> bool {
1699+
match self {
1700+
CommitSignature::Present(v) => v.status == status,
1701+
_ => false,
1702+
}
1703+
}
1704+
1705+
pub fn key(self) -> String {
1706+
match self {
1707+
CommitSignature::Present(v) => v.key.unwrap_or_default(),
1708+
_ => Default::default(),
1709+
}
1710+
}
1711+
1712+
pub fn display(self) -> String {
1713+
match self {
1714+
CommitSignature::Present(v) => v.display.unwrap_or_default(),
1715+
_ => Default::default(),
1716+
}
1717+
}
1718+
1719+
pub fn backend(self) -> String {
1720+
match self {
1721+
CommitSignature::Present(v) => v.backend().unwrap_or_default().to_owned(),
1722+
_ => Default::default(),
1723+
}
1724+
}
1725+
}
1726+
1727+
fn build_commit_signature_method<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, CommitSignature>
1728+
{
1729+
type L<'repo> = CommitTemplateLanguage<'repo>;
1730+
// Not using maplit::hashmap!{} or custom declarative macro here because
1731+
// code completion inside macro is quite restricted.
1732+
let mut map = CommitTemplateBuildMethodFnMap::<CommitSignature>::new();
1733+
map.insert(
1734+
"present",
1735+
|_language, _diagnostics, _build_ctx, self_property, function| {
1736+
function.expect_no_arguments()?;
1737+
let out_property = TemplateFunction::new(self_property, |sig| Ok(sig.is_present()));
1738+
Ok(L::wrap_boolean(out_property))
1739+
},
1740+
);
1741+
map.insert(
1742+
"good",
1743+
|_language, _diagnostics, _build_ctx, self_property, function| {
1744+
function.expect_no_arguments()?;
1745+
let out_property =
1746+
TemplateFunction::new(self_property, |sig| Ok(sig.is(SigStatus::Good)));
1747+
Ok(L::wrap_boolean(out_property))
1748+
},
1749+
);
1750+
map.insert(
1751+
"unknown",
1752+
|_language, _diagnostics, _build_ctx, self_property, function| {
1753+
function.expect_no_arguments()?;
1754+
let out_property =
1755+
TemplateFunction::new(self_property, |sig| Ok(sig.is(SigStatus::Unknown)));
1756+
Ok(L::wrap_boolean(out_property))
1757+
},
1758+
);
1759+
map.insert(
1760+
"bad",
1761+
|_language, _diagnostics, _build_ctx, self_property, function| {
1762+
function.expect_no_arguments()?;
1763+
let out_property =
1764+
TemplateFunction::new(self_property, |sig| Ok(sig.is(SigStatus::Bad)));
1765+
Ok(L::wrap_boolean(out_property))
1766+
},
1767+
);
1768+
map.insert(
1769+
"invalid",
1770+
|_language, _diagnostics, _build_ctx, self_property, function| {
1771+
function.expect_no_arguments()?;
1772+
let out_property = TemplateFunction::new(self_property, |sig| Ok(sig.is_invalid()));
1773+
Ok(L::wrap_boolean(out_property))
1774+
},
1775+
);
1776+
map.insert(
1777+
"key",
1778+
|_language, _diagnostics, _build_ctx, self_property, function| {
1779+
function.expect_no_arguments()?;
1780+
let out_property = TemplateFunction::new(self_property, |sig| Ok(sig.key()));
1781+
Ok(L::wrap_string(out_property))
1782+
},
1783+
);
1784+
map.insert(
1785+
"display",
1786+
|_language, _diagnostics, _build_ctx, self_property, function| {
1787+
function.expect_no_arguments()?;
1788+
let out_property = TemplateFunction::new(self_property, |sig| Ok(sig.display()));
1789+
Ok(L::wrap_string(out_property))
1790+
},
1791+
);
1792+
map.insert(
1793+
"backend",
1794+
|_language, _diagnostics, _build_ctx, self_property, function| {
1795+
function.expect_no_arguments()?;
1796+
let out_property = TemplateFunction::new(self_property, |sig| Ok(sig.backend()));
1797+
Ok(L::wrap_string(out_property))
1798+
},
1799+
);
1800+
map
1801+
}

cli/src/config/colors.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,10 @@
110110
"node current_operation" = { fg = "green", bold = true }
111111
"node immutable" = { fg = "bright cyan", bold = true }
112112
"node conflict" = { fg = "red", bold = true }
113+
114+
"signature good" = "green"
115+
"signature unknown" = "bright black"
116+
"signature bad" = "red"
117+
"signature invalid" = "yellow"
118+
"signature key" = "blue"
119+
"signature display" = "yellow"

cli/src/config/templates.toml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,3 +317,61 @@ coalesce(
317317
"o",
318318
)
319319
'''
320+
321+
'builtin_log_detailed_with_sig' = '''
322+
concat(
323+
"Commit ID: " ++ commit_id ++ "\n",
324+
"Change ID: " ++ change_id ++ "\n",
325+
surround("Bookmarks: ", "\n", separate(" ", local_bookmarks, remote_bookmarks)),
326+
surround("Tags: ", "\n", tags),
327+
"Author: " ++ format_detailed_signature(author) ++ "\n",
328+
"Committer: " ++ format_detailed_signature(committer) ++ "\n",
329+
builtin_sig_detailed,
330+
"\n",
331+
indent(" ", if(description, description, description_placeholder ++ "\n")),
332+
"\n",
333+
)
334+
'''
335+
336+
builtin_sig_status = '''
337+
if(signature.present(),
338+
label("signature",
339+
concat(
340+
"[",
341+
label("status",
342+
if(signature.good(), label("good", "✓︎"),
343+
if(signature.unknown(), label("unknown", "?"),
344+
if(signature.bad(), label("bad", "x"),
345+
if(signature.invalid(), label("invalid", "x"))
346+
)
347+
)
348+
)
349+
),
350+
"]"
351+
)
352+
)
353+
)
354+
'''
355+
356+
builtin_sig_detailed = '''
357+
if(signature.present(),
358+
concat(
359+
"Signature: ",
360+
label("signature",
361+
if(signature.good(), label("good", "Good"),
362+
if(signature.unknown(), label("unknown", "Unknown"),
363+
if(signature.bad(), label("bad", "Bad"),
364+
if(signature.invalid(), label("invalid", "Invalid"))
365+
)
366+
)
367+
)
368+
),
369+
if(signature.backend(), " " ++ label("backend", signature.backend()) ++ " signature"),
370+
if(signature.display(),
371+
". By " ++ label("display", signature.display()) ++ if(signature.key(), " (key " ++ label("key", signature.key()) ++ ")"),
372+
if(signature.key(), ". Key " ++ label("key", signature.key()))
373+
),
374+
"\n"
375+
)
376+
)
377+
'''

cli/tests/runner.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ mod test_revset_output;
6565
mod test_root;
6666
mod test_shell_completion;
6767
mod test_show_command;
68+
mod test_signature_templates;
6869
mod test_simplify_parents_command;
6970
mod test_sparse_command;
7071
mod test_split_command;

cli/tests/test_evolog_command.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,13 +336,16 @@ fn test_evolog_with_no_template() {
336336
- builtin_log_compact
337337
- builtin_log_compact_full_description
338338
- builtin_log_detailed
339+
- builtin_log_detailed_with_sig
339340
- builtin_log_node
340341
- builtin_log_node_ascii
341342
- builtin_log_oneline
342343
- builtin_op_log_comfortable
343344
- builtin_op_log_compact
344345
- builtin_op_log_node
345346
- builtin_op_log_node_ascii
347+
- builtin_sig_detailed
348+
- builtin_sig_status
346349
- commit_summary_separator
347350
- description_placeholder
348351
- email_placeholder

cli/tests/test_log_command.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,16 @@ fn test_log_with_no_template() {
4545
- builtin_log_compact
4646
- builtin_log_compact_full_description
4747
- builtin_log_detailed
48+
- builtin_log_detailed_with_sig
4849
- builtin_log_node
4950
- builtin_log_node_ascii
5051
- builtin_log_oneline
5152
- builtin_op_log_comfortable
5253
- builtin_op_log_compact
5354
- builtin_op_log_node
5455
- builtin_op_log_node_ascii
56+
- builtin_sig_detailed
57+
- builtin_sig_status
5558
- commit_summary_separator
5659
- description_placeholder
5760
- email_placeholder

0 commit comments

Comments
 (0)