Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: Update tracking without a running node
cloudhead committed 2 years ago
commit d150938b3823e58f0811fe03bd25bf590ca77deb
parent a70664368e76b5c7d4cd285f3dbc4d9b0193d2fa
10 files changed +108 -40
modified 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 .

modified 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'
+
```
modified 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(
modified 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<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 {
modified 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",
modified 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<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()),
+
    }
+
}
modified 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.
modified 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<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()
modified radicle-node/src/test/peer.rs
@@ -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(
modified 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<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);