1use std::any::Any;
18use std::collections::BTreeMap;
19use std::collections::HashSet;
20use std::fmt::Debug;
21use std::iter;
22use std::time::SystemTime;
23
24use async_trait::async_trait;
25use itertools::Itertools as _;
26use thiserror::Error;
27
28use crate::backend::CommitId;
29use crate::backend::MillisSinceEpoch;
30use crate::backend::Timestamp;
31use crate::content_hash::ContentHash;
32use crate::merge::Merge;
33use crate::object_id::HexPrefix;
34use crate::object_id::ObjectId as _;
35use crate::object_id::PrefixResolution;
36use crate::object_id::id_type;
37use crate::ref_name::GitRefNameBuf;
38use crate::ref_name::RefName;
39use crate::ref_name::RefNameBuf;
40use crate::ref_name::RemoteName;
41use crate::ref_name::RemoteNameBuf;
42use crate::ref_name::RemoteRefSymbol;
43use crate::ref_name::WorkspaceNameBuf;
44
45id_type!(
46 pub ViewId { hex() }
48);
49id_type!(
50 pub OperationId { hex() }
52);
53
54pub static ABSENT_REF_TARGET: RefTarget = RefTarget::absent();
58pub static ABSENT_REMOTE_REF: RemoteRef = RemoteRef::absent();
62
63pub type RefTarget = Merge<Option<CommitId>>;
65
66#[derive(ContentHash, Clone, Debug, Eq, Hash, PartialEq)]
68pub struct RemoteRef {
69 pub target: RefTarget,
71 pub state: RemoteRefState,
73}
74
75impl RemoteRef {
76 pub const fn absent() -> Self {
78 Self {
79 target: RefTarget::absent(),
80 state: RemoteRefState::New,
81 }
82 }
83
84 pub fn is_absent(&self) -> bool {
86 self.target.is_absent()
87 }
88
89 pub fn is_present(&self) -> bool {
91 self.target.is_present()
92 }
93
94 pub fn is_tracked(&self) -> bool {
96 self.state == RemoteRefState::Tracked
97 }
98
99 pub fn tracked_target(&self) -> &RefTarget {
104 if self.is_tracked() {
105 &self.target
106 } else {
107 &ABSENT_REF_TARGET
108 }
109 }
110}
111
112#[derive(ContentHash, Clone, Copy, Debug, Eq, Hash, PartialEq)]
114pub enum RemoteRefState {
115 New,
117 Tracked,
120}
121
122pub trait RefTargetOptionExt {
124 type Value;
126
127 fn flatten(self) -> Self::Value;
129}
130
131impl RefTargetOptionExt for Option<RefTarget> {
132 type Value = RefTarget;
133
134 fn flatten(self) -> Self::Value {
135 self.unwrap_or_else(RefTarget::absent)
136 }
137}
138
139impl<'a> RefTargetOptionExt for Option<&'a RefTarget> {
140 type Value = &'a RefTarget;
141
142 fn flatten(self) -> Self::Value {
143 self.unwrap_or(&ABSENT_REF_TARGET)
144 }
145}
146
147impl RefTargetOptionExt for Option<RemoteRef> {
148 type Value = RemoteRef;
149
150 fn flatten(self) -> Self::Value {
151 self.unwrap_or_else(RemoteRef::absent)
152 }
153}
154
155impl<'a> RefTargetOptionExt for Option<&'a RemoteRef> {
156 type Value = &'a RemoteRef;
157
158 fn flatten(self) -> Self::Value {
159 self.unwrap_or(&ABSENT_REMOTE_REF)
160 }
161}
162
163#[derive(PartialEq, Eq, Clone, Debug)]
165pub struct LocalRemoteRefTarget<'a> {
166 pub local_target: &'a RefTarget,
168 pub remote_refs: Vec<(&'a RemoteName, &'a RemoteRef)>,
170}
171
172#[derive(ContentHash, PartialEq, Eq, Clone, Debug)]
175pub struct View {
176 pub head_ids: HashSet<CommitId>,
178 pub local_bookmarks: BTreeMap<RefNameBuf, RefTarget>,
180 pub local_tags: BTreeMap<RefNameBuf, RefTarget>,
182 pub remote_views: BTreeMap<RemoteNameBuf, RemoteView>,
184 pub git_refs: BTreeMap<GitRefNameBuf, RefTarget>,
186 pub git_heads: BTreeMap<WorkspaceNameBuf, RefTarget>,
189 pub wc_commit_ids: BTreeMap<WorkspaceNameBuf, CommitId>,
194}
195
196impl View {
197 pub fn make_root(root_commit_id: CommitId) -> Self {
199 Self {
200 head_ids: HashSet::from([root_commit_id]),
201 local_bookmarks: BTreeMap::new(),
202 local_tags: BTreeMap::new(),
203 remote_views: BTreeMap::new(),
204 git_refs: BTreeMap::new(),
205 git_heads: BTreeMap::new(),
206 wc_commit_ids: BTreeMap::new(),
207 }
208 }
209}
210
211#[derive(ContentHash, Clone, Debug, Default, Eq, PartialEq)]
213pub struct RemoteView {
214 pub bookmarks: BTreeMap<RefNameBuf, RemoteRef>,
220 pub tags: BTreeMap<RefNameBuf, RemoteRef>,
222}
223
224pub fn merge_join_ref_views<'a>(
226 local_refs: &'a BTreeMap<RefNameBuf, RefTarget>,
227 remote_views: &'a BTreeMap<RemoteNameBuf, RemoteView>,
228 get_remote_refs: impl FnMut(&RemoteView) -> &BTreeMap<RefNameBuf, RemoteRef>,
229) -> impl Iterator<Item = (&'a RefName, LocalRemoteRefTarget<'a>)> {
230 let mut local_refs_iter = local_refs
231 .iter()
232 .map(|(name, target)| (&**name, target))
233 .peekable();
234 let mut remote_refs_iter = flatten_remote_refs(remote_views, get_remote_refs).peekable();
235
236 iter::from_fn(move || {
237 let (name, local_target) = if let Some((symbol, _)) = remote_refs_iter.peek() {
239 local_refs_iter
240 .next_if(|&(local_name, _)| local_name <= symbol.name)
241 .unwrap_or((symbol.name, &ABSENT_REF_TARGET))
242 } else {
243 local_refs_iter.next()?
244 };
245 let remote_refs = remote_refs_iter
246 .peeking_take_while(|(symbol, _)| symbol.name == name)
247 .map(|(symbol, remote_ref)| (symbol.remote, remote_ref))
248 .collect();
249 let local_remote_target = LocalRemoteRefTarget {
250 local_target,
251 remote_refs,
252 };
253 Some((name, local_remote_target))
254 })
255}
256
257pub fn flatten_remote_refs(
259 remote_views: &BTreeMap<RemoteNameBuf, RemoteView>,
260 mut get_remote_refs: impl FnMut(&RemoteView) -> &BTreeMap<RefNameBuf, RemoteRef>,
261) -> impl Iterator<Item = (RemoteRefSymbol<'_>, &RemoteRef)> {
262 remote_views
263 .iter()
264 .map(|(remote, remote_view)| {
265 get_remote_refs(remote_view)
266 .iter()
267 .map(move |(name, remote_ref)| (name.to_remote_symbol(remote), remote_ref))
268 })
269 .kmerge_by(|(symbol1, _), (symbol2, _)| symbol1 < symbol2)
270}
271
272#[derive(Clone, ContentHash, Debug, Eq, PartialEq, serde::Serialize)]
275pub struct TimestampRange {
276 pub start: Timestamp,
278 pub end: Timestamp,
280}
281
282#[derive(ContentHash, PartialEq, Eq, Clone, Debug, serde::Serialize)]
295pub struct Operation {
296 #[serde(skip)] pub view_id: ViewId,
299 pub parents: Vec<OperationId>,
301 #[serde(flatten)]
303 pub metadata: OperationMetadata,
304 #[serde(skip)] pub commit_predecessors: Option<BTreeMap<CommitId, Vec<CommitId>>>,
321}
322
323impl Operation {
324 pub fn make_root(root_view_id: ViewId) -> Self {
326 let timestamp = Timestamp {
327 timestamp: MillisSinceEpoch(0),
328 tz_offset: 0,
329 };
330 let metadata = OperationMetadata {
331 time: TimestampRange {
332 start: timestamp,
333 end: timestamp,
334 },
335 description: "".to_string(),
336 hostname: "".to_string(),
337 username: "".to_string(),
338 is_snapshot: false,
339 workspace_name: None,
340 attributes: BTreeMap::new(),
341 };
342 Self {
343 view_id: root_view_id,
344 parents: vec![],
345 metadata,
346 commit_predecessors: Some(BTreeMap::new()),
351 }
352 }
353}
354
355#[derive(ContentHash, PartialEq, Eq, Clone, Debug, serde::Serialize)]
357pub struct OperationMetadata {
358 pub time: TimestampRange,
360 pub description: String,
362 pub hostname: String,
364 pub username: String,
366 pub is_snapshot: bool,
369 pub workspace_name: Option<WorkspaceNameBuf>,
371 pub attributes: BTreeMap<String, String>,
373}
374
375#[derive(Clone, Debug)]
377pub struct RootOperationData {
378 pub root_commit_id: CommitId,
380}
381
382#[derive(Debug, Error)]
384pub enum OpStoreError {
385 #[error("Object {hash} of type {object_type} not found")]
387 ObjectNotFound {
388 object_type: String,
390 hash: String,
392 source: Option<Box<dyn std::error::Error + Send + Sync>>,
394 },
395 #[error("Error when reading object {hash} of type {object_type}")]
397 ReadObject {
398 object_type: String,
400 hash: String,
402 source: Box<dyn std::error::Error + Send + Sync>,
404 },
405 #[error("Could not write object of type {object_type}")]
407 WriteObject {
408 object_type: &'static str,
410 source: Box<dyn std::error::Error + Send + Sync>,
412 },
413 #[error(transparent)]
415 Other(Box<dyn std::error::Error + Send + Sync>),
416}
417
418pub type OpStoreResult<T> = Result<T, OpStoreError>;
420
421#[async_trait]
423pub trait OpStore: Any + Send + Sync + Debug {
424 fn name(&self) -> &str;
426
427 fn root_operation_id(&self) -> &OperationId;
429
430 async fn read_view(&self, id: &ViewId) -> OpStoreResult<View>;
432
433 async fn write_view(&self, contents: &View) -> OpStoreResult<ViewId>;
435
436 async fn read_operation(&self, id: &OperationId) -> OpStoreResult<Operation>;
438
439 async fn write_operation(&self, contents: &Operation) -> OpStoreResult<OperationId>;
441
442 async fn resolve_operation_id_prefix(
444 &self,
445 prefix: &HexPrefix,
446 ) -> OpStoreResult<PrefixResolution<OperationId>>;
447
448 async fn gc(&self, head_ids: &[OperationId], keep_newer: SystemTime) -> OpStoreResult<()>;
456}
457
458impl dyn OpStore {
459 pub fn downcast_ref<T: OpStore>(&self) -> Option<&T> {
461 (self as &dyn Any).downcast_ref()
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use maplit::btreemap;
468
469 use super::*;
470
471 #[test]
472 fn test_merge_join_bookmark_views() {
473 let remote_ref = |target: &RefTarget| RemoteRef {
474 target: target.clone(),
475 state: RemoteRefState::Tracked, };
477 let local_bookmark1_target = RefTarget::normal(CommitId::from_hex("111111"));
478 let local_bookmark2_target = RefTarget::normal(CommitId::from_hex("222222"));
479 let git_bookmark1_remote_ref = remote_ref(&RefTarget::normal(CommitId::from_hex("333333")));
480 let git_bookmark2_remote_ref = remote_ref(&RefTarget::normal(CommitId::from_hex("444444")));
481 let remote1_bookmark1_remote_ref =
482 remote_ref(&RefTarget::normal(CommitId::from_hex("555555")));
483 let remote2_bookmark2_remote_ref =
484 remote_ref(&RefTarget::normal(CommitId::from_hex("666666")));
485
486 let local_bookmarks = btreemap! {
487 "bookmark1".into() => local_bookmark1_target.clone(),
488 "bookmark2".into() => local_bookmark2_target.clone(),
489 };
490 let remote_views = btreemap! {
491 "git".into() => RemoteView {
492 bookmarks: btreemap! {
493 "bookmark1".into() => git_bookmark1_remote_ref.clone(),
494 "bookmark2".into() => git_bookmark2_remote_ref.clone(),
495 },
496 tags: btreemap! {},
497 },
498 "remote1".into() => RemoteView {
499 bookmarks: btreemap! {
500 "bookmark1".into() => remote1_bookmark1_remote_ref.clone(),
501 },
502 tags: btreemap! {},
503 },
504 "remote2".into() => RemoteView {
505 bookmarks: btreemap! {
506 "bookmark2".into() => remote2_bookmark2_remote_ref.clone(),
507 },
508 tags: btreemap! {},
509 },
510 };
511 assert_eq!(
512 merge_join_ref_views(&local_bookmarks, &remote_views, |view| &view.bookmarks)
513 .collect_vec(),
514 vec![
515 (
516 "bookmark1".as_ref(),
517 LocalRemoteRefTarget {
518 local_target: &local_bookmark1_target,
519 remote_refs: vec![
520 ("git".as_ref(), &git_bookmark1_remote_ref),
521 ("remote1".as_ref(), &remote1_bookmark1_remote_ref),
522 ],
523 },
524 ),
525 (
526 "bookmark2".as_ref(),
527 LocalRemoteRefTarget {
528 local_target: &local_bookmark2_target.clone(),
529 remote_refs: vec![
530 ("git".as_ref(), &git_bookmark2_remote_ref),
531 ("remote2".as_ref(), &remote2_bookmark2_remote_ref),
532 ],
533 },
534 ),
535 ],
536 );
537
538 let local_bookmarks = btreemap! {
540 "bookmark1".into() => local_bookmark1_target.clone(),
541 };
542 let remote_views = btreemap! {};
543 assert_eq!(
544 merge_join_ref_views(&local_bookmarks, &remote_views, |view| &view.bookmarks)
545 .collect_vec(),
546 vec![(
547 "bookmark1".as_ref(),
548 LocalRemoteRefTarget {
549 local_target: &local_bookmark1_target,
550 remote_refs: vec![]
551 },
552 )],
553 );
554
555 let local_bookmarks = btreemap! {};
557 let remote_views = btreemap! {
558 "remote1".into() => RemoteView {
559 bookmarks: btreemap! {
560 "bookmark1".into() => remote1_bookmark1_remote_ref.clone(),
561 },
562 tags: btreemap! {},
563 },
564 };
565 assert_eq!(
566 merge_join_ref_views(&local_bookmarks, &remote_views, |view| &view.bookmarks)
567 .collect_vec(),
568 vec![(
569 "bookmark1".as_ref(),
570 LocalRemoteRefTarget {
571 local_target: &RefTarget::absent(),
572 remote_refs: vec![("remote1".as_ref(), &remote1_bookmark1_remote_ref)],
573 },
574 )],
575 );
576 }
577}