cli: Implement automatic issue announcement
This commit is contained in:
parent
cb3b7a3765
commit
85df613682
|
|
@ -4,7 +4,7 @@ using the 'issue' subcommand.
|
|||
Let's say the new car you are designing with your peers has a problem with its flux capacitor.
|
||||
|
||||
```
|
||||
$ rad issue open --title "flux capacitor underpowered" --description "Flux capacitor power requirements exceed current supply"
|
||||
$ rad issue open --title "flux capacitor underpowered" --description "Flux capacitor power requirements exceed current supply" --no-announce
|
||||
```
|
||||
|
||||
The issue is now listed under our project.
|
||||
|
|
|
|||
|
|
@ -3,16 +3,17 @@ use std::ffi::OsString;
|
|||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{anyhow, Context as _};
|
||||
use radicle::node::Handle;
|
||||
use radicle::prelude::Did;
|
||||
|
||||
use crate::terminal as term;
|
||||
use crate::terminal::args::{Args, Error, Help};
|
||||
|
||||
use radicle::cob;
|
||||
use radicle::cob::common::{Reaction, Tag};
|
||||
use radicle::cob::issue;
|
||||
use radicle::cob::issue::{CloseReason, IssueId, Issues, State};
|
||||
use radicle::storage::WriteStorage;
|
||||
use radicle::{cob, Node};
|
||||
|
||||
pub const HELP: Help = Help {
|
||||
name: "issue",
|
||||
|
|
@ -31,7 +32,8 @@ Usage
|
|||
|
||||
Options
|
||||
|
||||
--help Print help
|
||||
--no-announce Don't announce issue to peers
|
||||
--help Print help
|
||||
"#,
|
||||
};
|
||||
|
||||
|
|
@ -89,6 +91,7 @@ pub enum Operation {
|
|||
#[derive(Debug)]
|
||||
pub struct Options {
|
||||
pub op: Operation,
|
||||
pub announce: bool,
|
||||
}
|
||||
|
||||
impl Args for Options {
|
||||
|
|
@ -103,6 +106,7 @@ impl Args for Options {
|
|||
let mut reaction: Option<Reaction> = None;
|
||||
let mut description: Option<String> = None;
|
||||
let mut state: Option<State> = None;
|
||||
let mut announce = true;
|
||||
|
||||
while let Some(arg) = parser.next()? {
|
||||
match arg {
|
||||
|
|
@ -142,6 +146,9 @@ impl Args for Options {
|
|||
assigned = Some(Assigned::Me);
|
||||
}
|
||||
}
|
||||
Long("no-announce") => {
|
||||
announce = false;
|
||||
}
|
||||
Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
|
||||
"c" | "show" => op = Some(OperationName::Show),
|
||||
"d" | "delete" => op = Some(OperationName::Delete),
|
||||
|
|
@ -187,16 +194,25 @@ impl Args for Options {
|
|||
OperationName::List => Operation::List { assigned },
|
||||
};
|
||||
|
||||
Ok((Options { op }, vec![]))
|
||||
Ok((Options { op, announce }, vec![]))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||
let profile = ctx.profile()?;
|
||||
let signer = term::signer(&profile)?;
|
||||
let storage = &profile.storage;
|
||||
let (_, id) = radicle::rad::cwd()?;
|
||||
let repo = storage.repository_mut(id)?;
|
||||
let (_, rid) = radicle::rad::cwd()?;
|
||||
let repo = profile.storage.repository_mut(rid)?;
|
||||
let announce = options.announce
|
||||
&& matches!(
|
||||
&options.op,
|
||||
Operation::Open { .. }
|
||||
| Operation::React { .. }
|
||||
| Operation::State { .. }
|
||||
| Operation::Delete { .. }
|
||||
);
|
||||
|
||||
let mut node = Node::new(profile.socket());
|
||||
let mut issues = Issues::open(&repo)?;
|
||||
|
||||
match options.op {
|
||||
|
|
@ -306,6 +322,16 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
if announce {
|
||||
match node.announce_refs(rid) {
|
||||
Ok(()) => {}
|
||||
Err(e) if e.is_connection_err() => {
|
||||
term::warning("Could not announce issue refs: node is not running");
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -345,6 +345,56 @@ fn test_clone_without_seeds() {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cob_replication() {
|
||||
logger::init(log::Level::Debug);
|
||||
|
||||
let mut environment = Environment::new();
|
||||
let working = tempfile::tempdir().unwrap();
|
||||
let mut alice = environment.node("alice");
|
||||
let bob = environment.node("bob");
|
||||
|
||||
let rid = alice.project("heartwood", "");
|
||||
|
||||
let mut alice = alice.spawn(Config::default());
|
||||
let mut bob = bob.spawn(Config::default());
|
||||
|
||||
alice.handle.track_node(bob.id, None).unwrap();
|
||||
alice.connect(&bob);
|
||||
|
||||
bob.routes_to(&[(rid, alice.id)]);
|
||||
bob.rad("clone", &[rid.to_string().as_str()], working.path())
|
||||
.unwrap();
|
||||
|
||||
let bob_repo = bob.storage.repository(rid).unwrap();
|
||||
let mut bob_issues = radicle::cob::issue::Issues::open(&bob_repo).unwrap();
|
||||
let issue = bob_issues
|
||||
.create(
|
||||
"Something's fishy",
|
||||
"I don't know what it is",
|
||||
&[],
|
||||
&[],
|
||||
&bob.signer,
|
||||
)
|
||||
.unwrap();
|
||||
log::debug!(target: "test", "Issue {} created", issue.id());
|
||||
|
||||
// Make sure that Bob's issue refs announcement has a different timestamp than his fork's
|
||||
// announcement, otherwise Alice will consider it stale.
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
|
||||
bob.handle.announce_refs(rid).unwrap();
|
||||
|
||||
// Wait for Alice to fetch the issue refs.
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
|
||||
let alice_repo = alice.storage.repository(rid).unwrap();
|
||||
let alice_issues = radicle::cob::issue::Issues::open(&alice_repo).unwrap();
|
||||
let alice_issue = alice_issues.get(issue.id()).unwrap().unwrap();
|
||||
|
||||
assert_eq!(alice_issue.title(), "Something's fishy");
|
||||
}
|
||||
|
||||
#[test]
|
||||
//
|
||||
// alice -- seed -- bob
|
||||
|
|
|
|||
|
|
@ -883,7 +883,7 @@ where
|
|||
// Discard announcement messages we've already seen, otherwise update
|
||||
// our last seen time.
|
||||
if !peer.refs_announced(message.rid, timestamp) {
|
||||
debug!(target: "service", "Ignoring stale refs announcement from {announcer}");
|
||||
debug!(target: "service", "Ignoring stale refs announcement from {announcer} (time={timestamp})");
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -255,7 +255,11 @@ impl fmt::Debug for AnnouncementMessage {
|
|||
)
|
||||
}
|
||||
Self::Refs(message) => {
|
||||
write!(f, "Refs({}, {:?})", message.rid, message.refs)
|
||||
write!(
|
||||
f,
|
||||
"Refs({}, {}, {:?})",
|
||||
message.rid, message.timestamp, message.refs
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -347,6 +347,13 @@ pub enum Error {
|
|||
EmptyResponse { cmd: CommandName },
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Check if the error is due to the not being able to connect to the local node.
|
||||
pub fn is_connection_err(&self) -> bool {
|
||||
matches!(self, Self::Connect(_))
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by [`Node::call`] iterator.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum CallError {
|
||||
|
|
|
|||
Loading…
Reference in New Issue