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
// Copyright © 2022 The Radicle Link Contributors

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

use crate::{change, change_graph::ChangeGraph, CollaborativeObject, ObjectId, Store, TypeName};

use super::error;

/// Result of an `update` operation.
#[derive(Debug)]
pub struct Updated {
    /// The new head commit of the DAG.
    pub head: Oid,
    /// The newly updated collaborative object.
    pub object: CollaborativeObject,
}

/// The data required to update an object
pub struct Update {
    /// The CRDT changes to add to the object.
    pub changes: NonEmpty<Vec<u8>>,
    /// The object ID of the object to be updated.
    pub object_id: ObjectId,
    /// The typename of the object to be updated.
    pub type_name: TypeName,
    /// The message to add when updating this object.
    pub message: String,
}

/// Update an existing [`CollaborativeObject`].
///
/// The `storage` is the backing storage for storing
/// [`crate::Change`]s at content-addressable locations. Please see
/// [`Store`] for further information.
///
/// The `signer` is expected to be a cryptographic signing key. This
/// ensures that the objects origin is cryptographically verifiable.
///
/// The `resource` is the resource this change lives under, eg. a project.
///
/// The `parents` are other the parents of this object, for example a
/// code commit.
///
/// The `identifier` is a unqiue id that is passed through to the
/// [`crate::object::Storage`].
///
/// The `args` are the metadata for this [`CollaborativeObject`]
/// udpate. See [`Update`] for further information.
pub fn update<S, I, G>(
    storage: &S,
    signer: &G,
    resource: Oid,
    parents: Vec<Oid>,
    identifier: &S::Identifier,
    args: Update,
) -> Result<Updated, error::Update>
where
    S: Store<I>,
    G: crypto::Signer,
{
    let Update {
        type_name: ref typename,
        object_id,
        changes,
        message,
    } = args;

    let existing_refs = storage
        .objects(typename, &object_id)
        .map_err(|err| error::Update::Refs { err: Box::new(err) })?;

    let mut object = ChangeGraph::load(storage, existing_refs.iter(), typename, &object_id)
        .map(|graph| graph.evaluate())
        .ok_or(error::Update::NoSuchObject)?;

    let change = storage.store(
        resource,
        parents,
        signer,
        change::Template {
            tips: object.tips().iter().cloned().collect(),
            contents: changes,
            type_name: typename.clone(),
            message,
        },
    )?;

    storage
        .update(identifier, typename, &object_id, &change)
        .map_err(|err| error::Update::Refs { err: Box::new(err) })?;

    object.history.extend(
        change.id,
        change.signature.key,
        change.resource,
        change.contents,
        change.timestamp,
        change.manifest,
    );

    Ok(Updated {
        object,
        head: change.id,
    })
}