cli: list tracked and untracked remotes
Add functionality for lising the remotes that are in the storage repository, but a working copy remote has not yet been created for them. This functionality is included in the `rad remote` command since it can be directly used for creating Git remotes using the same command, i.e. `rad remote add`. Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com> X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
parent
e3b3f3c4f4
commit
3cf51043b3
|
|
@ -51,3 +51,44 @@ $ rad remote add did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
|
|||
✓ Remote bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk added
|
||||
✓ Remote-tracking branch bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk/master created for z6Mkt67…v4N1tRk
|
||||
```
|
||||
|
||||
We can also use `rad remote` to list all the remotes that are
|
||||
available in the repository by using the `--untracked` flag:
|
||||
|
||||
```
|
||||
$ rad remote --untracked
|
||||
eve did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z
|
||||
```
|
||||
|
||||
If we use `--all`, then we can see all the remotes that we have
|
||||
created in the working copy, followed by all the available remotes:
|
||||
|
||||
```
|
||||
$ rad remote --all
|
||||
bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (fetch)
|
||||
rad (canonical upstream) (fetch)
|
||||
rad z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
|
||||
|
||||
eve did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z
|
||||
```
|
||||
|
||||
As we can see, we have also have another remote namespace `eve`, so
|
||||
let's add them to our set of working copy remotes:
|
||||
|
||||
```
|
||||
$ rad remote add did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --name eve
|
||||
✓ Remote eve added
|
||||
✓ Remote-tracking branch eve/master created for z6Mkux1…nVhib7Z
|
||||
```
|
||||
|
||||
After adding `eve`'s remote, we no longer see any entries that are
|
||||
untracked:
|
||||
|
||||
```
|
||||
$ rad remote --all
|
||||
bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (fetch)
|
||||
eve z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z (fetch)
|
||||
rad (canonical upstream) (fetch)
|
||||
rad z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use radicle::git::RefString;
|
|||
use radicle::prelude::NodeId;
|
||||
use radicle::storage::ReadStorage;
|
||||
|
||||
use crate::terminal as term;
|
||||
use crate::terminal::args;
|
||||
use crate::terminal::{Args, Context, Help};
|
||||
|
||||
|
|
@ -24,19 +25,28 @@ pub const HELP: Help = Help {
|
|||
usage: r#"
|
||||
Usage
|
||||
|
||||
rad remote
|
||||
rad remote list
|
||||
rad remote add (<did> | <nid>) [--name <string>]
|
||||
rad remote rm <name>
|
||||
rad remote [<option>...]
|
||||
rad remote list [--tracked | --untracked | --all] [<option>...]
|
||||
rad remote add (<did> | <nid>) [--name <string>] [<option>...]
|
||||
rad remote rm <name> [<option>...]
|
||||
|
||||
List options
|
||||
|
||||
--tracked Show all remotes that are listed in the working copy
|
||||
--untracked Show all remotes that are listed in the Radicle storage
|
||||
--all Show all remotes in both the Radicle storage and the working copy
|
||||
|
||||
Add options
|
||||
|
||||
--name Override the name of the remote that by default is set to the node alias
|
||||
|
||||
Options
|
||||
|
||||
--name Override the name of the remote that by default is set to the node alias
|
||||
--help Print help
|
||||
--help Print help
|
||||
"#,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum OperationName {
|
||||
Add,
|
||||
Rm,
|
||||
|
|
@ -48,7 +58,15 @@ pub enum OperationName {
|
|||
pub enum Operation {
|
||||
Add { id: NodeId, name: Option<RefString> },
|
||||
Rm { name: RefString },
|
||||
List,
|
||||
List { option: ListOption },
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub enum ListOption {
|
||||
All,
|
||||
#[default]
|
||||
Tracked,
|
||||
Untracked,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -64,6 +82,7 @@ impl Args for Options {
|
|||
let mut op: Option<OperationName> = None;
|
||||
let mut id: Option<NodeId> = None;
|
||||
let mut name: Option<RefString> = None;
|
||||
let mut list_op: ListOption = ListOption::default();
|
||||
|
||||
while let Some(arg) = parser.next()? {
|
||||
match arg {
|
||||
|
|
@ -82,10 +101,25 @@ impl Args for Options {
|
|||
"r" | "rm" => op = Some(OperationName::Rm),
|
||||
unknown => anyhow::bail!("unknown operation '{}'", unknown),
|
||||
},
|
||||
|
||||
// List options
|
||||
Long("all") if op.unwrap_or_default() == OperationName::List => {
|
||||
list_op = ListOption::All;
|
||||
}
|
||||
Long("tracked") if op.unwrap_or_default() == OperationName::List => {
|
||||
list_op = ListOption::Tracked;
|
||||
}
|
||||
Long("untracked") if op.unwrap_or_default() == OperationName::List => {
|
||||
list_op = ListOption::Untracked;
|
||||
}
|
||||
|
||||
// Add options
|
||||
Value(val) if op == Some(OperationName::Add) && id.is_none() => {
|
||||
let nid = args::pubkey(&val)?;
|
||||
id = Some(nid);
|
||||
}
|
||||
|
||||
// Remove options
|
||||
Value(val) if op == Some(OperationName::Rm) && name.is_none() => {
|
||||
let val = args::string(&val);
|
||||
let val = RefString::try_from(val)
|
||||
|
|
@ -104,7 +138,7 @@ impl Args for Options {
|
|||
))?,
|
||||
name,
|
||||
},
|
||||
OperationName::List => Operation::List,
|
||||
OperationName::List => Operation::List { option: list_op },
|
||||
OperationName::Rm => Operation::Rm {
|
||||
name: name.ok_or(anyhow!("name required, see `rad remote`"))?,
|
||||
},
|
||||
|
|
@ -127,7 +161,29 @@ pub fn run(options: Options, ctx: impl Context) -> anyhow::Result<()> {
|
|||
self::add::run(rid, id, name, Some(branch.clone()), &profile, &working)?
|
||||
}
|
||||
Operation::Rm { ref name } => self::rm::run(name, &working)?,
|
||||
Operation::List => self::list::run(&working)?,
|
||||
Operation::List { option } => match option {
|
||||
ListOption::All => {
|
||||
let tracked = list::tracked(&working)?;
|
||||
let untracked = list::untracked(rid, &profile, tracked.iter())?;
|
||||
// Only include a blank line if we're printing both tracked and untracked
|
||||
let include_blank_line = !tracked.is_empty() && !untracked.is_empty();
|
||||
|
||||
list::print_tracked(tracked.iter());
|
||||
if include_blank_line {
|
||||
term::blank();
|
||||
}
|
||||
list::print_untracked(untracked.iter());
|
||||
}
|
||||
ListOption::Tracked => {
|
||||
let tracked = list::tracked(&working)?;
|
||||
list::print_tracked(tracked.iter());
|
||||
}
|
||||
ListOption::Untracked => {
|
||||
let tracked = list::tracked(&working)?;
|
||||
let untracked = list::untracked(rid, &profile, tracked.iter())?;
|
||||
list::print_untracked(untracked.iter());
|
||||
}
|
||||
},
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,119 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use radicle::git::Url;
|
||||
use radicle::identity::{Did, Id};
|
||||
use radicle::node::{Alias, AliasStore as _, NodeId};
|
||||
use radicle::storage::ReadStorage as _;
|
||||
use radicle::Profile;
|
||||
use radicle_term::{Element, Table};
|
||||
|
||||
use crate::git;
|
||||
use crate::terminal as term;
|
||||
|
||||
pub fn run(repo: &git::Repository) -> anyhow::Result<()> {
|
||||
let mut table = Table::default();
|
||||
let remotes = git::rad_remotes(repo)?;
|
||||
for r in remotes {
|
||||
for (dir, url) in [("fetch", Some(r.url)), ("push", r.pushurl)] {
|
||||
let Some(url) = url else {
|
||||
continue;
|
||||
};
|
||||
#[derive(Debug)]
|
||||
pub enum Direction {
|
||||
Push(Url),
|
||||
Fetch(Url),
|
||||
}
|
||||
|
||||
let description = url.namespace.map_or(
|
||||
term::format::dim("(canonical upstream)".to_string()).italic(),
|
||||
|namespace| term::format::tertiary(namespace.to_string()),
|
||||
);
|
||||
table.push([
|
||||
term::format::bold(r.name.clone()),
|
||||
description,
|
||||
term::format::parens(term::format::secondary(dir.to_owned())),
|
||||
]);
|
||||
#[derive(Debug)]
|
||||
pub struct Tracked {
|
||||
name: String,
|
||||
direction: Option<Direction>,
|
||||
}
|
||||
|
||||
impl Tracked {
|
||||
fn namespace(&self) -> Option<NodeId> {
|
||||
match self.direction.as_ref()? {
|
||||
Direction::Push(url) => url.namespace,
|
||||
Direction::Fetch(url) => url.namespace,
|
||||
}
|
||||
}
|
||||
table.print();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Untracked {
|
||||
remote: NodeId,
|
||||
alias: Option<Alias>,
|
||||
}
|
||||
|
||||
pub fn tracked(working: &git::Repository) -> anyhow::Result<Vec<Tracked>> {
|
||||
Ok(git::rad_remotes(working)?
|
||||
.into_iter()
|
||||
.flat_map(|remote| {
|
||||
[
|
||||
Tracked {
|
||||
name: remote.name.clone(),
|
||||
direction: Some(Direction::Fetch(remote.url)),
|
||||
},
|
||||
Tracked {
|
||||
name: remote.name,
|
||||
direction: remote.pushurl.map(Direction::Push),
|
||||
},
|
||||
]
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn untracked<'a>(
|
||||
rid: Id,
|
||||
profile: &Profile,
|
||||
tracked: impl Iterator<Item = &'a Tracked>,
|
||||
) -> anyhow::Result<Vec<Untracked>> {
|
||||
let repo = profile.storage.repository(rid)?;
|
||||
let aliases = profile.aliases();
|
||||
let remotes = repo.remote_ids()?;
|
||||
let git_remotes = tracked
|
||||
.filter_map(|tracked| tracked.namespace())
|
||||
.collect::<HashSet<_>>();
|
||||
Ok(remotes
|
||||
.filter_map(|remote| {
|
||||
remote
|
||||
.map(|remote| {
|
||||
(!git_remotes.contains(&remote)).then_some(Untracked {
|
||||
remote,
|
||||
alias: aliases.alias(&remote),
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?)
|
||||
}
|
||||
|
||||
pub fn print_tracked<'a>(tracked: impl Iterator<Item = &'a Tracked>) {
|
||||
let mut table = Table::default();
|
||||
for Tracked { direction, name } in tracked {
|
||||
let Some(direction) = direction else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let (dir, url) = match direction {
|
||||
Direction::Push(url) => ("push", url),
|
||||
Direction::Fetch(url) => ("fetch", url),
|
||||
};
|
||||
let description = url.namespace.map_or(
|
||||
term::format::dim("(canonical upstream)".to_string()).italic(),
|
||||
|namespace| term::format::tertiary(namespace.to_string()),
|
||||
);
|
||||
table.push([
|
||||
term::format::bold(name.clone()),
|
||||
description,
|
||||
term::format::parens(term::format::secondary(dir.to_owned())),
|
||||
]);
|
||||
}
|
||||
table.print();
|
||||
}
|
||||
|
||||
pub fn print_untracked<'a>(untracked: impl Iterator<Item = &'a Untracked>) {
|
||||
let mut t = Table::default();
|
||||
for Untracked { remote, alias } in untracked {
|
||||
t.push([
|
||||
match alias {
|
||||
None => term::format::secondary("n/a".to_string()),
|
||||
Some(alias) => term::format::secondary(alias.to_string()),
|
||||
},
|
||||
term::format::highlight(Did::from(remote).to_string()),
|
||||
])
|
||||
}
|
||||
t.print();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1475,6 +1475,7 @@ fn rad_remote() {
|
|||
let mut environment = Environment::new();
|
||||
let alice = environment.node(Config::test(Alias::new("alice")));
|
||||
let bob = environment.node(Config::test(Alias::new("bob")));
|
||||
let eve = environment.node(Config::test(Alias::new("eve")));
|
||||
let working = environment.tmp().join("working");
|
||||
let home = alice.home.clone();
|
||||
let rid = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
|
||||
|
|
@ -1491,6 +1492,7 @@ fn rad_remote() {
|
|||
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
let mut eve = eve.spawn();
|
||||
alice
|
||||
.handle
|
||||
.follow(bob.id, Some(Alias::new("bob")))
|
||||
|
|
@ -1502,6 +1504,12 @@ fn rad_remote() {
|
|||
bob.announce(rid, bob.home.path()).unwrap();
|
||||
alice.has_inventory_of(&rid, &bob.id);
|
||||
|
||||
eve.connect(&bob);
|
||||
eve.routes_to(&[(rid, alice.id)]);
|
||||
eve.fork(rid, eve.home.path()).unwrap();
|
||||
eve.announce(rid, eve.home.path()).unwrap();
|
||||
alice.has_inventory_of(&rid, &eve.id);
|
||||
|
||||
test(
|
||||
"examples/rad-remote.md",
|
||||
working.join("alice"),
|
||||
|
|
|
|||
Loading…
Reference in New Issue