remote.rs
1 //! Remote Command implementation 2 #[path = "remote/add.rs"] 3 pub mod add; 4 #[path = "remote/list.rs"] 5 pub mod list; 6 #[path = "remote/rm.rs"] 7 pub mod rm; 8 9 use std::ffi::OsString; 10 11 use anyhow::anyhow; 12 13 use radicle::git::RefString; 14 use radicle::prelude::NodeId; 15 use radicle::storage::ReadStorage; 16 17 use crate::terminal::args; 18 use crate::terminal::{Args, Context, Help}; 19 20 pub const HELP: Help = Help { 21 name: "remote", 22 description: "Manage a project's remotes", 23 version: env!("CARGO_PKG_VERSION"), 24 usage: r#" 25 Usage 26 27 rad remote 28 rad remote list 29 rad remote add (<did> | <nid>) [--name <string>] 30 rad remote rm <name> 31 32 Options 33 34 --name Override the name of the remote that by default is set to the node alias 35 --help Print help 36 "#, 37 }; 38 39 #[derive(Debug, Default, PartialEq, Eq)] 40 pub enum OperationName { 41 Add, 42 Rm, 43 #[default] 44 List, 45 } 46 47 #[derive(Debug)] 48 pub enum Operation { 49 Add { id: NodeId, name: Option<RefString> }, 50 Rm { name: RefString }, 51 List, 52 } 53 54 #[derive(Debug)] 55 pub struct Options { 56 pub op: Operation, 57 } 58 59 impl Args for Options { 60 fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> { 61 use lexopt::prelude::*; 62 63 let mut parser = lexopt::Parser::from_args(args); 64 let mut op: Option<OperationName> = None; 65 let mut id: Option<NodeId> = None; 66 let mut name: Option<RefString> = None; 67 68 while let Some(arg) = parser.next()? { 69 match arg { 70 Long("help") | Short('h') => { 71 return Err(args::Error::Help.into()); 72 } 73 Long("name") | Short('n') => { 74 let value = parser.value()?; 75 let value = args::refstring("name", value)?; 76 77 name = Some(value); 78 } 79 Value(val) if op.is_none() => match val.to_string_lossy().as_ref() { 80 "a" | "add" => op = Some(OperationName::Add), 81 "l" | "list" => op = Some(OperationName::List), 82 "r" | "rm" => op = Some(OperationName::Rm), 83 unknown => anyhow::bail!("unknown operation '{}'", unknown), 84 }, 85 Value(val) if op == Some(OperationName::Add) && id.is_none() => { 86 let nid = args::pubkey(&val)?; 87 id = Some(nid); 88 } 89 Value(val) if op == Some(OperationName::Rm) && name.is_none() => { 90 let val = args::string(&val); 91 let val = RefString::try_from(val) 92 .map_err(|e| anyhow!("invalid remote name specified: {e}"))?; 93 94 name = Some(val); 95 } 96 _ => return Err(anyhow::anyhow!(arg.unexpected())), 97 } 98 } 99 100 let op = match op.unwrap_or_default() { 101 OperationName::Add => Operation::Add { 102 id: id.ok_or(anyhow!( 103 "`DID` required, try running `rad remote add <did>`" 104 ))?, 105 name, 106 }, 107 OperationName::List => Operation::List, 108 OperationName::Rm => Operation::Rm { 109 name: name.ok_or(anyhow!("name required, see `rad remote`"))?, 110 }, 111 }; 112 113 Ok((Options { op }, vec![])) 114 } 115 } 116 117 pub fn run(options: Options, ctx: impl Context) -> anyhow::Result<()> { 118 let (working, rid) = radicle::rad::cwd() 119 .map_err(|_| anyhow!("this command must be run in the context of a project"))?; 120 let profile = ctx.profile()?; 121 122 match options.op { 123 Operation::Add { ref id, name } => { 124 let proj = profile.storage.repository(rid)?.project()?; 125 let branch = proj.default_branch(); 126 127 self::add::run(rid, id, name, Some(branch.clone()), &profile, &working)? 128 } 129 Operation::Rm { ref name } => self::rm::run(name, &working)?, 130 Operation::List => self::list::run(&working)?, 131 }; 132 Ok(()) 133 }