//! The set of types that describe performing updates to a Git
//! repository.
//!
//! An [`Update`] describes a single update that can made to a Git
//! repository. **Note** that it currently does not support symbolic
//! references.
//!
//! A group of `Update`s is described by [`Updates`] which groups
//! those updates by each peer's namespace, i.e. their [`PublicKey`].
//!
//! When an `Update` is successful the corresponding [`Updated`] is
//! expected to be produced.
//!
//! The final result of applying a set of [`Updates`] is captured in
//! the [`Applied`] type, which contains any rejected, but non-fatal,
//! [`Update`]s and successful [`Updated`] values.
use std::collections::BTreeMap;
use either::Either;
use radicle::git::Oid;
use radicle::git::fmt::{Namespaced, Qualified};
use radicle::prelude::PublicKey;
pub use radicle::storage::RefUpdate;
/// The set of applied changes from a reference store update.
#[derive(Debug, Default)]
pub struct Applied<'a> {
/// Set of rejected updates if they did not meet the update
/// requirements, e.g. concurrent change to previous object id,
/// broke fast-forward policy, etc.
pub rejected: Vec<Update<'a>>,
/// Set of successfully updated references.
pub updated: Vec<RefUpdate>,
}
impl Applied<'_> {
pub fn append(&mut self, other: &mut Self) {
self.rejected.append(&mut other.rejected);
self.updated.append(&mut other.updated);
}
}
/// A set of [`Update`]s that are grouped by which namespace they are
/// affecting.
#[derive(Clone, Default, Debug)]
pub struct Updates<'a> {
pub tips: BTreeMap<PublicKey, Vec<Update<'a>>>,
}
impl<'a> Updates<'a> {
pub fn build(updates: impl IntoIterator<Item = (PublicKey, Update<'a>)>) -> Self {
let tips = updates.into_iter().fold(
BTreeMap::<_, Vec<Update<'a>>>::new(),
|mut tips, (remote, up)| {
tips.entry(remote)
.and_modify(|ups| ups.push(up.clone()))
.or_insert(vec![up]);
tips
},
);
Self { tips }
}
pub fn add(&mut self, remote: PublicKey, up: Update<'a>) {
self.tips
.entry(remote)
.and_modify(|ups| ups.push(up.clone()))
.or_insert(vec![up]);
}
pub fn append(&mut self, remote: PublicKey, mut new: Vec<Update<'a>>) {
self.tips
.entry(remote)
.and_modify(|ups| ups.append(&mut new))
.or_insert(new);
}
}
/// The policy to follow when an [`Update::Direct`] is not a
/// fast-forward.
#[derive(Clone, Copy, Debug)]
pub enum Policy {
/// Abort the entire transaction.
Abort,
/// Reject this update, but continue the transaction.
Reject,
/// Allow the update.
Allow,
}
/// An update that can be applied to a Git repository.
#[derive(Clone, Debug)]
pub enum Update<'a> {
/// Update a direct reference, i.e. a reference that points to an
/// object.
Direct {
/// The name of the reference that is being updated.
name: Namespaced<'a>,
/// The resulting target of the reference that is being
/// updated.
target: Oid,
/// Policy to apply when an [`Update`] would not apply as a
/// fast-forward.
no_ff: Policy,
},
/// Delete a reference.
Prune {
/// The name of the reference that is being deleted.
name: Namespaced<'a>,
/// The previous value of the reference.
///
/// It can either be a direct reference pointing to an
/// [`Oid`], or a symbolic reference pointing to a
/// [`Qualified`] reference name.
prev: Either<Oid, Qualified<'a>>,
},
}
impl<'a> Update<'a> {
pub fn refname(&self) -> &Namespaced<'a> {
match self {
Update::Direct { name, .. } => name,
Update::Prune { name, .. } => name,
}
}
pub fn into_owned<'b>(self) -> Update<'b> {
match self {
Self::Direct {
name,
target,
no_ff,
} => Update::Direct {
name: name.into_owned(),
target,
no_ff,
},
Self::Prune { name, prev } => Update::Prune {
name: name.into_owned(),
prev: prev.map_right(|q| q.into_owned()),
},
}
}
}