cli: Implement tracking correctly
The tracking command was tracking a project when it should have been tracking peers. We also add the ability to pass an alias when tracking from the CLI. Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
parent
46951b1b42
commit
a100f1c683
|
|
@ -84,7 +84,7 @@ pub fn clone(id: Id, _interactive: Interactive, ctx: impl term::Context) -> anyh
|
||||||
let signer = term::signer(&profile)?;
|
let signer = term::signer(&profile)?;
|
||||||
|
|
||||||
// Track & fetch project.
|
// Track & fetch project.
|
||||||
node.track(&id).context("track")?;
|
node.track_repo(&id).context("track")?;
|
||||||
node.fetch(&id).context("fetch")?;
|
node.fetch(&id).context("fetch")?;
|
||||||
|
|
||||||
// Create a local fork of the project, under our own id.
|
// Create a local fork of the project, under our own id.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context as _};
|
use anyhow::{anyhow, Context as _};
|
||||||
|
|
||||||
use radicle::identity::project::Id;
|
|
||||||
use radicle::node::Handle;
|
use radicle::node::Handle;
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
use radicle::storage::WriteStorage;
|
use radicle::storage::WriteStorage;
|
||||||
|
|
@ -12,15 +12,16 @@ use crate::terminal::args::{Args, Error, Help};
|
||||||
|
|
||||||
pub const HELP: Help = Help {
|
pub const HELP: Help = Help {
|
||||||
name: "track",
|
name: "track",
|
||||||
description: "Manage project tracking relationships",
|
description: "Manage project tracking policy",
|
||||||
version: env!("CARGO_PKG_VERSION"),
|
version: env!("CARGO_PKG_VERSION"),
|
||||||
usage: r#"
|
usage: r#"
|
||||||
Usage
|
Usage
|
||||||
|
|
||||||
rad track [<id>] [--fetch]
|
rad track <peer> [--fetch] [--alias <name>]
|
||||||
|
|
||||||
Options
|
Options
|
||||||
|
|
||||||
|
--alias <name> Add an alias to this peer identifier
|
||||||
--fetch Fetch the peer's refs into the working copy
|
--fetch Fetch the peer's refs into the working copy
|
||||||
--verbose, -v Verbose output
|
--verbose, -v Verbose output
|
||||||
--help Print help
|
--help Print help
|
||||||
|
|
@ -29,7 +30,8 @@ Options
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
pub id: Option<Id>,
|
pub peer: NodeId,
|
||||||
|
pub alias: Option<String>,
|
||||||
pub fetch: bool,
|
pub fetch: bool,
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -39,21 +41,31 @@ impl Args for Options {
|
||||||
use lexopt::prelude::*;
|
use lexopt::prelude::*;
|
||||||
|
|
||||||
let mut parser = lexopt::Parser::from_args(args);
|
let mut parser = lexopt::Parser::from_args(args);
|
||||||
let mut id: Option<Id> = None;
|
let mut peer: Option<NodeId> = None;
|
||||||
|
let mut alias: Option<String> = None;
|
||||||
let mut fetch = true;
|
let mut fetch = true;
|
||||||
let mut verbose = false;
|
let mut verbose = false;
|
||||||
|
|
||||||
while let Some(arg) = parser.next()? {
|
while let Some(arg) = parser.next()? {
|
||||||
match arg {
|
match arg {
|
||||||
|
Long("alias") => {
|
||||||
|
let name = parser.value()?;
|
||||||
|
let name = name
|
||||||
|
.to_str()
|
||||||
|
.to_owned()
|
||||||
|
.ok_or_else(|| anyhow!("alias specified is not UTF-8"))?;
|
||||||
|
|
||||||
|
alias = Some(name.to_owned());
|
||||||
|
}
|
||||||
Long("no-fetch") => fetch = false,
|
Long("no-fetch") => fetch = false,
|
||||||
Long("verbose") | Short('v') => verbose = true,
|
Long("verbose") | Short('v') => verbose = true,
|
||||||
Value(val) if id.is_none() => {
|
Value(val) if peer.is_none() => {
|
||||||
let val = val.to_string_lossy();
|
let val = val.to_string_lossy();
|
||||||
|
|
||||||
if let Ok(val) = Id::from_human(&val) {
|
if let Ok(val) = NodeId::from_str(&val) {
|
||||||
id = Some(val);
|
peer = Some(val);
|
||||||
} else {
|
} else {
|
||||||
return Err(anyhow!("invalid ID '{}'", val));
|
return Err(anyhow!("invalid Node ID '{}'", val));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Long("help") => {
|
Long("help") => {
|
||||||
|
|
@ -65,18 +77,24 @@ impl Args for Options {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((Options { id, fetch, verbose }, vec![]))
|
Ok((
|
||||||
|
Options {
|
||||||
|
peer: peer.ok_or_else(|| anyhow!("a peer to track must be supplied"))?,
|
||||||
|
alias,
|
||||||
|
fetch,
|
||||||
|
verbose,
|
||||||
|
},
|
||||||
|
vec![],
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
let id = options
|
let peer = options.peer;
|
||||||
.id
|
|
||||||
.or_else(|| radicle::rad::cwd().ok().map(|(_, id)| id))
|
|
||||||
.context("current directory is not a git repository; please supply an `<id>`")?;
|
|
||||||
let profile = ctx.profile()?;
|
let profile = ctx.profile()?;
|
||||||
let storage = &profile.storage;
|
let storage = &profile.storage;
|
||||||
let Doc { payload, .. } = storage.repository(id)?.project_of(profile.id())?;
|
let (_, rid) = radicle::rad::cwd().context("this command must be run within a project")?;
|
||||||
|
let Doc { payload, .. } = storage.repository(rid)?.project_of(profile.id())?;
|
||||||
let node = radicle::node::connect(&profile.node())?;
|
let node = radicle::node::connect(&profile.node())?;
|
||||||
|
|
||||||
term::info!(
|
term::info!(
|
||||||
|
|
@ -85,16 +103,22 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
);
|
);
|
||||||
term::blank();
|
term::blank();
|
||||||
|
|
||||||
let tracked = node.track(&id)?;
|
let tracked = node.track_node(&peer, options.alias.as_deref())?;
|
||||||
|
let outcome = if tracked { "established" } else { "exists" };
|
||||||
|
|
||||||
|
if let Some(alias) = options.alias {
|
||||||
term::success!(
|
term::success!(
|
||||||
"Tracking relationship for {} ({}) {}",
|
"Tracking relationship with {} ({}) {}",
|
||||||
term::format::tertiary(&payload.name),
|
term::format::tertiary(alias),
|
||||||
&id.to_human(),
|
peer,
|
||||||
if tracked { "established" } else { "exists" }
|
outcome
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
term::success!("Tracking relationship with {} {}", peer, outcome);
|
||||||
|
}
|
||||||
|
|
||||||
if options.fetch {
|
if options.fetch {
|
||||||
node.fetch(&id)?;
|
node.fetch(&rid)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -89,5 +89,5 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
|
|
||||||
pub fn untrack(id: Id, profile: &Profile) -> anyhow::Result<bool> {
|
pub fn untrack(id: Id, profile: &Profile) -> anyhow::Result<bool> {
|
||||||
let node = radicle::node::connect(profile.node())?;
|
let node = radicle::node::connect(profile.node())?;
|
||||||
node.untrack(&id).map_err(|e| anyhow!(e))
|
node.untrack_repo(&id).map_err(|e| anyhow!(e))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,15 +66,31 @@ impl<W: Waker> traits::Handle for Handle<W> {
|
||||||
receiver.recv().map_err(Error::from)
|
receiver.recv().map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn track(&mut self, id: Id) -> Result<bool, Error> {
|
fn track_node(&mut self, id: NodeId, alias: Option<String>) -> Result<bool, Error> {
|
||||||
let (sender, receiver) = chan::bounded(1);
|
let (sender, receiver) = chan::bounded(1);
|
||||||
self.commands.send(service::Command::Track(id, sender))?;
|
self.commands
|
||||||
|
.send(service::Command::TrackNode(id, alias, sender))?;
|
||||||
receiver.recv().map_err(Error::from)
|
receiver.recv().map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn untrack(&mut self, id: Id) -> Result<bool, Error> {
|
fn untrack_node(&mut self, id: NodeId) -> Result<bool, Error> {
|
||||||
let (sender, receiver) = chan::bounded(1);
|
let (sender, receiver) = chan::bounded(1);
|
||||||
self.commands.send(service::Command::Untrack(id, sender))?;
|
self.commands
|
||||||
|
.send(service::Command::UntrackNode(id, sender))?;
|
||||||
|
receiver.recv().map_err(Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn track_repo(&mut self, id: Id) -> Result<bool, Error> {
|
||||||
|
let (sender, receiver) = chan::bounded(1);
|
||||||
|
self.commands
|
||||||
|
.send(service::Command::TrackRepo(id, sender))?;
|
||||||
|
receiver.recv().map_err(Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn untrack_repo(&mut self, id: Id) -> Result<bool, Error> {
|
||||||
|
let (sender, receiver) = chan::bounded(1);
|
||||||
|
self.commands
|
||||||
|
.send(service::Command::UntrackRepo(id, sender))?;
|
||||||
receiver.recv().map_err(Error::from)
|
receiver.recv().map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,9 +162,13 @@ pub mod traits {
|
||||||
fn fetch(&mut self, id: Id) -> Result<FetchLookup, Error>;
|
fn fetch(&mut self, id: Id) -> Result<FetchLookup, Error>;
|
||||||
/// Start tracking the given project. Doesn't do anything if the project is already
|
/// Start tracking the given project. Doesn't do anything if the project is already
|
||||||
/// tracked.
|
/// tracked.
|
||||||
fn track(&mut self, id: Id) -> Result<bool, Error>;
|
fn track_repo(&mut self, id: Id) -> Result<bool, Error>;
|
||||||
|
/// Start tracking the given node.
|
||||||
|
fn track_node(&mut self, id: NodeId, alias: Option<String>) -> Result<bool, Error>;
|
||||||
/// Untrack the given project and delete it from storage.
|
/// Untrack the given project and delete it from storage.
|
||||||
fn untrack(&mut self, id: Id) -> Result<bool, Error>;
|
fn untrack_repo(&mut self, id: Id) -> Result<bool, Error>;
|
||||||
|
/// Untrack the given node.
|
||||||
|
fn untrack_node(&mut self, id: NodeId) -> Result<bool, Error>;
|
||||||
/// Notify the client that a project has been updated.
|
/// Notify the client that a project has been updated.
|
||||||
fn announce_refs(&mut self, id: Id) -> Result<(), Error>;
|
fn announce_refs(&mut self, id: Id) -> Result<(), Error>;
|
||||||
/// Send a command to the command channel, and wake up the event loop.
|
/// Send a command to the command channel, and wake up the event loop.
|
||||||
|
|
|
||||||
|
|
@ -83,9 +83,9 @@ fn drain<H: Handle>(stream: &UnixStream, handle: &mut H) -> Result<(), DrainErro
|
||||||
return Err(DrainError::InvalidCommandArg(arg.to_owned()));
|
return Err(DrainError::InvalidCommandArg(arg.to_owned()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(("track", arg)) => {
|
Some(("track-repo", arg)) => {
|
||||||
if let Ok(id) = arg.parse() {
|
if let Ok(id) = arg.parse() {
|
||||||
match handle.track(id) {
|
match handle.track_repo(id) {
|
||||||
Ok(updated) => {
|
Ok(updated) => {
|
||||||
if updated {
|
if updated {
|
||||||
writeln!(writer, "{}", node::RESPONSE_OK)?;
|
writeln!(writer, "{}", node::RESPONSE_OK)?;
|
||||||
|
|
@ -101,9 +101,50 @@ fn drain<H: Handle>(stream: &UnixStream, handle: &mut H) -> Result<(), DrainErro
|
||||||
return Err(DrainError::InvalidCommandArg(arg.to_owned()));
|
return Err(DrainError::InvalidCommandArg(arg.to_owned()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(("untrack", arg)) => {
|
Some(("untrack-repo", arg)) => {
|
||||||
if let Ok(id) = arg.parse() {
|
if let Ok(id) = arg.parse() {
|
||||||
match handle.untrack(id) {
|
match handle.untrack_repo(id) {
|
||||||
|
Ok(updated) => {
|
||||||
|
if updated {
|
||||||
|
writeln!(writer, "{}", node::RESPONSE_OK)?;
|
||||||
|
} else {
|
||||||
|
writeln!(writer, "{}", node::RESPONSE_NOOP)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(DrainError::Client(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(DrainError::InvalidCommandArg(arg.to_owned()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(("track-node", args)) => {
|
||||||
|
let (peer, alias) = if let Some((peer, alias)) = args.split_once(' ') {
|
||||||
|
(peer, Some(alias.to_owned()))
|
||||||
|
} else {
|
||||||
|
(args, None)
|
||||||
|
};
|
||||||
|
if let Ok(id) = peer.parse() {
|
||||||
|
match handle.track_node(id, alias) {
|
||||||
|
Ok(updated) => {
|
||||||
|
if updated {
|
||||||
|
writeln!(writer, "{}", node::RESPONSE_OK)?;
|
||||||
|
} else {
|
||||||
|
writeln!(writer, "{}", node::RESPONSE_NOOP)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(DrainError::Client(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(DrainError::InvalidCommandArg(args.to_owned()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(("untrack-node", arg)) => {
|
||||||
|
if let Ok(id) = arg.parse() {
|
||||||
|
match handle.untrack_node(id) {
|
||||||
Ok(updated) => {
|
Ok(updated) => {
|
||||||
if updated {
|
if updated {
|
||||||
writeln!(writer, "{}", node::RESPONSE_OK)?;
|
writeln!(writer, "{}", node::RESPONSE_OK)?;
|
||||||
|
|
@ -214,7 +255,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::identity::Id;
|
use crate::identity::Id;
|
||||||
use crate::node::Handle;
|
use crate::node::Handle;
|
||||||
use crate::node::Node;
|
use crate::node::{Node, NodeId};
|
||||||
use crate::test;
|
use crate::test;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -255,6 +296,7 @@ mod tests {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
let socket = tmp.path().join("node.sock");
|
let socket = tmp.path().join("node.sock");
|
||||||
let proj = test::arbitrary::gen::<Id>(1);
|
let proj = test::arbitrary::gen::<Id>(1);
|
||||||
|
let peer = test::arbitrary::gen::<NodeId>(1);
|
||||||
|
|
||||||
thread::spawn({
|
thread::spawn({
|
||||||
let socket = socket.clone();
|
let socket = socket.clone();
|
||||||
|
|
@ -269,9 +311,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(handle.track(&proj).unwrap());
|
assert!(handle.track_repo(&proj).unwrap());
|
||||||
assert!(!handle.track(&proj).unwrap());
|
assert!(!handle.track_repo(&proj).unwrap());
|
||||||
assert!(handle.untrack(&proj).unwrap());
|
assert!(handle.untrack_repo(&proj).unwrap());
|
||||||
assert!(!handle.untrack(&proj).unwrap());
|
assert!(!handle.untrack_repo(&proj).unwrap());
|
||||||
|
|
||||||
|
assert!(handle.track_node(&peer, Some("alice")).unwrap());
|
||||||
|
assert!(!handle.track_node(&peer, Some("alice")).unwrap());
|
||||||
|
assert!(handle.untrack_node(&peer).unwrap());
|
||||||
|
assert!(!handle.untrack_node(&peer).unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,9 +150,13 @@ pub enum Command {
|
||||||
/// Fetch the given project from the network.
|
/// Fetch the given project from the network.
|
||||||
Fetch(Id, chan::Sender<FetchLookup>),
|
Fetch(Id, chan::Sender<FetchLookup>),
|
||||||
/// Track the given project.
|
/// Track the given project.
|
||||||
Track(Id, chan::Sender<bool>),
|
TrackRepo(Id, chan::Sender<bool>),
|
||||||
/// Untrack the given project.
|
/// Untrack the given project.
|
||||||
Untrack(Id, chan::Sender<bool>),
|
UntrackRepo(Id, chan::Sender<bool>),
|
||||||
|
/// Track the given node.
|
||||||
|
TrackNode(NodeId, Option<String>, chan::Sender<bool>),
|
||||||
|
/// Untrack the given node.
|
||||||
|
UntrackNode(NodeId, chan::Sender<bool>),
|
||||||
/// Query the internal service state.
|
/// Query the internal service state.
|
||||||
QueryState(Arc<QueryState>, chan::Sender<Result<(), CommandError>>),
|
QueryState(Arc<QueryState>, chan::Sender<Result<(), CommandError>>),
|
||||||
}
|
}
|
||||||
|
|
@ -163,8 +167,10 @@ impl fmt::Debug for Command {
|
||||||
Self::AnnounceRefs(id) => write!(f, "AnnounceRefs({})", id),
|
Self::AnnounceRefs(id) => write!(f, "AnnounceRefs({})", id),
|
||||||
Self::Connect(addr) => write!(f, "Connect({})", addr),
|
Self::Connect(addr) => write!(f, "Connect({})", addr),
|
||||||
Self::Fetch(id, _) => write!(f, "Fetch({})", id),
|
Self::Fetch(id, _) => write!(f, "Fetch({})", id),
|
||||||
Self::Track(id, _) => write!(f, "Track({})", id),
|
Self::TrackRepo(id, _) => write!(f, "TrackRepo({})", id),
|
||||||
Self::Untrack(id, _) => write!(f, "Untrack({})", id),
|
Self::UntrackRepo(id, _) => write!(f, "UntrackRepo({})", id),
|
||||||
|
Self::TrackNode(id, _, _) => write!(f, "TrackNode({})", id),
|
||||||
|
Self::UntrackNode(id, _) => write!(f, "UntrackNode({})", id),
|
||||||
Self::QueryState { .. } => write!(f, "QueryState(..)"),
|
Self::QueryState { .. } => write!(f, "QueryState(..)"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -290,20 +296,20 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Track a project.
|
/// Track a repository.
|
||||||
/// Returns whether or not the tracking policy was updated.
|
/// Returns whether or not the tracking policy was updated.
|
||||||
pub fn track(&mut self, id: &Id, scope: tracking::Scope) -> Result<bool, tracking::Error> {
|
pub fn track_repo(&mut self, id: &Id, scope: tracking::Scope) -> Result<bool, tracking::Error> {
|
||||||
self.out_of_sync = self.tracking.track_repo(id, scope)?;
|
self.out_of_sync = self.tracking.track_repo(id, scope)?;
|
||||||
self.filter.insert(id);
|
self.filter.insert(id);
|
||||||
|
|
||||||
Ok(self.out_of_sync)
|
Ok(self.out_of_sync)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Untrack a project.
|
/// Untrack a repository.
|
||||||
/// Returns whether or not the tracking policy was updated.
|
/// Returns whether or not the tracking policy was updated.
|
||||||
/// Note that when untracking, we don't announce anything to the network. This is because by
|
/// Note that when untracking, we don't announce anything to the network. This is because by
|
||||||
/// simply not announcing it anymore, it will eventually be pruned by nodes.
|
/// simply not announcing it anymore, it will eventually be pruned by nodes.
|
||||||
pub fn untrack(&mut self, id: &Id) -> Result<bool, tracking::Error> {
|
pub fn untrack_repo(&mut self, id: &Id) -> Result<bool, tracking::Error> {
|
||||||
// Nb. This is potentially slow if we have lots of projects. We should probably
|
// Nb. This is potentially slow if we have lots of projects. We should probably
|
||||||
// only re-compute the filter when we've untracked a certain amount of projects
|
// only re-compute the filter when we've untracked a certain amount of projects
|
||||||
// and the filter is really out of date.
|
// and the filter is really out of date.
|
||||||
|
|
@ -491,18 +497,32 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Track(id, resp) => {
|
Command::TrackRepo(id, resp) => {
|
||||||
let tracked = self
|
let tracked = self
|
||||||
.track(&id, tracking::Scope::All)
|
.track_repo(&id, tracking::Scope::All)
|
||||||
.expect("Service::command: error tracking repository");
|
.expect("Service::command: error tracking repository");
|
||||||
resp.send(tracked).ok();
|
resp.send(tracked).ok();
|
||||||
}
|
}
|
||||||
Command::Untrack(id, resp) => {
|
Command::UntrackRepo(id, resp) => {
|
||||||
let untracked = self
|
let untracked = self
|
||||||
.untrack(&id)
|
.untrack_repo(&id)
|
||||||
.expect("Service::command: error untracking repository");
|
.expect("Service::command: error untracking repository");
|
||||||
resp.send(untracked).ok();
|
resp.send(untracked).ok();
|
||||||
}
|
}
|
||||||
|
Command::TrackNode(id, alias, resp) => {
|
||||||
|
let tracked = self
|
||||||
|
.tracking
|
||||||
|
.track_node(&id, alias.as_deref())
|
||||||
|
.expect("Service::command: error tracking node");
|
||||||
|
resp.send(tracked).ok();
|
||||||
|
}
|
||||||
|
Command::UntrackNode(id, resp) => {
|
||||||
|
let untracked = self
|
||||||
|
.tracking
|
||||||
|
.untrack_node(&id)
|
||||||
|
.expect("Service::command: error untracking node");
|
||||||
|
resp.send(untracked).ok();
|
||||||
|
}
|
||||||
Command::AnnounceRefs(id) => {
|
Command::AnnounceRefs(id) => {
|
||||||
if let Err(err) = self.announce_refs(id) {
|
if let Err(err) = self.announce_refs(id) {
|
||||||
error!("Error announcing refs: {}", err);
|
error!("Error announcing refs: {}", err);
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,13 @@ use crate::client::handle::Error;
|
||||||
use crate::identity::Id;
|
use crate::identity::Id;
|
||||||
use crate::service;
|
use crate::service;
|
||||||
use crate::service::FetchLookup;
|
use crate::service::FetchLookup;
|
||||||
|
use crate::service::NodeId;
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct Handle {
|
pub struct Handle {
|
||||||
pub updates: Arc<Mutex<Vec<Id>>>,
|
pub updates: Arc<Mutex<Vec<Id>>>,
|
||||||
pub tracking: HashSet<Id>,
|
pub tracking_repos: HashSet<Id>,
|
||||||
|
pub tracking_nodes: HashSet<NodeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl traits::Handle for Handle {
|
impl traits::Handle for Handle {
|
||||||
|
|
@ -24,12 +26,20 @@ impl traits::Handle for Handle {
|
||||||
Ok(FetchLookup::NotFound)
|
Ok(FetchLookup::NotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn track(&mut self, id: Id) -> Result<bool, Error> {
|
fn track_repo(&mut self, id: Id) -> Result<bool, Error> {
|
||||||
Ok(self.tracking.insert(id))
|
Ok(self.tracking_repos.insert(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn untrack(&mut self, id: Id) -> Result<bool, Error> {
|
fn untrack_repo(&mut self, id: Id) -> Result<bool, Error> {
|
||||||
Ok(self.tracking.remove(&id))
|
Ok(self.tracking_repos.remove(&id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn track_node(&mut self, id: NodeId, _alias: Option<String>) -> Result<bool, Error> {
|
||||||
|
Ok(self.tracking_nodes.insert(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn untrack_node(&mut self, id: NodeId) -> Result<bool, Error> {
|
||||||
|
Ok(self.tracking_nodes.remove(&id))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn announce_refs(&mut self, id: Id) -> Result<(), Error> {
|
fn announce_refs(&mut self, id: Id) -> Result<(), Error> {
|
||||||
|
|
|
||||||
|
|
@ -353,7 +353,7 @@ fn test_tracking() {
|
||||||
let proj_id: identity::Id = test::arbitrary::gen(1);
|
let proj_id: identity::Id = test::arbitrary::gen(1);
|
||||||
|
|
||||||
let (sender, receiver) = chan::bounded(1);
|
let (sender, receiver) = chan::bounded(1);
|
||||||
alice.command(Command::Track(proj_id, sender));
|
alice.command(Command::TrackRepo(proj_id, sender));
|
||||||
let policy_change = receiver
|
let policy_change = receiver
|
||||||
.recv()
|
.recv()
|
||||||
.map_err(client::handle::Error::from)
|
.map_err(client::handle::Error::from)
|
||||||
|
|
@ -362,7 +362,7 @@ fn test_tracking() {
|
||||||
assert!(alice.tracking().is_repo_tracked(&proj_id).unwrap());
|
assert!(alice.tracking().is_repo_tracked(&proj_id).unwrap());
|
||||||
|
|
||||||
let (sender, receiver) = chan::bounded(1);
|
let (sender, receiver) = chan::bounded(1);
|
||||||
alice.command(Command::Untrack(proj_id, sender));
|
alice.command(Command::UntrackRepo(proj_id, sender));
|
||||||
let policy_change = receiver
|
let policy_change = receiver
|
||||||
.recv()
|
.recv()
|
||||||
.map_err(client::handle::Error::from)
|
.map_err(client::handle::Error::from)
|
||||||
|
|
@ -556,9 +556,9 @@ fn test_refs_announcement_relay() {
|
||||||
};
|
};
|
||||||
let bob_inv = bob.inventory().unwrap();
|
let bob_inv = bob.inventory().unwrap();
|
||||||
|
|
||||||
alice.track(&bob_inv[0], tracking::Scope::All).unwrap();
|
alice.track_repo(&bob_inv[0], tracking::Scope::All).unwrap();
|
||||||
alice.track(&bob_inv[1], tracking::Scope::All).unwrap();
|
alice.track_repo(&bob_inv[1], tracking::Scope::All).unwrap();
|
||||||
alice.track(&bob_inv[2], tracking::Scope::All).unwrap();
|
alice.track_repo(&bob_inv[2], tracking::Scope::All).unwrap();
|
||||||
alice.connect_to(&bob);
|
alice.connect_to(&bob);
|
||||||
alice.connect_to(&eve);
|
alice.connect_to(&eve);
|
||||||
alice.receive(&eve.addr(), Message::Subscribe(Subscribe::all()));
|
alice.receive(&eve.addr(), Message::Subscribe(Subscribe::all()));
|
||||||
|
|
@ -598,7 +598,7 @@ fn test_refs_announcement_no_subscribe() {
|
||||||
let eve = Peer::new("eve", [9, 9, 9, 9], MockStorage::empty());
|
let eve = Peer::new("eve", [9, 9, 9, 9], MockStorage::empty());
|
||||||
let id = arbitrary::gen(1);
|
let id = arbitrary::gen(1);
|
||||||
|
|
||||||
alice.track(&id, tracking::Scope::All).unwrap();
|
alice.track_repo(&id, tracking::Scope::All).unwrap();
|
||||||
alice.connect_to(&bob);
|
alice.connect_to(&bob);
|
||||||
alice.connect_to(&eve);
|
alice.connect_to(&eve);
|
||||||
alice.receive(&bob.addr(), bob.refs_announcement(id));
|
alice.receive(&bob.addr(), bob.refs_announcement(id));
|
||||||
|
|
@ -857,11 +857,11 @@ fn test_push_and_pull() {
|
||||||
|
|
||||||
// Bob tracks Alice's project.
|
// Bob tracks Alice's project.
|
||||||
let (sender, _) = chan::bounded(1);
|
let (sender, _) = chan::bounded(1);
|
||||||
bob.command(service::Command::Track(proj_id, sender));
|
bob.command(service::Command::TrackRepo(proj_id, sender));
|
||||||
|
|
||||||
// Eve tracks Alice's project.
|
// Eve tracks Alice's project.
|
||||||
let (sender, _) = chan::bounded(1);
|
let (sender, _) = chan::bounded(1);
|
||||||
eve.command(service::Command::Track(proj_id, sender));
|
eve.command(service::Command::TrackRepo(proj_id, sender));
|
||||||
|
|
||||||
let mut sim = Simulation::new(
|
let mut sim = Simulation::new(
|
||||||
LocalTime::now(),
|
LocalTime::now(),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
mod features;
|
mod features;
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{BufRead, BufReader, Write};
|
use std::io::{BufRead, BufReader, Write};
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
|
|
@ -31,11 +30,15 @@ pub enum Error {
|
||||||
pub trait Handle {
|
pub trait Handle {
|
||||||
/// Fetch a project from the network. Fails if the project isn't tracked.
|
/// Fetch a project from the network. Fails if the project isn't tracked.
|
||||||
fn fetch(&self, id: &Id) -> Result<(), Error>;
|
fn fetch(&self, id: &Id) -> Result<(), Error>;
|
||||||
/// Start tracking the given project. Doesn't do anything if the project is already
|
/// Start tracking the given node. If the node is already tracked,
|
||||||
/// tracked.
|
/// updates the alias if necessary.
|
||||||
fn track(&self, id: &Id) -> Result<bool, Error>;
|
fn track_node(&self, id: &NodeId, alias: Option<&str>) -> Result<bool, Error>;
|
||||||
/// Untrack the given project and delete it from storage.
|
/// Start tracking the given repository.
|
||||||
fn untrack(&self, id: &Id) -> Result<bool, Error>;
|
fn track_repo(&self, id: &Id) -> Result<bool, Error>;
|
||||||
|
/// Untrack the given node.
|
||||||
|
fn untrack_node(&self, id: &NodeId) -> Result<bool, Error>;
|
||||||
|
/// Untrack the given repository and delete it from storage.
|
||||||
|
fn untrack_repo(&self, id: &Id) -> Result<bool, Error>;
|
||||||
/// Notify the network that we have new refs.
|
/// Notify the network that we have new refs.
|
||||||
fn announce_refs(&self, id: &Id) -> Result<(), Error>;
|
fn announce_refs(&self, id: &Id) -> Result<(), Error>;
|
||||||
/// Ask the node to shutdown.
|
/// Ask the node to shutdown.
|
||||||
|
|
@ -60,12 +63,17 @@ impl Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a command on the node.
|
/// Call a command on the node.
|
||||||
pub fn call<A: fmt::Display>(
|
pub fn call<A: ToString>(
|
||||||
&self,
|
&self,
|
||||||
cmd: &str,
|
cmd: &str,
|
||||||
arg: &A,
|
args: &[A],
|
||||||
) -> Result<impl Iterator<Item = Result<String, io::Error>> + '_, io::Error> {
|
) -> Result<impl Iterator<Item = Result<String, io::Error>> + '_, io::Error> {
|
||||||
writeln!(&self.stream, "{cmd} {arg}")?;
|
let args = args
|
||||||
|
.iter()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ");
|
||||||
|
writeln!(&self.stream, "{cmd} {args}")?;
|
||||||
|
|
||||||
Ok(BufReader::new(&self.stream).lines())
|
Ok(BufReader::new(&self.stream).lines())
|
||||||
}
|
}
|
||||||
|
|
@ -73,16 +81,23 @@ impl Node {
|
||||||
|
|
||||||
impl Handle for Node {
|
impl Handle for Node {
|
||||||
fn fetch(&self, id: &Id) -> Result<(), Error> {
|
fn fetch(&self, id: &Id) -> Result<(), Error> {
|
||||||
for line in self.call("fetch", id)? {
|
for line in self.call("fetch", &[id])? {
|
||||||
let line = line?;
|
let line = line?;
|
||||||
log::debug!("node: {}", line);
|
log::debug!("node: {}", line);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn track(&self, id: &Id) -> Result<bool, Error> {
|
fn track_node(&self, id: &NodeId, alias: Option<&str>) -> Result<bool, Error> {
|
||||||
let mut line = self.call("track", id)?;
|
let id = id.to_human();
|
||||||
let line = line.next().ok_or(Error::EmptyResponse { cmd: "track" })??;
|
let mut line = if let Some(alias) = alias {
|
||||||
|
self.call("track-node", &[id.as_str(), alias])
|
||||||
|
} else {
|
||||||
|
self.call("track-node", &[id.as_str()])
|
||||||
|
}?;
|
||||||
|
let line = line
|
||||||
|
.next()
|
||||||
|
.ok_or(Error::EmptyResponse { cmd: "track-node" })??;
|
||||||
|
|
||||||
log::debug!("node: {}", line);
|
log::debug!("node: {}", line);
|
||||||
|
|
||||||
|
|
@ -90,17 +105,17 @@ impl Handle for Node {
|
||||||
RESPONSE_OK => Ok(true),
|
RESPONSE_OK => Ok(true),
|
||||||
RESPONSE_NOOP => Ok(false),
|
RESPONSE_NOOP => Ok(false),
|
||||||
_ => Err(Error::InvalidResponse {
|
_ => Err(Error::InvalidResponse {
|
||||||
cmd: "track",
|
cmd: "track-node",
|
||||||
response: line,
|
response: line,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn untrack(&self, id: &Id) -> Result<bool, Error> {
|
fn track_repo(&self, id: &Id) -> Result<bool, Error> {
|
||||||
let mut line = self.call("untrack", id)?;
|
let mut line = self.call("track-repo", &[id])?;
|
||||||
let line = line
|
let line = line
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::EmptyResponse { cmd: "untrack" })??;
|
.ok_or(Error::EmptyResponse { cmd: "track-repo" })??;
|
||||||
|
|
||||||
log::debug!("node: {}", line);
|
log::debug!("node: {}", line);
|
||||||
|
|
||||||
|
|
@ -108,14 +123,50 @@ impl Handle for Node {
|
||||||
RESPONSE_OK => Ok(true),
|
RESPONSE_OK => Ok(true),
|
||||||
RESPONSE_NOOP => Ok(false),
|
RESPONSE_NOOP => Ok(false),
|
||||||
_ => Err(Error::InvalidResponse {
|
_ => Err(Error::InvalidResponse {
|
||||||
cmd: "untrack",
|
cmd: "track-repo",
|
||||||
|
response: line,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn untrack_node(&self, id: &NodeId) -> Result<bool, Error> {
|
||||||
|
let mut line = self.call("untrack-node", &[id])?;
|
||||||
|
let line = line.next().ok_or(Error::EmptyResponse {
|
||||||
|
cmd: "untrack-node",
|
||||||
|
})??;
|
||||||
|
|
||||||
|
log::debug!("node: {}", line);
|
||||||
|
|
||||||
|
match line.as_str() {
|
||||||
|
RESPONSE_OK => Ok(true),
|
||||||
|
RESPONSE_NOOP => Ok(false),
|
||||||
|
_ => Err(Error::InvalidResponse {
|
||||||
|
cmd: "untrack-node",
|
||||||
|
response: line,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn untrack_repo(&self, id: &Id) -> Result<bool, Error> {
|
||||||
|
let mut line = self.call("untrack-repo", &[id])?;
|
||||||
|
let line = line.next().ok_or(Error::EmptyResponse {
|
||||||
|
cmd: "untrack-repo",
|
||||||
|
})??;
|
||||||
|
|
||||||
|
log::debug!("node: {}", line);
|
||||||
|
|
||||||
|
match line.as_str() {
|
||||||
|
RESPONSE_OK => Ok(true),
|
||||||
|
RESPONSE_NOOP => Ok(false),
|
||||||
|
_ => Err(Error::InvalidResponse {
|
||||||
|
cmd: "untrack-repo",
|
||||||
response: line,
|
response: line,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn announce_refs(&self, id: &Id) -> Result<(), Error> {
|
fn announce_refs(&self, id: &Id) -> Result<(), Error> {
|
||||||
for line in self.call("announce-refs", id)? {
|
for line in self.call("announce-refs", &[id])? {
|
||||||
let line = line?;
|
let line = line?;
|
||||||
log::debug!("node: {}", line);
|
log::debug!("node: {}", line);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@ pub fn clone<P: AsRef<Path>, G: Signer, S: storage::WriteStorage, H: node::Handl
|
||||||
storage: &S,
|
storage: &S,
|
||||||
handle: &H,
|
handle: &H,
|
||||||
) -> Result<git2::Repository, CloneError> {
|
) -> Result<git2::Repository, CloneError> {
|
||||||
let _ = handle.track(&proj)?;
|
let _ = handle.track_repo(&proj)?;
|
||||||
let _ = handle.fetch(&proj)?;
|
let _ = handle.fetch(&proj)?;
|
||||||
let _ = fork(proj, signer, storage)?;
|
let _ = fork(proj, signer, storage)?;
|
||||||
let working = checkout(proj, signer.public_key(), path, storage)?;
|
let working = checkout(proj, signer.public_key(), path, storage)?;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue