1#![allow(clippy::type_complexity)]
2pub mod branch;
114pub mod capability;
116pub mod commit;
118pub mod hybridstore;
120pub mod memoryrepo;
122#[cfg(feature = "object-store")]
123pub mod objectstore;
125pub mod pile;
127
128pub trait StorageClose {
134 type Error: std::error::Error;
136
137 fn close(self) -> Result<(), Self::Error>;
139}
140
141impl<Storage> Repository<Storage>
143where
144 Storage: BlobStore + BranchStore + StorageClose,
145{
146 pub fn close(self) -> Result<(), <Storage as StorageClose>::Error> {
153 self.storage.close()
154 }
155}
156
157use crate::macros::pattern;
158use std::collections::{HashSet, VecDeque};
159use std::convert::Infallible;
160use std::error::Error;
161use std::fmt::Debug;
162use std::fmt::{self};
163
164use commit::commit_metadata;
165use hifitime::Epoch;
166use itertools::Itertools;
167
168use crate::blob::encodings::simplearchive::UnarchiveError;
169use crate::blob::encodings::UnknownBlob;
170use crate::blob::Blob;
171use crate::blob::BlobEncoding;
172use crate::blob::MemoryBlobStore;
173use crate::blob::IntoBlob;
174use crate::blob::TryFromBlob;
175use crate::find;
176use crate::id::genid;
177use crate::id::Id;
178use crate::patch::Entry;
179use crate::patch::IdentitySchema;
180use crate::patch::PATCH;
181use crate::prelude::inlineencodings::GenId;
182use crate::repo::branch::branch_metadata;
183use crate::trible::TribleSet;
184use crate::inline::encodings::hash::Handle;
185use crate::inline::Inline;
186use crate::inline::InlineEncoding;
187use crate::inline::INLINE_LEN;
188use ed25519_dalek::SigningKey;
189
190use crate::blob::encodings::longstring::LongString;
191use crate::blob::encodings::simplearchive::SimpleArchive;
192use crate::blob::encodings::succinctarchive::SuccinctArchiveBlob;
193use crate::prelude::*;
194use crate::inline::encodings::ed25519 as ed;
195use crate::inline::encodings::shortstring::ShortString;
196
197attributes! {
198 "4DD4DDD05CC31734B03ABB4E43188B1F" as pub content: Handle<SimpleArchive>;
200 "88B59BD497540AC5AECDB7518E737C87" as pub metadata: Handle<SimpleArchive>;
202 "317044B612C690000D798CA660ECFD2A" as pub parent: Handle<SimpleArchive>;
204 "B59D147839100B6ED4B165DF76EDF3BB" as pub message: Handle<LongString>;
206 "12290C0BE0E9207E324F24DDE0D89300" as pub short_message: ShortString;
208 "272FBC56108F336C4D2E17289468C35F" as pub head: Handle<SimpleArchive>;
210 "8694CC73AF96A5E1C7635C677D1B928A" as pub branch: GenId;
212 "ADB4FFAD247C886848161297EFF5A05B" as pub signed_by: ed::ED25519PublicKey;
214 "9DF34F84959928F93A3C40AEB6E9E499" as pub signature_r: ed::ED25519RComponent;
216 "1ACE03BF70242B289FDF00E4327C3BC6" as pub signature_s: ed::ED25519SComponent;
218 "D7D14C6737AA27A51E1E08D380D13EF9" as pub rollup: Handle<SuccinctArchiveBlob>;
227}
228
229pub trait BlobStoreList {
231 type Iter<'a>: Iterator<Item = Result<Inline<Handle<UnknownBlob>>, Self::Err>>
233 where
234 Self: 'a;
235 type Err: Error + Debug + Send + Sync + 'static;
237
238 fn blobs<'a>(&'a self) -> Self::Iter<'a>;
240
241 fn blobs_diff<'a>(&'a self, _old: &Self) -> Self::Iter<'a> {
255 self.blobs()
256 }
257}
258
259#[derive(Debug, Clone)]
261pub struct BlobMetadata {
262 pub timestamp: u64,
264 pub length: u64,
266}
267
268pub trait BlobStoreMeta {
270 type MetaError: std::error::Error + Send + Sync + 'static;
272
273 fn metadata<S>(
276 &self,
277 handle: Inline<Handle<S>>,
278 ) -> Result<Option<BlobMetadata>, Self::MetaError>
279 where
280 S: BlobEncoding + 'static,
281 Handle<S>: InlineEncoding;
282}
283
284pub trait BlobStoreForget {
289 type ForgetError: std::error::Error + Send + Sync + 'static;
291
292 fn forget<S>(&mut self, handle: Inline<Handle<S>>) -> Result<(), Self::ForgetError>
294 where
295 S: BlobEncoding + 'static,
296 Handle<S>: InlineEncoding;
297}
298
299pub trait BlobStoreGet {
301 type GetError<E: std::error::Error + Send + Sync + 'static>: Error + Send + Sync + 'static;
303
304 fn get<T, S>(
313 &self,
314 handle: Inline<Handle<S>>,
315 ) -> Result<T, Self::GetError<<T as TryFromBlob<S>>::Error>>
316 where
317 S: BlobEncoding + 'static,
318 T: TryFromBlob<S>,
319 Handle<S>: InlineEncoding;
320}
321
322pub trait BlobStorePut {
324 type PutError: Error + Debug + Send + Sync + 'static;
326
327 fn put<S, T>(&mut self, item: T) -> Result<Inline<Handle<S>>, Self::PutError>
329 where
330 S: BlobEncoding + 'static,
331 T: IntoBlob<S>,
332 Handle<S>: InlineEncoding;
333}
334
335pub trait BlobStore: BlobStorePut {
340 type Reader: BlobStoreGet + BlobStoreList + Clone + Send + PartialEq + Eq + 'static;
342 type ReaderError: Error + Debug + Send + Sync + 'static;
344 fn reader(&mut self) -> Result<Self::Reader, Self::ReaderError>;
346}
347
348pub trait BlobStoreKeep {
350 fn keep<I>(&mut self, handles: I)
352 where
353 I: IntoIterator<Item = Inline<Handle<UnknownBlob>>>;
354}
355
356pub trait BlobChildren: BlobStoreGet {
366 fn children(
368 &self,
369 handle: Inline<Handle<UnknownBlob>>,
370 ) -> Vec<Inline<Handle<UnknownBlob>>> {
371 let Ok(blob) = self.get::<Blob<UnknownBlob>, UnknownBlob>(handle) else {
372 return Vec::new();
373 };
374 let bytes = blob.bytes.as_ref();
375 let mut result = Vec::new();
376 let mut offset = 0usize;
377 while offset + INLINE_LEN <= bytes.len() {
378 let mut raw = [0u8; INLINE_LEN];
379 raw.copy_from_slice(&bytes[offset..offset + INLINE_LEN]);
380 let candidate = Inline::<Handle<UnknownBlob>>::new(raw);
381 if self.get::<anybytes::Bytes, UnknownBlob>(candidate).is_ok() {
382 result.push(candidate);
383 }
384 offset += INLINE_LEN;
385 }
386 result
387 }
388}
389
390#[derive(Debug)]
396pub enum PushResult {
397 Success(),
399 Conflict(Option<Inline<Handle<SimpleArchive>>>),
402}
403
404pub trait BranchStore {
411 type BranchesError: Error + Debug + Send + Sync + 'static;
413 type HeadError: Error + Debug + Send + Sync + 'static;
415 type UpdateError: Error + Debug + Send + Sync + 'static;
417
418 type ListIter<'a>: Iterator<Item = Result<Id, Self::BranchesError>>
420 where
421 Self: 'a;
422
423 fn branches<'a>(&'a mut self) -> Result<Self::ListIter<'a>, Self::BranchesError>;
426
427 fn head(&mut self, id: Id) -> Result<Option<Inline<Handle<SimpleArchive>>>, Self::HeadError>;
443
444 fn update(
455 &mut self,
456 id: Id,
457 old: Option<Inline<Handle<SimpleArchive>>>,
458 new: Option<Inline<Handle<SimpleArchive>>>,
459 ) -> Result<PushResult, Self::UpdateError>;
460}
461
462#[derive(Debug)]
464pub enum TransferError<ListErr, LoadErr, StoreErr> {
465 List(ListErr),
467 Load(LoadErr),
469 Store(StoreErr),
471}
472
473impl<ListErr, LoadErr, StoreErr> fmt::Display for TransferError<ListErr, LoadErr, StoreErr> {
474 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
475 write!(f, "failed to transfer blob")
476 }
477}
478
479impl<ListErr, LoadErr, StoreErr> Error for TransferError<ListErr, LoadErr, StoreErr>
480where
481 ListErr: Debug + Error + 'static,
482 LoadErr: Debug + Error + 'static,
483 StoreErr: Debug + Error + 'static,
484{
485 fn source(&self) -> Option<&(dyn Error + 'static)> {
486 match self {
487 Self::List(e) => Some(e),
488 Self::Load(e) => Some(e),
489 Self::Store(e) => Some(e),
490 }
491 }
492}
493
494pub fn transfer<'a, BS, BT, Handles>(
496 source: &'a BS,
497 target: &'a mut BT,
498 handles: Handles,
499) -> impl Iterator<
500 Item = Result<
501 (
502 Inline<Handle<UnknownBlob>>,
503 Inline<Handle<UnknownBlob>>,
504 ),
505 TransferError<
506 Infallible,
507 <BS as BlobStoreGet>::GetError<Infallible>,
508 <BT as BlobStorePut>::PutError,
509 >,
510 >,
511> + 'a
512where
513 BS: BlobStoreGet + 'a,
514 BT: BlobStorePut + 'a,
515 Handles: IntoIterator<Item = Inline<Handle<UnknownBlob>>> + 'a,
516 Handles::IntoIter: 'a,
517{
518 handles.into_iter().map(move |source_handle| {
519 let blob: Blob<UnknownBlob> = source.get(source_handle).map_err(TransferError::Load)?;
520
521 Ok((
522 source_handle,
523 (target.put(blob).map_err(TransferError::Store)?),
524 ))
525 })
526}
527
528pub struct ReachableHandles<'a, BS>
533where
534 BS: BlobChildren,
535{
536 source: &'a BS,
537 queue: VecDeque<Inline<Handle<UnknownBlob>>>,
538 visited: HashSet<[u8; INLINE_LEN]>,
539}
540
541impl<'a, BS> ReachableHandles<'a, BS>
542where
543 BS: BlobChildren,
544{
545 fn new(source: &'a BS, roots: impl IntoIterator<Item = Inline<Handle<UnknownBlob>>>) -> Self {
546 let mut queue = VecDeque::new();
547 for handle in roots {
548 queue.push_back(handle);
549 }
550
551 Self {
552 source,
553 queue,
554 visited: HashSet::new(),
555 }
556 }
557}
558
559impl<'a, BS> Iterator for ReachableHandles<'a, BS>
560where
561 BS: BlobChildren,
562{
563 type Item = Inline<Handle<UnknownBlob>>;
564
565 fn next(&mut self) -> Option<Self::Item> {
566 while let Some(handle) = self.queue.pop_front() {
567 let raw = handle.raw;
568
569 if !self.visited.insert(raw) {
570 continue;
571 }
572
573 for child in self.source.children(handle) {
576 if !self.visited.contains(&child.raw) {
577 self.queue.push_back(child);
578 }
579 }
580
581 return Some(handle);
582 }
583
584 None
585 }
586}
587
588pub fn reachable<'a, BS>(
593 source: &'a BS,
594 roots: impl IntoIterator<Item = Inline<Handle<UnknownBlob>>>,
595) -> ReachableHandles<'a, BS>
596where
597 BS: BlobChildren,
598{
599 ReachableHandles::new(source, roots)
600}
601
602pub fn potential_handles<'a>(
609 set: &'a TribleSet,
610) -> impl Iterator<Item = Inline<Handle<UnknownBlob>>> + 'a {
611 set.vae.iter().map(|raw| {
612 let mut value = [0u8; INLINE_LEN];
613 value.copy_from_slice(&raw[0..INLINE_LEN]);
614 Inline::<Handle<UnknownBlob>>::new(value)
615 })
616}
617
618#[derive(Debug)]
621pub enum CreateCommitError<BlobErr: Error + Debug + Send + Sync + 'static> {
622 ContentStorageError(BlobErr),
624 CommitStorageError(BlobErr),
626}
627
628impl<BlobErr: Error + Debug + Send + Sync + 'static> fmt::Display for CreateCommitError<BlobErr> {
629 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
630 match self {
631 CreateCommitError::ContentStorageError(e) => write!(f, "Content storage failed: {e}"),
632 CreateCommitError::CommitStorageError(e) => {
633 write!(f, "Commit metadata storage failed: {e}")
634 }
635 }
636 }
637}
638
639impl<BlobErr: Error + Debug + Send + Sync + 'static> Error for CreateCommitError<BlobErr> {
640 fn source(&self) -> Option<&(dyn Error + 'static)> {
641 match self {
642 CreateCommitError::ContentStorageError(e) => Some(e),
643 CreateCommitError::CommitStorageError(e) => Some(e),
644 }
645 }
646}
647
648#[derive(Debug)]
650pub enum MergeError {
651 DifferentRepos(),
653}
654
655#[derive(Debug)]
658pub enum RollupError<Storage: BranchStore + BlobStore> {
659 UnknownBranch,
661 EmptyBranch,
663 HeadAdvanced,
667 Push(PushError<Storage>),
669 Pull(PullError<Storage::HeadError,
671 <Storage as BlobStore>::ReaderError,
672 <<Storage as BlobStore>::Reader as BlobStoreGet>::GetError<UnarchiveError>>),
673 Checkout(WorkspaceCheckoutError<
675 <<Storage as BlobStore>::Reader as BlobStoreGet>::GetError<UnarchiveError>>),
676}
677
678#[derive(Debug)]
679pub enum PushError<Storage: BranchStore + BlobStore> {
680 StorageBranches(Storage::BranchesError),
682 StorageReader(<Storage as BlobStore>::ReaderError),
684 StorageGet(
686 <<Storage as BlobStore>::Reader as BlobStoreGet>::GetError<UnarchiveError>,
687 ),
688 StoragePut(<Storage as BlobStorePut>::PutError),
690 BranchUpdate(Storage::UpdateError),
692 BadBranchMetadata(),
694 MergeError(MergeError),
696}
697
698impl<Storage> From<MergeError> for PushError<Storage>
703where
704 Storage: BranchStore + BlobStore,
705{
706 fn from(e: MergeError) -> Self {
707 PushError::MergeError(e)
708 }
709}
710
711#[derive(Debug)]
719pub enum BranchError<Storage>
720where
721 Storage: BranchStore + BlobStore,
722{
723 StorageReader(<Storage as BlobStore>::ReaderError),
725 StorageGet(
727 <<Storage as BlobStore>::Reader as BlobStoreGet>::GetError<UnarchiveError>,
728 ),
729 StoragePut(<Storage as BlobStorePut>::PutError),
731 BranchHead(Storage::HeadError),
733 BranchUpdate(Storage::UpdateError),
735 AlreadyExists(),
737 BranchNotFound(Id),
739}
740
741#[derive(Debug)]
743pub enum LookupError<Storage>
744where
745 Storage: BranchStore + BlobStore,
746{
747 StorageBranches(Storage::BranchesError),
749 BranchHead(Storage::HeadError),
751 StorageReader(<Storage as BlobStore>::ReaderError),
753 StorageGet(
755 <<Storage as BlobStore>::Reader as BlobStoreGet>::GetError<UnarchiveError>,
756 ),
757 NameConflict(Vec<Id>),
759 BadBranchMetadata(),
761}
762
763#[derive(Debug)]
765pub enum EnsureBranchError<Storage>
766where
767 Storage: BranchStore + BlobStore,
768{
769 Lookup(LookupError<Storage>),
771 Create(BranchError<Storage>),
773}
774
775pub struct Repository<Storage: BlobStore + BranchStore> {
782 storage: Storage,
783 signing_key: SigningKey,
784 commit_metadata: MetadataHandle,
785}
786
787pub enum PullError<BranchStorageErr, BlobReaderErr, BlobStorageErr>
789where
790 BranchStorageErr: Error,
791 BlobReaderErr: Error,
792 BlobStorageErr: Error,
793{
794 BranchNotFound(Id),
796 BranchStorage(BranchStorageErr),
798 BlobReader(BlobReaderErr),
800 BlobStorage(BlobStorageErr),
802 BadBranchMetadata(),
804}
805
806impl<B, R, C> fmt::Debug for PullError<B, R, C>
807where
808 B: Error + fmt::Debug,
809 R: Error + fmt::Debug,
810 C: Error + fmt::Debug,
811{
812 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
813 match self {
814 PullError::BranchNotFound(id) => f.debug_tuple("BranchNotFound").field(id).finish(),
815 PullError::BranchStorage(e) => f.debug_tuple("BranchStorage").field(e).finish(),
816 PullError::BlobReader(e) => f.debug_tuple("BlobReader").field(e).finish(),
817 PullError::BlobStorage(e) => f.debug_tuple("BlobStorage").field(e).finish(),
818 PullError::BadBranchMetadata() => f.debug_tuple("BadBranchMetadata").finish(),
819 }
820 }
821}
822
823impl<Storage> Repository<Storage>
824where
825 Storage: BlobStore + BranchStore,
826{
827 pub fn new(
832 mut storage: Storage,
833 signing_key: SigningKey,
834 commit_metadata: TribleSet,
835 ) -> Result<Self, <Storage as BlobStorePut>::PutError> {
836 let commit_metadata = storage.put(commit_metadata)?;
837 Ok(Self {
838 storage,
839 signing_key,
840 commit_metadata,
841 })
842 }
843
844 pub fn into_storage(self) -> Storage {
850 self.storage
851 }
852
853 pub fn storage(&self) -> &Storage {
855 &self.storage
856 }
857
858 pub fn storage_mut(&mut self) -> &mut Storage {
860 &mut self.storage
861 }
862
863 pub fn set_signing_key(&mut self, signing_key: SigningKey) {
865 self.signing_key = signing_key;
866 }
867
868 pub fn commit_metadata(&self) -> MetadataHandle {
870 self.commit_metadata
871 }
872
873 pub fn create_branch(
887 &mut self,
888 branch_name: &str,
889 commit: Option<CommitHandle>,
890 ) -> Result<ExclusiveId, BranchError<Storage>> {
891 self.create_branch_with_key(branch_name, commit, self.signing_key.clone())
892 }
893
894 pub fn create_branch_with_key(
896 &mut self,
897 branch_name: &str,
898 commit: Option<CommitHandle>,
899 signing_key: SigningKey,
900 ) -> Result<ExclusiveId, BranchError<Storage>> {
901 let branch_id = genid();
902 let name_blob: Blob<LongString> = branch_name.to_owned().to_blob();
903 let name_handle = name_blob.get_handle();
904 self.storage
905 .put::<LongString, _>(name_blob)
906 .map_err(|e| BranchError::StoragePut(e))?;
907
908 let branch_set = if let Some(commit) = commit {
909 let reader = self
910 .storage
911 .reader()
912 .map_err(|e| BranchError::StorageReader(e))?;
913 let set: TribleSet = reader.get(commit).map_err(|e| BranchError::StorageGet(e))?;
914
915 branch::branch_metadata(
916 &signing_key,
917 *branch_id,
918 name_handle,
919 Some(set.to_blob()),
920 None,
921 )
922 } else {
923 branch::branch_unsigned(*branch_id, name_handle, None, None)
924 };
925
926 let branch_blob = branch_set.to_blob();
927 let branch_handle = self
928 .storage
929 .put(branch_blob)
930 .map_err(|e| BranchError::StoragePut(e))?;
931 let push_result = self
932 .storage
933 .update(*branch_id, None, Some(branch_handle))
934 .map_err(|e| BranchError::BranchUpdate(e))?;
935
936 match push_result {
937 PushResult::Success() => Ok(branch_id),
938 PushResult::Conflict(_) => Err(BranchError::AlreadyExists()),
939 }
940 }
941
942 pub fn lookup_branch(&mut self, name: &str) -> Result<Option<Id>, LookupError<Storage>> {
948 let branch_ids: Vec<Id> = self
949 .storage
950 .branches()
951 .map_err(LookupError::StorageBranches)?
952 .collect::<Result<Vec<_>, _>>()
953 .map_err(LookupError::StorageBranches)?;
954
955 let mut matches = Vec::new();
956
957 for branch_id in branch_ids {
958 let Some(meta_handle) = self
959 .storage
960 .head(branch_id)
961 .map_err(LookupError::BranchHead)?
962 else {
963 continue;
964 };
965
966 let reader = self.storage.reader().map_err(LookupError::StorageReader)?;
967 let meta_set: TribleSet = reader.get(meta_handle).map_err(LookupError::StorageGet)?;
968
969 let Ok((name_handle,)) = find!(
970 (n: Inline<Handle<LongString>>),
971 pattern!(&meta_set, [{ crate::metadata::name: ?n }])
972 )
973 .exactly_one() else {
974 continue;
975 };
976
977 let Ok(branch_name): Result<anybytes::View<str>, _> = reader.get(name_handle) else {
978 continue;
979 };
980
981 if branch_name.as_ref() == name {
982 matches.push(branch_id);
983 }
984 }
985
986 match matches.len() {
987 0 => Ok(None),
988 1 => Ok(Some(matches[0])),
989 _ => Err(LookupError::NameConflict(matches)),
990 }
991 }
992
993 pub fn ensure_branch(
1001 &mut self,
1002 name: &str,
1003 commit: Option<CommitHandle>,
1004 ) -> Result<Id, EnsureBranchError<Storage>> {
1005 match self
1006 .lookup_branch(name)
1007 .map_err(EnsureBranchError::Lookup)?
1008 {
1009 Some(id) => Ok(id),
1010 None => {
1011 let id = self
1012 .create_branch(name, commit)
1013 .map_err(EnsureBranchError::Create)?;
1014 Ok(*id)
1015 }
1016 }
1017 }
1018
1019 pub fn pull(
1022 &mut self,
1023 branch_id: Id,
1024 ) -> Result<
1025 Workspace<Storage>,
1026 PullError<
1027 Storage::HeadError,
1028 Storage::ReaderError,
1029 <Storage::Reader as BlobStoreGet>::GetError<UnarchiveError>,
1030 >,
1031 > {
1032 self.pull_with_key(branch_id, self.signing_key.clone())
1033 }
1034
1035 pub fn pull_with_key(
1037 &mut self,
1038 branch_id: Id,
1039 signing_key: SigningKey,
1040 ) -> Result<
1041 Workspace<Storage>,
1042 PullError<
1043 Storage::HeadError,
1044 Storage::ReaderError,
1045 <Storage::Reader as BlobStoreGet>::GetError<UnarchiveError>,
1046 >,
1047 > {
1048 let base_branch_meta_handle = match self.storage.head(branch_id) {
1050 Ok(Some(handle)) => handle,
1051 Ok(None) => return Err(PullError::BranchNotFound(branch_id)),
1052 Err(e) => return Err(PullError::BranchStorage(e)),
1053 };
1054 let reader = self.storage.reader().map_err(PullError::BlobReader)?;
1056 let base_branch_meta: TribleSet = match reader.get(base_branch_meta_handle) {
1057 Ok(meta_set) => meta_set,
1058 Err(e) => return Err(PullError::BlobStorage(e)),
1059 };
1060
1061 let head_ = match find!(
1062 (head_: Inline<_>),
1063 pattern!(&base_branch_meta, [{ head: ?head_ }])
1064 )
1065 .at_most_one()
1066 {
1067 Ok(Some((h,))) => Some(h),
1068 Ok(None) => None,
1069 Err(_) => return Err(PullError::BadBranchMetadata()),
1070 };
1071 let base_blobs = self.storage.reader().map_err(PullError::BlobReader)?;
1073 Ok(Workspace {
1074 base_blobs,
1075 staged: MemoryBlobStore::new(),
1076 head: head_,
1077 base_head: head_,
1078 base_branch_id: branch_id,
1079 base_branch_meta: base_branch_meta_handle,
1080 signing_key,
1081 commit_metadata: self.commit_metadata,
1082 })
1083 }
1084
1085 pub fn push(&mut self, workspace: &mut Workspace<Storage>) -> Result<(), PushError<Storage>> {
1089 while let Some(mut conflict_ws) = self.try_push(workspace)? {
1094 conflict_ws.merge(workspace)?;
1098
1099 *workspace = conflict_ws;
1104 }
1105
1106 Ok(())
1107 }
1108
1109 pub fn try_push(
1113 &mut self,
1114 workspace: &mut Workspace<Storage>,
1115 ) -> Result<Option<Workspace<Storage>>, PushError<Storage>> {
1116 let workspace_reader = workspace.staged.reader().unwrap();
1118 for handle in workspace_reader.blobs() {
1119 let handle = handle.expect("infallible blob enumeration");
1120 let blob: Blob<UnknownBlob> =
1121 workspace_reader.get(handle).expect("infallible blob read");
1122 self.storage
1123 .put::<UnknownBlob, _>(blob)
1124 .map_err(PushError::StoragePut)?;
1125 }
1126
1127 if workspace.base_head == workspace.head {
1132 return Ok(None);
1133 }
1134
1135 let repo_reader = self.storage.reader().map_err(PushError::StorageReader)?;
1137 let base_branch_meta: TribleSet = repo_reader
1138 .get(workspace.base_branch_meta)
1139 .map_err(PushError::StorageGet)?;
1140
1141 let Ok((branch_name,)) = find!(
1142 (name: Inline<Handle<LongString>>),
1143 pattern!(base_branch_meta, [{ crate::metadata::name: ?name }])
1144 )
1145 .exactly_one() else {
1146 return Err(PushError::BadBranchMetadata());
1147 };
1148
1149 let head_handle = workspace.head.ok_or(PushError::BadBranchMetadata())?;
1150 let head_: TribleSet = repo_reader
1151 .get(head_handle)
1152 .map_err(PushError::StorageGet)?;
1153
1154 let branch_meta = branch_metadata(
1155 &workspace.signing_key,
1156 workspace.base_branch_id,
1157 branch_name,
1158 Some(head_.to_blob()),
1159 None,
1163 );
1164
1165 let branch_meta_handle = self
1166 .storage
1167 .put(branch_meta)
1168 .map_err(PushError::StoragePut)?;
1169
1170 let result = self
1172 .storage
1173 .update(
1174 workspace.base_branch_id,
1175 Some(workspace.base_branch_meta),
1176 Some(branch_meta_handle),
1177 )
1178 .map_err(PushError::BranchUpdate)?;
1179
1180 match result {
1181 PushResult::Success() => {
1182 workspace.base_branch_meta = branch_meta_handle;
1185 workspace.base_head = workspace.head;
1186 workspace.base_blobs = self.storage.reader().map_err(PushError::StorageReader)?;
1189 workspace.staged = MemoryBlobStore::new();
1193 Ok(None)
1194 }
1195 PushResult::Conflict(conflicting_meta) => {
1196 let conflicting_meta = conflicting_meta.ok_or(PushError::BadBranchMetadata())?;
1197
1198 let repo_reader = self.storage.reader().map_err(PushError::StorageReader)?;
1199 let branch_meta: TribleSet = repo_reader
1200 .get(conflicting_meta)
1201 .map_err(PushError::StorageGet)?;
1202
1203 let head_ = match find!((head_: Inline<_>),
1204 pattern!(&branch_meta, [{ head: ?head_ }])
1205 )
1206 .at_most_one()
1207 {
1208 Ok(Some((h,))) => Some(h),
1209 Ok(None) => None,
1210 Err(_) => return Err(PushError::BadBranchMetadata()),
1211 };
1212
1213 let conflict_ws = Workspace {
1214 base_blobs: self.storage.reader().map_err(PushError::StorageReader)?,
1215 staged: MemoryBlobStore::new(),
1216 head: head_,
1217 base_head: head_,
1218 base_branch_id: workspace.base_branch_id,
1219 base_branch_meta: conflicting_meta,
1220 signing_key: workspace.signing_key.clone(),
1221 commit_metadata: workspace.commit_metadata,
1222 };
1223
1224 Ok(Some(conflict_ws))
1225 }
1226 }
1227 }
1228
1229 pub fn compute_rollup(
1246 &mut self,
1247 branch_id: Id,
1248 ) -> Result<
1249 Inline<Handle<crate::blob::encodings::succinctarchive::SuccinctArchiveBlob>>,
1250 RollupError<Storage>,
1251 > {
1252 use crate::blob::encodings::succinctarchive::{OrderedUniverse, SuccinctArchive};
1253 use crate::blob::IntoBlob;
1254
1255 let mut ws = self.pull(branch_id).map_err(RollupError::Pull)?;
1256 let head_handle = ws.head().ok_or(RollupError::EmptyBranch)?;
1257
1258 let space = ws.checkout(..).map_err(RollupError::Checkout)?;
1261 let archive: SuccinctArchive<OrderedUniverse> = (&*space).into();
1262 drop(space);
1263
1264 let archive_blob = (&archive).to_blob();
1267 let handle: Inline<
1268 Handle<crate::blob::encodings::succinctarchive::SuccinctArchiveBlob>,
1269 > = self
1270 .storage
1271 .put(archive_blob)
1272 .map_err(|e| RollupError::Push(PushError::StoragePut(e)))?;
1273
1274 let reader = self
1277 .storage
1278 .reader()
1279 .map_err(|e| RollupError::Push(PushError::StorageReader(e)))?;
1280 let base_meta: TribleSet = reader
1281 .get(ws.base_branch_meta)
1282 .map_err(|e| RollupError::Push(PushError::StorageGet(e)))?;
1283 let (branch_name,) = find!(
1284 (name: Inline<Handle<LongString>>),
1285 pattern!(&base_meta, [{ crate::metadata::name: ?name }])
1286 )
1287 .exactly_one()
1288 .map_err(|_| RollupError::Push(PushError::BadBranchMetadata()))?;
1289 let head_blob: TribleSet = reader
1290 .get(head_handle)
1291 .map_err(|e| RollupError::Push(PushError::StorageGet(e)))?;
1292
1293 let new_meta = branch::branch_metadata(
1294 &ws.signing_key,
1295 branch_id,
1296 branch_name,
1297 Some(head_blob.to_blob()),
1298 Some(handle),
1299 );
1300 let new_meta_handle = self
1301 .storage
1302 .put(new_meta)
1303 .map_err(|e| RollupError::Push(PushError::StoragePut(e)))?;
1304
1305 let update_result = self
1309 .storage
1310 .update(branch_id, Some(ws.base_branch_meta), Some(new_meta_handle))
1311 .map_err(|e| RollupError::Push(PushError::BranchUpdate(e)))?;
1312 match update_result {
1313 PushResult::Success() => Ok(handle),
1314 PushResult::Conflict(_) => Err(RollupError::HeadAdvanced),
1315 }
1316 }
1317}
1318
1319pub type CommitHandle = Inline<Handle<SimpleArchive>>;
1321type MetadataHandle = Inline<Handle<SimpleArchive>>;
1322pub type CommitSet = PATCH<INLINE_LEN, IdentitySchema, ()>;
1324type BranchMetaHandle = Inline<Handle<SimpleArchive>>;
1325
1326#[derive(Debug, Clone)]
1351pub struct Checkout {
1352 facts: TribleSet,
1353 commits: CommitSet,
1354}
1355
1356impl PartialEq<TribleSet> for Checkout {
1357 fn eq(&self, other: &TribleSet) -> bool {
1358 self.facts == *other
1359 }
1360}
1361
1362impl PartialEq<Checkout> for TribleSet {
1363 fn eq(&self, other: &Checkout) -> bool {
1364 *self == other.facts
1365 }
1366}
1367
1368impl Checkout {
1369 pub fn facts(&self) -> &TribleSet {
1371 &self.facts
1372 }
1373
1374 pub fn commits(&self) -> CommitSet {
1378 self.commits.clone()
1379 }
1380
1381 pub fn into_facts(self) -> TribleSet {
1383 self.facts
1384 }
1385}
1386
1387impl std::ops::Deref for Checkout {
1388 type Target = TribleSet;
1389 fn deref(&self) -> &TribleSet {
1390 &self.facts
1391 }
1392}
1393
1394impl std::ops::AddAssign<&Checkout> for Checkout {
1395 fn add_assign(&mut self, rhs: &Checkout) {
1396 self.facts += rhs.facts.clone();
1397 self.commits.union(rhs.commits.clone());
1398 }
1399}
1400
1401impl std::ops::Add for Checkout {
1402 type Output = Self;
1403 fn add(mut self, rhs: Self) -> Self {
1404 self.facts += rhs.facts;
1405 self.commits.union(rhs.commits);
1406 self
1407 }
1408}
1409
1410impl std::ops::Add<&Checkout> for Checkout {
1411 type Output = Self;
1412 fn add(mut self, rhs: &Checkout) -> Self {
1413 self += rhs;
1414 self
1415 }
1416}
1417
1418pub struct Workspace<Blobs: BlobStore> {
1422 pub staged: MemoryBlobStore,
1428 base_blobs: Blobs::Reader,
1430 base_branch_id: Id,
1432 base_branch_meta: BranchMetaHandle,
1434 head: Option<CommitHandle>,
1436 base_head: Option<CommitHandle>,
1442 signing_key: SigningKey,
1444 commit_metadata: MetadataHandle,
1446}
1447
1448impl<Blobs> fmt::Debug for Workspace<Blobs>
1449where
1450 Blobs: BlobStore,
1451 Blobs::Reader: fmt::Debug,
1452{
1453 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1454 f.debug_struct("Workspace")
1455 .field("staged", &self.staged)
1456 .field("base_blobs", &self.base_blobs)
1457 .field("base_branch_id", &self.base_branch_id)
1458 .field("base_branch_meta", &self.base_branch_meta)
1459 .field("base_head", &self.base_head)
1460 .field("head", &self.head)
1461 .field("commit_metadata", &self.commit_metadata)
1462 .finish()
1463 }
1464}
1465
1466pub trait CommitSelector<Blobs: BlobStore> {
1468 fn select(
1469 self,
1470 ws: &mut Workspace<Blobs>,
1471 ) -> Result<
1472 CommitSet,
1473 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1474 >;
1475}
1476
1477pub struct Ancestors<S>(pub S);
1479
1480pub fn ancestors<S>(selector: S) -> Ancestors<S> {
1482 Ancestors(selector)
1483}
1484
1485pub struct NthAncestors<S>(pub S, pub usize);
1493
1494pub fn nth_ancestors<S>(selector: S, n: usize) -> NthAncestors<S> {
1496 NthAncestors(selector, n)
1497}
1498
1499pub struct Parents<S>(pub S);
1501
1502pub fn parents<S>(selector: S) -> Parents<S> {
1504 Parents(selector)
1505}
1506
1507pub struct SymmetricDiff<A, B>(pub A, pub B);
1510
1511pub fn symmetric_diff<A, B>(a: A, b: B) -> SymmetricDiff<A, B> {
1513 SymmetricDiff(a, b)
1514}
1515
1516pub struct Union<A, B> {
1518 left: A,
1519 right: B,
1520}
1521
1522pub fn union<A, B>(left: A, right: B) -> Union<A, B> {
1524 Union { left, right }
1525}
1526
1527pub struct Intersect<A, B> {
1529 left: A,
1530 right: B,
1531}
1532
1533pub fn intersect<A, B>(left: A, right: B) -> Intersect<A, B> {
1535 Intersect { left, right }
1536}
1537
1538pub struct Difference<A, B> {
1541 left: A,
1542 right: B,
1543}
1544
1545pub fn difference<A, B>(left: A, right: B) -> Difference<A, B> {
1547 Difference { left, right }
1548}
1549
1550pub struct TimeRange(pub Epoch, pub Epoch);
1552
1553pub fn time_range(start: Epoch, end: Epoch) -> TimeRange {
1555 TimeRange(start, end)
1556}
1557
1558pub struct Filter<S, F> {
1560 selector: S,
1561 filter: F,
1562}
1563
1564pub fn filter<S, F>(selector: S, filter: F) -> Filter<S, F> {
1566 Filter { selector, filter }
1567}
1568
1569impl<Blobs> CommitSelector<Blobs> for CommitHandle
1570where
1571 Blobs: BlobStore,
1572{
1573 fn select(
1574 self,
1575 _ws: &mut Workspace<Blobs>,
1576 ) -> Result<
1577 CommitSet,
1578 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1579 > {
1580 let mut patch = CommitSet::new();
1581 patch.insert(&Entry::new(&self.raw));
1582 Ok(patch)
1583 }
1584}
1585
1586impl<Blobs> CommitSelector<Blobs> for CommitSet
1587where
1588 Blobs: BlobStore,
1589{
1590 fn select(
1591 self,
1592 _ws: &mut Workspace<Blobs>,
1593 ) -> Result<
1594 CommitSet,
1595 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1596 > {
1597 Ok(self)
1598 }
1599}
1600
1601impl<Blobs> CommitSelector<Blobs> for Vec<CommitHandle>
1602where
1603 Blobs: BlobStore,
1604{
1605 fn select(
1606 self,
1607 _ws: &mut Workspace<Blobs>,
1608 ) -> Result<
1609 CommitSet,
1610 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1611 > {
1612 let mut patch = CommitSet::new();
1613 for handle in self {
1614 patch.insert(&Entry::new(&handle.raw));
1615 }
1616 Ok(patch)
1617 }
1618}
1619
1620impl<Blobs> CommitSelector<Blobs> for &[CommitHandle]
1621where
1622 Blobs: BlobStore,
1623{
1624 fn select(
1625 self,
1626 _ws: &mut Workspace<Blobs>,
1627 ) -> Result<
1628 CommitSet,
1629 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1630 > {
1631 let mut patch = CommitSet::new();
1632 for handle in self {
1633 patch.insert(&Entry::new(&handle.raw));
1634 }
1635 Ok(patch)
1636 }
1637}
1638
1639impl<Blobs> CommitSelector<Blobs> for Option<CommitHandle>
1640where
1641 Blobs: BlobStore,
1642{
1643 fn select(
1644 self,
1645 _ws: &mut Workspace<Blobs>,
1646 ) -> Result<
1647 CommitSet,
1648 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1649 > {
1650 let mut patch = CommitSet::new();
1651 if let Some(handle) = self {
1652 patch.insert(&Entry::new(&handle.raw));
1653 }
1654 Ok(patch)
1655 }
1656}
1657
1658impl<S, Blobs> CommitSelector<Blobs> for Ancestors<S>
1659where
1660 S: CommitSelector<Blobs>,
1661 Blobs: BlobStore,
1662{
1663 fn select(
1664 self,
1665 ws: &mut Workspace<Blobs>,
1666 ) -> Result<
1667 CommitSet,
1668 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1669 > {
1670 let seeds = self.0.select(ws)?;
1671 collect_reachable_from_patch(ws, seeds)
1672 }
1673}
1674
1675impl<Blobs, S> CommitSelector<Blobs> for NthAncestors<S>
1676where
1677 Blobs: BlobStore,
1678 S: CommitSelector<Blobs>,
1679{
1680 fn select(
1681 self,
1682 ws: &mut Workspace<Blobs>,
1683 ) -> Result<
1684 CommitSet,
1685 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1686 > {
1687 let mut frontier = self.0.select(ws)?;
1688 let mut remaining = self.1;
1689
1690 while remaining > 0 && !frontier.is_empty() {
1691 let keys: Vec<[u8; INLINE_LEN]> = frontier.iter().copied().collect();
1693 let mut next_frontier = CommitSet::new();
1694 for raw in keys {
1695 let handle = CommitHandle::new(raw);
1696 let meta: TribleSet = ws.get(handle).map_err(WorkspaceCheckoutError::Storage)?;
1697 for (p,) in find!((p: Inline<_>), pattern!(&meta, [{ parent: ?p }])) {
1698 next_frontier.insert(&Entry::new(&p.raw));
1699 }
1700 }
1701 frontier = next_frontier;
1702 remaining -= 1;
1703 }
1704
1705 Ok(frontier)
1706 }
1707}
1708
1709impl<S, Blobs> CommitSelector<Blobs> for Parents<S>
1710where
1711 S: CommitSelector<Blobs>,
1712 Blobs: BlobStore,
1713{
1714 fn select(
1715 self,
1716 ws: &mut Workspace<Blobs>,
1717 ) -> Result<
1718 CommitSet,
1719 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1720 > {
1721 let seeds = self.0.select(ws)?;
1722 let mut result = CommitSet::new();
1723 for raw in seeds.iter() {
1724 let handle = Inline::new(*raw);
1725 let meta: TribleSet = ws.get(handle).map_err(WorkspaceCheckoutError::Storage)?;
1726 for (p,) in find!((p: Inline<_>), pattern!(&meta, [{ parent: ?p }])) {
1727 result.insert(&Entry::new(&p.raw));
1728 }
1729 }
1730 Ok(result)
1731 }
1732}
1733
1734impl<A, B, Blobs> CommitSelector<Blobs> for SymmetricDiff<A, B>
1735where
1736 A: CommitSelector<Blobs>,
1737 B: CommitSelector<Blobs>,
1738 Blobs: BlobStore,
1739{
1740 fn select(
1741 self,
1742 ws: &mut Workspace<Blobs>,
1743 ) -> Result<
1744 CommitSet,
1745 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1746 > {
1747 let seeds_a = self.0.select(ws)?;
1748 let seeds_b = self.1.select(ws)?;
1749 let a = collect_reachable_from_patch(ws, seeds_a)?;
1750 let b = collect_reachable_from_patch(ws, seeds_b)?;
1751 let inter = a.intersect(&b);
1752 let mut union = a;
1753 union.union(b);
1754 Ok(union.difference(&inter))
1755 }
1756}
1757
1758impl<A, B, Blobs> CommitSelector<Blobs> for Union<A, B>
1759where
1760 A: CommitSelector<Blobs>,
1761 B: CommitSelector<Blobs>,
1762 Blobs: BlobStore,
1763{
1764 fn select(
1765 self,
1766 ws: &mut Workspace<Blobs>,
1767 ) -> Result<
1768 CommitSet,
1769 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1770 > {
1771 let mut left = self.left.select(ws)?;
1772 let right = self.right.select(ws)?;
1773 left.union(right);
1774 Ok(left)
1775 }
1776}
1777
1778impl<A, B, Blobs> CommitSelector<Blobs> for Intersect<A, B>
1779where
1780 A: CommitSelector<Blobs>,
1781 B: CommitSelector<Blobs>,
1782 Blobs: BlobStore,
1783{
1784 fn select(
1785 self,
1786 ws: &mut Workspace<Blobs>,
1787 ) -> Result<
1788 CommitSet,
1789 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1790 > {
1791 let left = self.left.select(ws)?;
1792 let right = self.right.select(ws)?;
1793 Ok(left.intersect(&right))
1794 }
1795}
1796
1797impl<A, B, Blobs> CommitSelector<Blobs> for Difference<A, B>
1798where
1799 A: CommitSelector<Blobs>,
1800 B: CommitSelector<Blobs>,
1801 Blobs: BlobStore,
1802{
1803 fn select(
1804 self,
1805 ws: &mut Workspace<Blobs>,
1806 ) -> Result<
1807 CommitSet,
1808 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1809 > {
1810 let left = self.left.select(ws)?;
1811 let right = self.right.select(ws)?;
1812 Ok(left.difference(&right))
1813 }
1814}
1815
1816impl<S, F, Blobs> CommitSelector<Blobs> for Filter<S, F>
1817where
1818 Blobs: BlobStore,
1819 S: CommitSelector<Blobs>,
1820 F: for<'x, 'y> Fn(&'x TribleSet, &'y TribleSet) -> bool,
1821{
1822 fn select(
1823 self,
1824 ws: &mut Workspace<Blobs>,
1825 ) -> Result<
1826 CommitSet,
1827 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1828 > {
1829 let patch = self.selector.select(ws)?;
1830 let mut result = CommitSet::new();
1831 let filter = self.filter;
1832 for raw in patch.iter() {
1833 let handle = Inline::new(*raw);
1834 let meta: TribleSet = ws.get(handle).map_err(WorkspaceCheckoutError::Storage)?;
1835
1836 let Ok((content_handle,)) = find!(
1837 (c: Inline<_>),
1838 pattern!(&meta, [{ content: ?c }])
1839 )
1840 .exactly_one() else {
1841 return Err(WorkspaceCheckoutError::BadCommitMetadata());
1842 };
1843
1844 let payload: TribleSet = ws
1845 .get(content_handle)
1846 .map_err(WorkspaceCheckoutError::Storage)?;
1847
1848 if filter(&meta, &payload) {
1849 result.insert(&Entry::new(raw));
1850 }
1851 }
1852 Ok(result)
1853 }
1854}
1855
1856pub struct HistoryOf(pub Id);
1858
1859pub fn history_of(entity: Id) -> HistoryOf {
1861 HistoryOf(entity)
1862}
1863
1864impl<Blobs> CommitSelector<Blobs> for HistoryOf
1865where
1866 Blobs: BlobStore,
1867{
1868 fn select(
1869 self,
1870 ws: &mut Workspace<Blobs>,
1871 ) -> Result<
1872 CommitSet,
1873 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1874 > {
1875 let Some(head_) = ws.head else {
1876 return Ok(CommitSet::new());
1877 };
1878 let entity = self.0;
1879 filter(
1880 ancestors(head_),
1881 move |_: &TribleSet, payload: &TribleSet| payload.iter().any(|t| t.e() == &entity),
1882 )
1883 .select(ws)
1884 }
1885}
1886
1887fn collect_reachable_from_patch<Blobs: BlobStore>(
1895 ws: &mut Workspace<Blobs>,
1896 patch: CommitSet,
1897) -> Result<
1898 CommitSet,
1899 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1900> {
1901 let mut result = CommitSet::new();
1902 for raw in patch.iter() {
1903 let handle = Inline::new(*raw);
1904 let reach = collect_reachable(ws, handle)?;
1905 result.union(reach);
1906 }
1907 Ok(result)
1908}
1909
1910fn collect_reachable_from_patch_until<Blobs: BlobStore>(
1911 ws: &mut Workspace<Blobs>,
1912 seeds: CommitSet,
1913 stop: &CommitSet,
1914) -> Result<
1915 CommitSet,
1916 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1917> {
1918 let mut visited = HashSet::new();
1919 let mut stack: Vec<CommitHandle> = seeds.iter().map(|raw| Inline::new(*raw)).collect();
1920 let mut result = CommitSet::new();
1921
1922 while let Some(commit) = stack.pop() {
1923 if !visited.insert(commit) {
1924 continue;
1925 }
1926
1927 if stop.get(&commit.raw).is_some() {
1928 continue;
1929 }
1930
1931 result.insert(&Entry::new(&commit.raw));
1932
1933 let meta: TribleSet = ws
1934 .staged
1935 .reader()
1936 .unwrap()
1937 .get(commit)
1938 .or_else(|_| ws.base_blobs.get(commit))
1939 .map_err(WorkspaceCheckoutError::Storage)?;
1940
1941 for (p,) in find!((p: Inline<_>,), pattern!(&meta, [{ parent: ?p }])) {
1942 stack.push(p);
1943 }
1944 }
1945
1946 Ok(result)
1947}
1948
1949impl<T, Blobs> CommitSelector<Blobs> for std::ops::Range<T>
1950where
1951 T: CommitSelector<Blobs>,
1952 Blobs: BlobStore,
1953{
1954 fn select(
1955 self,
1956 ws: &mut Workspace<Blobs>,
1957 ) -> Result<
1958 CommitSet,
1959 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1960 > {
1961 let end_patch = self.end.select(ws)?;
1962 let start_patch = self.start.select(ws)?;
1963
1964 collect_reachable_from_patch_until(ws, end_patch, &start_patch)
1965 }
1966}
1967
1968impl<T, Blobs> CommitSelector<Blobs> for std::ops::RangeFrom<T>
1969where
1970 T: CommitSelector<Blobs>,
1971 Blobs: BlobStore,
1972{
1973 fn select(
1974 self,
1975 ws: &mut Workspace<Blobs>,
1976 ) -> Result<
1977 CommitSet,
1978 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
1979 > {
1980 let Some(head_) = ws.head else {
1981 return Ok(CommitSet::new());
1982 };
1983 let exclude_patch = self.start.select(ws)?;
1984
1985 let mut head_patch = CommitSet::new();
1986 head_patch.insert(&Entry::new(&head_.raw));
1987
1988 collect_reachable_from_patch_until(ws, head_patch, &exclude_patch)
1989 }
1990}
1991
1992impl<T, Blobs> CommitSelector<Blobs> for std::ops::RangeTo<T>
1993where
1994 T: CommitSelector<Blobs>,
1995 Blobs: BlobStore,
1996{
1997 fn select(
1998 self,
1999 ws: &mut Workspace<Blobs>,
2000 ) -> Result<
2001 CommitSet,
2002 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
2003 > {
2004 let end_patch = self.end.select(ws)?;
2005 collect_reachable_from_patch(ws, end_patch)
2006 }
2007}
2008
2009impl<Blobs> CommitSelector<Blobs> for std::ops::RangeFull
2010where
2011 Blobs: BlobStore,
2012{
2013 fn select(
2014 self,
2015 ws: &mut Workspace<Blobs>,
2016 ) -> Result<
2017 CommitSet,
2018 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
2019 > {
2020 let Some(head_) = ws.head else {
2021 return Ok(CommitSet::new());
2022 };
2023 collect_reachable(ws, head_)
2024 }
2025}
2026
2027impl<Blobs> CommitSelector<Blobs> for TimeRange
2028where
2029 Blobs: BlobStore,
2030{
2031 fn select(
2032 self,
2033 ws: &mut Workspace<Blobs>,
2034 ) -> Result<
2035 CommitSet,
2036 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
2037 > {
2038 let Some(head_) = ws.head else {
2039 return Ok(CommitSet::new());
2040 };
2041 let start = self.0;
2042 let end = self.1;
2043 filter(
2044 ancestors(head_),
2045 move |meta: &TribleSet, _payload: &TribleSet| {
2046 if let Ok(Some(((ts_start, ts_end),))) =
2047 find!((t: (Epoch, Epoch)), pattern!(meta, [{ crate::metadata::created_at: ?t }])).at_most_one()
2048 {
2049 ts_start <= end && ts_end >= start
2050 } else {
2051 false
2052 }
2053 },
2054 )
2055 .select(ws)
2056 }
2057}
2058
2059impl<Blobs: BlobStore> Workspace<Blobs> {
2060 pub fn branch_id(&self) -> Id {
2062 self.base_branch_id
2063 }
2064
2065 pub fn head(&self) -> Option<CommitHandle> {
2067 self.head
2068 }
2069
2070 pub fn metadata(&self) -> MetadataHandle {
2072 self.commit_metadata
2073 }
2074
2075 pub fn rollup(
2097 &mut self,
2098 ) -> Result<
2099 Option<Inline<Handle<SuccinctArchiveBlob>>>,
2100 <Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>,
2101 > {
2102 let base_meta: TribleSet = self.base_blobs.get(self.base_branch_meta)?;
2103 Ok(
2104 find!(
2105 (r: Inline<Handle<SuccinctArchiveBlob>>),
2106 pattern!(&base_meta, [{ rollup: ?r }])
2107 )
2108 .next()
2109 .map(|(r,)| r),
2110 )
2111 }
2112
2113 pub fn put<S, T>(&mut self, item: T) -> Inline<Handle<S>>
2116 where
2117 S: BlobEncoding + 'static,
2118 T: IntoBlob<S>,
2119 Handle<S>: InlineEncoding,
2120 {
2121 self.staged.put(item).expect("infallible blob put")
2122 }
2123
2124
2125 pub fn get<T, S>(
2130 &mut self,
2131 handle: Inline<Handle<S>>,
2132 ) -> Result<T, <Blobs::Reader as BlobStoreGet>::GetError<<T as TryFromBlob<S>>::Error>>
2133 where
2134 S: BlobEncoding + 'static,
2135 T: TryFromBlob<S>,
2136 Handle<S>: InlineEncoding,
2137 {
2138 self.staged
2139 .reader()
2140 .unwrap()
2141 .get(handle)
2142 .or_else(|_| self.base_blobs.get(handle))
2143 }
2144
2145 pub fn commit(&mut self, content_: impl Into<Fragment>, message_: &str) {
2155 self.commit_internal(content_.into(), Some(self.commit_metadata), Some(message_));
2156 }
2157
2158 pub fn commit_with_metadata(
2161 &mut self,
2162 content_: impl Into<Fragment>,
2163 metadata_: MetadataHandle,
2164 message_: &str,
2165 ) {
2166 self.commit_internal(content_.into(), Some(metadata_), Some(message_));
2167 }
2168
2169 fn commit_internal(
2170 &mut self,
2171 content_: Fragment,
2172 metadata_handle: Option<MetadataHandle>,
2173 message_: Option<&str>,
2174 ) {
2175 let (content_facts, content_blobs) = content_.into_facts_and_blobs();
2176 self.staged.union(content_blobs);
2180 let content_blob: Blob<SimpleArchive> = content_facts.to_blob();
2182 let message_handle = message_.map(|m| self.put(m.to_string()));
2184 let parents = self.head.iter().copied();
2185
2186 let commit_set = crate::repo::commit::commit_metadata(
2187 &self.signing_key,
2188 parents,
2189 message_handle,
2190 Some(content_blob.clone()),
2191 metadata_handle,
2192 );
2193 let _ = self
2195 .staged
2196 .put::<SimpleArchive, _>(content_blob)
2197 .expect("failed to put content blob");
2198 let commit_handle = self
2199 .staged
2200 .put(commit_set)
2201 .expect("failed to put commit blob");
2202 self.head = Some(commit_handle);
2204 }
2205
2206 pub fn merge(
2228 &mut self,
2229 other: &mut Workspace<Blobs>,
2230 ) -> Result<Option<CommitHandle>, MergeError> {
2231 let other_local = other.staged.reader().unwrap();
2236 for r in other_local.blobs() {
2237 let handle = r.expect("infallible blob enumeration");
2238 let blob: Blob<UnknownBlob> = other_local.get(handle).expect("infallible blob read");
2239 self.staged
2240 .put::<UnknownBlob, _>(blob)
2241 .expect("infallible blob put");
2242 }
2243
2244 match other.head {
2248 Some(other_head) => Ok(Some(self.merge_commit(other_head)?)),
2249 None => Ok(self.head),
2250 }
2251 }
2252
2253 pub fn merge_commit(
2272 &mut self,
2273 other: Inline<Handle<SimpleArchive>>,
2274 ) -> Result<CommitHandle, MergeError> {
2275 let local_head = match self.head {
2277 None => {
2278 self.head = Some(other);
2280 return Ok(other);
2281 }
2282 Some(h) if h == other => {
2283 return Ok(h);
2285 }
2286 Some(h) => h,
2287 };
2288
2289 let remote_in_local = ancestors(local_head)
2292 .select(self)
2293 .ok()
2294 .map(|set| set.get(&other.raw).is_some())
2295 .unwrap_or(false);
2296 if remote_in_local {
2297 return Ok(local_head);
2299 }
2300
2301 let local_in_remote = ancestors(other)
2302 .select(self)
2303 .ok()
2304 .map(|set| set.get(&local_head.raw).is_some())
2305 .unwrap_or(false);
2306 if local_in_remote {
2307 self.head = Some(other);
2309 return Ok(other);
2310 }
2311
2312 let parents = self.head.iter().copied().chain(Some(other));
2314 let merge_commit = commit_metadata(&self.signing_key, parents, None, None, None);
2315 let commit_handle = self
2316 .staged
2317 .put(merge_commit)
2318 .expect("failed to put merge commit blob");
2319 self.head = Some(commit_handle);
2320 Ok(commit_handle)
2321 }
2322
2323 pub fn set_head(&mut self, commit: CommitHandle) {
2333 self.head = Some(commit);
2334 }
2335
2336 fn checkout_commits<I>(
2343 &mut self,
2344 commits: I,
2345 ) -> Result<
2346 TribleSet,
2347 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
2348 >
2349 where
2350 I: IntoIterator<Item = CommitHandle>,
2351 {
2352 let local = self.staged.reader().unwrap();
2353 let mut result = TribleSet::new();
2354 for commit in commits {
2355 let meta: TribleSet = local
2356 .get(commit)
2357 .or_else(|_| self.base_blobs.get(commit))
2358 .map_err(WorkspaceCheckoutError::Storage)?;
2359
2360 let content_opt =
2365 match find!((c: Inline<_>), pattern!(&meta, [{ content: ?c }])).at_most_one() {
2366 Ok(Some((c,))) => Some(c),
2367 Ok(None) => None,
2368 Err(_) => return Err(WorkspaceCheckoutError::BadCommitMetadata()),
2369 };
2370
2371 if let Some(c) = content_opt {
2372 let set: TribleSet = local
2373 .get(c)
2374 .or_else(|_| self.base_blobs.get(c))
2375 .map_err(WorkspaceCheckoutError::Storage)?;
2376 result += set;
2377 } else {
2378 continue;
2380 }
2381 }
2382 Ok(result)
2383 }
2384
2385 fn checkout_commits_metadata<I>(
2386 &mut self,
2387 commits: I,
2388 ) -> Result<
2389 TribleSet,
2390 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
2391 >
2392 where
2393 I: IntoIterator<Item = CommitHandle>,
2394 {
2395 let local = self.staged.reader().unwrap();
2396 let mut result = TribleSet::new();
2397 for commit in commits {
2398 let meta: TribleSet = local
2399 .get(commit)
2400 .or_else(|_| self.base_blobs.get(commit))
2401 .map_err(WorkspaceCheckoutError::Storage)?;
2402
2403 let metadata_opt =
2404 match find!((c: Inline<_>), pattern!(&meta, [{ metadata: ?c }])).at_most_one() {
2405 Ok(Some((c,))) => Some(c),
2406 Ok(None) => None,
2407 Err(_) => return Err(WorkspaceCheckoutError::BadCommitMetadata()),
2408 };
2409
2410 if let Some(c) = metadata_opt {
2411 let set: TribleSet = local
2412 .get(c)
2413 .or_else(|_| self.base_blobs.get(c))
2414 .map_err(WorkspaceCheckoutError::Storage)?;
2415 result += set;
2416 }
2417 }
2418 Ok(result)
2419 }
2420
2421 fn checkout_commits_with_metadata<I>(
2422 &mut self,
2423 commits: I,
2424 ) -> Result<
2425 (TribleSet, TribleSet),
2426 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
2427 >
2428 where
2429 I: IntoIterator<Item = CommitHandle>,
2430 {
2431 let local = self.staged.reader().unwrap();
2432 let mut data = TribleSet::new();
2433 let mut metadata_set = TribleSet::new();
2434 for commit in commits {
2435 let meta: TribleSet = local
2436 .get(commit)
2437 .or_else(|_| self.base_blobs.get(commit))
2438 .map_err(WorkspaceCheckoutError::Storage)?;
2439
2440 let content_opt =
2441 match find!((c: Inline<_>), pattern!(&meta, [{ content: ?c }])).at_most_one() {
2442 Ok(Some((c,))) => Some(c),
2443 Ok(None) => None,
2444 Err(_) => return Err(WorkspaceCheckoutError::BadCommitMetadata()),
2445 };
2446
2447 if let Some(c) = content_opt {
2448 let set: TribleSet = local
2449 .get(c)
2450 .or_else(|_| self.base_blobs.get(c))
2451 .map_err(WorkspaceCheckoutError::Storage)?;
2452 data += set;
2453 }
2454
2455 let metadata_opt =
2456 match find!((c: Inline<_>), pattern!(&meta, [{ metadata: ?c }])).at_most_one() {
2457 Ok(Some((c,))) => Some(c),
2458 Ok(None) => None,
2459 Err(_) => return Err(WorkspaceCheckoutError::BadCommitMetadata()),
2460 };
2461
2462 if let Some(c) = metadata_opt {
2463 let set: TribleSet = local
2464 .get(c)
2465 .or_else(|_| self.base_blobs.get(c))
2466 .map_err(WorkspaceCheckoutError::Storage)?;
2467 metadata_set += set;
2468 }
2469 }
2470 Ok((data, metadata_set))
2471 }
2472
2473 pub fn checkout<R>(
2477 &mut self,
2478 spec: R,
2479 ) -> Result<
2480 Checkout,
2481 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
2482 >
2483 where
2484 R: CommitSelector<Blobs>,
2485 {
2486 let commits = spec.select(self)?;
2487 let facts = self.checkout_commits(commits.iter().map(|raw| Inline::new(*raw)))?;
2488 Ok(Checkout { facts, commits })
2489 }
2490
2491 pub fn checkout_metadata<R>(
2494 &mut self,
2495 spec: R,
2496 ) -> Result<
2497 TribleSet,
2498 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
2499 >
2500 where
2501 R: CommitSelector<Blobs>,
2502 {
2503 let patch = spec.select(self)?;
2504 let commits = patch.iter().map(|raw| Inline::new(*raw));
2505 self.checkout_commits_metadata(commits)
2506 }
2507
2508 pub fn checkout_with_metadata<R>(
2511 &mut self,
2512 spec: R,
2513 ) -> Result<
2514 (TribleSet, TribleSet),
2515 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
2516 >
2517 where
2518 R: CommitSelector<Blobs>,
2519 {
2520 let patch = spec.select(self)?;
2521 let commits = patch.iter().map(|raw| Inline::new(*raw));
2522 self.checkout_commits_with_metadata(commits)
2523 }
2524}
2525
2526#[derive(Debug)]
2527pub enum WorkspaceCheckoutError<GetErr: Error> {
2528 Storage(GetErr),
2530 BadCommitMetadata(),
2532}
2533
2534impl<E: Error + fmt::Debug> fmt::Display for WorkspaceCheckoutError<E> {
2535 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2536 match self {
2537 WorkspaceCheckoutError::Storage(e) => write!(f, "storage error: {e}"),
2538 WorkspaceCheckoutError::BadCommitMetadata() => {
2539 write!(f, "commit metadata malformed")
2540 }
2541 }
2542 }
2543}
2544
2545impl<E: Error + fmt::Debug> Error for WorkspaceCheckoutError<E> {}
2546
2547fn collect_reachable<Blobs: BlobStore>(
2548 ws: &mut Workspace<Blobs>,
2549 from: CommitHandle,
2550) -> Result<
2551 CommitSet,
2552 WorkspaceCheckoutError<<Blobs::Reader as BlobStoreGet>::GetError<UnarchiveError>>,
2553> {
2554 let mut visited = HashSet::new();
2555 let mut stack = vec![from];
2556 let mut result = CommitSet::new();
2557
2558 while let Some(commit) = stack.pop() {
2559 if !visited.insert(commit) {
2560 continue;
2561 }
2562 result.insert(&Entry::new(&commit.raw));
2563
2564 let meta: TribleSet = ws
2565 .staged
2566 .reader()
2567 .unwrap()
2568 .get(commit)
2569 .or_else(|_| ws.base_blobs.get(commit))
2570 .map_err(WorkspaceCheckoutError::Storage)?;
2571
2572 for (p,) in find!((p: Inline<_>,), pattern!(&meta, [{ parent: ?p }])) {
2573 stack.push(p);
2574 }
2575 }
2576
2577 Ok(result)
2578}