/ radicle-cli / src / commands / remote.rs
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  }