rm.rs
  1  use std::ffi::OsString;
  2  use std::fs;
  3  
  4  use anyhow::anyhow;
  5  
  6  use radicle::identity::Id;
  7  use radicle::node;
  8  use radicle::node::Handle as _;
  9  use radicle::storage;
 10  use radicle::Profile;
 11  
 12  use crate::git;
 13  use crate::terminal as term;
 14  use crate::terminal::args::{Args, Error, Help};
 15  
 16  pub const HELP: Help = Help {
 17      name: "rm",
 18      description: "Remove a project",
 19      version: env!("CARGO_PKG_VERSION"),
 20      usage: r#"
 21  Usage
 22  
 23      rad rm <rid> [<option>...]
 24  
 25      Removes a repository from storage. The repository is also untracked, if possible.
 26  
 27  Options
 28  
 29      --no-confirm        Do not ask for confirmation before removal (default: false)
 30      --help              Print help
 31  "#,
 32  };
 33  
 34  pub struct Options {
 35      rid: Id,
 36      confirm: bool,
 37  }
 38  
 39  impl Args for Options {
 40      fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
 41          use lexopt::prelude::*;
 42  
 43          let mut parser = lexopt::Parser::from_args(args);
 44          let mut id: Option<Id> = None;
 45          let mut confirm = true;
 46  
 47          while let Some(arg) = parser.next()? {
 48              match arg {
 49                  Long("no-confirm") => {
 50                      confirm = false;
 51                  }
 52                  Long("help") | Short('h') => {
 53                      return Err(Error::Help.into());
 54                  }
 55                  Value(val) if id.is_none() => {
 56                      id = Some(term::args::rid(&val)?);
 57                  }
 58                  _ => return Err(anyhow::anyhow!(arg.unexpected())),
 59              }
 60          }
 61  
 62          Ok((
 63              Options {
 64                  rid: id.ok_or_else(|| anyhow!("an RID must be provided; see `rad rm --help`"))?,
 65                  confirm,
 66              },
 67              vec![],
 68          ))
 69      }
 70  }
 71  
 72  pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
 73      let profile = ctx.profile()?;
 74      let storage = &profile.storage;
 75      let rid = options.rid;
 76      let path = storage::git::paths::repository(storage, &rid);
 77  
 78      if !path.exists() {
 79          anyhow::bail!("repository {rid} was not found");
 80      }
 81  
 82      if !options.confirm || term::confirm(format!("Remove {rid}?")) {
 83          untrack(&rid, &profile)?;
 84          remove_remote(&rid)?;
 85          fs::remove_dir_all(path)?;
 86          term::success!("Successfully removed {rid} from storage");
 87      }
 88  
 89      Ok(())
 90  }
 91  
 92  fn untrack(rid: &Id, profile: &Profile) -> anyhow::Result<()> {
 93      let mut node = radicle::Node::new(profile.socket());
 94  
 95      let result = if node.is_running() {
 96          node.untrack_repo(*rid).map_err(anyhow::Error::from)
 97      } else {
 98          let mut store =
 99              node::tracking::store::Config::open(profile.home.node().join(node::TRACKING_DB_FILE))?;
100          store.untrack_repo(rid).map_err(anyhow::Error::from)
101      };
102  
103      if let Err(e) = result {
104          term::warning(format!("Failed to untrack repository: {e}"));
105          term::warning("Make sure to untrack this repository when your node is running");
106      } else {
107          term::success!("Untracked {rid}")
108      }
109  
110      Ok(())
111  }
112  
113  fn remove_remote(rid: &Id) -> anyhow::Result<()> {
114      let cwd = std::env::current_dir()?;
115      if let Err(e) = git::Repository::open(cwd)
116          .map_err(|err| err.into())
117          .and_then(|repo| git::remove_remote(&repo, rid))
118      {
119          term::warning(format!(
120              "Attempted to remove 'rad' remote from working copy: {e}"
121          ));
122          term::warning("In case a working copy exists, make sure to `git remote remove rad`");
123      } else {
124          term::success!("Successfully removed 'rad' remote");
125      }
126  
127      Ok(())
128  }