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 }