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.
This commit is contained in:
cloudhead 2023-11-01 17:21:52 +01:00
parent a70664368e
commit d150938b38
No known key found for this signature in database
10 changed files with 108 additions and 40 deletions

View File

@ -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 .

View File

@ -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'
```

View File

@ -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(

View File

@ -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<Alias>, node: &mut Node) -> Result<(), node::Error> {
let tracked = node.track_node(nid, alias.clone())?;
pub fn track_node(
nid: NodeId,
alias: Option<Alias>,
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 {

View File

@ -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",

View File

@ -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<bool, anyhow::Error> {
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<bool, anyhow::Error> {
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()),
}
}

View File

@ -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.

View File

@ -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<G: cyphernet::Ecdh<Pk = NodeId> + Signer + Clone> Node<G> {
.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()

View File

@ -156,7 +156,7 @@ where
) -> Self {
let routing = routing::Table::memory().unwrap();
let tracking = tracking::Store::<tracking::store::Write>::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<Event> = Default::default();
let service = Service::new(

View File

@ -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<tracking::store::ConfigWriter, tracking::store::Error> {
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<routing::Table, routing::Error> {
let path = self.home.node().join(node::ROUTING_DB_FILE);