From 48b66cfd91439403849a498fd42b6ec291e586c6 Mon Sep 17 00:00:00 2001 From: Anton Bulakh Date: Mon, 27 Nov 2023 02:29:03 +0200 Subject: [PATCH] sign: Add templater methods to show signature info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Disclaimer: this is the work of @necauqua and @julienvincent (see #3141). I simply materialized the changes by rebasing them on latest `main` and making the necessary adjustments to pass CI. --- I 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 (2001-02-03 08:05:07) 3 3 │ │ Committer: Test User (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 d47fef1086..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\" ``` Thankfully, @yuja pointed out that libgit2 appends a trailing newline (see bfb7613d5d192d3c4dc533afa4f2ff0d6b9016c5). Co-authored-by: necauqua Co-authored-by: julienvincent --- CHANGELOG.md | 2 + cli/src/commit_templater.rs | 137 ++++++++++++++++++++++++++++++ cli/tests/test_commit_template.rs | 26 ++++++ docs/config.md | 11 +++ docs/templates.md | 26 ++++++ lib/src/signing.rs | 29 ++++++- lib/src/test_signing_backend.rs | 18 ++-- lib/tests/test_gpg.rs | 16 ++-- lib/tests/test_signing.rs | 91 ++++++++++++++++---- 9 files changed, 323 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69963939a52..22419aab354 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). * `jj evolog` now accepts `--reversed`. +* Add templater support for rendering commit signatures. + ### Fixed bugs ## [0.25.0] - 2025-01-01 diff --git a/cli/src/commit_templater.rs b/cli/src/commit_templater.rs index c0746a01bfc..5a387643bca 100644 --- a/cli/src/commit_templater.rs +++ b/cli/src/commit_templater.rs @@ -49,6 +49,10 @@ 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::SignResult; +use jj_lib::signing::Verification; use jj_lib::store::Store; use once_cell::unsync::OnceCell; @@ -243,6 +247,19 @@ 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::CryptographicSignatureOpt(property) => { + let type_name = "CryptographicSignature"; + let table = &self.build_fn_table.cryptographic_signature_methods; + let build = template_parser::lookup_method(type_name, table, function)?; + let inner_property = property.try_unwrap(type_name); + build( + self, + diagnostics, + build_ctx, + Box::new(inner_property), + function, + ) + } } } } @@ -319,6 +336,12 @@ impl<'repo> CommitTemplateLanguage<'repo> { ) -> CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::TreeDiff(Box::new(property)) } + + fn wrap_cryptographic_signature_opt( + property: impl TemplateProperty> + 'repo, + ) -> CommitTemplatePropertyKind<'repo> { + CommitTemplatePropertyKind::CryptographicSignatureOpt(Box::new(property)) + } } pub enum CommitTemplatePropertyKind<'repo> { @@ -332,6 +355,9 @@ pub enum CommitTemplatePropertyKind<'repo> { CommitOrChangeId(Box + 'repo>), ShortestIdPrefix(Box + 'repo>), TreeDiff(Box + 'repo>), + CryptographicSignatureOpt( + Box> + 'repo>, + ), } impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> { @@ -347,6 +373,9 @@ impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> { CommitTemplatePropertyKind::CommitOrChangeId(_) => "CommitOrChangeId", CommitTemplatePropertyKind::ShortestIdPrefix(_) => "ShortestIdPrefix", CommitTemplatePropertyKind::TreeDiff(_) => "TreeDiff", + CommitTemplatePropertyKind::CryptographicSignatureOpt(_) => { + "Option" + } } } @@ -372,6 +401,9 @@ 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::CryptographicSignatureOpt(property) => { + Some(Box::new(property.map(|sig| sig.is_some()))) + } } } @@ -408,6 +440,7 @@ impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> { Some(property.into_template()) } CommitTemplatePropertyKind::TreeDiff(_) => None, + CommitTemplatePropertyKind::CryptographicSignatureOpt(_) => None, } } @@ -426,6 +459,7 @@ impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> { (CommitTemplatePropertyKind::CommitOrChangeId(_), _) => None, (CommitTemplatePropertyKind::ShortestIdPrefix(_), _) => None, (CommitTemplatePropertyKind::TreeDiff(_), _) => None, + (CommitTemplatePropertyKind::CryptographicSignatureOpt(_), _) => None, } } @@ -447,6 +481,7 @@ impl<'repo> IntoTemplateProperty<'repo> for CommitTemplatePropertyKind<'repo> { (CommitTemplatePropertyKind::CommitOrChangeId(_), _) => None, (CommitTemplatePropertyKind::ShortestIdPrefix(_), _) => None, (CommitTemplatePropertyKind::TreeDiff(_), _) => None, + (CommitTemplatePropertyKind::CryptographicSignatureOpt(_), _) => None, } } } @@ -463,6 +498,8 @@ 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 cryptographic_signature_methods: + CommitTemplateBuildMethodFnMap<'repo, CryptographicSignature>, } impl<'repo> CommitTemplateBuildFnTable<'repo> { @@ -475,6 +512,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(), + cryptographic_signature_methods: builtin_cryptographic_signature_methods(), } } @@ -486,6 +524,7 @@ impl<'repo> CommitTemplateBuildFnTable<'repo> { commit_or_change_id_methods: HashMap::new(), shortest_id_prefix_methods: HashMap::new(), tree_diff_methods: HashMap::new(), + cryptographic_signature_methods: HashMap::new(), } } @@ -497,6 +536,7 @@ impl<'repo> CommitTemplateBuildFnTable<'repo> { commit_or_change_id_methods, shortest_id_prefix_methods, tree_diff_methods, + cryptographic_signature_methods, } = extension; self.core.merge(core); @@ -511,6 +551,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.cryptographic_signature_methods, + cryptographic_signature_methods, + ); } } @@ -621,6 +665,14 @@ 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()?; + let out_property = self_property.map(CryptographicSignature::new); + Ok(L::wrap_cryptographic_signature_opt(out_property)) + }, + ); map.insert( "working_copies", |language, _diagnostics, _build_ctx, self_property, function| { @@ -1671,3 +1723,88 @@ fn builtin_tree_diff_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, T // TODO: add files() or map() to support custom summary-like formatting? map } + +#[derive(Debug)] +pub struct CryptographicSignature { + commit: Commit, +} + +impl CryptographicSignature { + fn new(commit: Commit) -> Option { + commit.is_signed().then_some(Self { commit }) + } + + fn verify(&self) -> SignResult { + self.commit + .verification() + .transpose() + .expect("must have signature") + } + + fn status(&self) -> SignResult { + self.verify().map(|verification| verification.status) + } + + /// Defaults to empty string if key is not present. + fn key(&self) -> SignResult { + self.verify() + .map(|verification| verification.key.unwrap_or_default()) + } + + /// Defaults to empty string if display is not present. + fn display(&self) -> SignResult { + self.verify() + .map(|verification| verification.display.unwrap_or_default()) + } + + /// Defaults to empty string if backend is not present. + fn backend(&self) -> SignResult { + self.verify() + .map(|verification| verification.backend().unwrap_or_default().into()) + } +} + +pub fn builtin_cryptographic_signature_methods<'repo>( +) -> CommitTemplateBuildMethodFnMap<'repo, CryptographicSignature> { + 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::::new(); + map.insert( + "status", + |_language, _diagnostics, _build_ctx, self_property, function| { + function.expect_no_arguments()?; + let out_property = self_property.and_then(|sig| match sig.status() { + Ok(status) => Ok(status.to_string()), + Err(SignError::InvalidSignatureFormat) => Ok("invalid".to_string()), + Err(err) => Err(err.into()), + }); + Ok(L::wrap_string(out_property)) + }, + ); + map.insert( + "key", + |_language, _diagnostics, _build_ctx, self_property, function| { + function.expect_no_arguments()?; + let out_property = self_property.and_then(|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 = self_property.and_then(|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 = self_property.and_then(|sig| Ok(sig.backend()?)); + Ok(L::wrap_string(out_property)) + }, + ); + map +} diff --git a/cli/tests/test_commit_template.rs b/cli/tests/test_commit_template.rs index bbbb9a54b9d..75f610bc0b9 100644 --- a/cli/tests/test_commit_template.rs +++ b/cli/tests/test_commit_template.rs @@ -1209,3 +1209,29 @@ fn test_log_diff_predefined_formats() { +c "###); } + +#[test] +fn test_signature_templates() { + let test_env = TestEnvironment::default(); + + test_env.add_config(r#"signing.sign-all = true"#); + test_env.add_config(r#"signing.backend = "test""#); + + test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); + let repo_path = test_env.env_root().join("repo"); + + let template = r#"if(signature, + signature.status() ++ " " ++ + signature.display() ++ " " ++ + signature.backend(), + "no" + ) ++ " signature""#; + + let stdout = test_env.jj_cmd_success(&repo_path, &["log", "-T", template]); + insta::assert_snapshot!(stdout, @r" + @ good test-display test signature + ◆ no signature"); + + let stdout = test_env.jj_cmd_success(&repo_path, &["show", "-T", template]); + insta::assert_snapshot!(stdout, @"good test-display test signature"); +} diff --git a/docs/config.md b/docs/config.md index 9d6463f842a..836c19b3364 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1027,6 +1027,17 @@ as follows: backends.ssh.allowed-signers = "/path/to/allowed-signers" ``` +## Commit Signature Verification + +By default signature verification and display is **disabled** as it incurs a +performance cost when rendering medium to large change logs. + +If you want to display commit signatures in your templates, you can use +`commit.signature()` (see [Commit type](./templates.md#commit-type)). The +returned [CryptographicSignature +Type](./templates.md#cryptographicsignature-type) provides methods to retrieve +signature details. + ## Git settings ### Default remotes for `jj git fetch` and `jj git push` diff --git a/docs/templates.md b/docs/templates.md index 67691ce7c1c..e2280ecbb99 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -93,6 +93,7 @@ This type cannot be printed. The following methods are defined. * `parents() -> List` * `author() -> Signature` * `committer() -> Signature` +* `signature() -> Option` * `mine() -> Boolean`: Commits where the author's email matches the email of the current user. * `working_copies() -> String`: For multi-workspace repository, indicate @@ -129,6 +130,31 @@ The following methods are defined. * `.short([len: Integer]) -> String` * `.shortest([min_len: Integer]) -> ShortestIdPrefix`: Shortest unique prefix. +### CryptographicSignature type + +The following methods are defined. + +* `.status() -> String`: The signature's status (`"good"`, `"bad"`, `"unknown"`, `"invalid"`). +* `.key() -> String`: The signature's key id representation (for GPG, this is the key fingerprint). +* `.display() -> String`: The signature's display string (for GPG this is the formatted primary user ID). +* `.backend() -> String`: The signature backend (e.g. `"gpg"`). + +!!! warning + + Calling any of `.status()`, `.key()`, `.display()`, or `.backend()` is + slow, as it incurs the performance cost of verifying the signature (for + example shelling out to `gpg` or `ssh-keygen`). Though consecutive calls + will be faster, because the backend caches the verification result. + +!!! info + + As opposed to calling any of `.status()`, `.key()`, `.display()`, or + `.backend()`, checking for signature presence through boolean coercion is + fast: + ``` + if(commit.signature(), "commit has a signature", "commit is unsigned") + ``` + ### Email type The following methods are defined. diff --git a/lib/src/signing.rs b/lib/src/signing.rs index 5b9218c903f..239f561a777 100644 --- a/lib/src/signing.rs +++ b/lib/src/signing.rs @@ -16,6 +16,7 @@ //! various backends. use std::fmt::Debug; +use std::fmt::Display; use std::sync::Mutex; use clru::CLruCache; @@ -41,6 +42,17 @@ pub enum SigStatus { Bad, } +impl Display for SigStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + SigStatus::Good => "good", + SigStatus::Unknown => "unknown", + SigStatus::Bad => "bad", + }; + write!(f, "{s}") + } +} + /// The result of a signature verification. /// Key and display are optional additional info that backends can or can not /// provide to add additional information for the templater to potentially show. @@ -54,6 +66,11 @@ pub struct Verification { /// A display string, if available. For GPG, this will be formatted primary /// user ID. pub display: Option, + /// The name of the backend that provided this verification. + /// Is `None` when no backend was found that could read the signature. + /// + /// Always set by the Signer. + backend: Option, } impl Verification { @@ -64,6 +81,7 @@ impl Verification { status: SigStatus::Unknown, key: None, display: None, + backend: None, } } @@ -73,8 +91,14 @@ impl Verification { status, key, display, + backend: None, } } + + /// The name of the backend that provided this verification. + pub fn backend(&self) -> Option<&str> { + self.backend.as_deref() + } } /// The backend for signing and verifying cryptographic signatures. @@ -237,7 +261,10 @@ impl Signer { .find_map(|backend| match backend.verify(data, signature) { Ok(check) if check.status == SigStatus::Unknown => None, Err(SignError::InvalidSignatureFormat) => None, - e => Some(e), + e => Some(e.map(|mut v| { + v.backend = Some(backend.name().to_owned()); + v + })), }) .transpose()?; diff --git a/lib/src/test_signing_backend.rs b/lib/src/test_signing_backend.rs index 5b20ab60b9d..1a3bb856ba0 100644 --- a/lib/src/test_signing_backend.rs +++ b/lib/src/test_signing_backend.rs @@ -46,7 +46,7 @@ impl SigningBackend for TestSigningBackend { let hash: String = blake2b_hash(&body).encode_hex(); - Ok(format!("{PREFIX}{key}\n{hash}").into_bytes()) + Ok(format!("{PREFIX}{key}\n{hash}\n").into_bytes()) } fn verify(&self, data: &[u8], signature: &[u8]) -> SignResult { @@ -60,17 +60,17 @@ impl SigningBackend for TestSigningBackend { let sig = self.sign(data, key.as_deref())?; if sig == signature { - Ok(Verification { - status: SigStatus::Good, + Ok(Verification::new( + SigStatus::Good, key, - display: Some("test-display".into()), - }) + Some("test-display".into()), + )) } else { - Ok(Verification { - status: SigStatus::Bad, + Ok(Verification::new( + SigStatus::Bad, key, - display: Some("test-display".into()), - }) + Some("test-display".into()), + )) } } } diff --git a/lib/tests/test_gpg.rs b/lib/tests/test_gpg.rs index ddac993bcf8..bc279cd72ff 100644 --- a/lib/tests/test_gpg.rs +++ b/lib/tests/test_gpg.rs @@ -134,7 +134,7 @@ fn gpg_signing_roundtrip_explicit_key() { let data = b"hello world"; let signature = backend.sign(data, Some("Someone Else")).unwrap(); - assert_debug_snapshot!(backend.verify(data, &signature).unwrap(), @r###" + assert_debug_snapshot!(backend.verify(data, &signature).unwrap(), @r#" Verification { status: Good, key: Some( @@ -143,8 +143,9 @@ fn gpg_signing_roundtrip_explicit_key() { display: Some( "Someone Else (jj test signing key) ", ), + backend: None, } - "###); + "#); assert_debug_snapshot!(backend.verify(b"so so bad", &signature).unwrap(), @r###" Verification { status: Bad, @@ -154,6 +155,7 @@ fn gpg_signing_roundtrip_explicit_key() { display: Some( "Someone Else (jj test signing key) ", ), + backend: None, } "###); } @@ -172,24 +174,26 @@ fn unknown_key() { e+U6bvqw3pOBoI53Th35drQ0qPI+jAE= =kwsk -----END PGP SIGNATURE-----"; - assert_debug_snapshot!(backend.verify(b"hello world", signature).unwrap(), @r###" + assert_debug_snapshot!(backend.verify(b"hello world", signature).unwrap(), @r#" Verification { status: Unknown, key: Some( "071FE3E324DD7333", ), display: None, + backend: None, } - "###); - assert_debug_snapshot!(backend.verify(b"so bad", signature).unwrap(), @r###" + "#); + assert_debug_snapshot!(backend.verify(b"so bad", signature).unwrap(), @r#" Verification { status: Unknown, key: Some( "071FE3E324DD7333", ), display: None, + backend: None, } - "###); + "#); } #[test] diff --git a/lib/tests/test_signing.rs b/lib/tests/test_signing.rs index 448fc90525c..4acba1cc243 100644 --- a/lib/tests/test_signing.rs +++ b/lib/tests/test_signing.rs @@ -1,3 +1,5 @@ +use insta::allow_duplicates; +use insta::assert_debug_snapshot; use jj_lib::backend::MillisSinceEpoch; use jj_lib::backend::Signature; use jj_lib::backend::Timestamp; @@ -5,10 +7,8 @@ use jj_lib::config::ConfigLayer; use jj_lib::config::ConfigSource; use jj_lib::repo::Repo; use jj_lib::settings::UserSettings; -use jj_lib::signing::SigStatus; use jj_lib::signing::SignBehavior; use jj_lib::signing::Signer; -use jj_lib::signing::Verification; use jj_lib::test_signing_backend::TestSigningBackend; use test_case::test_case; use testutils::create_random_commit; @@ -44,14 +44,6 @@ fn someone_else() -> Signature { } } -fn good_verification() -> Option { - Some(Verification { - status: SigStatus::Good, - key: Some("impeccable".to_owned()), - display: Some("test".into()), - }) -} - #[test_case(TestRepoBackend::Local ; "local backend")] #[test_case(TestRepoBackend::Git ; "git backend")] fn manual(backend: TestRepoBackend) { @@ -75,11 +67,31 @@ fn manual(backend: TestRepoBackend) { .unwrap(); tx.commit("test").unwrap(); - let commit1 = repo.store().get_commit(commit1.id()).unwrap(); - assert_eq!(commit1.verification().unwrap(), good_verification()); + allow_duplicates! { + let commit1 = repo.store().get_commit(commit1.id()).unwrap(); + assert_debug_snapshot!( + commit1.verification().unwrap(), + @r#" + Some( + Verification { + status: Good, + key: Some( + "impeccable", + ), + display: Some( + "test-display", + ), + backend: Some( + "test", + ), + }, + ) + "#, + ); - let commit2 = repo.store().get_commit(commit2.id()).unwrap(); - assert_eq!(commit2.verification().unwrap(), None); + let commit2 = repo.store().get_commit(commit2.id()).unwrap(); + assert_debug_snapshot!(commit2.verification().unwrap(), @"None"); + } } #[test_case(TestRepoBackend::Git ; "git backend")] @@ -104,7 +116,22 @@ fn keep_on_rewrite(backend: TestRepoBackend) { let rewritten = mut_repo.rewrite_commit(&commit).write().unwrap(); let commit = repo.store().get_commit(rewritten.id()).unwrap(); - assert_eq!(commit.verification().unwrap(), good_verification()); + assert_debug_snapshot!(commit.verification().unwrap(), @r#" + Some( + Verification { + status: Good, + key: Some( + "impeccable", + ), + display: Some( + "test-display", + ), + backend: Some( + "test", + ), + }, + ) + "#); } #[test_case(TestRepoBackend::Git ; "git backend")] @@ -155,7 +182,22 @@ fn forced(backend: TestRepoBackend) { tx.commit("test").unwrap(); let commit = repo.store().get_commit(commit.id()).unwrap(); - assert_eq!(commit.verification().unwrap(), good_verification()); + assert_debug_snapshot!(commit.verification().unwrap(), @r#" + Some( + Verification { + status: Good, + key: Some( + "impeccable", + ), + display: Some( + "test-display", + ), + backend: Some( + "test", + ), + }, + ) + "#); } #[test_case(TestRepoBackend::Git ; "git backend")] @@ -173,5 +215,20 @@ fn configured(backend: TestRepoBackend) { tx.commit("test").unwrap(); let commit = repo.store().get_commit(commit.id()).unwrap(); - assert_eq!(commit.verification().unwrap(), good_verification()); + assert_debug_snapshot!(commit.verification().unwrap(), @r#" + Some( + Verification { + status: Good, + key: Some( + "impeccable", + ), + display: Some( + "test-display", + ), + backend: Some( + "test", + ), + }, + ) + "#); }