From 0dd177523fc370a283f33d85d73d832f65a0fe9a Mon Sep 17 00:00:00 2001 From: Anton Bulakh Date: Fri, 24 Nov 2023 23:08:16 +0200 Subject: [PATCH] sign: Define signing backend API and integrate it Finished everything except actual signing backend implementation(s) and the UI. --- cli/src/cli_util.rs | 9 ++ cli/src/config-schema.json | 25 +++ lib/src/backend.rs | 3 +- lib/src/commit.rs | 15 ++ lib/src/commit_builder.rs | 42 ++++- lib/src/git_backend.rs | 5 +- lib/src/lib.rs | 1 + lib/src/local_backend.rs | 2 +- lib/src/repo.rs | 19 ++- lib/src/settings.rs | 49 +++++- lib/src/signing.rs | 249 ++++++++++++++++++++++++++++++ lib/src/store.rs | 26 +++- lib/src/workspace.rs | 4 + lib/testutils/src/lib.rs | 2 +- lib/testutils/src/test_backend.rs | 2 +- 15 files changed, 434 insertions(+), 19 deletions(-) create mode 100644 lib/src/signing.rs diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index 68c514aede9..bbcd4d0ab6a 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -57,6 +57,7 @@ use jj_lib::revset::{ }; use jj_lib::rewrite::restore_tree; use jj_lib::settings::{ConfigResultExt as _, UserSettings}; +use jj_lib::signing::SignInitError; use jj_lib::str_util::{StringPattern, StringPatternParseError}; use jj_lib::transaction::Transaction; use jj_lib::tree::TreeMergeError; @@ -194,6 +195,10 @@ impl From for CommandError { WorkspaceInitError::WorkingCopyState(err) => { CommandError::InternalError(format!("Failed to access the repository: {err}")) } + WorkspaceInitError::SignInit(err @ SignInitError::UnknownBackend(_)) => { + user_error(format!("{err}")) + } + WorkspaceInitError::SignInit(err) => CommandError::InternalError(format!("{err}")), } } } @@ -1655,6 +1660,10 @@ jj init --git-repo=.", ) => CommandError::InternalError(format!( "The repository appears broken or inaccessible: {err}" )), + WorkspaceLoadError::StoreLoadError(StoreLoadError::Signing( + err @ SignInitError::UnknownBackend(_), + )) => user_error(format!("{err}")), + WorkspaceLoadError::StoreLoadError(err) => CommandError::InternalError(format!("{err}")), } } diff --git a/cli/src/config-schema.json b/cli/src/config-schema.json index 14e71edf97b..a6f980c6f41 100644 --- a/cli/src/config-schema.json +++ b/cli/src/config-schema.json @@ -355,6 +355,31 @@ "default": "1MiB" } } + }, + "signing": { + "type": "object", + "description": "Settings for verifying and creating cryptographic commit signatures", + "properties": { + "backend": { + "type": "string", + "description": "Which backend to use to create commit signatures" + }, + "key": { + "type": "string", + "description": "The key parameter to pass to the signing backend. Overridden by `jj sign` parameter or by the global `--sign-with` option" + }, + "sign-all": { + "type": "boolean", + "description": "Whether to sign all commits by default. Overridden by global `--no-sign` option", + "default": false + }, + "backends": { + "type": "object", + "description": "Tables of options to pass to specific signing backends", + "properties": {}, + "additionalProperties": true + } + } } } } \ No newline at end of file diff --git a/lib/src/backend.rs b/lib/src/backend.rs index 50980a96313..68ec0720991 100644 --- a/lib/src/backend.rs +++ b/lib/src/backend.rs @@ -27,6 +27,7 @@ use thiserror::Error; use crate::content_hash::ContentHash; use crate::merge::Merge; use crate::repo_path::{RepoPath, RepoPathComponent, RepoPathComponentBuf}; +use crate::signing::SignResult; pub trait ObjectId { fn new(value: Vec) -> Self; @@ -147,7 +148,7 @@ content_hash! { } } -pub type SigningFn = Box BackendResult>>; +pub type SigningFn = Box SignResult>>; /// Identifies a single legacy tree, which may have path-level conflicts, or a /// merge of multiple trees, where the individual trees do not have conflicts. diff --git a/lib/src/commit.rs b/lib/src/commit.rs index b5c250e8588..65e2e1379da 100644 --- a/lib/src/commit.rs +++ b/lib/src/commit.rs @@ -22,6 +22,7 @@ use std::sync::Arc; use crate::backend; use crate::backend::{BackendError, ChangeId, CommitId, MergedTreeId, Signature}; use crate::merged_tree::MergedTree; +use crate::signing::{SignResult, Verification}; use crate::store::Store; #[derive(Clone)] @@ -146,6 +147,20 @@ impl Commit { } false } + + /// A quick way to just check if a signature is present. + pub fn is_signed(&self) -> bool { + self.data.secure_sig.is_some() + } + + /// A slow (but cached) way to get the full verification. + pub fn verification(&self) -> SignResult> { + self.data + .secure_sig + .as_ref() + .map(|sig| self.store.signer().verify(&self.id, &sig.data, &sig.sig)) + .transpose() + } } /// Wrapper to sort `Commit` by committer timestamp. diff --git a/lib/src/commit_builder.rs b/lib/src/commit_builder.rs index 6bb10270521..9f816e29e49 100644 --- a/lib/src/commit_builder.rs +++ b/lib/src/commit_builder.rs @@ -16,10 +16,11 @@ use std::sync::Arc; -use crate::backend::{self, BackendResult, ChangeId, CommitId, MergedTreeId, Signature}; +use crate::backend::{self, BackendResult, ChangeId, CommitId, MergedTreeId, Signature, SigningFn}; use crate::commit::Commit; use crate::repo::{MutableRepo, Repo}; -use crate::settings::{JJRng, UserSettings}; +use crate::settings::{JJRng, SignSettings, UserSettings}; +use crate::signing::SignBehavior; #[must_use] pub struct CommitBuilder<'repo> { @@ -27,6 +28,7 @@ pub struct CommitBuilder<'repo> { rng: Arc, commit: backend::Commit, rewrite_source: Option, + sign_settings: SignSettings, } impl CommitBuilder<'_> { @@ -55,6 +57,7 @@ impl CommitBuilder<'_> { rng, commit, rewrite_source: None, + sign_settings: settings.sign_settings(), } } @@ -83,6 +86,7 @@ impl CommitBuilder<'_> { commit, rng: settings.get_rng(), rewrite_source: Some(predecessor.clone()), + sign_settings: settings.sign_settings(), } } @@ -157,14 +161,44 @@ impl CommitBuilder<'_> { self } - pub fn write(self) -> BackendResult { + pub fn sign_settings(&self) -> &SignSettings { + &self.sign_settings + } + + pub fn set_sign_behavior(mut self, sign_behavior: SignBehavior) -> Self { + self.sign_settings.behavior = sign_behavior; + self + } + + pub fn set_sign_key(mut self, sign_key: Option) -> Self { + self.sign_settings.key = sign_key; + self + } + + pub fn write(mut self) -> BackendResult { let mut rewrite_source_id = None; if let Some(rewrite_source) = self.rewrite_source { if *rewrite_source.change_id() == self.commit.change_id { rewrite_source_id.replace(rewrite_source.id().clone()); } } - let commit = self.mut_repo.write_commit(self.commit)?; + + let sign_settings = self.sign_settings; + let store = self.mut_repo.store(); + + let signing_fn = (store.signer().can_sign() && sign_settings.should_sign(&self.commit)) + .then(|| { + let store = store.clone(); + Box::new(move |data: &_| store.signer().sign(data, sign_settings.key.as_deref())) + as SigningFn + }); + + // Commit backend doesn't use secure_sig for writing and enforces it with an + // assert, but sign_settings.should_sign check above will want to know + // if we're rewriting a signed commit + self.commit.secure_sig = None; + + let commit = self.mut_repo.write_commit(self.commit, signing_fn)?; if let Some(rewrite_source_id) = rewrite_source_id { self.mut_repo .record_rewritten_commit(rewrite_source_id, commit.id().clone()) diff --git a/lib/src/git_backend.rs b/lib/src/git_backend.rs index 79863b9676d..866482f6994 100644 --- a/lib/src/git_backend.rs +++ b/lib/src/git_backend.rs @@ -967,7 +967,10 @@ impl Backend for GitBackend { let mut data = Vec::with_capacity(512); commit.write_to(&mut data).unwrap(); - let sig = sign(&data)?; + let sig = sign(&data).map_err(|err| BackendError::WriteObject { + object_type: "commit", + source: Box::new(err), + })?; commit .extra_headers .push(("gpgsig".into(), sig.clone().into())); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index f0ac9314a4c..cf649466e5b 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -58,6 +58,7 @@ pub mod revset; pub mod revset_graph; pub mod rewrite; pub mod settings; +pub mod signing; pub mod simple_op_heads_store; pub mod simple_op_store; pub mod stacked_table; diff --git a/lib/src/local_backend.rs b/lib/src/local_backend.rs index 4246bc9c051..c2283e26e81 100644 --- a/lib/src/local_backend.rs +++ b/lib/src/local_backend.rs @@ -281,7 +281,7 @@ impl Backend for LocalBackend { let mut proto = commit_to_proto(&commit); if let Some(mut sign) = sign_with { let data = proto.encode_to_vec(); - let sig = sign(&data)?; + let sig = sign(&data).map_err(to_other_err)?; proto.secure_sig = Some(sig.clone()); commit.secure_sig = Some(SecureSig { data, sig }); } diff --git a/lib/src/repo.rs b/lib/src/repo.rs index ac26994d015..15b7f114e11 100644 --- a/lib/src/repo.rs +++ b/lib/src/repo.rs @@ -31,7 +31,7 @@ use tracing::instrument; use self::dirty_cell::DirtyCell; use crate::backend::{ Backend, BackendError, BackendInitError, BackendLoadError, BackendResult, ChangeId, CommitId, - MergedTreeId, ObjectId, + MergedTreeId, ObjectId, SigningFn, }; use crate::commit::{Commit, CommitByCommitterTimestamp}; use crate::commit_builder::CommitBuilder; @@ -52,6 +52,7 @@ use crate::refs::{ use crate::revset::{self, ChangeIdIndex, Revset, RevsetExpression}; use crate::rewrite::{DescendantRebaser, RebaseOptions}; use crate::settings::{RepoSettings, UserSettings}; +use crate::signing::{SignInitError, Signer}; use crate::simple_op_heads_store::SimpleOpHeadsStore; use crate::simple_op_store::SimpleOpStore; use crate::store::Store; @@ -118,6 +119,8 @@ pub enum RepoInitError { Backend(#[from] BackendInitError), #[error(transparent)] Path(#[from] PathError), + #[error(transparent)] + SignInit(#[from] SignInitError), } impl ReadonlyRepo { @@ -156,7 +159,8 @@ impl ReadonlyRepo { let backend = backend_initializer(user_settings, &store_path)?; let backend_path = store_path.join("type"); fs::write(&backend_path, backend.name()).context(&backend_path)?; - let store = Store::new(backend, user_settings.use_tree_conflict_format()); + let signer = Signer::from_settings(user_settings)?; + let store = Store::new(backend, signer, user_settings.use_tree_conflict_format()); let repo_settings = user_settings.with_repo(&repo_path).unwrap(); let op_store_path = repo_path.join("op_store"); @@ -418,6 +422,8 @@ pub enum StoreLoadError { }, #[error(transparent)] Backend(#[from] BackendLoadError), + #[error(transparent)] + Signing(#[from] SignInitError), } impl StoreFactories { @@ -608,6 +614,7 @@ impl RepoLoader { ) -> Result { let store = Store::new( store_factories.load_backend(user_settings, &repo_path.join("store"))?, + Signer::from_settings(user_settings)?, user_settings.use_tree_conflict_format(), ); let repo_settings = user_settings.with_repo(repo_path).unwrap(); @@ -792,8 +799,12 @@ impl MutableRepo { CommitBuilder::for_rewrite_from(self, settings, predecessor) } - pub fn write_commit(&mut self, commit: backend::Commit) -> BackendResult { - let commit = self.store().write_commit(commit)?; + pub fn write_commit( + &mut self, + commit: backend::Commit, + sign_with: Option, + ) -> BackendResult { + let commit = self.store().write_commit(commit, sign_with)?; self.add_head(&commit); Ok(commit) } diff --git a/lib/src/settings.rs b/lib/src/settings.rs index a56bc83f9ae..3463c1abb3b 100644 --- a/lib/src/settings.rs +++ b/lib/src/settings.rs @@ -21,9 +21,10 @@ use chrono::DateTime; use rand::prelude::*; use rand_chacha::ChaCha20Rng; -use crate::backend::{ChangeId, ObjectId, Signature, Timestamp}; +use crate::backend::{ChangeId, Commit, ObjectId, Signature, Timestamp}; use crate::fmt_util::binary_prefix; use crate::fsmonitor::FsmonitorKind; +use crate::signing::SignBehavior; #[derive(Debug, Clone)] pub struct UserSettings { @@ -63,6 +64,42 @@ impl Default for GitSettings { } } +#[derive(Debug, Clone, Default)] +pub struct SignSettings { + pub behavior: SignBehavior, + pub user_email: String, + pub key: Option, +} + +impl SignSettings { + pub fn from_settings(settings: &UserSettings) -> Self { + let sign_all = settings + .config() + .get_bool("signing.sign-all") + .unwrap_or(false); + Self { + behavior: if sign_all { + SignBehavior::Own + } else { + SignBehavior::Keep + }, + user_email: settings.user_email(), + key: settings.config().get_string("signing.key").ok(), + } + } + + pub fn should_sign(&self, commit: &Commit) -> bool { + match self.behavior { + SignBehavior::Drop => false, + SignBehavior::Keep => { + commit.secure_sig.is_some() && commit.author.email == self.user_email + } + SignBehavior::Own => commit.author.email == self.user_email, + SignBehavior::Force => true, + } + } +} + fn get_timestamp_config(config: &config::Config, key: &str) -> Option { match config.get_string(key) { Ok(timestamp_str) => match DateTime::parse_from_rfc3339(×tamp_str) { @@ -215,6 +252,16 @@ impl UserSettings { e @ Err(_) => e, } } + + // separate from sign_settings as those two are needed in pretty different + // places + pub fn signing_backend(&self) -> Option { + self.config.get_string("signing.backend").ok() + } + + pub fn sign_settings(&self) -> SignSettings { + SignSettings::from_settings(self) + } } /// This Rng uses interior mutability to allow generating random values using an diff --git a/lib/src/signing.rs b/lib/src/signing.rs new file mode 100644 index 00000000000..8166dc1b1f2 --- /dev/null +++ b/lib/src/signing.rs @@ -0,0 +1,249 @@ +// Copyright 2023 The Jujutsu Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Generic APIs to work with cryptographic signatures created and verified by +//! various backends. + +use std::collections::HashMap; +use std::fmt::Debug; +use std::sync::RwLock; + +use thiserror::Error; + +use crate::backend::CommitId; +use crate::settings::UserSettings; + +/// A status of the signature, part of the [Verification] type. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SigStatus { + /// Valid signature that matches the data. + Good, + /// Valid signature that could not be verified (e.g. due to an unknown key). + Unknown, + /// Valid signature that does not match the signed data. + Bad, +} + +/// 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. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Verification { + /// The status of the signature. + pub status: SigStatus, + /// The key id representation, if available. For GPG, this will be the key + /// fingerprint. + pub key: Option, + /// A display string, if available. For GPG, this will be formatted primary + /// user ID. + pub display: Option, +} + +impl Verification { + /// A shortcut to create an `Unknown` verification with no additional + /// metadata. + pub fn unknown() -> Self { + Self { + status: SigStatus::Unknown, + key: None, + display: None, + } + } +} + +/// The backend for signing and verifying cryptographic signatures. +/// +/// This allows using different signers, such as GPG or SSH, or different +/// versions of them. +pub trait SigningBackend: Debug + Send + Sync { + /// Name of the backend, used in the config and for display. + fn name(&self) -> &str; + + /// Check if the signature can be read and verified by this backend. + /// + /// Should check the signature format, usually just looks at the prefix. + fn can_read(&self, signature: &[u8]) -> bool; + + /// Create a signature for arbitrary data. + /// + /// The `key` parameter is what `jj sign` receives as key argument, or what + /// is configured in the `signing.key` config. + fn sign(&self, data: &[u8], key: Option<&str>) -> SignResult>; + + /// Verify a signature. Should be reflexive with `sign`: + /// ```rust,ignore + /// verify(data, sign(data)?)?.status == SigStatus::Good + /// ``` + fn verify(&self, data: &[u8], signature: &[u8]) -> SignResult; +} + +/// An error type for the signing/verifying operations +#[derive(Debug, Error)] +pub enum SignError { + /// The verification failed because the signature *format* was invalid. + #[error("Invalid signature")] + InvalidSignatureFormat, + /// A generic error from the backend impl. + #[error("Signing error: {0}")] + Backend(Box), +} + +/// A result type for the signing/verifying operations +pub type SignResult = Result; + +/// An error type for the signing backend initialization. +#[derive(Debug, Error)] +pub enum SignInitError { + /// If the backend name specified in the config is not known. + #[error("Unknown signing backend configured: {0}")] + UnknownBackend(String), + /// A generic error from the backend impl. + #[error("Failed to initialize signing: {0}")] + Backend(Box), +} + +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum SignBehavior { + /// Drop existing signatures. + /// This is what jj did before signing support or does now when a signing + /// backend is not configured. + #[default] + Drop, + /// Only sign commits that were authored by self and already signed, + /// "preserving" the signature across rewrites. + /// This is what jj does when a signing backend is configured. + Keep, + /// Sign/re-sign commits that were authored by self and drop them for + /// others. This is what jj does when configured to always sign. + Own, + /// Always sign commits, regardless of who authored or signed them before. + /// This is what jj does on `jj sign -f`. + Force, +} + +/// Wraps low-level signing backends and adds caching, similar to `Store`. +#[derive(Debug, Default)] +pub struct Signer { + /// The backend that is used for signing commits. + /// Optional because signing might not be configured. + main_backend: Option>, + /// All known backends without the main one - used for verification. + /// Main backend is also used for verification, but it's not in this list + /// for ownership reasons. + backends: Vec>, + cache: RwLock>, +} + +impl Signer { + /// Creates a signer based on user settings. Uses all known backends, and + /// chooses one of them to be used for signing depending on the config. + pub fn from_settings(settings: &UserSettings) -> Result { + let mut backends: Vec> = vec![ + // Box::new(GpgBackend::from_settings(settings)?), + // Box::new(SshBackend::from_settings(settings)?), + // Box::new(X509Backend::from_settings(settings)?), + ]; + + let main_backend = settings + .signing_backend() + .map(|backend| { + backends + .iter() + .position(|b| b.name() == backend) + .map(|i| backends.remove(i)) + .ok_or(SignInitError::UnknownBackend(backend)) + }) + .transpose()?; + + Ok(Self::new(main_backend, backends)) + } + + /// Creates a signer with the given backends. + pub fn new( + main_backend: Option>, + other_backends: Vec>, + ) -> Self { + Self { + main_backend, + backends: other_backends, + cache: Default::default(), + } + } + + /// Checks if the signer can sign, i.e. if a main backend is configured. + pub fn can_sign(&self) -> bool { + self.main_backend.is_some() + } + + /// This is just a pass-through to the main backend that unconditionally + /// creates a signature. + pub fn sign(&self, data: &[u8], key: Option<&str>) -> SignResult> { + self.main_backend + .as_ref() + .expect("tried to sign without checking can_sign first") + .sign(data, key) + } + + /// Looks for backend that can verify the signature and returns the result + /// of its verification. + pub fn verify( + &self, + commit_id: &CommitId, + data: &[u8], + signature: &[u8], + ) -> SignResult { + let cached = self.cache.read().unwrap().get(commit_id).cloned(); + if let Some(check) = cached { + return Ok(check); + } + + let verification = self + .main_backend + .iter() + .chain(self.backends.iter()) + .filter(|b| b.can_read(signature)) + // skip unknown and invalid sigs to allow other backends that can read to try + // for example, we might have gpg and sq, both of which could read a PGP signature + .find_map(|backend| match backend.verify(data, signature) { + Ok(check) if check.status == SigStatus::Unknown => None, + Err(SignError::InvalidSignatureFormat) => None, + e => Some(e), + }) + .transpose()?; + + if let Some(verification) = verification { + // a key might get imported before next call?. + // realistically this is unlikely, but technically + // it's correct to not cache unknowns here + if verification.status != SigStatus::Unknown { + self.cache + .write() + .unwrap() + .insert(commit_id.clone(), verification.clone()); + } + Ok(verification) + } else { + // now here it's correct to cache unknowns, as we don't + // have a backend that knows how to handle this signature + // + // not sure about how much of an optimization this is + self.cache + .write() + .unwrap() + .insert(commit_id.clone(), Verification::unknown()); + Ok(Verification::unknown()) + } + } +} diff --git a/lib/src/store.rs b/lib/src/store.rs index c85df284318..0ab0149ce45 100644 --- a/lib/src/store.rs +++ b/lib/src/store.rs @@ -22,14 +22,15 @@ use std::sync::{Arc, RwLock}; use pollster::FutureExt; -use crate::backend; use crate::backend::{ - Backend, BackendResult, ChangeId, CommitId, ConflictId, FileId, MergedTreeId, SymlinkId, TreeId, + self, Backend, BackendResult, ChangeId, CommitId, ConflictId, FileId, MergedTreeId, SigningFn, + SymlinkId, TreeId, }; use crate::commit::Commit; use crate::merge::{Merge, MergedTreeValue}; use crate::merged_tree::MergedTree; use crate::repo_path::{RepoPath, RepoPathBuf}; +use crate::signing::Signer; use crate::tree::Tree; use crate::tree_builder::TreeBuilder; @@ -37,6 +38,7 @@ use crate::tree_builder::TreeBuilder; /// adds caching. pub struct Store { backend: Box, + signer: Signer, commit_cache: RwLock>>, tree_cache: RwLock>>, use_tree_conflict_format: bool, @@ -51,9 +53,14 @@ impl Debug for Store { } impl Store { - pub fn new(backend: Box, use_tree_conflict_format: bool) -> Arc { + pub fn new( + backend: Box, + signer: Signer, + use_tree_conflict_format: bool, + ) -> Arc { Arc::new(Store { backend, + signer, commit_cache: Default::default(), tree_cache: Default::default(), use_tree_conflict_format, @@ -64,6 +71,10 @@ impl Store { self.backend.as_any() } + pub fn signer(&self) -> &Signer { + &self.signer + } + /// Whether new tree should be written using the tree-level format. pub fn use_tree_conflict_format(&self) -> bool { self.use_tree_conflict_format @@ -124,9 +135,14 @@ impl Store { Ok(data) } - pub fn write_commit(self: &Arc, commit: backend::Commit) -> BackendResult { + pub fn write_commit( + self: &Arc, + commit: backend::Commit, + sign_with: Option, + ) -> BackendResult { assert!(!commit.parents.is_empty()); - let (commit_id, commit) = self.backend.write_commit(commit, None)?; + + let (commit_id, commit) = self.backend.write_commit(commit, sign_with)?; let data = Arc::new(commit); { let mut write_locked_cache = self.commit_cache.write().unwrap(); diff --git a/lib/src/workspace.rs b/lib/src/workspace.rs index 36b11bfed6e..da386a76334 100644 --- a/lib/src/workspace.rs +++ b/lib/src/workspace.rs @@ -36,6 +36,7 @@ use crate::repo::{ StoreFactories, StoreLoadError, SubmoduleStoreInitializer, }; use crate::settings::UserSettings; +use crate::signing::SignInitError; use crate::store::Store; use crate::working_copy::{ CheckoutError, CheckoutStats, LockedWorkingCopy, WorkingCopy, WorkingCopyStateError, @@ -55,6 +56,8 @@ pub enum WorkspaceInitError { Path(#[from] PathError), #[error(transparent)] Backend(#[from] BackendInitError), + #[error(transparent)] + SignInit(#[from] SignInitError), } #[derive(Error, Debug)] @@ -249,6 +252,7 @@ impl Workspace { .map_err(|repo_init_err| match repo_init_err { RepoInitError::Backend(err) => WorkspaceInitError::Backend(err), RepoInitError::Path(err) => WorkspaceInitError::Path(err), + RepoInitError::SignInit(err) => WorkspaceInitError::SignInit(err), })?; let (working_copy, repo) = init_working_copy( user_settings, diff --git a/lib/testutils/src/lib.rs b/lib/testutils/src/lib.rs index 5dc8dbdd0f0..ffe157bb3d2 100644 --- a/lib/testutils/src/lib.rs +++ b/lib/testutils/src/lib.rs @@ -336,7 +336,7 @@ pub fn commit_with_tree(store: &Arc, tree_id: MergedTreeId) -> Commit { committer: signature, secure_sig: None, }; - store.write_commit(commit).unwrap() + store.write_commit(commit, None).unwrap() } pub fn dump_tree(store: &Arc, tree_id: &MergedTreeId) -> String { diff --git a/lib/testutils/src/test_backend.rs b/lib/testutils/src/test_backend.rs index 8180d415332..a3d63aeae6b 100644 --- a/lib/testutils/src/test_backend.rs +++ b/lib/testutils/src/test_backend.rs @@ -282,7 +282,7 @@ impl Backend for TestBackend { if let Some(sign) = &mut sign_with { let data = format!("{contents:?}").into_bytes(); - let sig = sign(&data)?; + let sig = sign(&data).map_err(|err| BackendError::Other(Box::new(err)))?; contents.secure_sig = Some(SecureSig { data, sig }); }