cli: Consolidate working copy remote setup
We consolidate the setup done by `rad checkout`, `rad clone` and `rad remote add`, so that they all check for node aliases, and are all capable of setting up tracking branches etc.
This commit is contained in:
parent
5502676aa7
commit
e9ef0f4aa4
|
|
@ -0,0 +1,76 @@
|
||||||
|
<!-- TODO: Currently, `rad clone`, even with `--scope all` will not fetch all remotes -->
|
||||||
|
<!-- We have to issue a separate `rad sync --fetch` -->
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope all
|
||||||
|
✓ Tracking relationship established for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
|
||||||
|
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
|
||||||
|
✓ Forking under z6Mkux1…nVhib7Z..
|
||||||
|
✓ Creating checkout in ./heartwood..
|
||||||
|
✓ Remote z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
|
||||||
|
✓ Remote-tracking branch z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
|
||||||
|
✓ Repository successfully cloned under [..]/heartwood/
|
||||||
|
```
|
||||||
|
|
||||||
|
We can now have a look at the new working copy that was created from the cloned
|
||||||
|
repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd heartwood
|
||||||
|
$ cat README
|
||||||
|
Hello World!
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad sync --fetch
|
||||||
|
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
|
||||||
|
✓ Fetched repository from 1 seed(s)
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's check that we have all the namespaces in storage:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad inspect --refs
|
||||||
|
.
|
||||||
|
|-- z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||||
|
| `-- refs
|
||||||
|
| |-- heads
|
||||||
|
| | `-- master
|
||||||
|
| `-- rad
|
||||||
|
| |-- id
|
||||||
|
| `-- sigrefs
|
||||||
|
|-- z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
|
||||||
|
| `-- refs
|
||||||
|
| |-- heads
|
||||||
|
| | `-- master
|
||||||
|
| `-- rad
|
||||||
|
| |-- id
|
||||||
|
| `-- sigrefs
|
||||||
|
`-- z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z
|
||||||
|
`-- refs
|
||||||
|
|-- heads
|
||||||
|
| `-- master
|
||||||
|
`-- rad
|
||||||
|
|-- id
|
||||||
|
`-- sigrefs
|
||||||
|
```
|
||||||
|
|
||||||
|
We can then setup a git remote for `bob`:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad remote add z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --name bob
|
||||||
|
✓ Remote bob added
|
||||||
|
```
|
||||||
|
|
||||||
|
And fetch his refs:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git fetch --all
|
||||||
|
Fetching rad
|
||||||
|
Fetching z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||||
|
Fetching bob
|
||||||
|
$ git branch --remotes
|
||||||
|
bob/master
|
||||||
|
rad/master
|
||||||
|
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
|
||||||
|
```
|
||||||
|
|
@ -2,12 +2,12 @@ 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:
|
`clone` command, followed by the identifier or *RID* of the repository:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope trusted
|
||||||
✓ Tracking relationship established for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
|
✓ Tracking relationship established for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'trusted'
|
||||||
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
|
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
|
||||||
✓ Forking under z6Mkt67…v4N1tRk..
|
✓ Forking under z6Mkt67…v4N1tRk..
|
||||||
✓ Creating checkout in ./heartwood..
|
✓ Creating checkout in ./heartwood..
|
||||||
✓ Remote z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi created
|
✓ Remote z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
|
||||||
✓ Remote-tracking branch z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
|
✓ Remote-tracking branch z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
|
||||||
✓ Repository successfully cloned under [..]/heartwood/
|
✓ Repository successfully cloned under [..]/heartwood/
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,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
|
||||||
✓ Remote bob added with rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
|
✓ Remote bob added
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, we can see that there is a new remote in the list of remotes:
|
Now, we can see that there is a new remote in the list of remotes:
|
||||||
|
|
@ -14,7 +14,8 @@ rad (canonical upstream) (fetch)
|
||||||
rad z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
|
rad z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
|
||||||
```
|
```
|
||||||
|
|
||||||
You can see both `bob` and `rad` as remotes. The `rad` remote is our personal remote of the project.
|
You can see both `bob` and `rad` as remotes. The `rad` remote is our personal
|
||||||
|
remote of the project.
|
||||||
|
|
||||||
When we're finished with the `bob` remote, we can remove it:
|
When we're finished with the `bob` remote, we can remove it:
|
||||||
|
|
||||||
|
|
@ -23,9 +24,10 @@ $ rad remote rm bob
|
||||||
✓ Remote `bob` removed
|
✓ Remote `bob` removed
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, add another time `bob` but without specify the `name`, so we should be able to fetch the node alias from our db!
|
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
|
||||||
✓ Remote bob added with rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
|
✓ Remote bob added
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ peer. Upcoming versions of radicle will not require this step.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rad remote add z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --name bob
|
$ rad remote add z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --name bob
|
||||||
✓ Remote bob added with rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
|
✓ Remote bob added
|
||||||
```
|
```
|
||||||
|
|
||||||
``` (stderr)
|
``` (stderr)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
|
#![allow(clippy::box_default)]
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
|
|
||||||
|
use radicle::git;
|
||||||
|
use radicle::node::AliasStore;
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
use radicle::storage::git::transport;
|
use radicle::storage::git::transport;
|
||||||
|
|
||||||
|
|
@ -118,31 +122,63 @@ fn execute(options: Options, profile: &Profile) -> anyhow::Result<PathBuf> {
|
||||||
// Setup remote tracking branches for project delegates.
|
// Setup remote tracking branches for project delegates.
|
||||||
setup_remotes(
|
setup_remotes(
|
||||||
project::SetupRemote {
|
project::SetupRemote {
|
||||||
project: id,
|
rid: id,
|
||||||
default_branch: payload.default_branch().clone(),
|
tracking: Some(payload.default_branch().clone()),
|
||||||
repo: &repo,
|
repo: &repo,
|
||||||
fetch: true,
|
fetch: true,
|
||||||
tracking: true,
|
|
||||||
},
|
},
|
||||||
&remotes,
|
&remotes,
|
||||||
|
profile,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Setup a remote and tracking branch for each given remote.
|
/// Setup a remote and tracking branch for each given remote.
|
||||||
pub fn setup_remotes(setup: project::SetupRemote, remotes: &[NodeId]) -> anyhow::Result<()> {
|
pub fn setup_remotes(
|
||||||
|
setup: project::SetupRemote,
|
||||||
|
remotes: &[NodeId],
|
||||||
|
profile: &Profile,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let aliases = if let Ok(aliases) = profile.aliases() {
|
||||||
|
Box::new(aliases) as Box<dyn AliasStore>
|
||||||
|
} else {
|
||||||
|
Box::new(HashMap::new()) as Box<dyn AliasStore>
|
||||||
|
};
|
||||||
for remote_id in remotes {
|
for remote_id in remotes {
|
||||||
if let Some((remote, branch)) = setup.run(*remote_id)? {
|
if let Err(e) = setup_remote(&setup, remote_id, None, &aliases) {
|
||||||
let remote = remote.name().unwrap(); // Only valid UTF-8 is used.
|
term::warning(format!("Failed to setup remote for {remote_id}: {e}").as_str());
|
||||||
|
|
||||||
term::success!("Remote {} created", term::format::tertiary(remote));
|
|
||||||
term::success!(
|
|
||||||
"Remote-tracking branch {} created for {}",
|
|
||||||
term::format::tertiary(branch),
|
|
||||||
term::format::tertiary(term::format::node(remote_id))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Setup a remote and tracking branch for the given remote.
|
||||||
|
pub fn setup_remote(
|
||||||
|
setup: &project::SetupRemote,
|
||||||
|
remote_id: &NodeId,
|
||||||
|
remote_name: Option<git::RefString>,
|
||||||
|
aliases: &impl AliasStore,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let remote_name = if let Some(alias) = remote_name {
|
||||||
|
alias
|
||||||
|
} else {
|
||||||
|
let alias = aliases
|
||||||
|
.alias(remote_id)
|
||||||
|
.unwrap_or_else(|| remote_id.to_string());
|
||||||
|
git::RefString::try_from(alias.clone())
|
||||||
|
.map_err(|_| anyhow!("invalid remote name: '{alias}'"))?
|
||||||
|
};
|
||||||
|
let (remote, branch) = setup.run(remote_name, *remote_id)?;
|
||||||
|
|
||||||
|
term::success!("Remote {} added", term::format::tertiary(remote.name));
|
||||||
|
|
||||||
|
if let Some(branch) = branch {
|
||||||
|
term::success!(
|
||||||
|
"Remote-tracking branch {} created for {}",
|
||||||
|
term::format::tertiary(branch),
|
||||||
|
term::format::tertiary(term::format::node(remote_id))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,13 +126,13 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
radicle::git::configure_repository(&working)?;
|
radicle::git::configure_repository(&working)?;
|
||||||
checkout::setup_remotes(
|
checkout::setup_remotes(
|
||||||
project::SetupRemote {
|
project::SetupRemote {
|
||||||
project: options.id,
|
rid: options.id,
|
||||||
default_branch,
|
tracking: Some(default_branch),
|
||||||
repo: &working,
|
repo: &working,
|
||||||
fetch: true,
|
fetch: true,
|
||||||
tracking: true,
|
|
||||||
},
|
},
|
||||||
&delegates,
|
&delegates,
|
||||||
|
&profile,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
term::success!(
|
term::success!(
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@ use std::ffi::OsString;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
||||||
|
use radicle::git::RefString;
|
||||||
use radicle::prelude::NodeId;
|
use radicle::prelude::NodeId;
|
||||||
|
|
||||||
use crate::terminal::args::{self, string, Error};
|
use crate::terminal::args;
|
||||||
use crate::terminal::{Args, Context, Help};
|
use crate::terminal::{Args, Context, Help};
|
||||||
|
|
||||||
pub const HELP: Help = Help {
|
pub const HELP: Help = Help {
|
||||||
|
|
@ -44,8 +45,8 @@ pub enum OperationName {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Operation {
|
pub enum Operation {
|
||||||
Add { id: NodeId, name: Option<String> },
|
Add { id: NodeId, name: Option<RefString> },
|
||||||
Rm { name: String },
|
Rm { name: RefString },
|
||||||
List,
|
List,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,16 +62,17 @@ impl Args for Options {
|
||||||
let mut parser = lexopt::Parser::from_args(args);
|
let mut parser = lexopt::Parser::from_args(args);
|
||||||
let mut op: Option<OperationName> = None;
|
let mut op: Option<OperationName> = None;
|
||||||
let mut id: Option<NodeId> = None;
|
let mut id: Option<NodeId> = None;
|
||||||
let mut name: Option<String> = None;
|
let mut name: Option<RefString> = None;
|
||||||
|
|
||||||
while let Some(arg) = parser.next()? {
|
while let Some(arg) = parser.next()? {
|
||||||
match arg {
|
match arg {
|
||||||
Long("help") => {
|
Long("help") => {
|
||||||
return Err(Error::Help.into());
|
return Err(args::Error::Help.into());
|
||||||
}
|
}
|
||||||
Long("name") | Short('n') => {
|
Long("name") | Short('n') => {
|
||||||
let value = parser.value()?;
|
let value = parser.value()?;
|
||||||
let value = string(&value);
|
let value = args::refstring("name", value)?;
|
||||||
|
|
||||||
name = Some(value);
|
name = Some(value);
|
||||||
}
|
}
|
||||||
Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
|
Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
|
||||||
|
|
@ -84,7 +86,10 @@ impl Args for Options {
|
||||||
id = Some(nid);
|
id = Some(nid);
|
||||||
}
|
}
|
||||||
Value(val) if op == Some(OperationName::Rm) && name.is_none() => {
|
Value(val) if op == Some(OperationName::Rm) && name.is_none() => {
|
||||||
let val = string(&val);
|
let val = args::string(&val);
|
||||||
|
let val = RefString::try_from(val)
|
||||||
|
.map_err(|e| anyhow!("invalid remote name specified: {e}"))?;
|
||||||
|
|
||||||
name = Some(val);
|
name = Some(val);
|
||||||
}
|
}
|
||||||
_ => return Err(anyhow::anyhow!(arg.unexpected())),
|
_ => return Err(anyhow::anyhow!(arg.unexpected())),
|
||||||
|
|
@ -114,7 +119,8 @@ pub fn run(options: Options, ctx: impl Context) -> anyhow::Result<()> {
|
||||||
let profile = ctx.profile()?;
|
let profile = ctx.profile()?;
|
||||||
|
|
||||||
match options.op {
|
match options.op {
|
||||||
Operation::Add { ref id, name } => self::add::run(rid, id, name, &profile, &working)?,
|
// TODO: Support remote-tracking.
|
||||||
|
Operation::Add { ref id, name } => self::add::run(rid, id, name, None, &profile, &working)?,
|
||||||
Operation::Rm { ref name } => self::rm::run(name, &working)?,
|
Operation::Rm { ref name } => self::rm::run(name, &working)?,
|
||||||
Operation::List => self::list::run(&working)?,
|
Operation::List => self::list::run(&working)?,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,26 @@
|
||||||
use radicle::{git::Url, prelude::Id, Profile};
|
use radicle::git::RefString;
|
||||||
|
use radicle::prelude::*;
|
||||||
|
use radicle::Profile;
|
||||||
use radicle_crypto::PublicKey;
|
use radicle_crypto::PublicKey;
|
||||||
|
|
||||||
use crate::git::add_remote;
|
use crate::commands::rad_checkout as checkout;
|
||||||
use crate::{git, terminal as term};
|
use crate::git;
|
||||||
|
use crate::project::SetupRemote;
|
||||||
|
|
||||||
pub fn run(
|
pub fn run(
|
||||||
id: Id,
|
rid: Id,
|
||||||
pubkey: &PublicKey,
|
nid: &PublicKey,
|
||||||
name: Option<String>,
|
name: Option<RefString>,
|
||||||
|
tracking: Option<BranchName>,
|
||||||
profile: &Profile,
|
profile: &Profile,
|
||||||
repository: &git::Repository,
|
repo: &git::Repository,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let name = match name {
|
let aliases = profile.aliases()?;
|
||||||
Some(name) => name,
|
let setup = SetupRemote {
|
||||||
_ => profile
|
rid,
|
||||||
.tracking()?
|
tracking,
|
||||||
.node_policy(pubkey)?
|
fetch: false,
|
||||||
.and_then(|node| node.alias)
|
repo,
|
||||||
.ok_or(anyhow::anyhow!("a `name` needs to be specified"))?,
|
|
||||||
};
|
};
|
||||||
if git::is_remote(repository, &name)? {
|
checkout::setup_remote(&setup, nid, name, &aliases)
|
||||||
anyhow::bail!("remote `{name}` already exists");
|
|
||||||
}
|
|
||||||
|
|
||||||
let url = Url::from(id).with_namespace(*pubkey);
|
|
||||||
let remote = add_remote(repository, &name, &url)?;
|
|
||||||
term::success!("Remote {} added with {url}", remote.name,);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use std::fmt::Display;
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
@ -74,11 +75,10 @@ pub enum RemoteError {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Remote<'a> {
|
pub struct Remote<'a> {
|
||||||
pub(crate) name: String,
|
pub name: String,
|
||||||
pub(crate) url: radicle::git::Url,
|
pub url: radicle::git::Url,
|
||||||
pub(crate) pushurl: Option<radicle::git::Url>,
|
pub pushurl: Option<radicle::git::Url>,
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
inner: git2::Remote<'a>,
|
inner: git2::Remote<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,6 +104,20 @@ impl<'a> TryFrom<git2::Remote<'a>> for Remote<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Deref for Remote<'a> {
|
||||||
|
type Target = git2::Remote<'a>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DerefMut for Remote<'a> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the git repository in the current directory.
|
/// Get the git repository in the current directory.
|
||||||
pub fn repository() -> Result<Repository, anyhow::Error> {
|
pub fn repository() -> Result<Repository, anyhow::Error> {
|
||||||
match Repository::open(".") {
|
match Repository::open(".") {
|
||||||
|
|
@ -273,15 +287,6 @@ pub fn remove_remote(repo: &Repository, rid: &Id) -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_remote<'a>(
|
|
||||||
repo: &'a Repository,
|
|
||||||
name: &'a str,
|
|
||||||
url: &'a radicle::git::Url,
|
|
||||||
) -> anyhow::Result<Remote<'a>> {
|
|
||||||
let remote = repo.remote(name, &url.to_string())?;
|
|
||||||
Ok(Remote::try_from(remote)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Setup an upstream tracking branch for the given remote and branch.
|
/// Setup an upstream tracking branch for the given remote and branch.
|
||||||
/// Creates the tracking branch if it does not exist.
|
/// Creates the tracking branch if it does not exist.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,50 @@
|
||||||
use radicle::git::raw::Remote;
|
|
||||||
use radicle::git::RefString;
|
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
|
|
||||||
use crate::git;
|
use crate::git;
|
||||||
|
use radicle::git::RefStr;
|
||||||
|
|
||||||
/// Setup a project remote and tracking branch.
|
/// Setup a project remote and tracking branch.
|
||||||
pub struct SetupRemote<'a> {
|
pub struct SetupRemote<'a> {
|
||||||
/// The project id.
|
/// The project id.
|
||||||
pub project: Id,
|
pub rid: Id,
|
||||||
/// The project default branch.
|
/// Whether or not to setup a remote tracking branch.
|
||||||
pub default_branch: BranchName,
|
pub tracking: Option<BranchName>,
|
||||||
/// The repository in which to setup the remote.
|
|
||||||
pub repo: &'a git::Repository,
|
|
||||||
/// Whether or not to fetch the remote immediately.
|
/// Whether or not to fetch the remote immediately.
|
||||||
pub fetch: bool,
|
pub fetch: bool,
|
||||||
/// Whether or not to setup a remote tracking branch.
|
/// The repository in which to setup the remote.
|
||||||
pub tracking: bool,
|
pub repo: &'a git::Repository,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SetupRemote<'a> {
|
impl<'a> SetupRemote<'a> {
|
||||||
/// Run the setup for the given peer.
|
/// Run the setup for the given peer.
|
||||||
pub fn run(&self, node: NodeId) -> anyhow::Result<Option<(Remote, RefString)>> {
|
pub fn run(
|
||||||
let url = radicle::git::Url::from(self.project).with_namespace(node);
|
&self,
|
||||||
let mut remote = radicle::git::configure_remote(self.repo, &node.to_string(), &url, &url)?;
|
name: impl AsRef<RefStr>,
|
||||||
|
node: NodeId,
|
||||||
|
) -> anyhow::Result<(git::Remote, Option<BranchName>)> {
|
||||||
|
let remote_url = radicle::git::Url::from(self.rid).with_namespace(node);
|
||||||
|
let remote_name = name.as_ref();
|
||||||
|
|
||||||
|
if git::is_remote(self.repo, remote_name)? {
|
||||||
|
anyhow::bail!("remote `{remote_name}` already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
let remote =
|
||||||
|
radicle::git::configure_remote(self.repo, remote_name, &remote_url, &remote_url)?;
|
||||||
|
let mut remote = git::Remote::try_from(remote)?;
|
||||||
|
|
||||||
// Fetch the refs into the working copy.
|
// Fetch the refs into the working copy.
|
||||||
if self.fetch {
|
if self.fetch {
|
||||||
remote.fetch::<&str>(&[], None, None)?;
|
remote.fetch::<&str>(&[], None, None)?;
|
||||||
}
|
}
|
||||||
// Setup remote-tracking branch.
|
// Setup remote-tracking branch.
|
||||||
if self.tracking {
|
if let Some(branch) = &self.tracking {
|
||||||
// SAFETY: Node IDs are valid ref strings.
|
let tracking_branch = remote_name.join(branch);
|
||||||
let node_ref = RefString::try_from(node.to_string()).unwrap();
|
let local_branch = radicle::git::refs::workdir::branch(tracking_branch.as_refstr());
|
||||||
let node_ref = node_ref.as_refstr();
|
radicle::git::set_upstream(self.repo, remote_name, &tracking_branch, local_branch)?;
|
||||||
let branch_name = node_ref.join(&self.default_branch);
|
|
||||||
let local_branch = radicle::git::refs::workdir::branch(
|
|
||||||
node_ref.join(&self.default_branch).as_refstr(),
|
|
||||||
);
|
|
||||||
radicle::git::set_upstream(self.repo, node.to_string(), &branch_name, local_branch)?;
|
|
||||||
|
|
||||||
return Ok(Some((remote, branch_name)));
|
return Ok((remote, Some(tracking_branch)));
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok((remote, None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use anyhow::anyhow;
|
||||||
|
|
||||||
use radicle::cob::{self, issue, patch};
|
use radicle::cob::{self, issue, patch};
|
||||||
use radicle::crypto;
|
use radicle::crypto;
|
||||||
|
use radicle::git::RefString;
|
||||||
use radicle::node::Address;
|
use radicle::node::Address;
|
||||||
use radicle::prelude::{Did, Id, NodeId};
|
use radicle::prelude::{Did, Id, NodeId};
|
||||||
|
|
||||||
|
|
@ -77,6 +78,20 @@ pub fn finish(unparsed: Vec<OsString>) -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn refstring(flag: &str, value: OsString) -> anyhow::Result<RefString> {
|
||||||
|
RefString::try_from(
|
||||||
|
value
|
||||||
|
.into_string()
|
||||||
|
.map_err(|_| anyhow!("the value specified for '--{}' is not valid UTF-8", flag))?,
|
||||||
|
)
|
||||||
|
.map_err(|_| {
|
||||||
|
anyhow!(
|
||||||
|
"the value specified for '--{}' is not a valid ref string",
|
||||||
|
flag
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn did(val: &OsString) -> anyhow::Result<Did> {
|
pub fn did(val: &OsString) -> anyhow::Result<Did> {
|
||||||
let val = val.to_string_lossy();
|
let val = val.to_string_lossy();
|
||||||
let Ok(peer) = Did::from_str(&val) else {
|
let Ok(peer) = Did::from_str(&val) else {
|
||||||
|
|
|
||||||
|
|
@ -370,6 +370,47 @@ fn rad_clone() {
|
||||||
test("examples/rad-clone.md", working, Some(&bob.home), []).unwrap();
|
test("examples/rad-clone.md", working, Some(&bob.home), []).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rad_clone_all() {
|
||||||
|
logger::init(log::Level::Debug);
|
||||||
|
|
||||||
|
let mut environment = Environment::new();
|
||||||
|
let mut alice = environment.node("alice");
|
||||||
|
let bob = environment.node("bob");
|
||||||
|
let eve = environment.node("eve");
|
||||||
|
let working = environment.tmp().join("working");
|
||||||
|
|
||||||
|
// Setup a test project.
|
||||||
|
let acme = alice.project("heartwood", "Radicle Heartwood Protocol & Stack");
|
||||||
|
|
||||||
|
let mut alice = alice.spawn(Config::default());
|
||||||
|
let mut bob = bob.spawn(Config::default());
|
||||||
|
let mut eve = eve.spawn(Config::default());
|
||||||
|
|
||||||
|
alice.handle.track_repo(acme, Scope::All).unwrap();
|
||||||
|
bob.connect(&alice).converge([&alice]);
|
||||||
|
eve.connect(&alice).converge([&alice]);
|
||||||
|
|
||||||
|
test(
|
||||||
|
"examples/rad-clone.md",
|
||||||
|
working.join("bob"),
|
||||||
|
Some(&bob.home),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
bob.has_inventory_of(&acme, &alice.id);
|
||||||
|
alice.has_inventory_of(&acme, &bob.id);
|
||||||
|
|
||||||
|
test(
|
||||||
|
"examples/rad-clone-all.md",
|
||||||
|
working.join("eve"),
|
||||||
|
Some(&eve.home),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
eve.has_inventory_of(&acme, &bob.id);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rad_self() {
|
fn rad_self() {
|
||||||
let mut environment = Environment::new();
|
let mut environment = Environment::new();
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ use radicle::node::TRACKING_DB_FILE;
|
||||||
use radicle::profile::Home;
|
use radicle::profile::Home;
|
||||||
use radicle::profile::Profile;
|
use radicle::profile::Profile;
|
||||||
use radicle::rad;
|
use radicle::rad;
|
||||||
use radicle::storage::{ReadStorage as _, WriteRepository};
|
use radicle::storage::{ReadRepository, ReadStorage as _, WriteRepository};
|
||||||
use radicle::test::fixtures;
|
use radicle::test::fixtures;
|
||||||
use radicle::Storage;
|
use radicle::Storage;
|
||||||
|
|
||||||
|
|
@ -220,6 +220,27 @@ impl<G: Signer + cyphernet::Ecdh> NodeHandle<G> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wait until this node has the inventory of another node.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn has_inventory_of(&self, rid: &Id, nid: &NodeId) {
|
||||||
|
log::debug!(target: "test", "Waiting for {} to have {rid}/{nid}", self.id);
|
||||||
|
let events = self.handle.events();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Ok(repo) = self.storage.repository(*rid) {
|
||||||
|
if repo.identity_of(nid).is_ok() && repo.remote(nid).is_ok() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
events
|
||||||
|
.wait(
|
||||||
|
|e| matches!(e, Event::RefsFetched { .. }),
|
||||||
|
time::Duration::from_secs(6),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Run a `rad` CLI command.
|
/// Run a `rad` CLI command.
|
||||||
pub fn rad<P: AsRef<Path>>(&self, cmd: &str, args: &[&str], cwd: P) -> io::Result<()> {
|
pub fn rad<P: AsRef<Path>>(&self, cmd: &str, args: &[&str], cwd: P) -> io::Result<()> {
|
||||||
let cwd = cwd.as_ref();
|
let cwd = cwd.as_ref();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue