From d150938b3823e58f0811fe03bd25bf590ca77deb Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 1 Nov 2023 17:21:52 +0100 Subject: [PATCH] cli: Update tracking without a running node This change allows users to track/untrack peers and repos without a running node. This fixes a bug when running `rad init` without the node running, where the repo would be tracked with a different scope than what was specified on the CLI. --- radicle-cli/examples/rad-init.md | 2 +- radicle-cli/examples/rad-sync-without-node.md | 14 ++++--- radicle-cli/src/commands/init.rs | 13 ++++--- radicle-cli/src/commands/track.rs | 38 ++++++++++++------- radicle-cli/src/commands/untrack.rs | 21 ++++++---- radicle-cli/src/project.rs | 34 +++++++++++++++++ radicle-node/src/service.rs | 7 +--- radicle-node/src/test/environment.rs | 6 +++ radicle-node/src/test/peer.rs | 5 ++- radicle/src/profile.rs | 8 ++++ 10 files changed, 108 insertions(+), 40 deletions(-) diff --git a/radicle-cli/examples/rad-init.md b/radicle-cli/examples/rad-init.md index 9ba774be..a6ff8270 100644 --- a/radicle-cli/examples/rad-init.md +++ b/radicle-cli/examples/rad-init.md @@ -3,7 +3,7 @@ To create your first radicle project, navigate to a git repository, and run the `init` command. Make sure you have [authenticated](../rad-auth.md) beforehand. ``` -$ rad init --name heartwood --description "Radicle Heartwood Protocol & Stack" --no-confirm --no-track --public -v +$ rad init --name heartwood --description "Radicle Heartwood Protocol & Stack" --no-confirm --public -v Initializing public radicle 👾 project in . diff --git a/radicle-cli/examples/rad-sync-without-node.md b/radicle-cli/examples/rad-sync-without-node.md index 67285906..a6f32b5f 100644 --- a/radicle-cli/examples/rad-sync-without-node.md +++ b/radicle-cli/examples/rad-sync-without-node.md @@ -1,9 +1,4 @@ -When you try to track, clone, or sync without your node running, it gives you an error: - -``` ~alice (fail) -$ rad track rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 --no-fetch -✗ Error: to track a repository, your node must be running. To start it, run `rad node start` -``` +When you try to clone or sync without your node running, it gives you an error: ``` ~bob (fail) $ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 @@ -14,3 +9,10 @@ $ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 $ rad sync --fetch rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 --seed z6MksmpU5b1dS7oaqF2bHXhQi1DWy2hB7Mh9CuN7y1DN6QSz ✗ Error: to sync a repository, your node must be running. To start it, run `rad node start` ``` + +Note that tracking works fine without a running node: + +``` ~alice +$ rad track rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 +✓ Tracking policy updated for rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 with scope 'trusted' +``` diff --git a/radicle-cli/src/commands/init.rs b/radicle-cli/src/commands/init.rs index 3065237b..4891bac2 100644 --- a/radicle-cli/src/commands/init.rs +++ b/radicle-cli/src/commands/init.rs @@ -17,6 +17,7 @@ use radicle::node::{Handle, NodeId}; use radicle::prelude::Doc; use radicle::{profile, Node}; +use crate as cli; use crate::git; use crate::terminal as term; use crate::terminal::args::{Args, Error, Help}; @@ -268,12 +269,6 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()> Ok((id, doc, _)) => { let proj = doc.project()?; - if options.track && node.is_running() { - // It's important to track our own repositories to make sure that our node signals - // interest for them. This ensures that messages relating to them are relayed to us. - node.track_repo(id, options.scope)?; - } - spinner.message(format!( "Project {} created.", term::format::highlight(proj.name()) @@ -284,6 +279,12 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()> term::blob(json::to_string_pretty(&proj)?); } + // It's important to track our own repositories to make sure that our node signals + // interest for them. This ensures that messages relating to them are relayed to us. + if options.track { + cli::project::track(id, options.scope, &mut node, profile)?; + } + if options.set_upstream || git::branch_remote(&repo, proj.default_branch()).is_err() { // Setup eg. `master` -> `rad/master` radicle::git::set_upstream( diff --git a/radicle-cli/src/commands/track.rs b/radicle-cli/src/commands/track.rs index 4193489c..64e72e57 100644 --- a/radicle-cli/src/commands/track.rs +++ b/radicle-cli/src/commands/track.rs @@ -3,14 +3,13 @@ use std::time; use anyhow::anyhow; -use radicle::node; use radicle::node::tracking::{Alias, Scope}; use radicle::node::{Handle, NodeId}; use radicle::{prelude::*, Node}; use crate::commands::rad_sync as sync; -use crate::terminal as term; use crate::terminal::args::{Args, Error, Help}; +use crate::{project, terminal as term}; pub const HELP: Help = Help { name: "track", @@ -119,16 +118,12 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { match options.op { Operation::TrackNode { nid, alias } => { - if let Err(node::Error::Connect(_)) = track_node(nid, alias, &mut node) { - anyhow::bail!("to track another node, your node must be running. To start it, run `rad node start`"); - } + track_node(nid, alias, &mut node, &profile)?; } Operation::TrackRepo { rid, scope } => { - if let Err(node::Error::Connect(_)) = track_repo(rid, scope, &mut node) { - anyhow::bail!("to track a repository, your node must be running. To start it, run `rad node start`"); - } + track_repo(rid, scope, &mut node, &profile)?; - if options.fetch { + if options.fetch && node.is_running() { sync::fetch( rid, sync::RepoSync::default(), @@ -141,8 +136,13 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { Ok(()) } -pub fn track_repo(rid: Id, scope: Scope, node: &mut Node) -> Result<(), node::Error> { - let tracked = node.track_repo(rid, scope)?; +pub fn track_repo( + rid: Id, + scope: Scope, + node: &mut Node, + profile: &Profile, +) -> Result<(), anyhow::Error> { + let tracked = project::track(rid, scope, node, profile)?; let outcome = if tracked { "updated" } else { "exists" }; term::success!( @@ -153,8 +153,20 @@ pub fn track_repo(rid: Id, scope: Scope, node: &mut Node) -> Result<(), node::Er Ok(()) } -pub fn track_node(nid: NodeId, alias: Option, node: &mut Node) -> Result<(), node::Error> { - let tracked = node.track_node(nid, alias.clone())?; +pub fn track_node( + nid: NodeId, + alias: Option, + node: &mut Node, + profile: &Profile, +) -> Result<(), anyhow::Error> { + let tracked = match node.track_node(nid, alias.clone()) { + Ok(updated) => updated, + Err(e) if e.is_connection_err() => { + let mut config = profile.tracking_mut()?; + config.track_node(&nid, alias.as_deref())? + } + Err(e) => return Err(e.into()), + }; let outcome = if tracked { "updated" } else { "exists" }; if let Some(alias) = alias { diff --git a/radicle-cli/src/commands/untrack.rs b/radicle-cli/src/commands/untrack.rs index e9ee4f5a..986d3354 100644 --- a/radicle-cli/src/commands/untrack.rs +++ b/radicle-cli/src/commands/untrack.rs @@ -5,6 +5,7 @@ use anyhow::anyhow; use radicle::node::{Handle, NodeId}; use radicle::{prelude::*, Node}; +use crate::project; use crate::terminal as term; use crate::terminal::args::{Args, Error, Help}; @@ -84,16 +85,15 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { let mut node = radicle::Node::new(profile.socket()); match options.op { - Operation::UntrackNode { nid } => untrack_node(nid, &mut node), - Operation::UntrackRepo { rid } => untrack_repo(rid, &mut node), + Operation::UntrackNode { nid } => untrack_node(nid, &mut node, &profile), + Operation::UntrackRepo { rid } => untrack_repo(rid, &mut node, &profile), }?; Ok(()) } -pub fn untrack_repo(rid: Id, node: &mut Node) -> anyhow::Result<()> { - let untracked = node.untrack_repo(rid)?; - if untracked { +pub fn untrack_repo(rid: Id, node: &mut Node, profile: &Profile) -> anyhow::Result<()> { + if project::untrack(rid, node, profile)? { term::success!( "Tracking policy for {} removed", term::format::tertiary(rid), @@ -102,8 +102,15 @@ pub fn untrack_repo(rid: Id, node: &mut Node) -> anyhow::Result<()> { Ok(()) } -pub fn untrack_node(nid: NodeId, node: &mut Node) -> anyhow::Result<()> { - let untracked = node.untrack_node(nid)?; +pub fn untrack_node(nid: NodeId, node: &mut Node, profile: &Profile) -> anyhow::Result<()> { + let untracked = match node.untrack_node(nid) { + Ok(updated) => updated, + Err(e) if e.is_connection_err() => { + let mut config = profile.tracking_mut()?; + config.untrack_node(&nid)? + } + Err(e) => return Err(e.into()), + }; if untracked { term::success!( "Tracking policy for {} removed", diff --git a/radicle-cli/src/project.rs b/radicle-cli/src/project.rs index 864a6a38..939fe2e1 100644 --- a/radicle-cli/src/project.rs +++ b/radicle-cli/src/project.rs @@ -2,6 +2,9 @@ use radicle::prelude::*; use crate::git; use radicle::git::RefStr; +use radicle::node::tracking::Scope; +use radicle::node::{Handle, NodeId}; +use radicle::Node; /// Setup a project remote and tracking branch. pub struct SetupRemote<'a> { @@ -48,3 +51,34 @@ impl<'a> SetupRemote<'a> { Ok((remote, None)) } } + +/// Track a repository by first trying to track through the node, and if the node isn't running, +/// by updating the tracking database directly. +pub fn track( + rid: Id, + scope: Scope, + node: &mut Node, + profile: &Profile, +) -> Result { + match node.track_repo(rid, scope) { + Ok(updated) => Ok(updated), + Err(e) if e.is_connection_err() => { + let mut config = profile.tracking_mut()?; + config.track_repo(&rid, scope).map_err(|e| e.into()) + } + Err(e) => Err(e.into()), + } +} + +/// Untrack a repository by first trying to untrack through the node, and if the node isn't running, +/// by updating the tracking database directly. +pub fn untrack(rid: Id, node: &mut Node, profile: &Profile) -> Result { + match node.untrack_repo(rid) { + Ok(updated) => Ok(updated), + Err(e) if e.is_connection_err() => { + let mut config = profile.tracking_mut()?; + config.untrack_repo(&rid).map_err(|e| e.into()) + } + Err(e) => Err(e.into()), + } +} diff --git a/radicle-node/src/service.rs b/radicle-node/src/service.rs index 2e7c9767..955d2e3a 100644 --- a/radicle-node/src/service.rs +++ b/radicle-node/src/service.rs @@ -414,12 +414,7 @@ where for rid in rids { if !self.is_tracking(&rid)? { - if self - .track_repo(&rid, tracking::Scope::Trusted) - .expect("Service::initialize: error tracking repository") - { - info!(target: "service", "Tracking local repository {rid}"); - } + warn!(target: "service", "Local repository {rid} is not tracked"); } } // Ensure that our local node is in our address database. diff --git a/radicle-node/src/test/environment.rs b/radicle-node/src/test/environment.rs index e16e1c28..f4ffe8c8 100644 --- a/radicle-node/src/test/environment.rs +++ b/radicle-node/src/test/environment.rs @@ -18,6 +18,7 @@ use radicle::crypto::{KeyPair, Seed, Signer}; use radicle::git; use radicle::git::refname; use radicle::identity::{Id, Visibility}; +use radicle::node; use radicle::node::address::Book; use radicle::node::routing; use radicle::node::routing::Store; @@ -406,6 +407,11 @@ impl + Signer + Clone> Node { .map(|(id, _, _)| id) .unwrap(); + assert!(self + .tracking + .track_repo(&id, node::tracking::Scope::Trusted) + .unwrap()); + log::debug!( target: "test", "Initialized project {id} for node {}", self.signer.public_key() diff --git a/radicle-node/src/test/peer.rs b/radicle-node/src/test/peer.rs index e178034e..3e4dbf16 100644 --- a/radicle-node/src/test/peer.rs +++ b/radicle-node/src/test/peer.rs @@ -156,7 +156,7 @@ where ) -> Self { let routing = routing::Table::memory().unwrap(); let tracking = tracking::Store::::memory().unwrap(); - let tracking = tracking::Config::new(config.policy, config.scope, tracking); + let mut tracking = tracking::Config::new(config.policy, config.scope, tracking); let tempdir = tempfile::tempdir().unwrap(); let id = *config.signer.public_key(); let ip = ip.into(); @@ -165,6 +165,9 @@ where // Make sure the peer address is advertized. config.config.external_addresses.push(local_addr.into()); + for rid in storage.inventory().unwrap() { + tracking.track_repo(&rid, Scope::Trusted).unwrap(); + } let announcement = service::gossip::node(&config.config, config.local_time.as_secs()); let emitter: Emitter = Default::default(); let service = Service::new( diff --git a/radicle/src/profile.rs b/radicle/src/profile.rs index 3872bb83..bf9838a9 100644 --- a/radicle/src/profile.rs +++ b/radicle/src/profile.rs @@ -243,6 +243,14 @@ impl Profile { Ok(config) } + /// Return a read-write handle to the tracking configuration of the node. + pub fn tracking_mut(&self) -> Result { + let path = self.home.node().join(node::TRACKING_DB_FILE); + let config = tracking::store::Config::open(path)?; + + Ok(config) + } + /// Return a read-only handle to the routing database of the node. pub fn routing(&self) -> Result { let path = self.home.node().join(node::ROUTING_DB_FILE);