Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
chore: remove radicle-crdt and radicle-tools
Merged fintohaps opened 10 months ago
21 files changed +0 -1766 78ba263d d8d00666
modified Cargo.lock
@@ -2456,20 +2456,6 @@ dependencies = [
]

[[package]]
-
name = "radicle-crdt"
-
version = "0.1.0"
-
dependencies = [
-
 "fastrand",
-
 "num-traits",
-
 "qcheck",
-
 "qcheck-macros",
-
 "radicle-crypto",
-
 "serde",
-
 "tempfile",
-
 "thiserror 1.0.69",
-
]
-

-
[[package]]
name = "radicle-crypto"
version = "0.12.0"
dependencies = [
@@ -2660,17 +2646,6 @@ dependencies = [
]

[[package]]
-
name = "radicle-tools"
-
version = "0.9.0"
-
dependencies = [
-
 "anyhow",
-
 "radicle",
-
 "radicle-cli",
-
 "radicle-git-ext",
-
 "radicle-term",
-
]
-

-
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
deleted crates/radicle-crdt/Cargo.toml
@@ -1,24 +0,0 @@
-
[package]
-
name = "radicle-crdt"
-
version = "0.1.0"
-
license.workspace = true
-
edition.workspace = true
-
rust-version.workspace = true
-

-
[features]
-
test = ["fastrand", "qcheck"]
-

-
[dependencies]
-
fastrand = { workspace = true, optional = true }
-
num-traits = { version = "0.2.15", default-features = false, features = ["std"] }
-
qcheck = { workspace = true, optional = true }
-
radicle-crypto = { workspace = true }
-
serde = { workspace = true }
-
thiserror = { workspace = true }
-

-
[dev-dependencies]
-
fastrand = { workspace = true }
-
qcheck = { workspace = true }
-
qcheck-macros = { workspace = true }
-
radicle-crypto = { workspace = true, features = ["test"] }
-
tempfile = { workspace = true }
deleted crates/radicle-crdt/src/clock.rs
@@ -1,155 +0,0 @@
-
use std::fmt;
-
use std::str::FromStr;
-
use std::time::SystemTime;
-
use std::time::UNIX_EPOCH;
-

-
use num_traits::Bounded;
-
use serde::{Deserialize, Serialize};
-
use thiserror::Error;
-

-
use crate::ord::Max;
-
use crate::Semilattice as _;
-

-
/// Lamport clock.
-
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
-
#[serde(transparent)]
-
pub struct Lamport {
-
    counter: Max<u64>,
-
}
-

-
impl Lamport {
-
    /// Return the clock value.
-
    pub fn get(&self) -> u64 {
-
        *self.counter.get()
-
    }
-

-
    /// The initial value of the clock.
-
    pub fn initial() -> Self {
-
        Self::default()
-
    }
-

-
    /// Increment clock and return new value.
-
    /// Must be called before sending a message.
-
    pub fn tick(&mut self) -> Self {
-
        self.counter.incr();
-
        *self
-
    }
-

-
    /// Merge clock with another clock, and increment value.
-
    /// Must be called whenever a message is received.
-
    pub fn merge(&mut self, other: Self) -> Self {
-
        self.counter.merge(other.counter);
-
        self.tick()
-
    }
-

-
    /// Reset clock to default state.
-
    pub fn reset(&mut self) {
-
        self.counter = Max::default();
-
    }
-
}
-

-
impl fmt::Display for Lamport {
-
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-
        write!(f, "{}", self.get())
-
    }
-
}
-

-
impl From<u64> for Lamport {
-
    fn from(counter: u64) -> Self {
-
        Self {
-
            counter: Max::from(counter),
-
        }
-
    }
-
}
-

-
impl From<Lamport> for u64 {
-
    fn from(arg: Lamport) -> Self {
-
        arg.get()
-
    }
-
}
-

-
/// Error decoding an operation from an entry.
-
#[derive(Error, Debug)]
-
pub enum LamportError {
-
    #[error("invalid lamport clock value")]
-
    Invalid,
-
}
-

-
impl FromStr for Lamport {
-
    type Err = LamportError;
-

-
    fn from_str(s: &str) -> Result<Self, Self::Err> {
-
        Self::try_from(s)
-
    }
-
}
-

-
impl TryFrom<&str> for Lamport {
-
    type Error = LamportError;
-

-
    fn try_from(s: &str) -> Result<Self, Self::Error> {
-
        let v = s.parse::<u64>().map_err(|_| LamportError::Invalid)?;
-
        Ok(v.into())
-
    }
-
}
-

-
impl Bounded for Lamport {
-
    fn min_value() -> Self {
-
        Self::from(u64::MIN)
-
    }
-

-
    fn max_value() -> Self {
-
        Self::from(u64::MAX)
-
    }
-
}
-

-
/// Physical clock. Tracks real-time by the second.
-
#[derive(Debug, Default, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Serialize, Deserialize)]
-
#[serde(transparent)]
-
pub struct Physical {
-
    seconds: u64,
-
}
-

-
impl Physical {
-
    pub fn new(seconds: u64) -> Self {
-
        Self { seconds }
-
    }
-

-
    pub fn now() -> Self {
-
        #[allow(clippy::unwrap_used)] // Safe because Unix was already invented!
-
        let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
-

-
        Self {
-
            seconds: duration.as_secs(),
-
        }
-
    }
-

-
    pub fn as_secs(&self) -> u64 {
-
        self.seconds
-
    }
-
}
-

-
impl From<u64> for Physical {
-
    fn from(seconds: u64) -> Self {
-
        Self { seconds }
-
    }
-
}
-

-
impl std::ops::Add<u64> for Physical {
-
    type Output = Self;
-

-
    fn add(self, rhs: u64) -> Self::Output {
-
        Self {
-
            seconds: self.seconds + rhs,
-
        }
-
    }
-
}
-

-
impl Bounded for Physical {
-
    fn min_value() -> Self {
-
        Self { seconds: u64::MIN }
-
    }
-

-
    fn max_value() -> Self {
-
        Self { seconds: u64::MAX }
-
    }
-
}
deleted crates/radicle-crdt/src/gmap.rs
@@ -1,118 +0,0 @@
-
use std::collections::btree_map::{Entry, IntoIter, IntoKeys};
-
use std::collections::BTreeMap;
-
use std::ops::Deref;
-

-
use crate::Semilattice;
-

-
/// Grow-only map.
-
///
-
/// Conflicting elements are merged via the [`Semilattice`] instance.
-
#[derive(Debug, Clone, PartialEq, Eq)]
-
pub struct GMap<K, V> {
-
    inner: BTreeMap<K, V>,
-
}
-

-
impl<K: Ord, V: Semilattice> GMap<K, V> {
-
    pub fn singleton(key: K, value: V) -> Self {
-
        Self {
-
            inner: BTreeMap::from_iter([(key, value)]),
-
        }
-
    }
-

-
    pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
-
        self.inner.get_mut(key)
-
    }
-

-
    pub fn insert(&mut self, key: K, value: V) {
-
        match self.inner.entry(key) {
-
            Entry::Occupied(mut e) => {
-
                e.get_mut().merge(value);
-
            }
-
            Entry::Vacant(e) => {
-
                e.insert(value);
-
            }
-
        }
-
    }
-
}
-

-
impl<K, V> GMap<K, V> {
-
    pub fn into_keys(self) -> IntoKeys<K, V> {
-
        self.inner.into_keys()
-
    }
-
}
-

-
impl<K: Ord, V: Semilattice> FromIterator<(K, V)> for GMap<K, V> {
-
    fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
-
        let mut map = GMap::default();
-
        for (k, v) in iter.into_iter() {
-
            map.insert(k, v);
-
        }
-
        map
-
    }
-
}
-

-
impl<K: Ord, V: Semilattice> Extend<(K, V)> for GMap<K, V> {
-
    fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
-
        for (k, v) in iter.into_iter() {
-
            self.insert(k, v);
-
        }
-
    }
-
}
-

-
impl<K, V> IntoIterator for GMap<K, V> {
-
    type Item = (K, V);
-
    type IntoIter = IntoIter<K, V>;
-

-
    fn into_iter(self) -> Self::IntoIter {
-
        self.inner.into_iter()
-
    }
-
}
-

-
impl<K, V> Default for GMap<K, V> {
-
    fn default() -> Self {
-
        Self {
-
            inner: BTreeMap::default(),
-
        }
-
    }
-
}
-

-
impl<K: Ord, V: Semilattice> Semilattice for GMap<K, V> {
-
    fn merge(&mut self, other: Self) {
-
        for (k, v) in other.into_iter() {
-
            self.insert(k, v);
-
        }
-
    }
-
}
-

-
impl<K, V> Deref for GMap<K, V> {
-
    type Target = BTreeMap<K, V>;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.inner
-
    }
-
}
-

-
#[cfg(test)]
-
mod tests {
-
    use qcheck_macros::quickcheck;
-

-
    use super::*;
-
    use crate::ord::Max;
-

-
    #[quickcheck]
-
    fn prop_semilattice(
-
        a: Vec<(u8, Max<u8>)>,
-
        b: Vec<(u8, Max<u8>)>,
-
        c: Vec<(u8, Max<u8>)>,
-
        mix: Vec<(u8, Max<u8>)>,
-
    ) {
-
        let mut a = GMap::from_iter(a);
-
        let mut b = GMap::from_iter(b);
-
        let c = GMap::from_iter(c);
-

-
        a.extend(mix.clone());
-
        b.extend(mix);
-

-
        crate::test::assert_laws(&a, &b, &c);
-
    }
-
}
deleted crates/radicle-crdt/src/gset.rs
@@ -1,97 +0,0 @@
-
use std::collections::btree_map::{IntoKeys, Keys};
-
use std::ops::Deref;
-

-
use crate::GMap;
-
use crate::Semilattice;
-

-
/// Grow-only set.
-
#[derive(Debug, Clone, PartialEq, Eq)]
-
pub struct GSet<K> {
-
    inner: GMap<K, ()>,
-
}
-

-
impl<K: Ord> GSet<K> {
-
    pub fn singleton(key: K) -> Self {
-
        Self {
-
            inner: GMap::from_iter([(key, ())]),
-
        }
-
    }
-

-
    pub fn insert(&mut self, key: K) {
-
        self.inner.insert(key, ());
-
    }
-

-
    pub fn iter(&self) -> Keys<'_, K, ()> {
-
        self.inner.keys()
-
    }
-
}
-

-
impl<K: Ord> FromIterator<K> for GSet<K> {
-
    fn from_iter<I: IntoIterator<Item = K>>(iter: I) -> Self {
-
        let mut map = GSet::default();
-
        for k in iter.into_iter() {
-
            map.insert(k);
-
        }
-
        map
-
    }
-
}
-

-
impl<K: Ord> Extend<K> for GSet<K> {
-
    fn extend<I: IntoIterator<Item = K>>(&mut self, iter: I) {
-
        for k in iter.into_iter() {
-
            self.insert(k);
-
        }
-
    }
-
}
-

-
impl<K> IntoIterator for GSet<K> {
-
    type Item = K;
-
    type IntoIter = IntoKeys<K, ()>;
-

-
    fn into_iter(self) -> Self::IntoIter {
-
        self.inner.into_keys()
-
    }
-
}
-

-
impl<K> Default for GSet<K> {
-
    fn default() -> Self {
-
        Self {
-
            inner: GMap::default(),
-
        }
-
    }
-
}
-

-
impl<K: Ord> Semilattice for GSet<K> {
-
    fn merge(&mut self, other: Self) {
-
        for k in other.into_iter() {
-
            self.insert(k);
-
        }
-
    }
-
}
-

-
impl<K> Deref for GSet<K> {
-
    type Target = GMap<K, ()>;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.inner
-
    }
-
}
-

-
#[cfg(test)]
-
mod tests {
-
    use qcheck_macros::quickcheck;
-

-
    use super::*;
-

-
    #[quickcheck]
-
    fn prop_semilattice(a: Vec<u8>, b: Vec<u8>, c: Vec<u8>, mix: Vec<u8>) {
-
        let mut a = GSet::from_iter(a);
-
        let mut b = GSet::from_iter(b);
-
        let c = GSet::from_iter(c);
-

-
        a.extend(mix.clone());
-
        b.extend(mix);
-

-
        crate::test::assert_laws(&a, &b, &c);
-
    }
-
}
deleted crates/radicle-crdt/src/immutable.rs
@@ -1,52 +0,0 @@
-
use crate::Semilattice;
-

-
/// A [`Semilattice`] that panics when attempting to merge inequal elements.
-
/// Use this for types that will never merge.
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-
pub struct Immutable<T>(pub T);
-

-
impl<T> Immutable<T> {
-
    /// Create a new immutable object.
-
    pub fn new(inner: T) -> Self {
-
        Self(inner)
-
    }
-
}
-

-
impl<T> std::ops::Deref for Immutable<T> {
-
    type Target = T;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.0
-
    }
-
}
-

-
impl<T: PartialEq> Semilattice for Immutable<T> {
-
    fn merge(&mut self, other: Self) {
-
        if self.0 != other.0 {
-
            panic!("Immutable::merge: Cannot merge inequal objects");
-
        }
-
    }
-
}
-

-
#[cfg(test)]
-
mod test {
-
    use super::*;
-

-
    #[test]
-
    #[should_panic]
-
    fn test_merge_inequal() {
-
        let mut a = Immutable::new(0);
-
        let b = Immutable::new(1);
-

-
        a.merge(b);
-
    }
-

-
    #[test]
-
    fn test_merge_equal() {
-
        let mut a = Immutable::new(1);
-
        let b = Immutable::new(1);
-

-
        a.merge(b);
-
        assert_eq!(a, b);
-
    }
-
}
deleted crates/radicle-crdt/src/lib.rs
@@ -1,121 +0,0 @@
-
#![allow(clippy::collapsible_if)]
-
#![allow(clippy::bool_assert_comparison)]
-
#![allow(clippy::collapsible_else_if)]
-
#![allow(clippy::type_complexity)]
-
pub mod clock;
-
pub mod gmap;
-
pub mod gset;
-
pub mod immutable;
-
pub mod lwwmap;
-
pub mod lwwreg;
-
pub mod lwwset;
-
pub mod ord;
-
pub mod redactable;
-

-
#[cfg(any(test, feature = "test"))]
-
pub mod test;
-

-
////////////////////////////////////////////////////////////////////////////////
-

-
pub use clock::Lamport;
-
pub use gmap::GMap;
-
pub use gset::GSet;
-
pub use immutable::Immutable;
-
pub use lwwmap::LWWMap;
-
pub use lwwreg::LWWReg;
-
pub use lwwset::LWWSet;
-
pub use ord::{Max, Min};
-
pub use redactable::Redactable;
-

-
////////////////////////////////////////////////////////////////////////////////
-

-
/// A join-semilattice.
-
pub trait Semilattice: Sized {
-
    /// Merge an other semilattice into this one.
-
    ///
-
    /// This operation should obbey the semilattice laws and should thus be idempotent,
-
    /// associative and commutative.
-
    fn merge(&mut self, other: Self);
-

-
    /// Like [`Semilattice::merge`] but takes and returns a new semilattice.
-
    fn join(mut self, other: Self) -> Self {
-
        self.merge(other);
-
        self
-
    }
-
}
-

-
impl<T: Semilattice> Semilattice for Option<T> {
-
    fn merge(&mut self, other: Self) {
-
        match (self, other) {
-
            (this @ None, other @ Some(_)) => {
-
                *this = other;
-
            }
-
            (Some(ref mut a), Some(b)) => {
-
                a.merge(b);
-
            }
-
            (Some(_), None) => {}
-
            (None, None) => {}
-
        }
-
    }
-
}
-

-
impl Semilattice for () {
-
    fn merge(&mut self, _other: Self) {}
-
}
-

-
impl Semilattice for bool {
-
    fn merge(&mut self, other: Self) {
-
        match (&self, other) {
-
            (false, true) => *self = true,
-
            (true, false) => *self = true,
-
            (false, false) | (true, true) => {}
-
        }
-
    }
-
}
-

-
pub fn fold<S>(i: impl IntoIterator<Item = S>) -> S
-
where
-
    S: Semilattice + Default,
-
{
-
    i.into_iter().fold(S::default(), S::join)
-
}
-

-
#[cfg(test)]
-
mod tests {
-
    use crate::{test, Max, Min, Semilattice};
-
    use qcheck_macros::quickcheck;
-

-
    #[quickcheck]
-
    fn prop_option_laws(a: Max<u8>, b: Max<u8>, c: Max<u8>) {
-
        test::assert_laws(&a, &b, &c);
-
    }
-

-
    #[quickcheck]
-
    fn prop_bool_laws(a: bool, b: bool, c: bool) {
-
        test::assert_laws(&a, &b, &c);
-
    }
-

-
    #[test]
-
    fn test_bool() {
-
        assert_eq!(false.join(false), false);
-
        assert_eq!(true.join(true), true);
-
        assert_eq!(true.join(false), true);
-
        assert_eq!(false.join(true), true);
-
    }
-

-
    #[test]
-
    fn test_option() {
-
        assert_eq!(None::<()>.join(None), None);
-
        assert_eq!(None::<()>.join(Some(())), Some(()));
-
        assert_eq!(Some(()).join(None), Some(()));
-
        assert_eq!(Some(()).join(Some(())), Some(()));
-
        assert_eq!(
-
            Some(Max::from(0)).join(Some(Max::from(1))),
-
            Some(Max::from(1))
-
        );
-
        assert_eq!(
-
            Some(Min::from(0)).join(Some(Min::from(1))),
-
            Some(Min::from(0))
-
        );
-
    }
-
}
deleted crates/radicle-crdt/src/lwwmap.rs
@@ -1,187 +0,0 @@
-
use crate::gmap::GMap;
-
use crate::lwwreg::LWWReg;
-
use crate::{clock, Semilattice};
-

-
/// Last-Write-Wins Map.
-
///
-
/// In case a value is added and removed under a key at the same time,
-
/// the "add" takes precedence over the "remove".
-
#[derive(Debug, Clone, PartialEq, Eq)]
-
pub struct LWWMap<K, V, C = clock::Lamport> {
-
    inner: GMap<K, LWWReg<Option<V>, C>>,
-
}
-

-
impl<K: Ord, V: Semilattice, C: PartialOrd + Ord> LWWMap<K, V, C> {
-
    pub fn singleton(key: K, value: V, clock: C) -> Self {
-
        Self {
-
            inner: GMap::singleton(key, LWWReg::new(Some(value), clock)),
-
        }
-
    }
-

-
    pub fn get(&self, key: &K) -> Option<&V> {
-
        let Some(value) = self.inner.get(key) else {
-
            // If the element was never added, return nothing.
-
            return None;
-
        };
-
        value.get().as_ref()
-
    }
-

-
    pub fn insert(&mut self, key: K, value: V, clock: C) {
-
        self.inner.insert(key, LWWReg::new(Some(value), clock));
-
    }
-

-
    pub fn remove(&mut self, key: K, clock: C) {
-
        self.inner.insert(key, LWWReg::new(None, clock));
-
    }
-

-
    pub fn contains_key(&self, key: &K) -> bool {
-
        let Some(value) = self.inner.get(key) else {
-
            // If the element was never added, return false.
-
            return false;
-
        };
-
        value.get().is_some()
-
    }
-

-
    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
-
        self.inner
-
            .iter()
-
            .filter_map(|(k, v)| v.get().as_ref().map(|v| (k, v)))
-
    }
-

-
    pub fn len(&self) -> usize {
-
        self.iter().count()
-
    }
-

-
    pub fn is_empty(&self) -> bool {
-
        self.iter().next().is_none()
-
    }
-
}
-

-
impl<K, V, C> Default for LWWMap<K, V, C> {
-
    fn default() -> Self {
-
        Self {
-
            inner: GMap::default(),
-
        }
-
    }
-
}
-

-
impl<K: Ord, V: Semilattice, C: Ord> FromIterator<(K, V, C)> for LWWMap<K, V, C> {
-
    fn from_iter<I: IntoIterator<Item = (K, V, C)>>(iter: I) -> Self {
-
        let mut map = LWWMap::default();
-
        for (k, v, c) in iter.into_iter() {
-
            map.insert(k, v, c);
-
        }
-
        map
-
    }
-
}
-

-
impl<K: Ord, V: Semilattice, C: Ord> Extend<(K, V, C)> for LWWMap<K, V, C> {
-
    fn extend<I: IntoIterator<Item = (K, V, C)>>(&mut self, iter: I) {
-
        for (k, v, c) in iter.into_iter() {
-
            self.insert(k, v, c);
-
        }
-
    }
-
}
-

-
impl<K, V, C> Semilattice for LWWMap<K, V, C>
-
where
-
    K: Ord,
-
    V: Semilattice,
-
    C: Ord,
-
{
-
    fn merge(&mut self, other: Self) {
-
        self.inner.merge(other.inner);
-
    }
-
}
-

-
#[cfg(test)]
-
mod tests {
-
    use qcheck_macros::quickcheck;
-

-
    use super::*;
-
    use crate::ord::Max;
-

-
    #[quickcheck]
-
    fn prop_semilattice(
-
        a: Vec<(u8, Max<u8>, u16)>,
-
        b: Vec<(u8, Max<u8>, u16)>,
-
        c: Vec<(u8, Max<u8>, u16)>,
-
        mix: Vec<(u8, Max<u8>, u16)>,
-
    ) {
-
        let mut a = LWWMap::from_iter(a);
-
        let mut b = LWWMap::from_iter(b);
-
        let c = LWWMap::from_iter(c);
-

-
        a.extend(mix.clone());
-
        b.extend(mix);
-

-
        crate::test::assert_laws(&a, &b, &c);
-
    }
-

-
    #[test]
-
    fn test_insert() {
-
        let mut map = LWWMap::default();
-

-
        map.insert('a', Max::from(1), 0);
-
        map.insert('b', Max::from(2), 0);
-
        map.insert('c', Max::from(3), 0);
-

-
        assert_eq!(map.get(&'a'), Some(&Max::from(1)));
-
        assert_eq!(map.get(&'b'), Some(&Max::from(2)));
-
        assert_eq!(map.get(&'?'), None);
-

-
        let values = map.iter().collect::<Vec<(&char, &Max<u8>)>>();
-
        assert!(values.contains(&(&'a', &Max::from(1))));
-
        assert!(values.contains(&(&'b', &Max::from(2))));
-
        assert!(values.contains(&(&'c', &Max::from(3))));
-
        assert_eq!(values.len(), 3);
-
    }
-

-
    #[test]
-
    fn test_insert_remove() {
-
        let mut map = LWWMap::default();
-

-
        map.insert('a', Max::from("alice"), 1);
-
        assert!(map.contains_key(&'a'));
-

-
        map.remove('a', 0);
-
        assert!(map.contains_key(&'a'));
-

-
        map.remove('a', 1);
-
        assert!(map.contains_key(&'a')); // Add takes precedence over remove.
-
        assert!(map.iter().any(|(c, _)| *c == 'a'));
-

-
        map.remove('a', 2);
-
        assert!(!map.contains_key(&'a'));
-
        assert!(!map.iter().any(|(c, _)| *c == 'a'));
-
    }
-

-
    #[test]
-
    fn test_is_empty() {
-
        let mut map = LWWMap::default();
-
        assert!(map.is_empty());
-

-
        map.insert('a', Max::from("alice"), 1);
-
        assert!(!map.is_empty());
-

-
        map.remove('a', 2);
-
        assert!(map.is_empty());
-
    }
-

-
    #[test]
-
    fn test_remove_insert() {
-
        let mut map = LWWMap::default();
-

-
        map.insert('a', Max::from("alice"), 1);
-
        assert_eq!(map.get(&'a'), Some(&Max::from("alice")));
-

-
        map.remove('a', 2);
-
        assert!(!map.contains_key(&'a'));
-

-
        map.insert('a', Max::from("alice"), 1);
-
        assert!(!map.contains_key(&'a'));
-

-
        map.insert('a', Max::from("amy"), 2);
-
        assert_eq!(map.get(&'a'), Some(&Max::from("amy")));
-
    }
-
}
deleted crates/radicle-crdt/src/lwwreg.rs
@@ -1,134 +0,0 @@
-
use num_traits::Bounded;
-

-
use crate::clock;
-
use crate::ord::Max;
-
use crate::Semilattice;
-

-
/// Last-Write-Wins Register.
-
///
-
/// In case of conflict, uses the [`Semilattice`] instance of `T` to merge.
-
#[derive(Debug, Clone, PartialEq, Eq)]
-
pub struct LWWReg<T, C = clock::Lamport> {
-
    clock: Max<C>,
-
    value: T,
-
}
-

-
impl<T: Semilattice, C: PartialOrd> LWWReg<T, C> {
-
    pub fn initial(value: T) -> Self
-
    where
-
        C: Default,
-
    {
-
        Self {
-
            clock: Max::from(C::default()),
-
            value,
-
        }
-
    }
-

-
    pub fn new(value: T, clock: C) -> Self {
-
        Self {
-
            clock: Max::from(clock),
-
            value,
-
        }
-
    }
-

-
    pub fn set(&mut self, value: impl Into<T>, clock: C) {
-
        let clock = Max::from(clock);
-
        let value = value.into();
-

-
        if clock == self.clock {
-
            self.value.merge(value);
-
        } else if clock > self.clock {
-
            self.clock.merge(clock);
-
            self.value = value;
-
        }
-
    }
-

-
    pub fn get(&self) -> &T {
-
        &self.value
-
    }
-

-
    pub fn clock(&self) -> &Max<C> {
-
        &self.clock
-
    }
-

-
    pub fn into_inner(self) -> (T, C) {
-
        (self.value, self.clock.into_inner())
-
    }
-
}
-

-
impl<T: Default, C: Default + Bounded> Default for LWWReg<T, C> {
-
    fn default() -> Self {
-
        Self {
-
            clock: Max::default(),
-
            value: T::default(),
-
        }
-
    }
-
}
-

-
impl<T, C> Semilattice for LWWReg<T, C>
-
where
-
    T: Semilattice,
-
    C: PartialOrd,
-
{
-
    fn merge(&mut self, other: Self) {
-
        self.set(other.value, other.clock.into_inner());
-
    }
-
}
-

-
#[cfg(test)]
-
mod tests {
-
    use qcheck_macros::quickcheck;
-

-
    use super::*;
-
    use crate::Min;
-

-
    #[quickcheck]
-
    fn prop_semilattice(a: (Max<u8>, u16), b: (Max<u8>, u16), c: (Max<u8>, u16)) {
-
        let a = LWWReg::new(a.0, a.1);
-
        let b = LWWReg::new(b.0, b.1);
-
        let c = LWWReg::new(c.0, c.1);
-

-
        crate::test::assert_laws(&a, &b, &c);
-
    }
-

-
    #[test]
-
    fn test_merge() {
-
        let a = LWWReg::new(Max::from(0), 0);
-
        let b = LWWReg::new(Max::from(1), 0);
-

-
        assert_eq!(a.join(b).get(), &Max::from(1));
-

-
        let a = LWWReg::new(Min::from(0), 0);
-
        let b = LWWReg::new(Min::from(1), 0);
-

-
        assert_eq!(a.join(b).get(), &Min::from(0));
-
    }
-

-
    #[test]
-
    fn test_set_get() {
-
        let mut reg = LWWReg::new(Max::from(42), 1);
-
        assert_eq!(*reg.get(), Max::from(42));
-

-
        reg.set(84, 0);
-
        assert_eq!(*reg.get(), Max::from(42));
-

-
        reg.set(84, 2);
-
        assert_eq!(*reg.get(), Max::from(84));
-

-
        // Smaller value, same clock: smaller value loses.
-
        reg.set(42, 2);
-
        assert_eq!(*reg.get(), Max::from(84));
-

-
        // Bigger value, same clock: bigger value wins.
-
        reg.set(168, 2);
-
        assert_eq!(*reg.get(), Max::from(168));
-

-
        // Smaller value, newer clock: smaller value wins.
-
        reg.set(42, 3);
-
        assert_eq!(*reg.get(), Max::from(42));
-

-
        // Same value, newer clock: newer clock is set.
-
        reg.set(42, 4);
-
        assert_eq!(*reg.clock(), Max::from(4));
-
    }
-
}
deleted crates/radicle-crdt/src/lwwset.rs
@@ -1,161 +0,0 @@
-
use crate::clock;
-
use crate::{lwwmap::LWWMap, Semilattice};
-

-
/// Last-Write-Wins Set.
-
///
-
/// In case the same value is added and removed at the same time,
-
/// the "add" takes precedence over the "remove".
-
#[derive(Debug, Clone, PartialEq, Eq)]
-
pub struct LWWSet<T, C = clock::Lamport> {
-
    inner: LWWMap<T, (), C>,
-
}
-

-
impl<T: Ord, C: Ord> LWWSet<T, C> {
-
    pub fn singleton(value: T, clock: C) -> Self {
-
        Self {
-
            inner: LWWMap::from_iter([(value, (), clock)]),
-
        }
-
    }
-

-
    pub fn insert(&mut self, value: T, clock: C) {
-
        self.inner.insert(value, (), clock);
-
    }
-

-
    pub fn remove(&mut self, value: T, clock: C) {
-
        self.inner.remove(value, clock);
-
    }
-

-
    pub fn contains(&self, value: &T) -> bool {
-
        self.inner.contains_key(value)
-
    }
-

-
    pub fn iter(&self) -> impl Iterator<Item = &T> {
-
        self.inner.iter().map(|(k, _)| k)
-
    }
-

-
    pub fn is_empty(&self) -> bool {
-
        self.inner.is_empty()
-
    }
-
}
-

-
impl<T, C> Default for LWWSet<T, C> {
-
    fn default() -> Self {
-
        Self {
-
            inner: LWWMap::default(),
-
        }
-
    }
-
}
-

-
impl<T: Ord, C: Ord> FromIterator<(T, C)> for LWWSet<T, C> {
-
    fn from_iter<I: IntoIterator<Item = (T, C)>>(iter: I) -> Self {
-
        let mut set = LWWSet::default();
-
        for (v, c) in iter.into_iter() {
-
            set.insert(v, c);
-
        }
-
        set
-
    }
-
}
-

-
impl<T: Ord, C: Ord> Extend<(T, C)> for LWWSet<T, C> {
-
    fn extend<I: IntoIterator<Item = (T, C)>>(&mut self, iter: I) {
-
        for (v, c) in iter.into_iter() {
-
            self.insert(v, c);
-
        }
-
    }
-
}
-

-
impl<T, C> Semilattice for LWWSet<T, C>
-
where
-
    T: Ord,
-
    C: Ord + Default,
-
{
-
    fn merge(&mut self, other: Self) {
-
        self.inner.merge(other.inner);
-
    }
-
}
-

-
#[cfg(test)]
-
mod tests {
-
    use super::*;
-
    use qcheck_macros::quickcheck;
-

-
    #[quickcheck]
-
    fn prop_semilattice(
-
        a: Vec<(u8, u16)>,
-
        b: Vec<(u8, u16)>,
-
        c: Vec<(u8, u16)>,
-
        mix: Vec<(u8, u16)>,
-
    ) {
-
        let mut a = LWWSet::from_iter(a);
-
        let mut b = LWWSet::from_iter(b);
-
        let c = LWWSet::from_iter(c);
-

-
        a.extend(mix.clone());
-
        b.extend(mix);
-

-
        crate::test::assert_laws(&a, &b, &c);
-
    }
-

-
    #[test]
-
    fn test_insert() {
-
        let mut set = LWWSet::default();
-

-
        set.insert('a', 0);
-
        set.insert('b', 0);
-
        set.insert('c', 0);
-

-
        assert!(set.contains(&'a'));
-
        assert!(set.contains(&'b'));
-
        assert!(!set.contains(&'?'));
-

-
        let values = set.iter().cloned().collect::<Vec<_>>();
-
        assert!(values.contains(&'a'));
-
        assert!(values.contains(&'b'));
-
        assert!(values.contains(&'c'));
-
        assert_eq!(values.len(), 3);
-
    }
-

-
    #[test]
-
    fn test_insert_remove() {
-
        let mut set = LWWSet::default();
-

-
        set.insert('a', 1);
-
        assert!(set.contains(&'a'));
-

-
        set.remove('a', 0);
-
        assert!(set.contains(&'a'));
-

-
        set.remove('a', 1);
-
        assert!(set.contains(&'a')); // Add takes precedence over remove.
-
        assert!(set.iter().any(|c| *c == 'a'));
-

-
        set.remove('a', 2);
-
        assert!(!set.contains(&'a'));
-
        assert!(!set.iter().any(|c| *c == 'a'));
-

-
        set.insert('b', 3);
-
        set.remove('b', 3);
-
        assert!(set.contains(&'b')); // Insert precedence.
-

-
        set.remove('c', 3);
-
        set.insert('c', 3);
-
        assert!(set.contains(&'c')); // Insert precedence.
-
    }
-

-
    #[test]
-
    fn test_remove_insert() {
-
        let mut set = LWWSet::default();
-

-
        set.insert('a', 1);
-
        assert!(set.contains(&'a'));
-

-
        set.remove('a', 2);
-
        assert!(!set.contains(&'a'));
-

-
        set.insert('a', 1);
-
        assert!(!set.contains(&'a'));
-

-
        set.insert('a', 2);
-
        assert!(set.contains(&'a'));
-
    }
-
}
deleted crates/radicle-crdt/src/ord.rs
@@ -1,129 +0,0 @@
-
use std::{cmp, ops};
-

-
use num_traits::Bounded;
-
use serde::{Deserialize, Serialize};
-

-
use crate::Semilattice;
-

-
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
-
#[serde(transparent)]
-
pub struct Max<T>(T);
-

-
impl<T> Max<T> {
-
    pub fn get(&self) -> &T {
-
        &self.0
-
    }
-

-
    pub fn into_inner(self) -> T {
-
        self.0
-
    }
-
}
-

-
impl<T: num_traits::SaturatingAdd + num_traits::One> Max<T> {
-
    pub fn incr(&mut self) {
-
        self.0 = self.0.saturating_add(&T::one());
-
    }
-
}
-

-
impl<T> Default for Max<T>
-
where
-
    T: Bounded,
-
{
-
    fn default() -> Self {
-
        Self(T::min_value())
-
    }
-
}
-

-
impl<T> ops::Deref for Max<T> {
-
    type Target = T;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.0
-
    }
-
}
-

-
impl<T> From<T> for Max<T> {
-
    fn from(t: T) -> Self {
-
        Self(t)
-
    }
-
}
-

-
impl<T: PartialOrd> Semilattice for Max<T> {
-
    fn merge(&mut self, other: Self) {
-
        if other.0 > self.0 {
-
            self.0 = other.0;
-
        }
-
    }
-
}
-

-
impl<T: Bounded> Bounded for Max<T> {
-
    fn min_value() -> Self {
-
        Self::from(T::min_value())
-
    }
-

-
    fn max_value() -> Self {
-
        Self::from(T::max_value())
-
    }
-
}
-

-
#[allow(clippy::derive_ord_xor_partial_ord)]
-
#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, Serialize, Deserialize)]
-
#[serde(transparent)]
-
pub struct Min<T>(pub T);
-

-
impl<T> Default for Min<T>
-
where
-
    T: Bounded,
-
{
-
    fn default() -> Self {
-
        Self(T::max_value())
-
    }
-
}
-

-
impl<T> ops::Deref for Min<T> {
-
    type Target = T;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.0
-
    }
-
}
-

-
impl<T> From<T> for Min<T> {
-
    fn from(t: T) -> Self {
-
        Self(t)
-
    }
-
}
-

-
impl<T> cmp::PartialOrd for Min<T>
-
where
-
    T: PartialOrd,
-
{
-
    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
-
        other.0.partial_cmp(&self.0)
-
    }
-
}
-

-
impl<T: PartialOrd> Semilattice for Min<T> {
-
    fn merge(&mut self, other: Self) {
-
        if other.0 < self.0 {
-
            self.0 = other.0;
-
        }
-
    }
-
}
-

-
#[cfg(any(test, feature = "test"))]
-
mod arbitrary {
-
    use super::*;
-

-
    impl<T: qcheck::Arbitrary> qcheck::Arbitrary for Max<T> {
-
        fn arbitrary(g: &mut qcheck::Gen) -> Self {
-
            Self::from(T::arbitrary(g))
-
        }
-
    }
-

-
    impl<T: qcheck::Arbitrary> qcheck::Arbitrary for Min<T> {
-
        fn arbitrary(g: &mut qcheck::Gen) -> Self {
-
            Self::from(T::arbitrary(g))
-
        }
-
    }
-
}
deleted crates/radicle-crdt/src/redactable.rs
@@ -1,111 +0,0 @@
-
use crate::Semilattice;
-

-
/// An object that can be either present or removed.
-
///
-
/// The "redacted" state is the top-most element and takes precedence
-
/// over other states.
-
///
-
/// There is no `Default` instance, since this is not a "bounded" semilattice.
-
///
-
/// Nb. The merge rules are such that if two redactables with different
-
/// values present are merged; the result is redacted. This is the preserve
-
/// the semilattice laws.
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-
pub enum Redactable<T> {
-
    /// When the object is present.
-
    Present(T),
-
    /// When the object has been removed.
-
    Redacted,
-
}
-

-
impl<T> Redactable<T> {
-
    pub fn get(&self) -> Option<&T> {
-
        match self {
-
            Self::Present(val) => Some(val),
-
            Self::Redacted => None,
-
        }
-
    }
-

-
    pub fn get_mut(&mut self) -> Option<&mut T> {
-
        match self {
-
            Self::Present(ref mut val) => Some(val),
-
            Self::Redacted => None,
-
        }
-
    }
-
}
-

-
impl<T> From<Option<T>> for Redactable<T> {
-
    fn from(option: Option<T>) -> Self {
-
        match option {
-
            Some(v) => Self::Present(v),
-
            None => Self::Redacted,
-
        }
-
    }
-
}
-

-
impl<T> From<Redactable<T>> for Option<T> {
-
    fn from(redactable: Redactable<T>) -> Self {
-
        match redactable {
-
            Redactable::Present(v) => Some(v),
-
            Redactable::Redacted => None,
-
        }
-
    }
-
}
-

-
impl<'a, T> From<&'a Redactable<T>> for Option<&'a T> {
-
    fn from(redactable: &'a Redactable<T>) -> Self {
-
        redactable.get()
-
    }
-
}
-

-
impl<T: PartialEq> Semilattice for Redactable<T> {
-
    fn merge(&mut self, other: Self) {
-
        match (&self, other) {
-
            (Self::Redacted, _) => {}
-
            (Self::Present(_), Self::Redacted) => {
-
                *self = Self::Redacted;
-
            }
-
            (Self::Present(a), Self::Present(b)) => {
-
                if a != &b {
-
                    *self = Self::Redacted;
-
                }
-
            }
-
        }
-
    }
-
}
-

-
#[cfg(test)]
-
mod test {
-
    use qcheck_macros::quickcheck;
-

-
    use super::*;
-
    use crate::test;
-

-
    #[quickcheck]
-
    fn prop_invariants(a: Option<u8>, b: Option<u8>, c: Option<u8>) {
-
        let a = Redactable::from(a);
-
        let b = Redactable::from(b);
-
        let c = Redactable::from(c);
-

-
        test::assert_laws(&a, &b, &c);
-
    }
-

-
    #[test]
-
    fn test_redacted() {
-
        let a = Redactable::Present(0);
-
        let b = Redactable::Redacted;
-

-
        assert_eq!(a.join(b), Redactable::Redacted);
-
        assert_eq!(b.join(a), Redactable::Redacted);
-
        assert_eq!(a.join(a), a);
-
    }
-

-
    #[test]
-
    fn test_both_present() {
-
        let a = Redactable::Present(0);
-
        let b = Redactable::Present(1);
-

-
        assert_eq!(a.join(b), Redactable::Redacted);
-
        assert_eq!(a.join(b), b.join(a));
-
    }
-
}
deleted crates/radicle-crdt/src/test.rs
@@ -1,111 +0,0 @@
-
use std::fmt::Debug;
-
use std::rc::Rc;
-

-
use super::*;
-

-
/// Generate test values following a weight distribution.
-
pub struct WeightedGenerator<'a, T, C> {
-
    cases: Vec<Rc<dyn Fn(&mut C, fastrand::Rng) -> Option<T> + 'a>>,
-
    rng: fastrand::Rng,
-
    ctx: C,
-
}
-

-
impl<T, C> Iterator for WeightedGenerator<'_, T, C> {
-
    type Item = T;
-

-
    fn next(&mut self) -> Option<Self::Item> {
-
        let cases = self.cases.len();
-

-
        loop {
-
            let r = self.rng.usize(0..cases);
-
            let g = &self.cases[r];
-

-
            if let Some(val) = g(&mut self.ctx, self.rng.clone()) {
-
                return Some(val);
-
            }
-
        }
-
    }
-
}
-

-
impl<'a, T, C: Default> WeightedGenerator<'a, T, C> {
-
    /// Create a new distribution.
-
    pub fn new(rng: fastrand::Rng) -> Self {
-
        Self {
-
            cases: Vec::new(),
-
            rng,
-
            ctx: C::default(),
-
        }
-
    }
-

-
    /// Add a new variant with a given weight and generator function.
-
    pub fn variant(
-
        mut self,
-
        weight: usize,
-
        generator: impl Fn(&mut C, fastrand::Rng) -> Option<T> + 'a,
-
    ) -> Self {
-
        let gen = Rc::new(generator);
-
        for _ in 0..weight {
-
            self.cases.push(gen.clone());
-
        }
-
        self
-
    }
-
}
-

-
/// Assert semilattice ACI laws.
-
pub fn assert_laws<S: Debug + Semilattice + PartialEq + Clone>(a: &S, b: &S, c: &S) {
-
    assert_associative(a, b, c);
-
    assert_commutative(a, b);
-
    assert_commutative(b, c);
-
    assert_idempotent(a);
-
    assert_idempotent(b);
-
    assert_idempotent(c);
-
}
-

-
pub fn assert_associative<S: Debug + Semilattice + PartialEq + Clone>(a: &S, b: &S, c: &S) {
-
    // (a ^ b) ^ c
-
    let s1 = a.clone().join(b.clone()).join(c.clone());
-
    // a ^ (b ^ c)
-
    let s2 = a.clone().join(b.clone().join(c.clone()));
-
    // (a ^ b) ^ c = a ^ (b ^ c)
-
    assert_eq!(s1, s2, "associativity");
-
}
-

-
pub fn assert_commutative<S: Debug + Semilattice + PartialEq + Clone>(a: &S, b: &S) {
-
    // a ^ b
-
    let s1 = a.clone().join(b.clone());
-
    // b ^ a
-
    let s2 = b.clone().join(a.clone());
-
    // a ^ b = b ^ a
-
    assert_eq!(s1, s2, "commutativity");
-
}
-

-
pub fn assert_idempotent<S: Debug + Semilattice + PartialEq + Clone>(a: &S) {
-
    // a ^ a
-
    let s1 = a.clone().join(a.clone());
-
    // a
-
    let s2 = a.clone();
-
    // a ^ a = a
-
    assert_eq!(s1, s2, "idempotence");
-
}
-

-
#[test]
-
fn test_generator() {
-
    let rng = fastrand::Rng::with_seed(0);
-
    let dist = WeightedGenerator::<char, ()>::new(rng)
-
        .variant(1, |_, _| Some('a'))
-
        .variant(2, |_, _| Some('b'))
-
        .variant(4, |_, _| Some('c'))
-
        .variant(8, |_, _| Some('d'));
-

-
    let values = dist.take(1000).collect::<Vec<_>>();
-

-
    let a = values.iter().filter(|c| **c == 'a').count();
-
    let b = values.iter().filter(|c| **c == 'b').count();
-
    let c = values.iter().filter(|c| **c == 'c').count();
-
    let d = values.iter().filter(|c| **c == 'd').count();
-

-
    assert_eq!(a, 63);
-
    assert_eq!(b, 151);
-
    assert_eq!(c, 255);
-
    assert_eq!(d, 531);
-
}
deleted crates/radicle-tools/Cargo.toml
@@ -1,44 +0,0 @@
-
[package]
-
name = "radicle-tools"
-
license.workspace = true
-
version = "0.9.0"
-
authors = ["Alexis Sellier <alexis@radicle.xyz>"]
-
edition.workspace = true
-
rust-version.workspace = true
-

-
[[bin]]
-
name = "rad-init"
-
path = "src/rad-init.rs"
-

-
[[bin]]
-
name = "rad-self"
-
path = "src/rad-self.rs"
-

-
[[bin]]
-
name = "rad-merge"
-
path = "src/rad-merge.rs"
-

-
[[bin]]
-
name = "rad-set-canonical-refs"
-
path = "src/rad-set-canonical-refs.rs"
-

-
[[bin]]
-
name = "rad-push"
-
path = "src/rad-push.rs"
-

-
[[bin]]
-
name = "rad-agent"
-
path = "src/rad-agent.rs"
-

-
[[bin]]
-
name = "rad-cli-demo"
-
path = "src/rad-cli-demo.rs"
-

-
[dependencies]
-
anyhow = { workspace = true }
-
radicle = { workspace = true }
-
radicle-cli = { workspace = true }
-
# N.b. this is required to use macros, even though it's re-exported
-
# through radicle
-
radicle-git-ext = { workspace = true, features = ["serde"] }
-
radicle-term = { workspace = true }

\ No newline at end of file
deleted crates/radicle-tools/src/rad-agent.rs
@@ -1,60 +0,0 @@
-
use anyhow::{anyhow, Context as _};
-
use radicle::{crypto, crypto::ssh};
-
use std::io::prelude::*;
-
use std::{env, io};
-

-
fn main() -> anyhow::Result<()> {
-
    let profile = radicle::Profile::load()?;
-
    let mut agent = ssh::agent::Agent::connect()?;
-

-
    println!("key: {}", ssh::fmt::key(profile.id()));
-
    println!("hash: {}", ssh::fmt::fingerprint(profile.id()));
-

-
    match env::args().nth(1).as_deref() {
-
        Some("add") => {
-
            print!("passphrase: ");
-
            io::stdout().flush()?;
-

-
            let mut passphrase = String::new();
-
            io::stdin().lock().read_line(&mut passphrase)?;
-

-
            let passphrase = passphrase.trim().to_owned().into();
-
            let secret = profile
-
                .keystore
-
                .secret_key(Some(passphrase))?
-
                .ok_or_else(|| anyhow!("Key not found in {:?}", profile.keystore.path()))?;
-

-
            agent.register(&secret)?;
-
            println!("ok");
-
        }
-
        Some("remove") => {
-
            agent.unregister(profile.id())?;
-
            println!("ok");
-
        }
-
        Some("remove-all") => {
-
            agent.unregister_all()?;
-
            println!("ok");
-
        }
-
        Some("sign") => {
-
            let mut stdin = Vec::new();
-
            io::stdin().read_to_end(&mut stdin)?;
-

-
            let sig = agent.sign(profile.id(), &stdin).context("Signing failed")?;
-
            let sig = crypto::Signature::from(sig);
-

-
            println!("{}", &sig);
-
        }
-
        Some(other) => {
-
            anyhow::bail!("Unknown command `{}`", other);
-
        }
-
        None => {
-
            if agent.signer(profile.public_key).is_ready()? {
-
                println!("ready: yes");
-
            } else {
-
                println!("ready: no");
-
            }
-
        }
-
    }
-

-
    Ok(())
-
}
deleted crates/radicle-tools/src/rad-cli-demo.rs
@@ -1,88 +0,0 @@
-
use std::{thread, time};
-

-
use radicle_cli::terminal;
-

-
fn main() -> anyhow::Result<()> {
-
    let demo = terminal::io::select(
-
        "Choose something to try out:",
-
        &[
-
            "confirm",
-
            "pager",
-
            "spinner",
-
            "spinner-drop",
-
            "spinner-error",
-
            "editor",
-
            "prompt",
-
        ],
-
        "Choose wisely!",
-
    )?;
-

-
    match *demo {
-
        "confirm" => {
-
            if terminal::confirm("Would you like to proceed?") {
-
                terminal::success!("You said 'yes'");
-
            }
-
        }
-
        "pager" => {
-
            let mut table = radicle_term::Table::<1, radicle_term::Label>::new(
-
                radicle_term::TableOptions::bordered(),
-
            );
-
            let rows = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/rad-cli-demo.rs"));
-

-
            for row in rows.lines() {
-
                table.push([row.into()]);
-
            }
-
            radicle_term::pager::page(table)?;
-
        }
-
        "editor" => {
-
            let output = terminal::editor::Editor::comment()
-
                .extension("rs")
-
                .initial("// Enter code here.")?
-
                .edit();
-

-
            match output {
-
                Ok(Some(s)) => {
-
                    terminal::info!("You entered:");
-
                    terminal::blob(s);
-
                }
-
                Ok(None) => {
-
                    terminal::info!("You didn't enter anything.");
-
                }
-
                Err(e) => {
-
                    return Err(e.into());
-
                }
-
            }
-
        }
-
        "spinner" => {
-
            let mut spinner = terminal::spinner("Spinning turbines..");
-
            thread::sleep(time::Duration::from_secs(1));
-
            spinner.message("Still spinning..");
-
            thread::sleep(time::Duration::from_secs(1));
-
            spinner.message("Almost done..");
-
            thread::sleep(time::Duration::from_secs(1));
-
            spinner.message("Done.");
-

-
            spinner.finish();
-
        }
-
        "spinner-drop" => {
-
            let _spinner = terminal::spinner("Spinning turbines..");
-
            thread::sleep(time::Duration::from_secs(3));
-
        }
-
        "spinner-error" => {
-
            let spinner = terminal::spinner("Spinning turbines..");
-
            thread::sleep(time::Duration::from_secs(3));
-
            spinner.error("broken turbine");
-
        }
-
        "prompt" => {
-
            let fruit = terminal::io::select(
-
                "Enter your favorite fruit:",
-
                &["apple", "pear", "banana", "strawberry"],
-
                "Choose wisely!",
-
            )?;
-
            terminal::success!("You have chosen '{fruit}'");
-
        }
-
        _ => {}
-
    }
-

-
    Ok(())
-
}
deleted crates/radicle-tools/src/rad-init.rs
@@ -1,24 +0,0 @@
-
use std::path::Path;
-

-
use radicle::{git, identity::Visibility, Profile};
-

-
fn main() -> anyhow::Result<()> {
-
    let cwd = Path::new(".").canonicalize()?;
-
    let name = cwd.file_name().unwrap().to_string_lossy().to_string();
-
    let repo = radicle::git::raw::Repository::open(cwd)?;
-
    let profile = Profile::load()?;
-
    let signer = profile.signer()?;
-
    let (id, _, _) = radicle::rad::init(
-
        &repo,
-
        name.try_into()?,
-
        "",
-
        git::refname!("master"),
-
        Visibility::default(),
-
        &signer,
-
        &profile.storage,
-
    )?;
-

-
    println!("ok: {id}");
-

-
    Ok(())
-
}
deleted crates/radicle-tools/src/rad-merge.rs
@@ -1,65 +0,0 @@
-
use std::collections::HashSet;
-
use std::env;
-

-
use anyhow::{anyhow, bail};
-
use radicle::cob::patch::{PatchId, RevisionId};
-
use radicle::git::Oid;
-
use radicle::storage::ReadStorage;
-
use radicle_cli::terminal as term;
-

-
fn main() -> anyhow::Result<()> {
-
    let args = env::args().skip(1).collect::<Vec<_>>();
-
    let (pid, rev) = match args.as_slice() {
-
        [pid, rev] => {
-
            let pid: PatchId = pid.parse()?;
-
            let rev: Oid = rev.parse()?;
-

-
            (pid, Some(RevisionId::from(rev)))
-
        }
-
        [pid] => {
-
            let pid: PatchId = pid.parse()?;
-

-
            (pid, None)
-
        }
-
        _ => bail!("usage: rad-merge <patch-id> [<revision-id>]"),
-
    };
-
    let profile = radicle::Profile::load()?;
-
    let (working, rid) = radicle::rad::cwd()?;
-
    let stored = profile.storage.repository(rid)?;
-
    let mut patches = profile.patches_mut(&stored)?;
-
    let mut patch = patches.get_mut(&pid)?;
-

-
    if patch.is_merged() {
-
        anyhow::bail!("fatal: patch {pid} is already merged");
-
    }
-
    let (revision, r) = if let Some(id) = rev {
-
        let r = patch
-
            .revision(&id)
-
            .ok_or_else(|| anyhow!("revision {id} not found"))?;
-
        (id, r)
-
    } else {
-
        patch.latest()
-
    };
-
    let head = r.head();
-

-
    let mut revwalk = stored.backend.revwalk()?;
-
    revwalk.push_head()?;
-

-
    let commits = revwalk
-
        .map(|r| r.map(Oid::from))
-
        .collect::<Result<HashSet<Oid>, _>>()?;
-

-
    if !commits.contains(&head) {
-
        anyhow::bail!("fatal: patch head {head} is not in default branch");
-
    }
-
    let signer = term::signer(&profile)?;
-

-
    patch
-
        .merge(revision, head, &signer)?
-
        .cleanup(&working, &signer)?;
-

-
    println!("✓ Patch {pid} merged at commit {head}");
-
    println!("You may now run `rad sync --announce`.");
-

-
    Ok(())
-
}
deleted crates/radicle-tools/src/rad-push.rs
@@ -1,28 +0,0 @@
-
use std::path::Path;
-

-
use radicle::{
-
    node::Handle,
-
    storage::{ReadStorage, SignRepository, WriteRepository},
-
};
-

-
fn main() -> anyhow::Result<()> {
-
    let cwd = Path::new(".").canonicalize()?;
-
    let repo = radicle::git::raw::Repository::open(&cwd)?;
-
    let profile = radicle::Profile::load()?;
-
    let (_, id) = radicle::rad::remote(&repo)?;
-

-
    let output = radicle::git::run::<_, _, &str, &str>(&cwd, ["push", "rad"], None)?;
-
    println!("{output}");
-

-
    let signer = profile.signer()?;
-
    let project = profile.storage.repository(id)?;
-
    let sigrefs = project.sign_refs(&signer)?;
-
    let head = project.set_head()?;
-

-
    radicle::Node::new(profile.socket()).announce_refs(id)?;
-

-
    println!("head: {}", head.new);
-
    println!("ok: {}", sigrefs.signature);
-

-
    Ok(())
-
}
deleted crates/radicle-tools/src/rad-self.rs
@@ -1,13 +0,0 @@
-
fn main() -> anyhow::Result<()> {
-
    let profile = radicle::Profile::load()?;
-

-
    println!("id: {}", profile.id());
-
    println!("key: {}", radicle::crypto::ssh::fmt::key(profile.id()));
-
    println!(
-
        "fingerprint: {}",
-
        radicle::crypto::ssh::fmt::fingerprint(profile.id())
-
    );
-
    println!("home: {}", profile.home().path().display());
-

-
    Ok(())
-
}
deleted crates/radicle-tools/src/rad-set-canonical-refs.rs
@@ -1,19 +0,0 @@
-
use radicle::{
-
    storage::{WriteRepository, WriteStorage},
-
    Profile,
-
};
-

-
fn main() -> anyhow::Result<()> {
-
    let profile = Profile::load()?;
-

-
    let (_, rid) = radicle::rad::cwd()?;
-
    let repo = profile.storage.repository_mut(rid)?;
-

-
    let id_oid = repo.set_identity_head()?;
-
    let branch = repo.set_head()?;
-

-
    println!("ok: identity: {id_oid}");
-
    println!("ok: branch: {}", branch.new);
-

-
    Ok(())
-
}