1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// Copyright © 2022 The Radicle Link Contributors
use std::convert::Infallible;
use std::fmt::Debug;

use git_ext::Oid;
use nonempty::NonEmpty;

use crate::change::store::{Manifest, Version};
use crate::{change, Entry, History, ObjectId, TypeName};

pub mod error;

mod create;
pub use create::{create, Create};

mod get;
pub use get::get;

pub mod info;

mod list;
pub use list::list;

mod remove;
pub use remove::remove;

mod update;
pub use update::{update, Update, Updated};

/// A collaborative object
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CollaborativeObject<T> {
    /// The manifest of this object
    pub manifest: Manifest,
    /// The materialized object resulting from traversing the history.
    pub object: T,
    /// The history DAG.
    pub history: History,
    /// The id of the object
    pub id: ObjectId,
}

impl<T> CollaborativeObject<T> {
    pub fn object(&self) -> &T {
        &self.object
    }

    pub fn history(&self) -> &History {
        &self.history
    }

    pub fn id(&self) -> &ObjectId {
        &self.id
    }

    pub fn typename(&self) -> &TypeName {
        &self.manifest.type_name
    }

    pub fn manifest(&self) -> &Manifest {
        &self.manifest
    }
}

/// An object that can be built by evaluating a history.
pub trait Evaluate<R>: Sized + Debug + 'static {
    type Error: std::error::Error + Send + Sync + 'static;

    /// Initialize the object with the first (root) history entry.
    fn init(entry: &Entry, store: &R) -> Result<Self, Self::Error>;

    /// Apply a history entry to the evaluated state.
    fn apply<'a, I: Iterator<Item = (&'a Oid, &'a Entry)>>(
        &mut self,
        entry: &Entry,
        concurrent: I,
        store: &R,
    ) -> Result<(), Self::Error>;
}

impl<R> Evaluate<R> for NonEmpty<Entry> {
    type Error = Infallible;

    fn init(entry: &Entry, _store: &R) -> Result<Self, Self::Error> {
        Ok(Self::new(entry.clone()))
    }

    fn apply<'a, I: Iterator<Item = (&'a Oid, &'a Entry)>>(
        &mut self,
        entry: &Entry,
        _concurrent: I,
        _store: &R,
    ) -> Result<(), Self::Error> {
        self.push(entry.clone());

        Ok(())
    }
}

/// Takes a `refname` and performs a best attempt to extract out the
/// [`TypeName`] and [`ObjectId`] from it.
///
/// This assumes that the `refname` is in a
/// [`git_ext::ref_format::Qualified`] format. If it has any
/// `refs/namespaces`, they will be stripped to access the underlying
/// [`git_ext::ref_format::Qualified`] format.
///
/// In the [`git_ext::ref_format::Qualified`] format it assumes that the
/// reference name is of the form:
///
///   `refs/<category>/<typename>/<object_id>[/<rest>*]`
///
/// Note that their may be more components to the path after the
/// [`ObjectId`] but they are ignored.
///
/// Also note that this will return `None` if:
///
///   * The `refname` is not [`git_ext::ref_format::Qualified`]
///   * The parsing of the [`ObjectId`] fails
///   * The parsing of the [`TypeName`] fails
pub fn parse_refstr<R>(name: &R) -> Option<(TypeName, ObjectId)>
where
    R: AsRef<git_ext::ref_format::RefStr>,
{
    use git_ext::ref_format::Qualified;
    let name = name.as_ref();
    let refs_cobs = match name.to_namespaced() {
        None => Qualified::from_refstr(name)?,
        Some(ns) => ns.strip_namespace_recursive(),
    };

    let (_refs, _cobs, typename, mut object_id) = refs_cobs.non_empty_components();
    let object = object_id
        .next()
        .and_then(|oid| oid.parse::<ObjectId>().ok())?;
    let name = typename.parse::<TypeName>().ok()?;
    Some((name, object))
}