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
// Copyright © 2022 The Radicle Link Contributors
use std::iter;

use git_ext::Oid;
use nonempty::NonEmpty;
use radicle_crypto::PublicKey;

use crate::{
    change, change_graph::ChangeGraph, history::EntryId, CollaborativeObject, Embed, Evaluate,
    ObjectId, Store, TypeName,
};

use super::error;

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

/// 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,
    /// Embedded files.
    pub embeds: Vec<Embed>,
}

/// Update an existing [`CollaborativeObject`].
///
/// The `storage` is the backing storage for storing
/// [`crate::Entry`]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<T, S, G>(
    storage: &S,
    signer: &G,
    resource: Option<Oid>,
    related: Vec<Oid>,
    identifier: &PublicKey,
    args: Update,
) -> Result<Updated<T>, error::Update>
where
    T: Evaluate<S>,
    S: Store,
    G: crypto::Signer,
{
    let Update {
        type_name: ref typename,
        object_id,
        embeds,
        changes,
        message,
    } = args;

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

    let graph = ChangeGraph::load(storage, existing_refs.iter(), typename, &object_id)
        .ok_or(error::Update::NoSuchObject)?;
    let mut object: CollaborativeObject<T> =
        graph.evaluate(storage).map_err(error::Update::evaluate)?;

    // Create a commit for this change, but don't update any references yet.
    let entry = storage.store(
        resource,
        related,
        signer,
        change::Template {
            tips: object.history.tips().into_iter().collect(),
            embeds,
            contents: changes,
            type_name: typename.clone(),
            message,
        },
    )?;
    let head = entry.id;
    let parents = entry.parents.to_vec();

    // Try to apply this change to our object. This prevents storing invalid updates.
    // Note that if this returns with an error, we are left with an unreachable
    // commit object created above. This is fine, as it will eventually get
    // garbage-collected by Git.
    object
        .object
        .apply(&entry, iter::empty(), storage)
        .map_err(error::Update::evaluate)?;
    object.history.extend(entry);

    // Here we actually update the references to point to the new update.
    storage
        .update(identifier, typename, &object_id, &head)
        .map_err(|err| error::Update::Refs { err: Box::new(err) })?;

    Ok(Updated {
        object,
        head,
        parents,
    })
}