cli: Support `--seed` on `rad clone`
Allows specifying one or more seeds to clone from. This is useful for cloning private repositories which are not in the routing table.
This commit is contained in:
parent
150130e99b
commit
04bfbca15d
|
|
@ -0,0 +1,45 @@
|
||||||
|
Given a private repo `rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu` belonging to Alice,
|
||||||
|
Alice allows Bob to fetch it, and Bob, without the updated identity document
|
||||||
|
is able to fetch it by specifiying Alice as a seed.
|
||||||
|
|
||||||
|
``` ~alice
|
||||||
|
$ rad id update --title "Allow Bob" --description "" --allow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk -q
|
||||||
|
...
|
||||||
|
$ rad inspect --identity
|
||||||
|
{
|
||||||
|
"payload": {
|
||||||
|
"xyz.radicle.project": {
|
||||||
|
"defaultBranch": "master",
|
||||||
|
"description": "radicle heartwood protocol & stack",
|
||||||
|
"name": "heartwood"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delegates": [
|
||||||
|
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
|
||||||
|
],
|
||||||
|
"threshold": 1,
|
||||||
|
"visibility": {
|
||||||
|
"type": "private",
|
||||||
|
"allow": [
|
||||||
|
"did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
``` ~bob
|
||||||
|
$ rad ls --all --private
|
||||||
|
$ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
|
||||||
|
✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all'
|
||||||
|
✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi..
|
||||||
|
✓ Creating checkout in ./heartwood..
|
||||||
|
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
|
||||||
|
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
|
||||||
|
✓ Repository successfully cloned under [...]/.radicle/heartwood/
|
||||||
|
╭────────────────────────────────────╮
|
||||||
|
│ heartwood │
|
||||||
|
│ radicle heartwood protocol & stack │
|
||||||
|
│ 0 issues · 0 patches │
|
||||||
|
╰────────────────────────────────────╯
|
||||||
|
Run `cd ./heartwood` to go to the project directory.
|
||||||
|
```
|
||||||
|
|
@ -2,14 +2,13 @@ Given a private repo `rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu` belonging to Alice,
|
||||||
Bob tries to fetch it, and even though he's connected to Alice, it fails.
|
Bob tries to fetch it, and even though he's connected to Alice, it fails.
|
||||||
|
|
||||||
``` ~bob
|
``` ~bob
|
||||||
$ rad seed rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --scope followed
|
|
||||||
✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'followed'
|
|
||||||
$ rad ls
|
$ rad ls
|
||||||
```
|
```
|
||||||
``` ~bob (fail)
|
``` ~bob (fail)
|
||||||
$ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
|
$ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
|
||||||
|
✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all'
|
||||||
✗ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi.. error: failed to perform fetch handshake
|
✗ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi.. error: failed to perform fetch handshake
|
||||||
✗ Error: repository fetch from 1 seed(s) failed
|
✗ Error: repository rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu not found
|
||||||
```
|
```
|
||||||
|
|
||||||
She allows Bob to view the repository. And when she syncs, one node (Bob) gets
|
She allows Bob to view the repository. And when she syncs, one node (Bob) gets
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,18 @@ Usage
|
||||||
|
|
||||||
rad clone <rid> [<directory>] [--scope <scope>] [<option>...]
|
rad clone <rid> [<directory>] [--scope <scope>] [<option>...]
|
||||||
|
|
||||||
|
The `clone` command will use your local node's routing table to find seeds from
|
||||||
|
which it can clone the repository.
|
||||||
|
|
||||||
|
For private repositories, the `--seed` option can be passed to clone directly
|
||||||
|
from a known seed in the privacy set.
|
||||||
|
|
||||||
Options
|
Options
|
||||||
|
|
||||||
--scope <scope> Follow scope (default: all)
|
--scope <scope> Follow scope (default: all)
|
||||||
--help Print help
|
-s, --seed <nid> Clone from this seed (may be specified multiple times)
|
||||||
|
--timeout <secs> Timeout for fetching repository (default: 9)
|
||||||
|
--help Print help
|
||||||
|
|
||||||
"#,
|
"#,
|
||||||
};
|
};
|
||||||
|
|
@ -52,6 +60,10 @@ pub struct Options {
|
||||||
directory: Option<PathBuf>,
|
directory: Option<PathBuf>,
|
||||||
/// The seeding scope of the repository.
|
/// The seeding scope of the repository.
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
|
/// Sync mode.
|
||||||
|
mode: sync::RepoSync,
|
||||||
|
/// Fetch timeout.
|
||||||
|
timeout: time::Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Args for Options {
|
impl Args for Options {
|
||||||
|
|
@ -61,15 +73,33 @@ impl Args for Options {
|
||||||
let mut parser = lexopt::Parser::from_args(args);
|
let mut parser = lexopt::Parser::from_args(args);
|
||||||
let mut id: Option<Id> = None;
|
let mut id: Option<Id> = None;
|
||||||
let mut scope = Scope::All;
|
let mut scope = Scope::All;
|
||||||
|
let mut mode = sync::RepoSync::default();
|
||||||
|
let mut timeout = time::Duration::from_secs(9);
|
||||||
let mut directory = None;
|
let mut directory = None;
|
||||||
|
|
||||||
while let Some(arg) = parser.next()? {
|
while let Some(arg) = parser.next()? {
|
||||||
match arg {
|
match arg {
|
||||||
|
Long("seed") | Short('s') => {
|
||||||
|
let value = parser.value()?;
|
||||||
|
let value = term::args::nid(&value)?;
|
||||||
|
|
||||||
|
if let sync::RepoSync::Seeds(seeds) = &mut mode {
|
||||||
|
seeds.push(value);
|
||||||
|
} else {
|
||||||
|
mode = sync::RepoSync::Seeds(vec![value]);
|
||||||
|
}
|
||||||
|
}
|
||||||
Long("scope") => {
|
Long("scope") => {
|
||||||
let value = parser.value()?;
|
let value = parser.value()?;
|
||||||
|
|
||||||
scope = term::args::parse_value("scope", value)?;
|
scope = term::args::parse_value("scope", value)?;
|
||||||
}
|
}
|
||||||
|
Long("timeout") => {
|
||||||
|
let value = parser.value()?;
|
||||||
|
let secs = term::args::number(&value)?;
|
||||||
|
|
||||||
|
timeout = time::Duration::from_secs(secs as u64);
|
||||||
|
}
|
||||||
Long("no-confirm") => {
|
Long("no-confirm") => {
|
||||||
// We keep this flag here for consistency though it doesn't have any effect,
|
// We keep this flag here for consistency though it doesn't have any effect,
|
||||||
// since the command is fully non-interactive.
|
// since the command is fully non-interactive.
|
||||||
|
|
@ -99,6 +129,8 @@ impl Args for Options {
|
||||||
id,
|
id,
|
||||||
directory,
|
directory,
|
||||||
scope,
|
scope,
|
||||||
|
mode,
|
||||||
|
timeout,
|
||||||
},
|
},
|
||||||
vec![],
|
vec![],
|
||||||
))
|
))
|
||||||
|
|
@ -120,6 +152,8 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
options.id,
|
options.id,
|
||||||
options.directory.clone(),
|
options.directory.clone(),
|
||||||
options.scope,
|
options.scope,
|
||||||
|
options.mode,
|
||||||
|
options.timeout,
|
||||||
&mut node,
|
&mut node,
|
||||||
&signer,
|
&signer,
|
||||||
&profile.storage,
|
&profile.storage,
|
||||||
|
|
@ -204,6 +238,8 @@ pub fn clone<G: Signer>(
|
||||||
id: Id,
|
id: Id,
|
||||||
directory: Option<PathBuf>,
|
directory: Option<PathBuf>,
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
|
mode: sync::RepoSync,
|
||||||
|
timeout: time::Duration,
|
||||||
node: &mut Node,
|
node: &mut Node,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
storage: &Storage,
|
storage: &Storage,
|
||||||
|
|
@ -218,7 +254,7 @@ pub fn clone<G: Signer>(
|
||||||
> {
|
> {
|
||||||
let me = *signer.public_key();
|
let me = *signer.public_key();
|
||||||
|
|
||||||
// Track.
|
// Seed repository.
|
||||||
if node.seed(id, scope)? {
|
if node.seed(id, scope)? {
|
||||||
term::success!(
|
term::success!(
|
||||||
"Seeding policy updated for {} with scope '{scope}'",
|
"Seeding policy updated for {} with scope '{scope}'",
|
||||||
|
|
@ -226,12 +262,7 @@ pub fn clone<G: Signer>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let results = sync::fetch(
|
let results = sync::fetch(id, mode, timeout, node)?;
|
||||||
id,
|
|
||||||
sync::RepoSync::default(),
|
|
||||||
time::Duration::from_secs(9),
|
|
||||||
node,
|
|
||||||
)?;
|
|
||||||
let Ok(repository) = storage.repository(id) else {
|
let Ok(repository) = storage.repository(id) else {
|
||||||
// If we don't have the project locally, even after attempting to fetch,
|
// If we don't have the project locally, even after attempting to fetch,
|
||||||
// there's nothing we can do.
|
// there's nothing we can do.
|
||||||
|
|
|
||||||
|
|
@ -1683,6 +1683,47 @@ fn rad_init_private_clone() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rad_init_private_clone_seed() {
|
||||||
|
let mut environment = Environment::new();
|
||||||
|
let alice = environment.node(Config::test(Alias::new("alice")));
|
||||||
|
let bob = environment.node(Config::test(Alias::new("bob")));
|
||||||
|
let working = environment.tmp().join("working");
|
||||||
|
|
||||||
|
fixtures::repository(working.join("alice"));
|
||||||
|
|
||||||
|
let alice = alice.spawn();
|
||||||
|
let mut bob = bob.spawn();
|
||||||
|
|
||||||
|
test(
|
||||||
|
"examples/rad-init-private.md",
|
||||||
|
working.join("alice"),
|
||||||
|
Some(&alice.home),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
bob.connect(&alice).converge([&alice]);
|
||||||
|
|
||||||
|
formula(
|
||||||
|
&environment.tmp(),
|
||||||
|
"examples/rad-init-private-clone-seed.md",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.home(
|
||||||
|
"alice",
|
||||||
|
working.join("alice"),
|
||||||
|
[("RAD_HOME", alice.home.path().display())],
|
||||||
|
)
|
||||||
|
.home(
|
||||||
|
"bob",
|
||||||
|
bob.home.path(),
|
||||||
|
[("RAD_HOME", bob.home.path().display())],
|
||||||
|
)
|
||||||
|
.run()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rad_publish() {
|
fn rad_publish() {
|
||||||
let mut environment = Environment::new();
|
let mut environment = Environment::new();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue