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:
parent
a70664368e
commit
d150938b38
|
|
@ -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.
|
`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 .
|
Initializing public radicle 👾 project in .
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
When you try to track, clone, or sync without your node running, it gives you an error:
|
When you try to 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`
|
|
||||||
```
|
|
||||||
|
|
||||||
``` ~bob (fail)
|
``` ~bob (fail)
|
||||||
$ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
|
$ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
|
||||||
|
|
@ -14,3 +9,10 @@ $ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
|
||||||
$ rad sync --fetch rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 --seed z6MksmpU5b1dS7oaqF2bHXhQi1DWy2hB7Mh9CuN7y1DN6QSz
|
$ rad sync --fetch rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 --seed z6MksmpU5b1dS7oaqF2bHXhQi1DWy2hB7Mh9CuN7y1DN6QSz
|
||||||
✗ Error: to sync a repository, your node must be running. To start it, run `rad node start`
|
✗ 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'
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ use radicle::node::{Handle, NodeId};
|
||||||
use radicle::prelude::Doc;
|
use radicle::prelude::Doc;
|
||||||
use radicle::{profile, Node};
|
use radicle::{profile, Node};
|
||||||
|
|
||||||
|
use crate as cli;
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
use crate::terminal::args::{Args, Error, Help};
|
use crate::terminal::args::{Args, Error, Help};
|
||||||
|
|
@ -268,12 +269,6 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
|
||||||
Ok((id, doc, _)) => {
|
Ok((id, doc, _)) => {
|
||||||
let proj = doc.project()?;
|
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!(
|
spinner.message(format!(
|
||||||
"Project {} created.",
|
"Project {} created.",
|
||||||
term::format::highlight(proj.name())
|
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)?);
|
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() {
|
if options.set_upstream || git::branch_remote(&repo, proj.default_branch()).is_err() {
|
||||||
// Setup eg. `master` -> `rad/master`
|
// Setup eg. `master` -> `rad/master`
|
||||||
radicle::git::set_upstream(
|
radicle::git::set_upstream(
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,13 @@ use std::time;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
||||||
use radicle::node;
|
|
||||||
use radicle::node::tracking::{Alias, Scope};
|
use radicle::node::tracking::{Alias, Scope};
|
||||||
use radicle::node::{Handle, NodeId};
|
use radicle::node::{Handle, NodeId};
|
||||||
use radicle::{prelude::*, Node};
|
use radicle::{prelude::*, Node};
|
||||||
|
|
||||||
use crate::commands::rad_sync as sync;
|
use crate::commands::rad_sync as sync;
|
||||||
use crate::terminal as term;
|
|
||||||
use crate::terminal::args::{Args, Error, Help};
|
use crate::terminal::args::{Args, Error, Help};
|
||||||
|
use crate::{project, terminal as term};
|
||||||
|
|
||||||
pub const HELP: Help = Help {
|
pub const HELP: Help = Help {
|
||||||
name: "track",
|
name: "track",
|
||||||
|
|
@ -119,16 +118,12 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
|
|
||||||
match options.op {
|
match options.op {
|
||||||
Operation::TrackNode { nid, alias } => {
|
Operation::TrackNode { nid, alias } => {
|
||||||
if let Err(node::Error::Connect(_)) = track_node(nid, alias, &mut node) {
|
track_node(nid, alias, &mut node, &profile)?;
|
||||||
anyhow::bail!("to track another node, your node must be running. To start it, run `rad node start`");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Operation::TrackRepo { rid, scope } => {
|
Operation::TrackRepo { rid, scope } => {
|
||||||
if let Err(node::Error::Connect(_)) = track_repo(rid, scope, &mut node) {
|
track_repo(rid, scope, &mut node, &profile)?;
|
||||||
anyhow::bail!("to track a repository, your node must be running. To start it, run `rad node start`");
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.fetch {
|
if options.fetch && node.is_running() {
|
||||||
sync::fetch(
|
sync::fetch(
|
||||||
rid,
|
rid,
|
||||||
sync::RepoSync::default(),
|
sync::RepoSync::default(),
|
||||||
|
|
@ -141,8 +136,13 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn track_repo(rid: Id, scope: Scope, node: &mut Node) -> Result<(), node::Error> {
|
pub fn track_repo(
|
||||||
let tracked = node.track_repo(rid, scope)?;
|
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" };
|
let outcome = if tracked { "updated" } else { "exists" };
|
||||||
|
|
||||||
term::success!(
|
term::success!(
|
||||||
|
|
@ -153,8 +153,20 @@ pub fn track_repo(rid: Id, scope: Scope, node: &mut Node) -> Result<(), node::Er
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn track_node(nid: NodeId, alias: Option<Alias>, node: &mut Node) -> Result<(), node::Error> {
|
pub fn track_node(
|
||||||
let tracked = node.track_node(nid, alias.clone())?;
|
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" };
|
let outcome = if tracked { "updated" } else { "exists" };
|
||||||
|
|
||||||
if let Some(alias) = alias {
|
if let Some(alias) = alias {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use anyhow::anyhow;
|
||||||
use radicle::node::{Handle, NodeId};
|
use radicle::node::{Handle, NodeId};
|
||||||
use radicle::{prelude::*, Node};
|
use radicle::{prelude::*, Node};
|
||||||
|
|
||||||
|
use crate::project;
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
use crate::terminal::args::{Args, Error, Help};
|
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());
|
let mut node = radicle::Node::new(profile.socket());
|
||||||
|
|
||||||
match options.op {
|
match options.op {
|
||||||
Operation::UntrackNode { nid } => untrack_node(nid, &mut node),
|
Operation::UntrackNode { nid } => untrack_node(nid, &mut node, &profile),
|
||||||
Operation::UntrackRepo { rid } => untrack_repo(rid, &mut node),
|
Operation::UntrackRepo { rid } => untrack_repo(rid, &mut node, &profile),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn untrack_repo(rid: Id, node: &mut Node) -> anyhow::Result<()> {
|
pub fn untrack_repo(rid: Id, node: &mut Node, profile: &Profile) -> anyhow::Result<()> {
|
||||||
let untracked = node.untrack_repo(rid)?;
|
if project::untrack(rid, node, profile)? {
|
||||||
if untracked {
|
|
||||||
term::success!(
|
term::success!(
|
||||||
"Tracking policy for {} removed",
|
"Tracking policy for {} removed",
|
||||||
term::format::tertiary(rid),
|
term::format::tertiary(rid),
|
||||||
|
|
@ -102,8 +102,15 @@ pub fn untrack_repo(rid: Id, node: &mut Node) -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn untrack_node(nid: NodeId, node: &mut Node) -> anyhow::Result<()> {
|
pub fn untrack_node(nid: NodeId, node: &mut Node, profile: &Profile) -> anyhow::Result<()> {
|
||||||
let untracked = node.untrack_node(nid)?;
|
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 {
|
if untracked {
|
||||||
term::success!(
|
term::success!(
|
||||||
"Tracking policy for {} removed",
|
"Tracking policy for {} removed",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ use radicle::prelude::*;
|
||||||
|
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use radicle::git::RefStr;
|
use radicle::git::RefStr;
|
||||||
|
use radicle::node::tracking::Scope;
|
||||||
|
use radicle::node::{Handle, NodeId};
|
||||||
|
use radicle::Node;
|
||||||
|
|
||||||
/// Setup a project remote and tracking branch.
|
/// Setup a project remote and tracking branch.
|
||||||
pub struct SetupRemote<'a> {
|
pub struct SetupRemote<'a> {
|
||||||
|
|
@ -48,3 +51,34 @@ impl<'a> SetupRemote<'a> {
|
||||||
Ok((remote, None))
|
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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -414,12 +414,7 @@ where
|
||||||
|
|
||||||
for rid in rids {
|
for rid in rids {
|
||||||
if !self.is_tracking(&rid)? {
|
if !self.is_tracking(&rid)? {
|
||||||
if self
|
warn!(target: "service", "Local repository {rid} is not tracked");
|
||||||
.track_repo(&rid, tracking::Scope::Trusted)
|
|
||||||
.expect("Service::initialize: error tracking repository")
|
|
||||||
{
|
|
||||||
info!(target: "service", "Tracking local repository {rid}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Ensure that our local node is in our address database.
|
// Ensure that our local node is in our address database.
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ use radicle::crypto::{KeyPair, Seed, Signer};
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
use radicle::git::refname;
|
use radicle::git::refname;
|
||||||
use radicle::identity::{Id, Visibility};
|
use radicle::identity::{Id, Visibility};
|
||||||
|
use radicle::node;
|
||||||
use radicle::node::address::Book;
|
use radicle::node::address::Book;
|
||||||
use radicle::node::routing;
|
use radicle::node::routing;
|
||||||
use radicle::node::routing::Store;
|
use radicle::node::routing::Store;
|
||||||
|
|
@ -406,6 +407,11 @@ impl<G: cyphernet::Ecdh<Pk = NodeId> + Signer + Clone> Node<G> {
|
||||||
.map(|(id, _, _)| id)
|
.map(|(id, _, _)| id)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(self
|
||||||
|
.tracking
|
||||||
|
.track_repo(&id, node::tracking::Scope::Trusted)
|
||||||
|
.unwrap());
|
||||||
|
|
||||||
log::debug!(
|
log::debug!(
|
||||||
target: "test",
|
target: "test",
|
||||||
"Initialized project {id} for node {}", self.signer.public_key()
|
"Initialized project {id} for node {}", self.signer.public_key()
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ where
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let routing = routing::Table::memory().unwrap();
|
let routing = routing::Table::memory().unwrap();
|
||||||
let tracking = tracking::Store::<tracking::store::Write>::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 tempdir = tempfile::tempdir().unwrap();
|
||||||
let id = *config.signer.public_key();
|
let id = *config.signer.public_key();
|
||||||
let ip = ip.into();
|
let ip = ip.into();
|
||||||
|
|
@ -165,6 +165,9 @@ where
|
||||||
// Make sure the peer address is advertized.
|
// Make sure the peer address is advertized.
|
||||||
config.config.external_addresses.push(local_addr.into());
|
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 announcement = service::gossip::node(&config.config, config.local_time.as_secs());
|
||||||
let emitter: Emitter<Event> = Default::default();
|
let emitter: Emitter<Event> = Default::default();
|
||||||
let service = Service::new(
|
let service = Service::new(
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,14 @@ impl Profile {
|
||||||
Ok(config)
|
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.
|
/// Return a read-only handle to the routing database of the node.
|
||||||
pub fn routing(&self) -> Result<routing::Table, routing::Error> {
|
pub fn routing(&self) -> Result<routing::Table, routing::Error> {
|
||||||
let path = self.home.node().join(node::ROUTING_DB_FILE);
|
let path = self.home.node().join(node::ROUTING_DB_FILE);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue