radicle_fetch/git/refs/update.rs
1//! The set of types that describe performing updates to a Git
2//! repository.
3//!
4//! An [`Update`] describes a single update that can made to a Git
5//! repository. **Note** that it currently does not support symbolic
6//! references.
7//!
8//! A group of `Update`s is described by [`Updates`] which groups
9//! those updates by each peer's namespace, i.e. their [`PublicKey`].
10//!
11//! When an `Update` is successful the corresponding [`Updated`] is
12//! expected to be produced.
13//!
14//! The final result of applying a set of [`Updates`] is captured in
15//! the [`Applied`] type, which contains any rejected, but non-fatal,
16//! [`Update`]s and successful [`Updated`] values.
17
18use std::collections::BTreeMap;
19
20use either::Either;
21use radicle::git::fmt::{Namespaced, Qualified};
22use radicle::git::Oid;
23use radicle::prelude::PublicKey;
24
25pub use radicle::storage::RefUpdate;
26
27/// The set of applied changes from a reference store update.
28#[derive(Debug, Default)]
29pub struct Applied<'a> {
30 /// Set of rejected updates if they did not meet the update
31 /// requirements, e.g. concurrent change to previous object id,
32 /// broke fast-forward policy, etc.
33 pub rejected: Vec<Update<'a>>,
34 /// Set of successfully updated references.
35 pub updated: Vec<RefUpdate>,
36}
37
38impl Applied<'_> {
39 pub fn append(&mut self, other: &mut Self) {
40 self.rejected.append(&mut other.rejected);
41 self.updated.append(&mut other.updated);
42 }
43}
44
45/// A set of [`Update`]s that are grouped by which namespace they are
46/// affecting.
47#[derive(Clone, Default, Debug)]
48pub struct Updates<'a> {
49 pub tips: BTreeMap<PublicKey, Vec<Update<'a>>>,
50}
51
52impl<'a> Updates<'a> {
53 pub fn build(updates: impl IntoIterator<Item = (PublicKey, Update<'a>)>) -> Self {
54 let tips = updates.into_iter().fold(
55 BTreeMap::<_, Vec<Update<'a>>>::new(),
56 |mut tips, (remote, up)| {
57 tips.entry(remote)
58 .and_modify(|ups| ups.push(up.clone()))
59 .or_insert(vec![up]);
60 tips
61 },
62 );
63 Self { tips }
64 }
65
66 pub fn add(&mut self, remote: PublicKey, up: Update<'a>) {
67 self.tips
68 .entry(remote)
69 .and_modify(|ups| ups.push(up.clone()))
70 .or_insert(vec![up]);
71 }
72
73 pub fn append(&mut self, remote: PublicKey, mut new: Vec<Update<'a>>) {
74 self.tips
75 .entry(remote)
76 .and_modify(|ups| ups.append(&mut new))
77 .or_insert(new);
78 }
79}
80
81/// The policy to follow when an [`Update::Direct`] is not a
82/// fast-forward.
83#[derive(Clone, Copy, Debug)]
84pub enum Policy {
85 /// Abort the entire transaction.
86 Abort,
87 /// Reject this update, but continue the transaction.
88 Reject,
89 /// Allow the update.
90 Allow,
91}
92
93/// An update that can be applied to a Git repository.
94#[derive(Clone, Debug)]
95pub enum Update<'a> {
96 /// Update a direct reference, i.e. a reference that points to an
97 /// object.
98 Direct {
99 /// The name of the reference that is being updated.
100 name: Namespaced<'a>,
101 /// The resulting target of the reference that is being
102 /// updated.
103 target: Oid,
104 /// Policy to apply when an [`Update`] would not apply as a
105 /// fast-forward.
106 no_ff: Policy,
107 },
108 /// Delete a reference.
109 Prune {
110 /// The name of the reference that is being deleted.
111 name: Namespaced<'a>,
112 /// The previous value of the reference.
113 ///
114 /// It can either be a direct reference pointing to an
115 /// [`Oid`], or a symbolic reference pointing to a
116 /// [`Qualified`] reference name.
117 prev: Either<Oid, Qualified<'a>>,
118 },
119}
120
121impl<'a> Update<'a> {
122 pub fn refname(&self) -> &Namespaced<'a> {
123 match self {
124 Update::Direct { name, .. } => name,
125 Update::Prune { name, .. } => name,
126 }
127 }
128
129 pub fn into_owned<'b>(self) -> Update<'b> {
130 match self {
131 Self::Direct {
132 name,
133 target,
134 no_ff,
135 } => Update::Direct {
136 name: name.into_owned(),
137 target,
138 no_ff,
139 },
140 Self::Prune { name, prev } => Update::Prune {
141 name: name.into_owned(),
142 prev: prev.map_right(|q| q.into_owned()),
143 },
144 }
145 }
146}