diff --git a/radicle-cli/examples/rad-clone.md b/radicle-cli/examples/rad-clone.md new file mode 100644 index 00000000..aedcb76f --- /dev/null +++ b/radicle-cli/examples/rad-clone.md @@ -0,0 +1,45 @@ +To create a local copy of a repository on the radicle network, we use the +`clone` command, followed by the identifier or *RID* of the repository: + +``` +$ rad clone rad:zVNuptPuk5XauitpCWSNVCXGGfXW +ok Tracking relationship established for rad:zVNuptPuk5XauitpCWSNVCXGGfXW +ok Fetching rad:zVNuptPuk5XauitpCWSNVCXGGfXW from z6MknSL…StBU8Vi.. +ok Forking under z6Mkt67…v4N1tRk.. +ok Creating checkout in ./acme.. +ok Remote z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi created +ok Remote-tracking branch z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi + +🌱 Project successfully cloned under [..]/acme/ + +``` + +We can now have a look at the new working copy that was created from the cloned +repository: + +``` +$ cd acme +$ ls +README +$ cat README +Hello World! +``` + +Let's check that the remote tracking branch was setup correctly: + +``` +$ git branch --remotes + rad/master + z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master +``` + +The first branch is ours, and the second points to the repository delegate. +We can also take a look at the remotes: + +``` +$ git remote -v +rad rad://zVNuptPuk5XauitpCWSNVCXGGfXW/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (fetch) +rad rad://zVNuptPuk5XauitpCWSNVCXGGfXW/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (push) +z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://zVNuptPuk5XauitpCWSNVCXGGfXW/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (fetch) +z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://zVNuptPuk5XauitpCWSNVCXGGfXW/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push) +``` diff --git a/radicle-cli/src/commands/clone.rs b/radicle-cli/src/commands/clone.rs index f5d6eb05..bf78c88f 100644 --- a/radicle-cli/src/commands/clone.rs +++ b/radicle-cli/src/commands/clone.rs @@ -144,7 +144,7 @@ pub fn clone( // Track. if node.track_repo(id)? { term::success!( - "Tracking relationship restablished for {}", + "Tracking relationship established for {}", term::format::tertiary(id) ); } diff --git a/radicle-cli/tests/commands.rs b/radicle-cli/tests/commands.rs index ae84a97a..67c12603 100644 --- a/radicle-cli/tests/commands.rs +++ b/radicle-cli/tests/commands.rs @@ -5,7 +5,10 @@ use radicle::git; use radicle::profile::Home; use radicle::test::fixtures; -use radicle_node::test::environment::Environment; +use radicle_node::test::{ + environment::{Config, Environment}, + logger, +}; mod framework; use framework::TestFormula; @@ -64,15 +67,15 @@ fn rad_issue() { let mut environment = Environment::new(); let profile = environment.profile("alice"); let home = &profile.home; - let working = tempfile::tempdir().unwrap(); + let working = environment.tmp().join("working"); // Setup a test repository. - fixtures::repository(working.path()); + fixtures::repository(&working); // Set a fixed commit time. env::set_var(radicle_cob::git::RAD_COMMIT_TIME, "1671125284"); - test("examples/rad-init.md", working.path(), Some(home), []).unwrap(); - test("examples/rad-issue.md", working.path(), Some(home), []).unwrap(); + test("examples/rad-init.md", &working, Some(home), []).unwrap(); + test("examples/rad-issue.md", &working, Some(home), []).unwrap(); } #[test] @@ -149,3 +152,23 @@ fn rad_patch() { test("examples/rad-issue.md", working.path(), Some(home), []).unwrap(); test("examples/rad-patch.md", working.path(), Some(home), []).unwrap(); } + +#[test] +fn rad_clone() { + logger::init(log::Level::Debug); + + let mut environment = Environment::new(); + let mut alice = environment.node("alice"); + let bob = environment.node("bob"); + let working = environment.tmp().join("working"); + + // Setup a test project. + let _ = alice.project("acme", ""); + + let alice = alice.spawn(Config::default()); + let mut bob = bob.spawn(Config::default()); + + bob.connect(&alice).converge([&alice]); + + test("examples/rad-clone.md", working, Some(&bob.home), []).unwrap(); +} diff --git a/radicle-cli/tests/framework/mod.rs b/radicle-cli/tests/framework/mod.rs index 0755afb6..37d77584 100644 --- a/radicle-cli/tests/framework/mod.rs +++ b/radicle-cli/tests/framework/mod.rs @@ -139,6 +139,8 @@ impl TestFormula { pub fn run(&mut self) -> Result { let assert = Assert::new().substitutions(self.subs.clone()); + fs::create_dir_all(&self.cwd)?; + for test in &self.tests { for assertion in &test.assertions { let program = if assertion.program == "rad" { @@ -162,7 +164,14 @@ impl TestFormula { } else { PathBuf::from(&assertion.program) }; + log::debug!(target: "test", "Running `{}` in `{}`..", program.display(), self.cwd.display()); + if !program.exists() { + log::error!(target: "test", "Program {} does not exist..", program.display()); + } + if !self.cwd.exists() { + log::error!(target: "test", "Directory {} does not exist..", self.cwd.display()); + } let result = Command::new(program.clone()) .env_clear() .envs(env::vars().filter(|(k, _)| k == "PATH")) diff --git a/radicle-node/src/test/environment.rs b/radicle-node/src/test/environment.rs index 1e2ce71a..4529bb2e 100644 --- a/radicle-node/src/test/environment.rs +++ b/radicle-node/src/test/environment.rs @@ -1,5 +1,5 @@ use std::mem::ManuallyDrop; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::{ collections::{BTreeMap, BTreeSet}, iter, net, thread, @@ -23,6 +23,8 @@ use crate::node::NodeId; use crate::storage::git::transport; use crate::{runtime, runtime::Handle, service, Runtime}; +pub use service::Config; + /// Test environment. pub struct Environment { tempdir: tempfile::TempDir, @@ -44,6 +46,11 @@ impl Environment { Self::default() } + /// Return the temp directory path. + pub fn tmp(&self) -> PathBuf { + self.tempdir.path().join("misc") + } + /// Create a new node in this environment. This should be used when a running node /// is required. Use [`Environment::profile`] otherwise. pub fn node(&mut self, name: &str) -> Node { @@ -60,7 +67,9 @@ impl Environment { /// Create a new profile in this environment. /// This should be used when a running node is not required. pub fn profile(&mut self, name: &str) -> Profile { - let home = Home::new(self.tempdir.path().join(name)).init().unwrap(); + let home = Home::new(self.tmp().join("home").join(name)) + .init() + .unwrap(); let storage = Storage::open(home.storage()).unwrap(); let keystore = Keystore::new(&home.keys()); let keypair = KeyPair::from_seed(Seed::from([!(self.users as u8); 32])); @@ -94,6 +103,7 @@ pub struct NodeHandle { pub id: NodeId, pub storage: Storage, pub signer: G, + pub home: Home, pub addr: net::SocketAddr, pub thread: ManuallyDrop>>, pub handle: ManuallyDrop>, @@ -115,7 +125,7 @@ impl Drop for NodeHandle { impl NodeHandle { /// Connect this node to another node, and wait for the connection to be established both ways. - pub fn connect(&mut self, remote: &NodeHandle) { + pub fn connect(&mut self, remote: &NodeHandle) -> &mut Self { self.handle.connect(remote.id, remote.addr.into()).unwrap(); loop { @@ -137,6 +147,15 @@ impl NodeHandle { } thread::sleep(Duration::from_millis(100)); } + self + } + + /// Wait until this node's routing table matches the remotes. + pub fn converge<'a>( + &'a self, + remotes: impl IntoIterator>, + ) -> BTreeSet<(Id, NodeId)> { + converge(iter::once(self).chain(remotes.into_iter())) } } @@ -167,7 +186,7 @@ impl + Signer + Clone> Node + Signer + Clone> Node + Signer + Clone> Node Id { + pub fn project(&mut self, name: &str, description: &str) -> Id { transport::local::register(self.storage.clone()); let tmp = tempfile::tempdir().unwrap(); let (repo, _) = fixtures::gen::repository(tmp.path()); - let description = iter::repeat_with(fastrand::alphabetic) - .take(12) - .collect::(); let id = rad::init( &repo, name, - &description, + description, refname!("master"), &self.signer, &self.storage, diff --git a/radicle-node/src/tests/e2e.rs b/radicle-node/src/tests/e2e.rs index 9860cc23..9a7af353 100644 --- a/radicle-node/src/tests/e2e.rs +++ b/radicle-node/src/tests/e2e.rs @@ -20,8 +20,8 @@ fn test_inventory_sync_basic() { let mut alice = Node::init(tmp.path()); let mut bob = Node::init(tmp.path()); - alice.project("alice"); - bob.project("bob"); + alice.project("alice", ""); + bob.project("bob", ""); let mut alice = alice.spawn(service::Config::default()); let bob = bob.spawn(service::Config::default()); @@ -45,9 +45,9 @@ fn test_inventory_sync_bridge() { let mut bob = Node::init(tmp.path()); let mut eve = Node::init(tmp.path()); - alice.project("alice"); - bob.project("bob"); - eve.project("eve"); + alice.project("alice", ""); + bob.project("bob", ""); + eve.project("eve", ""); let mut alice = alice.spawn(service::Config::default()); let mut bob = bob.spawn(service::Config::default()); @@ -76,10 +76,10 @@ fn test_inventory_sync_ring() { let mut eve = Node::init(tmp.path()); let mut carol = Node::init(tmp.path()); - alice.project("alice"); - bob.project("bob"); - eve.project("eve"); - carol.project("carol"); + alice.project("alice", ""); + bob.project("bob", ""); + eve.project("eve", ""); + carol.project("carol", ""); let mut alice = alice.spawn(service::Config::default()); let mut bob = bob.spawn(service::Config::default()); @@ -114,11 +114,11 @@ fn test_inventory_sync_star() { let mut carol = Node::init(tmp.path()); let mut dave = Node::init(tmp.path()); - alice.project("alice"); - bob.project("bob"); - eve.project("eve"); - carol.project("carol"); - dave.project("dave"); + alice.project("alice", ""); + bob.project("bob", ""); + eve.project("eve", ""); + carol.project("carol", ""); + dave.project("dave", ""); let alice = alice.spawn(service::Config::default()); let mut bob = bob.spawn(service::Config::default()); @@ -142,7 +142,7 @@ fn test_replication() { let tmp = tempfile::tempdir().unwrap(); let alice = Node::init(tmp.path()); let mut bob = Node::init(tmp.path()); - let acme = bob.project("acme"); + let acme = bob.project("acme", ""); let mut alice = alice.spawn(service::Config::default()); let bob = bob.spawn(service::Config::default()); @@ -199,7 +199,7 @@ fn test_clone() { let tmp = tempfile::tempdir().unwrap(); let alice = Node::init(tmp.path()); let mut bob = Node::init(tmp.path()); - let acme = bob.project("acme"); + let acme = bob.project("acme", ""); let mut alice = alice.spawn(service::Config::default()); let bob = bob.spawn(service::Config::default()); @@ -249,7 +249,7 @@ fn test_fetch_up_to_date() { let tmp = tempfile::tempdir().unwrap(); let alice = Node::init(tmp.path()); let mut bob = Node::init(tmp.path()); - let acme = bob.project("acme"); + let acme = bob.project("acme", ""); let mut alice = alice.spawn(service::Config::default()); let bob = bob.spawn(service::Config::default());