mirror of
https://seed.radicle.dev/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5.git
synced 2026-10-18 19:10:30 +08:00
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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user