cli/remote: migrate to clap
This commit is contained in:
parent
93578340d1
commit
6cfed884bf
|
|
@ -122,7 +122,10 @@ const COMMANDS: &[CommandItem] = &[
|
||||||
name: "unseed",
|
name: "unseed",
|
||||||
about: crate::commands::unseed::ABOUT,
|
about: crate::commands::unseed::ABOUT,
|
||||||
},
|
},
|
||||||
CommandItem::Lexopt(crate::commands::remote::HELP),
|
CommandItem::Clap {
|
||||||
|
name: "remote",
|
||||||
|
about: crate::commands::remote::ABOUT,
|
||||||
|
},
|
||||||
CommandItem::Clap {
|
CommandItem::Clap {
|
||||||
name: "stats",
|
name: "stats",
|
||||||
about: crate::commands::stats::ABOUT,
|
about: crate::commands::stats::ABOUT,
|
||||||
|
|
|
||||||
|
|
@ -4,203 +4,48 @@ pub mod add;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
pub mod rm;
|
pub mod rm;
|
||||||
|
|
||||||
use std::ffi::OsString;
|
mod args;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
||||||
use radicle::git::fmt::RefString;
|
|
||||||
use radicle::prelude::NodeId;
|
|
||||||
use radicle::storage::ReadStorage;
|
use radicle::storage::ReadStorage;
|
||||||
|
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
use crate::terminal::args;
|
use crate::terminal::Context;
|
||||||
use crate::terminal::{Args, Context, Help};
|
|
||||||
|
|
||||||
pub const HELP: Help = Help {
|
pub use args::Args;
|
||||||
name: "remote",
|
pub(crate) use args::ABOUT;
|
||||||
description: "Manage a repository's remotes",
|
use args::{Command, ListOption};
|
||||||
version: env!("RADICLE_VERSION"),
|
|
||||||
usage: r#"
|
|
||||||
Usage
|
|
||||||
|
|
||||||
rad remote [<option>...]
|
pub fn run(args: Args, ctx: impl Context) -> anyhow::Result<()> {
|
||||||
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
|
|
||||||
--[no-]fetch Fetch the remote from local storage (default: fetch)
|
|
||||||
--[no-]sync Sync the remote refs from the network (default: sync)
|
|
||||||
|
|
||||||
Options
|
|
||||||
|
|
||||||
--help Print help
|
|
||||||
"#,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
||||||
pub enum OperationName {
|
|
||||||
Add,
|
|
||||||
Rm,
|
|
||||||
#[default]
|
|
||||||
List,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Operation {
|
|
||||||
Add {
|
|
||||||
id: NodeId,
|
|
||||||
name: Option<RefString>,
|
|
||||||
fetch: bool,
|
|
||||||
sync: bool,
|
|
||||||
},
|
|
||||||
Rm {
|
|
||||||
name: RefString,
|
|
||||||
},
|
|
||||||
List {
|
|
||||||
option: ListOption,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub enum ListOption {
|
|
||||||
All,
|
|
||||||
#[default]
|
|
||||||
Tracked,
|
|
||||||
Untracked,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Options {
|
|
||||||
pub op: Operation,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Args for Options {
|
|
||||||
fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
|
|
||||||
use lexopt::prelude::*;
|
|
||||||
|
|
||||||
let mut parser = lexopt::Parser::from_args(args);
|
|
||||||
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();
|
|
||||||
let mut fetch = true;
|
|
||||||
let mut sync = true;
|
|
||||||
|
|
||||||
while let Some(arg) = parser.next()? {
|
|
||||||
match arg {
|
|
||||||
Long("help") | Short('h') => {
|
|
||||||
return Err(args::Error::Help.into());
|
|
||||||
}
|
|
||||||
Long("name") | Short('n') => {
|
|
||||||
let value = parser.value()?;
|
|
||||||
let value = args::refstring("name", value)?;
|
|
||||||
|
|
||||||
name = Some(value);
|
|
||||||
}
|
|
||||||
Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
|
|
||||||
"a" | "add" => op = Some(OperationName::Add),
|
|
||||||
"l" | "list" => op = Some(OperationName::List),
|
|
||||||
"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
|
|
||||||
Long("sync") if op == Some(OperationName::Add) => {
|
|
||||||
sync = true;
|
|
||||||
}
|
|
||||||
Long("no-sync") if op == Some(OperationName::Add) => {
|
|
||||||
sync = false;
|
|
||||||
}
|
|
||||||
Long("fetch") if op == Some(OperationName::Add) => {
|
|
||||||
fetch = true;
|
|
||||||
}
|
|
||||||
Long("no-fetch") if op == Some(OperationName::Add) => {
|
|
||||||
fetch = false;
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
.map_err(|e| anyhow!("invalid remote name specified: {e}"))?;
|
|
||||||
|
|
||||||
name = Some(val);
|
|
||||||
}
|
|
||||||
_ => anyhow::bail!(arg.unexpected()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let op = match op.unwrap_or_default() {
|
|
||||||
OperationName::Add => Operation::Add {
|
|
||||||
id: id.ok_or(anyhow!(
|
|
||||||
"`DID` required, try running `rad remote add <did>`"
|
|
||||||
))?,
|
|
||||||
name,
|
|
||||||
fetch,
|
|
||||||
sync,
|
|
||||||
},
|
|
||||||
OperationName::List => Operation::List { option: list_op },
|
|
||||||
OperationName::Rm => Operation::Rm {
|
|
||||||
name: name.ok_or(anyhow!("name required, see `rad remote`"))?,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((Options { op }, vec![]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(options: Options, ctx: impl Context) -> anyhow::Result<()> {
|
|
||||||
let (working, rid) = radicle::rad::cwd()
|
let (working, rid) = radicle::rad::cwd()
|
||||||
.map_err(|_| anyhow!("this command must be run in the context of a repository"))?;
|
.map_err(|_| anyhow!("this command must be run in the context of a repository"))?;
|
||||||
let profile = ctx.profile()?;
|
let profile = ctx.profile()?;
|
||||||
|
let command = args
|
||||||
match options.op {
|
.command
|
||||||
Operation::Add {
|
.unwrap_or_else(|| Command::List(args.empty.into()));
|
||||||
ref id,
|
match command {
|
||||||
|
Command::Add {
|
||||||
|
nid,
|
||||||
name,
|
name,
|
||||||
fetch,
|
fetch,
|
||||||
sync,
|
sync,
|
||||||
} => {
|
} => {
|
||||||
let proj = profile.storage.repository(rid)?.project()?;
|
let proj = profile.storage.repository(rid)?.project()?;
|
||||||
let branch = proj.default_branch();
|
let branch = proj.default_branch();
|
||||||
|
|
||||||
self::add::run(
|
self::add::run(
|
||||||
rid,
|
rid,
|
||||||
id,
|
&nid,
|
||||||
name,
|
name,
|
||||||
Some(branch.clone()),
|
Some(branch.clone()),
|
||||||
&profile,
|
&profile,
|
||||||
&working,
|
&working,
|
||||||
fetch,
|
fetch.should_fetch(),
|
||||||
sync,
|
sync.should_sync(),
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
Operation::Rm { ref name } => self::rm::run(name, &working)?,
|
Command::Rm { ref name } => self::rm::run(name, &working)?,
|
||||||
Operation::List { option } => match option {
|
Command::List(args) => match ListOption::from(args) {
|
||||||
ListOption::All => {
|
ListOption::All => {
|
||||||
let tracked = list::tracked(&working)?;
|
let tracked = list::tracked(&working)?;
|
||||||
let untracked = list::untracked(rid, &profile, tracked.iter())?;
|
let untracked = list::untracked(rid, &profile, tracked.iter())?;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
|
use radicle::git;
|
||||||
|
use radicle::git::fmt::RefString;
|
||||||
|
use radicle::node::NodeId;
|
||||||
|
|
||||||
|
use crate::terminal as term;
|
||||||
|
|
||||||
|
pub(crate) const ABOUT: &str = "Manage a repository's remotes";
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(about = ABOUT, disable_version_flag = true)]
|
||||||
|
pub struct Args {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub(super) command: Option<Command>,
|
||||||
|
|
||||||
|
/// Arguments for the empty subcommand.
|
||||||
|
/// Will fall back to [`Command::List`].
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub(super) empty: EmptyArgs,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub(super) enum Command {
|
||||||
|
/// Add a Git remote for the provided NID
|
||||||
|
#[clap(alias = "a")]
|
||||||
|
Add {
|
||||||
|
/// The DID or NID of the remote to add
|
||||||
|
#[arg(value_parser = term::args::parse_nid)]
|
||||||
|
nid: NodeId,
|
||||||
|
|
||||||
|
/// Override the name of the Git remote
|
||||||
|
///
|
||||||
|
/// [default: <ALIAS>@<NID>]
|
||||||
|
#[arg(long, short, value_name = "REMOTE", value_parser = parse_refstr)]
|
||||||
|
name: Option<RefString>,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
fetch: FetchArgs,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
sync: SyncArgs,
|
||||||
|
},
|
||||||
|
/// Remove the Git remote identified by REMOTE
|
||||||
|
#[clap(alias = "r")]
|
||||||
|
Rm {
|
||||||
|
/// The name of the remote to delete
|
||||||
|
#[arg(value_name = "REMOTE", value_parser = parse_refstr)]
|
||||||
|
name: RefString,
|
||||||
|
},
|
||||||
|
/// List the stored remotes
|
||||||
|
///
|
||||||
|
/// Filter the listed remotes using the provided options
|
||||||
|
#[clap(alias = "l")]
|
||||||
|
List(ListArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
pub(super) struct FetchArgs {
|
||||||
|
/// Fetch the remote from local storage (default)
|
||||||
|
#[arg(long, conflicts_with = "no_fetch")]
|
||||||
|
fetch: bool,
|
||||||
|
|
||||||
|
/// Do not fetch the remote from local storage
|
||||||
|
#[arg(long)]
|
||||||
|
no_fetch: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FetchArgs {
|
||||||
|
pub(super) fn should_fetch(&self) -> bool {
|
||||||
|
let Self { fetch, no_fetch } = self;
|
||||||
|
*fetch || !no_fetch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
pub(super) struct SyncArgs {
|
||||||
|
/// Sync the remote refs from the network (default)
|
||||||
|
#[arg(long, conflicts_with = "no_sync")]
|
||||||
|
sync: bool,
|
||||||
|
|
||||||
|
/// Do not sync the remote refs from the network
|
||||||
|
#[arg(long)]
|
||||||
|
no_sync: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyncArgs {
|
||||||
|
pub(super) fn should_sync(&self) -> bool {
|
||||||
|
let Self { sync, no_sync } = self;
|
||||||
|
*sync || !no_sync
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Clone, Copy, Debug)]
|
||||||
|
#[group(multiple = false)]
|
||||||
|
pub struct ListArgs {
|
||||||
|
/// Show all remotes in both the Radicle storage and the working copy
|
||||||
|
#[arg(long)]
|
||||||
|
all: bool,
|
||||||
|
|
||||||
|
/// Show all remotes that are listed in the working copy
|
||||||
|
#[arg(long)]
|
||||||
|
tracked: bool,
|
||||||
|
|
||||||
|
/// Show all remotes that are listed in the Radicle storage
|
||||||
|
#[arg(long)]
|
||||||
|
untracked: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ListArgs> for ListOption {
|
||||||
|
fn from(
|
||||||
|
ListArgs {
|
||||||
|
all,
|
||||||
|
tracked,
|
||||||
|
untracked,
|
||||||
|
}: ListArgs,
|
||||||
|
) -> Self {
|
||||||
|
match (all, tracked, untracked) {
|
||||||
|
(true, false, false) => Self::All,
|
||||||
|
(false, true, false) | (false, false, false) => Self::Tracked,
|
||||||
|
(false, false, true) => Self::Untracked,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) enum ListOption {
|
||||||
|
/// Show all remotes in both the Radicle storage and the working copy
|
||||||
|
All,
|
||||||
|
/// Show all remotes that are listed in the working copy
|
||||||
|
Tracked,
|
||||||
|
/// Show all remotes that are listed in the Radicle storage
|
||||||
|
Untracked,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Clone, Copy, Debug)]
|
||||||
|
#[group(multiple = false)]
|
||||||
|
pub(super) struct EmptyArgs {
|
||||||
|
#[arg(long, hide = true)]
|
||||||
|
all: bool,
|
||||||
|
|
||||||
|
#[arg(long, hide = true)]
|
||||||
|
tracked: bool,
|
||||||
|
|
||||||
|
#[arg(long, hide = true)]
|
||||||
|
untracked: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EmptyArgs> for ListArgs {
|
||||||
|
fn from(args: EmptyArgs) -> Self {
|
||||||
|
Self {
|
||||||
|
all: args.all,
|
||||||
|
tracked: args.tracked,
|
||||||
|
untracked: args.untracked,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_refstr(refstr: &str) -> Result<RefString, git::fmt::Error> {
|
||||||
|
RefString::try_from(refstr)
|
||||||
|
}
|
||||||
|
|
@ -74,6 +74,7 @@ enum Commands {
|
||||||
Ls(ls::Args),
|
Ls(ls::Args),
|
||||||
Path(path::Args),
|
Path(path::Args),
|
||||||
Publish(publish::Args),
|
Publish(publish::Args),
|
||||||
|
Remote(remote::Args),
|
||||||
Seed(seed::Args),
|
Seed(seed::Args),
|
||||||
#[command(name = "self")]
|
#[command(name = "self")]
|
||||||
RadSelf(rad_self::Args),
|
RadSelf(rad_self::Args),
|
||||||
|
|
@ -340,7 +341,9 @@ pub(crate) fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyho
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"remote" => {
|
"remote" => {
|
||||||
term::run_command_args::<remote::Options, _>(remote::HELP, remote::run, args.to_vec())
|
if let Some(Commands::Remote(args)) = CliArgs::parse().command {
|
||||||
|
term::run_command_fn(remote::run, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"stats" => {
|
"stats" => {
|
||||||
if let Some(Commands::Stats(args)) = CliArgs::parse().command {
|
if let Some(Commands::Stats(args)) = CliArgs::parse().command {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue