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#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct RepositoryInfo {
37 pub rid: RepoId,
39 pub head: Oid,
41 pub doc: Doc,
43 pub refs: Option<refs::SignedRefsAt>,
46 pub synced_at: Option<SyncedAt>,
48}
49
50#[derive(Default, Debug, Clone, PartialEq, Eq)]
52pub enum Namespaces {
53 #[default]
55 All,
56 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
91pub struct SetHead {
93 pub old: Option<Oid>,
95 pub new: Oid,
97}
98
99impl SetHead {
100 pub fn is_updated(&self) -> bool {
102 self.old != Some(self.new)
103 }
104}
105
106#[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#[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 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#[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#[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 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 #[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 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 pub fn is_updated(&self) -> bool {
282 matches!(self, RefUpdate::Updated { .. })
283 }
284
285 pub fn is_created(&self) -> bool {
287 matches!(self, RefUpdate::Created { .. })
288 }
289
290 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#[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#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
377pub struct Remote<V = Verified> {
378 #[serde(flatten)]
380 pub refs: SignedRefs<V>,
381}
382
383impl Remote<Unverified> {
384 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 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 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
435pub trait ReadStorage {
437 type Repository: ReadRepository;
438
439 fn info(&self) -> &UserInfo;
441 fn path(&self) -> &Path;
443 fn path_of(&self, rid: &RepoId) -> PathBuf;
445 fn contains(&self, rid: &RepoId) -> Result<bool, RepositoryError>;
447 fn repositories(&self) -> Result<Vec<RepositoryInfo>, Error>;
449 fn repository(&self, rid: RepoId) -> Result<Self::Repository, RepositoryError>;
451 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
461pub trait WriteStorage: ReadStorage {
463 type RepositoryMut: WriteRepository;
464
465 fn repository_mut(&self, rid: RepoId) -> Result<Self::RepositoryMut, RepositoryError>;
467 fn create(&self, rid: RepoId) -> Result<Self::RepositoryMut, Error>;
469
470 fn clean(&self, rid: RepoId) -> Result<Vec<RemoteId>, RepositoryError>;
479}
480
481pub 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
492pub trait ReadRepository: Sized + ValidateRepository {
494 fn id(&self) -> RepoId;
496
497 fn is_empty(&self) -> Result<bool, crate::git::raw::Error>;
499
500 fn path(&self) -> &Path;
502
503 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 fn blob(&self, oid: Oid) -> Result<crate::git::raw::Blob<'_>, crate::git::raw::Error>;
512
513 fn head(&self) -> Result<(Qualified<'_>, Oid), RepositoryError>;
520
521 fn canonical_head(&self) -> Result<(Qualified<'_>, Oid), RepositoryError>;
527
528 fn identity_head(&self) -> Result<Oid, RepositoryError>;
533
534 fn identity_head_of(&self, remote: &RemoteId) -> Result<Oid, crate::git::raw::Error>;
536
537 fn identity_root(&self) -> Result<Oid, RepositoryError>;
539
540 fn identity_root_of(&self, remote: &RemoteId) -> Result<Oid, RepositoryError>;
542
543 fn identity(&self) -> Result<Identity, RepositoryError>
545 where
546 Self: cob::Store,
547 {
548 Identity::load(self)
549 }
550
551 fn canonical_identity_head(&self) -> Result<Oid, RepositoryError>;
555
556 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 fn reference(
568 &self,
569 remote: &RemoteId,
570 reference: &Qualified,
571 ) -> Result<crate::git::raw::Reference<'_>, crate::git::raw::Error>;
572
573 fn commit(&self, oid: Oid) -> Result<crate::git::raw::Commit<'_>, crate::git::raw::Error>;
577
578 fn revwalk(&self, head: Oid) -> Result<crate::git::raw::Revwalk<'_>, crate::git::raw::Error>;
580
581 fn contains(&self, oid: Oid) -> Result<bool, crate::git::raw::Error>;
583
584 fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result<bool, crate::git::raw::Error>;
586
587 fn reference_oid(
589 &self,
590 remote: &RemoteId,
591 reference: &Qualified,
592 ) -> Result<Oid, crate::git::raw::Error>;
593
594 fn references_of(&self, remote: &RemoteId) -> Result<Refs, Error>;
596
597 fn references_glob(
603 &self,
604 pattern: &crate::git::fmt::refspec::PatternStr,
605 ) -> Result<Vec<(Qualified<'_>, Oid)>, crate::git::raw::Error>;
606
607 fn delegates(&self) -> Result<NonEmpty<Did>, RepositoryError> {
609 let doc = self.identity_doc()?;
610
611 Ok(doc.delegates().clone().into())
612 }
613
614 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 fn identity_doc_at(&self, head: Oid) -> Result<DocAt, DocError>;
624
625 fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, crate::git::raw::Error>;
627}
628
629pub trait RemoteRepository {
631 fn remote(&self, remote: &RemoteId) -> Result<Remote<Verified>, refs::Error>;
633
634 fn remotes(&self) -> Result<Remotes<Verified>, refs::Error>;
636
637 fn remote_refs_at(&self) -> Result<Vec<RefsAt>, refs::Error>;
639}
640
641pub trait ValidateRepository
642where
643 Self: RemoteRepository,
644{
645 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 fn validate_remote(&self, remote: &Remote<Verified>) -> Result<Validations, Error>;
659}
660
661pub trait WriteRepository: ReadRepository + SignRepository {
663 fn set_head(&self) -> Result<SetHead, RepositoryError>;
666 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 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 fn set_remote_identity_root_to(
682 &self,
683 remote: &RemoteId,
684 root: Oid,
685 ) -> Result<(), RepositoryError>;
686 fn set_identity_head_to(&self, commit: Oid) -> Result<(), RepositoryError>;
688 fn set_user(&self, info: &UserInfo) -> Result<(), Error>;
690 fn raw(&self) -> &crate::git::raw::Repository;
692}
693
694pub trait SignRepository {
696 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}