Skip to main content

radicle/
storage.rs

1pub mod git;
2pub mod refs;
3
4use std::collections::{hash_map, HashSet};
5use std::ops::Deref;
6use std::path::{Path, PathBuf};
7use std::{fmt, io};
8
9use nonempty::NonEmpty;
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12
13pub use crate::git::Oid;
14use crypto::{PublicKey, Unverified, Verified};
15pub use git::{Validation, Validations};
16
17use crate::cob;
18use crate::collections::RandomMap;
19use crate::git::canonical;
20use crate::git::fmt::{refspec::PatternString, refspec::Refspec, Qualified, RefStr, RefString};
21use crate::git::raw::ErrorExt as _;
22use crate::git::RefError;
23use crate::identity::{doc, Did, PayloadError};
24use crate::identity::{Doc, DocAt, DocError};
25use crate::identity::{Identity, RepoId};
26use crate::node::device::Device;
27use crate::node::SyncedAt;
28use crate::storage::git::NAMESPACES_GLOB;
29use crate::storage::refs::Refs;
30
31use self::refs::{RefsAt, SignedRefs};
32use crate::git::UserInfo;
33
34/// Basic repository information.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct RepositoryInfo {
37    /// Repository identifier.
38    pub rid: RepoId,
39    /// Head of default branch.
40    pub head: Oid,
41    /// Identity document.
42    pub doc: Doc,
43    /// Local signed refs, if any.
44    /// Repositories with this set to `None` are ones that are seeded but not forked.
45    pub refs: Option<refs::SignedRefsAt>,
46    /// Sync time of the repository.
47    pub synced_at: Option<SyncedAt>,
48}
49
50/// Describes one or more namespaces.
51#[derive(Default, Debug, Clone, PartialEq, Eq)]
52pub enum Namespaces {
53    /// All namespaces.
54    #[default]
55    All,
56    /// The followed set of namespaces.
57    Followed(HashSet<PublicKey>),
58}
59
60impl Namespaces {
61    pub fn to_refspecs(&self) -> Vec<Refspec<PatternString, PatternString>> {
62        match self {
63            Namespaces::All => vec![Refspec {
64                src: (*NAMESPACES_GLOB).clone(),
65                dst: (*NAMESPACES_GLOB).clone(),
66                force: true,
67            }],
68            Namespaces::Followed(pks) => pks
69                .iter()
70                .map(|pk| {
71                    let ns = pk
72                        .to_namespace()
73                        .with_pattern(crate::git::fmt::refspec::STAR);
74                    Refspec {
75                        src: ns.clone(),
76                        dst: ns,
77                        force: true,
78                    }
79                })
80                .collect(),
81        }
82    }
83}
84
85impl FromIterator<PublicKey> for Namespaces {
86    fn from_iter<T: IntoIterator<Item = PublicKey>>(iter: T) -> Self {
87        Self::Followed(iter.into_iter().collect())
88    }
89}
90
91/// Output of [`WriteRepository::set_head`].
92pub struct SetHead {
93    /// Old branch head.
94    pub old: Option<Oid>,
95    /// New branch head.
96    pub new: Oid,
97}
98
99impl SetHead {
100    /// Check if the head was updated.
101    pub fn is_updated(&self) -> bool {
102        self.old != Some(self.new)
103    }
104}
105
106/// Repository error.
107#[derive(Error, Debug)]
108pub enum RepositoryError {
109    #[error(transparent)]
110    Storage(#[from] Error),
111    #[error(transparent)]
112    Store(#[from] cob::store::Error),
113    #[error(transparent)]
114    Doc(#[from] DocError),
115    #[error(transparent)]
116    Payload(#[from] PayloadError),
117    #[error(transparent)]
118    Git(#[from] crate::git::raw::Error),
119    #[error(transparent)]
120    Quorum(#[from] canonical::error::QuorumError),
121    #[error(transparent)]
122    Refs(#[from] refs::Error),
123    #[error("missing canonical reference rule for default branch")]
124    MissingBranchRule,
125    #[error("could not get the default branch rule: {0}")]
126    DefaultBranchRule(#[from] doc::DefaultBranchRuleError),
127    #[error("failed to get canonical reference rules: {0}")]
128    CanonicalRefs(#[from] doc::CanonicalRefsError),
129    #[error(transparent)]
130    FindObjects(#[from] canonical::effects::FindObjectsError),
131}
132
133impl RepositoryError {
134    pub fn is_not_found(&self) -> bool {
135        match self {
136            Self::Storage(e) => e.is_not_found(),
137            Self::Git(e) => e.is_not_found(),
138            _ => false,
139        }
140    }
141}
142
143/// Storage error.
144#[derive(Error, Debug)]
145pub enum Error {
146    #[error("invalid git reference")]
147    InvalidRef,
148    #[error("identity doc: {0}")]
149    Doc(#[from] DocError),
150    #[error("git reference error: {0}")]
151    Ref(#[from] RefError),
152    #[error(transparent)]
153    Refs(#[from] refs::Error),
154    #[error("git: {0}")]
155    Git(#[from] crate::git::raw::Error),
156    #[error("invalid repository identifier {0:?}")]
157    InvalidId(std::ffi::OsString),
158    #[error("i/o: {0}")]
159    Io(#[from] io::Error),
160}
161
162impl Error {
163    /// Whether this error is caused by something not being found.
164    pub fn is_not_found(&self) -> bool {
165        match self {
166            Self::Io(e) if e.kind() == io::ErrorKind::NotFound => true,
167            Self::Git(e) if e.is_not_found() => true,
168            Self::Doc(e) if e.is_not_found() => true,
169            _ => false,
170        }
171    }
172}
173
174/// Fetch error.
175#[derive(Error, Debug)]
176#[allow(clippy::large_enum_variant)]
177pub enum FetchError {
178    #[error("git: {0}")]
179    Git(#[from] crate::git::raw::Error),
180    #[error("i/o: {0}")]
181    Io(#[from] io::Error),
182    #[error(transparent)]
183    Refs(#[from] refs::Error),
184    #[error(transparent)]
185    Storage(#[from] Error),
186    #[error("failed to validate remote layouts in storage")]
187    Validation { validations: Validations },
188    #[error("repository head: {0}")]
189    SetHead(#[from] DocError),
190    #[error("repository: {0}")]
191    Repository(#[from] RepositoryError),
192}
193
194pub type RemoteId = PublicKey;
195
196/// An update to a reference.
197#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
198#[serde(rename_all = "camelCase")]
199#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
200pub enum RefUpdate {
201    Updated {
202        #[cfg_attr(
203            feature = "schemars",
204            schemars(with = "crate::schemars_ext::git::fmt::RefString")
205        )]
206        name: RefString,
207        old: Oid,
208        new: Oid,
209    },
210    Created {
211        #[cfg_attr(
212            feature = "schemars",
213            schemars(with = "crate::schemars_ext::git::fmt::RefString")
214        )]
215        name: RefString,
216        oid: Oid,
217    },
218    Deleted {
219        #[cfg_attr(
220            feature = "schemars",
221            schemars(with = "crate::schemars_ext::git::fmt::RefString")
222        )]
223        name: RefString,
224        oid: Oid,
225    },
226    Skipped {
227        #[cfg_attr(feature = "schemars", schemars(with = "String"))]
228        name: RefString,
229        oid: Oid,
230    },
231}
232
233impl RefUpdate {
234    pub fn from(name: RefString, old: impl Into<Oid>, new: impl Into<Oid>) -> Self {
235        let old = old.into();
236        let new = new.into();
237
238        if old.is_zero() {
239            Self::Created { name, oid: new }
240        } else if new.is_zero() {
241            Self::Deleted { name, oid: old }
242        } else if old != new {
243            Self::Updated { name, old, new }
244        } else {
245            Self::Skipped { name, oid: old }
246        }
247    }
248
249    /// Get the old OID, if any.
250    pub fn old(&self) -> Option<Oid> {
251        match self {
252            RefUpdate::Updated { old, .. } => Some(*old),
253            RefUpdate::Created { .. } => None,
254            RefUpdate::Deleted { oid, .. } => Some(*oid),
255            RefUpdate::Skipped { oid, .. } => Some(*oid),
256        }
257    }
258
259    /// Get the new OID, if any.
260    #[allow(clippy::new_ret_no_self)]
261    pub fn new(&self) -> Option<Oid> {
262        match self {
263            RefUpdate::Updated { new, .. } => Some(*new),
264            RefUpdate::Created { oid, .. } => Some(*oid),
265            RefUpdate::Deleted { .. } => None,
266            RefUpdate::Skipped { .. } => None,
267        }
268    }
269
270    /// Get the ref name.
271    pub fn name(&self) -> &RefStr {
272        match self {
273            RefUpdate::Updated { name, .. } => name.as_refstr(),
274            RefUpdate::Created { name, .. } => name.as_refstr(),
275            RefUpdate::Deleted { name, .. } => name.as_refstr(),
276            RefUpdate::Skipped { name, .. } => name.as_refstr(),
277        }
278    }
279
280    /// Is it an update.
281    pub fn is_updated(&self) -> bool {
282        matches!(self, RefUpdate::Updated { .. })
283    }
284
285    /// Is it a create.
286    pub fn is_created(&self) -> bool {
287        matches!(self, RefUpdate::Created { .. })
288    }
289
290    /// Is it a skip.
291    pub fn is_skipped(&self) -> bool {
292        matches!(self, RefUpdate::Skipped { .. })
293    }
294}
295
296impl fmt::Display for RefUpdate {
297    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298        match self {
299            Self::Updated { name, old, new } => {
300                write!(f, "~ {old:.7}..{new:.7} {name}")
301            }
302            Self::Created { name, oid } => {
303                write!(f, "* 0000000..{oid:.7} {name}")
304            }
305            Self::Deleted { name, oid } => {
306                write!(f, "- {oid:.7}..0000000 {name}")
307            }
308            Self::Skipped { name, oid } => {
309                write!(f, "= {oid:.7}..{oid:.7} {name}")
310            }
311        }
312    }
313}
314
315/// Project remotes. Tracks the git state of a project.
316#[derive(Debug, Clone, PartialEq, Eq)]
317pub struct Remotes<V>(RandomMap<RemoteId, Remote<V>>);
318
319impl<V> FromIterator<(RemoteId, Remote<V>)> for Remotes<V> {
320    fn from_iter<T: IntoIterator<Item = (RemoteId, Remote<V>)>>(iter: T) -> Self {
321        Self(iter.into_iter().collect())
322    }
323}
324
325impl<V> Deref for Remotes<V> {
326    type Target = RandomMap<RemoteId, Remote<V>>;
327
328    fn deref(&self) -> &Self::Target {
329        &self.0
330    }
331}
332
333impl<V> Remotes<V> {
334    pub fn new(remotes: RandomMap<RemoteId, Remote<V>>) -> Self {
335        Self(remotes)
336    }
337}
338
339impl Remotes<Verified> {
340    pub fn unverified(self) -> Remotes<Unverified> {
341        Remotes(
342            self.into_iter()
343                .map(|(id, r)| (id, r.unverified()))
344                .collect(),
345        )
346    }
347}
348
349impl<V> Default for Remotes<V> {
350    fn default() -> Self {
351        Self(RandomMap::default())
352    }
353}
354
355impl<V> IntoIterator for Remotes<V> {
356    type Item = (RemoteId, Remote<V>);
357    type IntoIter = hash_map::IntoIter<RemoteId, Remote<V>>;
358
359    fn into_iter(self) -> Self::IntoIter {
360        self.0.into_iter()
361    }
362}
363
364impl<V> From<Remotes<V>> for RandomMap<RemoteId, Refs> {
365    fn from(other: Remotes<V>) -> Self {
366        let mut remotes = RandomMap::with_hasher(fastrand::Rng::new().into());
367
368        for (k, v) in other.into_iter() {
369            remotes.insert(k, v.refs.into());
370        }
371        remotes
372    }
373}
374
375/// A project remote.
376#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
377pub struct Remote<V = Verified> {
378    /// Git references published under this remote, and their hashes.
379    #[serde(flatten)]
380    pub refs: SignedRefs<V>,
381}
382
383impl Remote<Unverified> {
384    /// Create a new unverified remotes object.
385    pub fn new(refs: impl Into<SignedRefs<Unverified>>) -> Self {
386        Self { refs: refs.into() }
387    }
388}
389
390impl Remote<Unverified> {
391    pub fn verified<R: ReadRepository>(self, repo: &R) -> Result<Remote<Verified>, Error> {
392        let refs = self.refs.verified(repo)?;
393
394        Ok(Remote { refs })
395    }
396}
397
398impl Remote<Verified> {
399    /// Create a new unverified remotes object.
400    pub fn new(refs: impl Into<SignedRefs<Verified>>) -> Self {
401        Self { refs: refs.into() }
402    }
403
404    pub fn unverified(self) -> Remote<Unverified> {
405        Remote {
406            refs: self.refs.unverified(),
407        }
408    }
409
410    pub fn to_refspecs(&self) -> Vec<Refspec<PatternString, PatternString>> {
411        let ns = self.id.to_namespace();
412        // Nb. the references in Refs are expected to be Qualified
413        self.refs
414            .keys()
415            .map(|name| {
416                let name = PatternString::from(ns.join(name));
417                Refspec {
418                    src: name.clone(),
419                    dst: name,
420                    force: true,
421                }
422            })
423            .collect()
424    }
425}
426
427impl<V> Deref for Remote<V> {
428    type Target = SignedRefs<V>;
429
430    fn deref(&self) -> &Self::Target {
431        &self.refs
432    }
433}
434
435/// Read-only operations on a storage instance.
436pub trait ReadStorage {
437    type Repository: ReadRepository;
438
439    /// Get user info for this storage.
440    fn info(&self) -> &UserInfo;
441    /// Get the storage base path.
442    fn path(&self) -> &Path;
443    /// Get a repository's path.
444    fn path_of(&self, rid: &RepoId) -> PathBuf;
445    /// Check whether storage contains a repository.
446    fn contains(&self, rid: &RepoId) -> Result<bool, RepositoryError>;
447    /// Return all repositories (public and private).
448    fn repositories(&self) -> Result<Vec<RepositoryInfo>, Error>;
449    /// Open or create a read-only repository.
450    fn repository(&self, rid: RepoId) -> Result<Self::Repository, RepositoryError>;
451    /// Get a repository's identity if it exists.
452    fn get(&self, rid: RepoId) -> Result<Option<Doc>, RepositoryError> {
453        match self.repository(rid) {
454            Ok(repo) => Ok(Some(repo.identity_doc()?.into())),
455            Err(e) if e.is_not_found() => Ok(None),
456            Err(e) => Err(e),
457        }
458    }
459}
460
461/// Allows access to individual storage repositories.
462pub trait WriteStorage: ReadStorage {
463    type RepositoryMut: WriteRepository;
464
465    /// Open a read-write repository.
466    fn repository_mut(&self, rid: RepoId) -> Result<Self::RepositoryMut, RepositoryError>;
467    /// Create a read-write repository.
468    fn create(&self, rid: RepoId) -> Result<Self::RepositoryMut, Error>;
469
470    /// Clean the repository found at `rid`.
471    ///
472    /// If the local peer has initialised `rad/sigrefs` by forking or
473    /// creating any COBs, then this will delete all remote namespaces
474    /// that are neither the local's or a delegate's.
475    ///
476    /// If the local peer has no initialised `rad/sigrefs`, then the
477    /// repository will be entirely removed from storage.
478    fn clean(&self, rid: RepoId) -> Result<Vec<RemoteId>, RepositoryError>;
479}
480
481/// Anything can return the [`RepoId`] that it is associated with.
482pub trait HasRepoId {
483    fn rid(&self) -> RepoId;
484}
485
486impl<T: ReadRepository> HasRepoId for T {
487    fn rid(&self) -> RepoId {
488        ReadRepository::id(self)
489    }
490}
491
492/// Allows read-only access to a repository.
493pub trait ReadRepository: Sized + ValidateRepository {
494    /// Return the repository id.
495    fn id(&self) -> RepoId;
496
497    /// Returns `true` if there are no references in the repository.
498    fn is_empty(&self) -> Result<bool, crate::git::raw::Error>;
499
500    /// The [`Path`] to the git repository.
501    fn path(&self) -> &Path;
502
503    /// Get a blob in this repository at the given commit and path.
504    fn blob_at<P: AsRef<Path>>(
505        &self,
506        commit: Oid,
507        path: P,
508    ) -> Result<crate::git::raw::Blob<'_>, crate::git::raw::Error>;
509
510    /// Get a blob in this repository, given its id.
511    fn blob(&self, oid: Oid) -> Result<crate::git::raw::Blob<'_>, crate::git::raw::Error>;
512
513    /// Get the head of this repository.
514    ///
515    /// Returns the reference pointed to by `HEAD` if it is set. Otherwise, computes the canonical
516    /// head using [`ReadRepository::canonical_head`].
517    ///
518    /// Returns the [`Oid`] as well as the qualified reference name.
519    fn head(&self) -> Result<(Qualified<'_>, Oid), RepositoryError>;
520
521    /// Compute the canonical head of this repository.
522    ///
523    /// Ignores any existing `HEAD` reference.
524    ///
525    /// Returns the [`Oid`] as well as the qualified reference name.
526    fn canonical_head(&self) -> Result<(Qualified<'_>, Oid), RepositoryError>;
527
528    /// Get the head of the `rad/id` reference in this repository.
529    ///
530    /// Returns the reference pointed to by `rad/id` if it is set. Otherwise, computes the canonical
531    /// `rad/id` using [`ReadRepository::canonical_identity_head`].
532    fn identity_head(&self) -> Result<Oid, RepositoryError>;
533
534    /// Get the identity head of a specific remote.
535    fn identity_head_of(&self, remote: &RemoteId) -> Result<Oid, crate::git::raw::Error>;
536
537    /// Get the root commit of the canonical identity branch.
538    fn identity_root(&self) -> Result<Oid, RepositoryError>;
539
540    /// Get the root commit of the identity branch of a specific remote.
541    fn identity_root_of(&self, remote: &RemoteId) -> Result<Oid, RepositoryError>;
542
543    /// Load the identity history.
544    fn identity(&self) -> Result<Identity, RepositoryError>
545    where
546        Self: cob::Store,
547    {
548        Identity::load(self)
549    }
550
551    /// Compute the canonical `rad/id` of this repository.
552    ///
553    /// Ignores any existing `rad/id` reference.
554    fn canonical_identity_head(&self) -> Result<Oid, RepositoryError>;
555
556    /// Compute the canonical identity document.
557    fn canonical_identity_doc(&self) -> Result<DocAt, RepositoryError> {
558        let head = self.canonical_identity_head()?;
559        let doc = self.identity_doc_at(head)?;
560
561        Ok(doc)
562    }
563
564    /// Get the `reference` for the given `remote`.
565    ///
566    /// Returns `None` is the reference did not exist.
567    fn reference(
568        &self,
569        remote: &RemoteId,
570        reference: &Qualified,
571    ) -> Result<crate::git::raw::Reference<'_>, crate::git::raw::Error>;
572
573    /// Get the [`crate::git::raw::Commit`] found using its `oid`.
574    ///
575    /// Returns `Err` if the commit did not exist.
576    fn commit(&self, oid: Oid) -> Result<crate::git::raw::Commit<'_>, crate::git::raw::Error>;
577
578    /// Perform a revision walk of a commit history starting from the given head.
579    fn revwalk(&self, head: Oid) -> Result<crate::git::raw::Revwalk<'_>, crate::git::raw::Error>;
580
581    /// Check if the underlying ODB contains the given `oid`.
582    fn contains(&self, oid: Oid) -> Result<bool, crate::git::raw::Error>;
583
584    /// Check whether the given commit is an ancestor of another commit.
585    fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result<bool, crate::git::raw::Error>;
586
587    /// Get the object id of a reference under the given remote.
588    fn reference_oid(
589        &self,
590        remote: &RemoteId,
591        reference: &Qualified,
592    ) -> Result<Oid, crate::git::raw::Error>;
593
594    /// Get all references of the given remote.
595    fn references_of(&self, remote: &RemoteId) -> Result<Refs, Error>;
596
597    /// Get all references following a pattern.
598    /// Skips references with names that are not parseable into [`Qualified`].
599    ///
600    /// This function always peels reference to the commit. For tags, this means the [`Oid`] of the
601    /// commit pointed to by the tag is returned, and not the [`Oid`] of the tag itself.
602    fn references_glob(
603        &self,
604        pattern: &crate::git::fmt::refspec::PatternStr,
605    ) -> Result<Vec<(Qualified<'_>, Oid)>, crate::git::raw::Error>;
606
607    /// Get repository delegates.
608    fn delegates(&self) -> Result<NonEmpty<Did>, RepositoryError> {
609        let doc = self.identity_doc()?;
610
611        Ok(doc.delegates().clone().into())
612    }
613
614    /// Get the repository's identity document.
615    fn identity_doc(&self) -> Result<DocAt, RepositoryError> {
616        let head = self.identity_head()?;
617        let doc = self.identity_doc_at(head)?;
618
619        Ok(doc)
620    }
621
622    /// Get the repository's identity document at a specific commit.
623    fn identity_doc_at(&self, head: Oid) -> Result<DocAt, DocError>;
624
625    /// Get the merge base of two commits.
626    fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, crate::git::raw::Error>;
627}
628
629/// Access the remotes of a repository.
630pub trait RemoteRepository {
631    /// Get the given remote.
632    fn remote(&self, remote: &RemoteId) -> Result<Remote<Verified>, refs::Error>;
633
634    /// Get all remotes.
635    fn remotes(&self) -> Result<Remotes<Verified>, refs::Error>;
636
637    /// Get [`RefsAt`] of all remotes.
638    fn remote_refs_at(&self) -> Result<Vec<RefsAt>, refs::Error>;
639}
640
641pub trait ValidateRepository
642where
643    Self: RemoteRepository,
644{
645    /// Validate all remotes with [`ValidateRepository::validate_remote`].
646    fn validate(&self) -> Result<Validations, Error> {
647        let mut failures = Validations::default();
648        for (_, remote) in self.remotes()? {
649            failures.append(&mut self.validate_remote(&remote)?);
650        }
651        Ok(failures)
652    }
653
654    /// Validates a remote's signed refs and identity.
655    ///
656    /// Returns any ref found under that remote that isn't signed.
657    /// If a signed ref is missing from the repository, an error is returned.
658    fn validate_remote(&self, remote: &Remote<Verified>) -> Result<Validations, Error>;
659}
660
661/// Allows read-write access to a repository.
662pub trait WriteRepository: ReadRepository + SignRepository {
663    /// Set the repository head to the canonical branch.
664    /// This computes the head based on the delegate set.
665    fn set_head(&self) -> Result<SetHead, RepositoryError>;
666    /// Set the repository 'rad/id' to the canonical commit, agreed by quorum.
667    fn set_identity_head(&self) -> Result<Oid, RepositoryError> {
668        let head = self.canonical_identity_head()?;
669        self.set_identity_head_to(head)?;
670
671        Ok(head)
672    }
673    /// Set the identity root reference to the canonical identity root commit.
674    fn set_remote_identity_root(&self, remote: &RemoteId) -> Result<Oid, RepositoryError> {
675        let root = self.identity_root()?;
676        self.set_remote_identity_root_to(remote, root)?;
677
678        Ok(root)
679    }
680    /// Set the identity root reference to the given commit.
681    fn set_remote_identity_root_to(
682        &self,
683        remote: &RemoteId,
684        root: Oid,
685    ) -> Result<(), RepositoryError>;
686    /// Set the repository 'rad/id' to the given commit.
687    fn set_identity_head_to(&self, commit: Oid) -> Result<(), RepositoryError>;
688    /// Set the user info of the Git repository.
689    fn set_user(&self, info: &UserInfo) -> Result<(), Error>;
690    /// Get the underlying git repository.
691    fn raw(&self) -> &crate::git::raw::Repository;
692}
693
694/// Allows signing refs.
695pub trait SignRepository {
696    /// Sign the repository's refs under the `refs/rad/sigrefs` branch.
697    fn sign_refs<G>(&self, signer: &Device<G>) -> Result<SignedRefs<Verified>, RepositoryError>
698    where
699        G: crypto::signature::Signer<crypto::Signature>;
700}
701
702impl<T, S> ReadStorage for T
703where
704    T: Deref<Target = S>,
705    S: ReadStorage + 'static,
706{
707    type Repository = S::Repository;
708
709    fn info(&self) -> &UserInfo {
710        self.deref().info()
711    }
712
713    fn path(&self) -> &Path {
714        self.deref().path()
715    }
716
717    fn path_of(&self, rid: &RepoId) -> PathBuf {
718        self.deref().path_of(rid)
719    }
720
721    fn contains(&self, rid: &RepoId) -> Result<bool, RepositoryError> {
722        self.deref().contains(rid)
723    }
724
725    fn get(&self, rid: RepoId) -> Result<Option<Doc>, RepositoryError> {
726        self.deref().get(rid)
727    }
728
729    fn repository(&self, rid: RepoId) -> Result<Self::Repository, RepositoryError> {
730        self.deref().repository(rid)
731    }
732
733    fn repositories(&self) -> Result<Vec<RepositoryInfo>, Error> {
734        self.deref().repositories()
735    }
736}
737
738impl<T, S> WriteStorage for T
739where
740    T: Deref<Target = S>,
741    S: WriteStorage + 'static,
742{
743    type RepositoryMut = S::RepositoryMut;
744
745    fn repository_mut(&self, rid: RepoId) -> Result<Self::RepositoryMut, RepositoryError> {
746        self.deref().repository_mut(rid)
747    }
748
749    fn create(&self, rid: RepoId) -> Result<Self::RepositoryMut, Error> {
750        self.deref().create(rid)
751    }
752
753    fn clean(&self, rid: RepoId) -> Result<Vec<RemoteId>, RepositoryError> {
754        self.deref().clean(rid)
755    }
756}
757
758#[cfg(test)]
759mod tests {
760    #[test]
761    fn test_storage() {}
762}