//! Transport-agnostic protocol (spec §16): what a client asks of a remote and //! what a server does with a push. `LocalRemote` serves another repository //! directory in-process; an HTTPS transport implements the same trait and the //! server side reuses `Repo::receive_push` and friends unchanged. use crate::attest::Attest; use crate::hash::{ChangeHash, ObjectRef, ObjectType}; use crate::identity::Signature; use crate::objects::Change; use crate::repo::{Repo, RepoError, Result}; use std::path::Path; /// An attestation together with the signatures over its ObjectRef. #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignedAttest { pub attest: Attest, pub sigs: Vec, } /// One reference as a remote reports it. #[derive(Clone, Debug)] pub struct RemoteRef { pub name: String, pub state: ObjectRef, pub heads: Vec, pub attestation: Option, } /// Everything needed to add a set of changes to a repository. #[derive(Clone, Debug, Default)] pub struct Bundle { /// In dependency order, each with its individual signatures. pub changes: Vec<(Change, Vec)>, /// Seals covering the changes. pub attests: Vec, /// Chunks, blobs, identities, and state objects referenced by the changes. pub objects: Vec<(ObjectType, Vec)>, } impl Bundle { pub fn is_empty(&self) -> bool { self.changes.is_empty() && self.attests.is_empty() && self.objects.is_empty() } } /// A requested bookmark move (spec §10.2, §16.3). #[derive(Clone, Debug)] pub struct RefUpdate { pub name: String, pub old_state: ObjectRef, pub new_state: ObjectRef, pub force: bool, pub attestation: Option, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct RefResult { pub name: String, pub ok: bool, pub reason: String, } pub trait Remote { fn repo_id(&self) -> Result<[u8; 16]>; fn refs(&self) -> Result>; /// Changes in the closure of `want` but not of `have`, in dependency order. fn negotiate(&self, have: &[ChangeHash], want: &[ChangeHash]) -> Result>; fn fetch(&self, changes: &[ChangeHash]) -> Result; fn push(&self, bundle: Bundle, updates: Vec) -> Result>; } /// Another repository on this machine, served in-process. pub struct LocalRemote { pub repo: Repo, } impl LocalRemote { pub fn open(path: &Path) -> Result { Ok(LocalRemote { repo: Repo::open(path)? }) } } impl Remote for LocalRemote { fn repo_id(&self) -> Result<[u8; 16]> { self.repo.id() } fn refs(&self) -> Result> { self.repo.remote_refs() } fn negotiate(&self, have: &[ChangeHash], want: &[ChangeHash]) -> Result> { self.repo.negotiate(have, want) } fn fetch(&self, changes: &[ChangeHash]) -> Result { self.repo.bundle_for(changes) } fn push(&self, bundle: Bundle, updates: Vec) -> Result> { self.repo.receive_push(bundle, updates) } } /// Open a remote by URL. Only local paths (optionally `file://`) exist so far. pub fn open_remote(url: &str) -> Result> { let path = url.strip_prefix("file://").unwrap_or(url); if path.contains("://") { return Err(RepoError::Msg(format!("unsupported remote URL {url:?}: only local paths are implemented"))); } Ok(Box::new(LocalRemote::open(Path::new(path))?)) }