Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: Support `--seed` on `rad clone`
cloudhead committed 2 years ago
commit 04bfbca15d5fd6b8ae5d540a8c3166af463db653
parent 150130e99beb04cd5d989932a617fcd160c7345a
4 files changed +129 -13
added radicle-cli/examples/rad-init-private-clone-seed.md
@@ -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.
+
```
modified radicle-cli/examples/rad-init-private-clone.md
@@ -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
modified radicle-cli/src/commands/clone.rs
@@ -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.
modified radicle-cli/tests/commands.rs
@@ -1684,6 +1684,47 @@ fn rad_init_private_clone() {
}

#[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();
    let alice = environment.node(Config::test(Alias::new("alice")));