diff --git a/radicle-cli/examples/git/git-push-diverge.md b/radicle-cli/examples/git/git-push-diverge.md index 7a7f2334..09adb837 100644 --- a/radicle-cli/examples/git/git-push-diverge.md +++ b/radicle-cli/examples/git/git-push-diverge.md @@ -7,9 +7,6 @@ First we add a second delegate, Bob, to our repo: ``` ~alice $ rad id update --title "Add Bob" --description "" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji -q c036c0d89ce26aef3ad7da402157dba16b5163b4 -$ rad remote add did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk -✓ Remote bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk added -✓ Remote-tracking branch bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk/master created for z6Mkt67…v4N1tRk ``` Then, as Bob, we commit some code on top of the canonical head: @@ -32,7 +29,9 @@ As Alice, we fetch that code, but commit on top of our own master, which is no longer canonical, since Bob pushed a more recent commit, and the threshold is 1: ``` ~alice -$ git fetch bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk +$ rad remote add did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --fetch --no-sync +✓ Remote bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk added +✓ Remote-tracking branch bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk/master created for z6Mkt67…v4N1tRk $ git branch -arv bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk/master 319a7dc Third commit rad/master f2de534 Second commit diff --git a/radicle-cli/examples/rad-clone-all.md b/radicle-cli/examples/rad-clone-all.md index 8e0fc084..f6ee9b61 100644 --- a/radicle-cli/examples/rad-clone-all.md +++ b/radicle-cli/examples/rad-clone-all.md @@ -48,18 +48,9 @@ z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk We can then setup a git remote for `bob`: ``` -$ rad remote add z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --name bob +$ rad remote add z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --name bob --no-sync --fetch ✓ Remote bob added ✓ Remote-tracking branch bob/master created for z6Mkt67…v4N1tRk -``` - -And fetch his refs: - -``` -$ git fetch --all -Fetching rad -Fetching alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi -Fetching bob $ git branch --remotes alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master bob/master diff --git a/radicle-cli/examples/rad-push-and-pull-patches.md b/radicle-cli/examples/rad-push-and-pull-patches.md index 8228f375..84ac901f 100644 --- a/radicle-cli/examples/rad-push-and-pull-patches.md +++ b/radicle-cli/examples/rad-push-and-pull-patches.md @@ -8,9 +8,6 @@ $ git push -o patch.message="Changes" rad HEAD:refs/patches ``` ``` ~alice -$ rad sync -f -✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk.. -✓ Fetched repository from 1 seed(s) $ git checkout -b alice/1 -q $ git rev-parse HEAD f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 @@ -19,13 +16,12 @@ $ rad patch checkout 0fd67a0 ✓ Switched to branch patch/0fd67a0 ✓ Branch patch/0fd67a0 setup to track rad/patches/0fd67a0364af1f79ed8770a35ed09d85571d4c21 $ rad remote add z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk +✓ Follow policy updated for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk +✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk.. ✓ Remote bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk added ✓ Remote-tracking branch bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk/master created for z6Mkt67…v4N1tRk $ git checkout master -q -$ git fetch --all -q $ cat .git/FETCH_HEAD -f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 branch 'master' of rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji -8d5f1bae4b69d8e3f6cbfc6f4bd675ed19990afc not-for-merge branch 'patches/0fd67a0364af1f79ed8770a35ed09d85571d4c21' of rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 not-for-merge branch 'master' of rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk 8d5f1bae4b69d8e3f6cbfc6f4bd675ed19990afc not-for-merge branch 'patches/0fd67a0364af1f79ed8770a35ed09d85571d4c21' of rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk $ git rev-parse master diff --git a/radicle-cli/examples/rad-remote.md b/radicle-cli/examples/rad-remote.md index 14d1f867..f9c1acfd 100644 --- a/radicle-cli/examples/rad-remote.md +++ b/radicle-cli/examples/rad-remote.md @@ -1,7 +1,7 @@ Now, let's add a bob as a new remote: ``` -$ rad remote add did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --name bob +$ rad remote add did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --name bob --no-sync ✓ Remote bob added ✓ Remote-tracking branch bob/master created for z6Mkt67…v4N1tRk ``` @@ -18,14 +18,6 @@ rad z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push) You can see both `bob` and `rad` as remotes. The `rad` remote is our personal remote of the project. -For the remote-tracking branch to work, we fetch bob: - -``` RAD_SOCKET=/dev/null (stderr) -$ git fetch bob -From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk - * [new branch] master -> bob/master -``` - We can now see the remote-tracking branch that was setup: ``` @@ -47,7 +39,7 @@ Now, add another time `bob` but without specify the `name`, so we should be able to fetch the node alias from our db! ``` -$ rad remote add did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk +$ rad remote add did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-sync ✓ Remote bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk added ✓ Remote-tracking branch bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk/master created for z6Mkt67…v4N1tRk ``` @@ -76,7 +68,7 @@ As we can see, we have also have another remote namespace `eve`, so let's add them to our set of working copy remotes: ``` -$ rad remote add did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --name eve +$ rad remote add did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --name eve --no-sync ✓ Remote eve added ✓ Remote-tracking branch eve/master created for z6Mkux1…nVhib7Z ``` diff --git a/radicle-cli/examples/workflow/5-patching-maintainer.md b/radicle-cli/examples/workflow/5-patching-maintainer.md index d25f4757..274d58ad 100644 --- a/radicle-cli/examples/workflow/5-patching-maintainer.md +++ b/radicle-cli/examples/workflow/5-patching-maintainer.md @@ -1,29 +1,16 @@ Back to being the project maintainer. -Changes have been proposed by another person (or peer) via a radicle patch. To -follow changes by another, we must 'follow' them. +Changes have been proposed by another peer via a radicle patch. To track +changes from another peer, we must first follow them, and then create +a tracking branch in our working copy. The `rad remote add` command does all +of this. ``` -$ rad follow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --alias bob +$ rad remote add z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --name bob --sync --fetch ✓ Follow policy updated for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob) -``` - -Additionally, we need to add a new 'git remote' to our working copy for the -peer. Upcoming versions of radicle will not require this step. - -``` -$ rad remote add z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --name bob +✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk.. ✓ Remote bob added ✓ Remote-tracking branch bob/master created for z6Mkt67…v4N1tRk -$ rad sync -f -✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk.. -✓ Fetched repository from 1 seed(s) -``` - -``` (stderr) -$ git fetch bob -From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk - * [new branch] patches/3581e83ad18f5cdd806ab50fa11cfd5dd4e8ae1c -> bob/patches/3581e83ad18f5cdd806ab50fa11cfd5dd4e8ae1c ``` The contributor's changes are now visible to us. diff --git a/radicle-cli/src/commands/checkout.rs b/radicle-cli/src/commands/checkout.rs index 4c7bcacb..da6f009f 100644 --- a/radicle-cli/src/commands/checkout.rs +++ b/radicle-cli/src/commands/checkout.rs @@ -156,7 +156,7 @@ pub fn setup_remote( remote_id: &NodeId, remote_name: Option, aliases: &impl AliasStore, -) -> anyhow::Result<()> { +) -> anyhow::Result { let remote_name = if let Some(name) = remote_name { name } else { @@ -168,7 +168,7 @@ pub fn setup_remote( git::RefString::try_from(name.as_str()) .map_err(|_| anyhow!("invalid remote name: '{name}'"))? }; - let (remote, branch) = setup.run(remote_name, *remote_id)?; + let (remote, branch) = setup.run(&remote_name, *remote_id)?; term::success!("Remote {} added", term::format::tertiary(remote.name)); @@ -179,5 +179,5 @@ pub fn setup_remote( term::format::tertiary(term::format::node(remote_id)) ); } - Ok(()) + Ok(remote_name) } diff --git a/radicle-cli/src/commands/issue.rs b/radicle-cli/src/commands/issue.rs index 34d5f287..a990b905 100644 --- a/radicle-cli/src/commands/issue.rs +++ b/radicle-cli/src/commands/issue.rs @@ -528,7 +528,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { if announce { let mut node = Node::new(profile.socket()); - node::sync(rid, &mut node)?; + node::announce(rid, &mut node)?; } Ok(()) diff --git a/radicle-cli/src/commands/ls.rs b/radicle-cli/src/commands/ls.rs index f8b2d5ea..26374fba 100644 --- a/radicle-cli/src/commands/ls.rs +++ b/radicle-cli/src/commands/ls.rs @@ -106,7 +106,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { if refs.is_none() && !options.all { continue; } - let seeded = policy.is_repo_seeded(&rid)?; + let seeded = policy.is_seeded(&rid)?; if !seeded && !options.all { continue; diff --git a/radicle-cli/src/commands/patch.rs b/radicle-cli/src/commands/patch.rs index 0dc12c78..c3fc3337 100644 --- a/radicle-cli/src/commands/patch.rs +++ b/radicle-cli/src/commands/patch.rs @@ -794,7 +794,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { if announce { let mut node = Node::new(profile.socket()); - node::sync(id, &mut node)?; + node::announce(id, &mut node)?; } Ok(()) } diff --git a/radicle-cli/src/commands/remote.rs b/radicle-cli/src/commands/remote.rs index b031eb1b..403bd9ba 100644 --- a/radicle-cli/src/commands/remote.rs +++ b/radicle-cli/src/commands/remote.rs @@ -39,6 +39,8 @@ List options Add options --name Override the name of the remote that by default is set to the node alias + --[no-]fetch Fetch the remote from local storage (default: fetch) + --[no-]sync Sync the remote refs from the network (default: sync) Options @@ -56,9 +58,18 @@ pub enum OperationName { #[derive(Debug)] pub enum Operation { - Add { id: NodeId, name: Option }, - Rm { name: RefString }, - List { option: ListOption }, + Add { + id: NodeId, + name: Option, + fetch: bool, + sync: bool, + }, + Rm { + name: RefString, + }, + List { + option: ListOption, + }, } #[derive(Debug, Default)] @@ -83,6 +94,8 @@ impl Args for Options { let mut id: Option = None; let mut name: Option = None; let mut list_op: ListOption = ListOption::default(); + let mut fetch = true; + let mut sync = true; while let Some(arg) = parser.next()? { match arg { @@ -114,6 +127,18 @@ impl Args for Options { } // Add options + Long("sync") if op == Some(OperationName::Add) => { + sync = true; + } + Long("no-sync") if op == Some(OperationName::Add) => { + sync = false; + } + Long("fetch") if op == Some(OperationName::Add) => { + fetch = true; + } + Long("no-fetch") if op == Some(OperationName::Add) => { + fetch = false; + } Value(val) if op == Some(OperationName::Add) && id.is_none() => { let nid = args::pubkey(&val)?; id = Some(nid); @@ -137,6 +162,8 @@ impl Args for Options { "`DID` required, try running `rad remote add `" ))?, name, + fetch, + sync, }, OperationName::List => Operation::List { option: list_op }, OperationName::Rm => Operation::Rm { @@ -154,11 +181,25 @@ pub fn run(options: Options, ctx: impl Context) -> anyhow::Result<()> { let profile = ctx.profile()?; match options.op { - Operation::Add { ref id, name } => { + Operation::Add { + ref id, + name, + fetch, + sync, + } => { let proj = profile.storage.repository(rid)?.project()?; let branch = proj.default_branch(); - self::add::run(rid, id, name, Some(branch.clone()), &profile, &working)? + self::add::run( + rid, + id, + name, + Some(branch.clone()), + &profile, + &working, + fetch, + sync, + )? } Operation::Rm { ref name } => self::rm::run(name, &working)?, Operation::List { option } => match option { diff --git a/radicle-cli/src/commands/remote/add.rs b/radicle-cli/src/commands/remote/add.rs index d17382a0..5ac0921c 100644 --- a/radicle-cli/src/commands/remote/add.rs +++ b/radicle-cli/src/commands/remote/add.rs @@ -1,10 +1,15 @@ +use std::str::FromStr; +use std::time; + +use radicle::git; use radicle::git::RefString; use radicle::prelude::*; use radicle::Profile; use radicle_crypto::PublicKey; use crate::commands::rad_checkout as checkout; -use crate::git; +use crate::commands::rad_follow as follow; +use crate::commands::rad_sync as sync; use crate::project::SetupRemote; pub fn run( @@ -13,14 +18,33 @@ pub fn run( name: Option, tracking: Option, profile: &Profile, - repo: &git::Repository, + repo: &git::raw::Repository, + fetch: bool, + sync: bool, ) -> anyhow::Result<()> { + if sync { + let mut node = radicle::Node::new(profile.socket()); + + if !profile.policies()?.is_followed(nid)? { + let alias = name.as_ref().and_then(|n| Alias::from_str(n.as_str()).ok()); + + follow::follow(*nid, alias, &mut node, profile)?; + sync::fetch( + rid, + sync::RepoSync::default(), + time::Duration::from_secs(9), + &mut node, + )?; + } + } let aliases = profile.aliases(); let setup = SetupRemote { rid, tracking, - fetch: false, + fetch, repo, }; - checkout::setup_remote(&setup, nid, name, &aliases) + checkout::setup_remote(&setup, nid, name, &aliases)?; + + Ok(()) } diff --git a/radicle-cli/src/commands/sync.rs b/radicle-cli/src/commands/sync.rs index a940d69e..22e59046 100644 --- a/radicle-cli/src/commands/sync.rs +++ b/radicle-cli/src/commands/sync.rs @@ -280,7 +280,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { } Operation::Synchronize(SyncMode::Repo { mode, direction }) => { if [SyncDirection::Fetch, SyncDirection::Both].contains(&direction) { - if !profile.policies()?.is_repo_seeded(&rid)? { + if !profile.policies()?.is_seeded(&rid)? { anyhow::bail!("repository {rid} is not seeded"); } let results = fetch(rid, mode.clone(), options.timeout, &mut node)?; diff --git a/radicle-cli/src/node.rs b/radicle-cli/src/node.rs index 667a2cef..463f1a01 100644 --- a/radicle-cli/src/node.rs +++ b/radicle-cli/src/node.rs @@ -7,8 +7,9 @@ use radicle::Node; use crate::terminal as term; -pub fn sync(rid: Id, node: &mut Node) -> anyhow::Result<()> { - match sync_(rid, node) { +/// Announce changes to the network. +pub fn announce(rid: Id, node: &mut Node) -> anyhow::Result<()> { + match announce_(rid, node) { Ok(()) => Ok(()), Err(e) if e.is_connection_err() => { term::hint("Node is stopped. To announce changes to the network, start it with `rad node start`."); @@ -18,8 +19,7 @@ pub fn sync(rid: Id, node: &mut Node) -> anyhow::Result<()> { } } -/// Sync with the network. -fn sync_(rid: Id, node: &mut Node) -> Result<(), radicle::node::Error> { +fn announce_(rid: Id, node: &mut Node) -> Result<(), radicle::node::Error> { let seeds = node.seeds(rid)?; let connected = seeds.connected().map(|s| s.nid).collect::>(); diff --git a/radicle/src/node/policy/store.rs b/radicle/src/node/policy/store.rs index 1616ec38..35d65351 100644 --- a/radicle/src/node/policy/store.rs +++ b/radicle/src/node/policy/store.rs @@ -204,7 +204,7 @@ impl Store { /// `Config` can access these functions as well. impl Store { /// Check if a node is followed. - pub fn is_node_followed(&self, id: &NodeId) -> Result { + pub fn is_followed(&self, id: &NodeId) -> Result { Ok(matches!( self.follow_policy(id)?, Some(Node { @@ -215,7 +215,7 @@ impl Store { } /// Check if a repository is seeded. - pub fn is_repo_seeded(&self, id: &Id) -> Result { + pub fn is_seeded(&self, id: &Id) -> Result { Ok(matches!( self.seed_policy(id)?, Some(Repo { @@ -335,10 +335,10 @@ mod test { let mut db = Store::open(":memory:").unwrap(); assert!(db.follow(&id, Some("eve")).unwrap()); - assert!(db.is_node_followed(&id).unwrap()); + assert!(db.is_followed(&id).unwrap()); assert!(!db.follow(&id, Some("eve")).unwrap()); assert!(db.unfollow(&id).unwrap()); - assert!(!db.is_node_followed(&id).unwrap()); + assert!(!db.is_followed(&id).unwrap()); } #[test] @@ -347,10 +347,10 @@ mod test { let mut db = Store::open(":memory:").unwrap(); assert!(db.seed(&id, Scope::All).unwrap()); - assert!(db.is_repo_seeded(&id).unwrap()); + assert!(db.is_seeded(&id).unwrap()); assert!(!db.seed(&id, Scope::All).unwrap()); assert!(db.unseed(&id).unwrap()); - assert!(!db.is_repo_seeded(&id).unwrap()); + assert!(!db.is_seeded(&id).unwrap()); } #[test] diff --git a/radicle/src/profile.rs b/radicle/src/profile.rs index 5120fc96..063da411 100644 --- a/radicle/src/profile.rs +++ b/radicle/src/profile.rs @@ -20,6 +20,7 @@ use thiserror::Error; use crate::crypto::ssh::agent::Agent; use crate::crypto::ssh::{keystore, Keystore, Passphrase}; use crate::crypto::{PublicKey, Signer}; +use crate::node::policy::config::store::Read; use crate::node::{policy, Alias, AliasStore}; use crate::prelude::Did; use crate::prelude::{Id, NodeId}; @@ -346,6 +347,17 @@ impl Profile { &self.home } + /// Return a read-only handle to the policies of the node. + pub fn policies(&self) -> Result, policy::store::Error> { + let path = self.node().join(node::POLICIES_DB_FILE); + let config = policy::config::Config::new( + self.config.node.policy, + self.config.node.scope, + policy::store::Store::reader(path)?, + ); + Ok(config) + } + /// Return a multi-source store for aliases. pub fn aliases(&self) -> Aliases { let policies = self.home.policies().ok(); @@ -471,15 +483,7 @@ impl Home { .unwrap_or_else(|| self.node().join(node::DEFAULT_SOCKET_NAME)) } - /// Return a read-only handle to the policies of the node. - pub fn policies(&self) -> Result { - let path = self.node().join(node::POLICIES_DB_FILE); - let config = policy::store::Store::reader(path)?; - - Ok(config) - } - - /// Return a read-write handle to the policies of the node. + /// Return a read-write handle to the policies store of the node. pub fn policies_mut(&self) -> Result { let path = self.node().join(node::POLICIES_DB_FILE); let config = policy::store::Store::open(path)?; @@ -504,6 +508,17 @@ impl Home { } } +// Private methods. +impl Home { + /// Return a read-only handle to the policies store of the node. + fn policies(&self) -> Result { + let path = self.node().join(node::POLICIES_DB_FILE); + let config = policy::store::Store::reader(path)?; + + Ok(config) + } +} + #[cfg(test)] #[cfg(not(target_os = "macos"))] mod test {