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
|
||||
$ rad seed rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --scope followed
|
||||
✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'followed'
|
||||
$ rad ls
|
||||
```
|
||||
``` ~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
|
||||
✗ 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
|
||||
|
|
|
|||
|
|
@ -36,10 +36,18 @@ Usage
|
|||
|
||||
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
|
||||
|
||||
--scope <scope> Follow scope (default: all)
|
||||
--help Print help
|
||||
--scope <scope> Follow scope (default: all)
|
||||
-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>,
|
||||
/// The seeding scope of the repository.
|
||||
scope: Scope,
|
||||
/// Sync mode.
|
||||
mode: sync::RepoSync,
|
||||
/// Fetch timeout.
|
||||
timeout: time::Duration,
|
||||
}
|
||||
|
||||
impl Args for Options {
|
||||
|
|
@ -61,15 +73,33 @@ impl Args for Options {
|
|||
let mut parser = lexopt::Parser::from_args(args);
|
||||
let mut id: Option<Id> = None;
|
||||
let mut scope = Scope::All;
|
||||
let mut mode = sync::RepoSync::default();
|
||||
let mut timeout = time::Duration::from_secs(9);
|
||||
let mut directory = None;
|
||||
|
||||
while let Some(arg) = parser.next()? {
|
||||
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") => {
|
||||
let value = parser.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") => {
|
||||
// We keep this flag here for consistency though it doesn't have any effect,
|
||||
// since the command is fully non-interactive.
|
||||
|
|
@ -99,6 +129,8 @@ impl Args for Options {
|
|||
id,
|
||||
directory,
|
||||
scope,
|
||||
mode,
|
||||
timeout,
|
||||
},
|
||||
vec![],
|
||||
))
|
||||
|
|
@ -120,6 +152,8 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
options.id,
|
||||
options.directory.clone(),
|
||||
options.scope,
|
||||
options.mode,
|
||||
options.timeout,
|
||||
&mut node,
|
||||
&signer,
|
||||
&profile.storage,
|
||||
|
|
@ -204,6 +238,8 @@ pub fn clone<G: Signer>(
|
|||
id: Id,
|
||||
directory: Option<PathBuf>,
|
||||
scope: Scope,
|
||||
mode: sync::RepoSync,
|
||||
timeout: time::Duration,
|
||||
node: &mut Node,
|
||||
signer: &G,
|
||||
storage: &Storage,
|
||||
|
|
@ -218,7 +254,7 @@ pub fn clone<G: Signer>(
|
|||
> {
|
||||
let me = *signer.public_key();
|
||||
|
||||
// Track.
|
||||
// Seed repository.
|
||||
if node.seed(id, scope)? {
|
||||
term::success!(
|
||||
"Seeding policy updated for {} with scope '{scope}'",
|
||||
|
|
@ -226,12 +262,7 @@ pub fn clone<G: Signer>(
|
|||
);
|
||||
}
|
||||
|
||||
let results = sync::fetch(
|
||||
id,
|
||||
sync::RepoSync::default(),
|
||||
time::Duration::from_secs(9),
|
||||
node,
|
||||
)?;
|
||||
let results = sync::fetch(id, mode, timeout, node)?;
|
||||
let Ok(repository) = storage.repository(id) else {
|
||||
// If we don't have the project locally, even after attempting to fetch,
|
||||
// there's nothing we can do.
|
||||
|
|
|
|||
|
|
@ -1683,6 +1683,47 @@ fn rad_init_private_clone() {
|
|||
.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]
|
||||
fn rad_publish() {
|
||||
let mut environment = Environment::new();
|
||||
|
|
|
|||
Loading…
Reference in New Issue