From 7f2197d6b2cff93fb546a2890249b899672720eb Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Thu, 9 Nov 2023 16:36:49 -0600 Subject: [PATCH] uninstall: added --all and --dry-run features (#989) --- README.md | 14 ++++++-- completions/_rtx | 4 +++ completions/rtx.bash | 2 +- completions/rtx.fish | 2 ++ e2e/test_uninstall | 17 +++++++++- src/cli/uninstall.rs | 81 ++++++++++++++++++++++++++++++++------------ 6 files changed, 94 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 963004ef89..449a21adc3 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ v20.0.0 - [`rtx sync node <--brew|--nvm|--nodenv>`](#rtx-sync-node---brew--nvm--nodenv) - [`rtx sync python --pyenv`](#rtx-sync-python---pyenv) - [`rtx trust [OPTIONS] [CONFIG_FILE]`](#rtx-trust-options-config_file) - - [`rtx uninstall ...`](#rtx-uninstall-toolversion) + - [`rtx uninstall [OPTIONS] ...`](#rtx-uninstall-options-toolversion) - [`rtx upgrade [TOOL@VERSION]...`](#rtx-upgrade-toolversion) - [`rtx use [OPTIONS] [TOOL@VERSION]...`](#rtx-use-options-toolversion) - [`rtx version`](#rtx-version) @@ -2455,20 +2455,28 @@ Examples: # trusts .rtx.toml in the current or parent directory $ rtx trust ``` -### `rtx uninstall ...` +### `rtx uninstall [OPTIONS] ...` ``` Removes runtime versions -Usage: uninstall ... +Usage: uninstall [OPTIONS] ... Arguments: ... Tool(s) to remove +Options: + -a, --all + Delete all installed versions + + -n, --dry-run + Do not actually delete anything + Examples: $ rtx uninstall node@18.0.0 # will uninstall specific version $ rtx uninstall node # will uninstall current node version + $ rtx uninstall --all node@18.0.0 # will uninstall all node versions ``` ### `rtx upgrade [TOOL@VERSION]...` diff --git a/completions/_rtx b/completions/_rtx index 508bc24c98..56db671a49 100644 --- a/completions/_rtx +++ b/completions/_rtx @@ -1608,6 +1608,10 @@ _arguments "${_arguments_options[@]}" \ '--jobs=[Number of plugins and runtimes to install in parallel \[default\: 4\]]: : ' \ '--log-level=[Set the log output verbosity]:LEVEL: ' \ +'-a[Delete all installed versions]' \ +'--all[Delete all installed versions]' \ +'-n[Do not actually delete anything]' \ +'--dry-run[Do not actually delete anything]' \ '--debug[Sets log level to debug]' \ '--install-missing[Automatically install missing tools]' \ '-r[Directly pipe stdin/stdout/stderr to user. diff --git a/completions/rtx.bash b/completions/rtx.bash index f009f975c1..54a70bc384 100644 --- a/completions/rtx.bash +++ b/completions/rtx.bash @@ -3301,7 +3301,7 @@ _rtx() { return 0 ;; rtx__uninstall) - opts="-j -r -y -v -h --debug --install-missing --jobs --log-level --raw --yes --trace --verbose --help ..." + opts="-a -n -j -r -y -v -h --all --dry-run --debug --install-missing --jobs --log-level --raw --yes --trace --verbose --help ..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/completions/rtx.fish b/completions/rtx.fish index 70302a00b1..ea83de65be 100644 --- a/completions/rtx.fish +++ b/completions/rtx.fish @@ -743,6 +743,8 @@ complete -c rtx -n "__fish_seen_subcommand_from trust" -s h -l help -d 'Print he complete -c rtx -n "__fish_seen_subcommand_from uninstall" -s j -l jobs -d 'Number of plugins and runtimes to install in parallel [default: 4]' -r complete -c rtx -n "__fish_seen_subcommand_from uninstall" -l log-level -d 'Set the log output verbosity' -r +complete -c rtx -n "__fish_seen_subcommand_from uninstall" -s a -l all -d 'Delete all installed versions' +complete -c rtx -n "__fish_seen_subcommand_from uninstall" -s n -l dry-run -d 'Do not actually delete anything' complete -c rtx -n "__fish_seen_subcommand_from uninstall" -l debug -d 'Sets log level to debug' complete -c rtx -n "__fish_seen_subcommand_from uninstall" -l install-missing -d 'Automatically install missing tools' complete -c rtx -n "__fish_seen_subcommand_from uninstall" -s r -l raw -d 'Directly pipe stdin/stdout/stderr to user. diff --git a/e2e/test_uninstall b/e2e/test_uninstall index f2fbb5efea..afd8035481 100755 --- a/e2e/test_uninstall +++ b/e2e/test_uninstall @@ -5,4 +5,19 @@ source "$(dirname "$0")/assert.sh" rtx i rtx uninstall tiny export CLICOLOR=0 -assert_contains "rtx ls" "3.1.0 (missing)" +assert_contains "rtx ls tiny" "3.1.0 (missing)" + +rtx i tiny@1 tiny@2.0 tiny@2.1 +assert_contains "rtx ls tiny" "1.0.1" +assert_contains "rtx ls tiny" "2.0.1" +assert_contains "rtx ls tiny" "2.1.0" + +rtx rm -a tiny@2 +assert_contains "rtx ls tiny" "1.0.1" +assert_not_contains "rtx ls tiny" "2.0.1" +assert_not_contains "rtx ls tiny" "2.1.0" + +rtx rm -a tiny +assert_not_contains "rtx ls tiny" "1.0.1" +assert_not_contains "rtx ls tiny" "2.0.1" +assert_not_contains "rtx ls tiny" "2.1.0" diff --git a/src/cli/uninstall.rs b/src/cli/uninstall.rs index 68870e9f42..ef1fafb466 100644 --- a/src/cli/uninstall.rs +++ b/src/cli/uninstall.rs @@ -5,7 +5,7 @@ use crate::cli::args::tool::{ToolArg, ToolArgParser}; use crate::cli::command::Command; use crate::config::Config; use crate::output::Output; -use crate::toolset::ToolsetBuilder; +use crate::toolset::{ToolVersion, ToolVersionRequest, ToolsetBuilder}; use crate::ui::multi_progress_report::MultiProgressReport; use crate::{runtime_symlinks, shims}; @@ -16,30 +16,68 @@ pub struct Uninstall { /// Tool(s) to remove #[clap(required = true, value_name="TOOL@VERSION", value_parser = ToolArgParser)] tool: Vec, + + /// Delete all installed versions + #[clap(long, short = 'a')] + all: bool, + + /// Do not actually delete anything + #[clap(long, short = 'n')] + dry_run: bool, } impl Command for Uninstall { fn run(self, mut config: Config, _out: &mut Output) -> Result<()> { let runtimes = ToolArg::double_tool_condition(&self.tool); - let tool_versions = runtimes - .iter() - .map(|a| { - let tool = config.get_or_create_tool(&a.plugin); - let tv = match &a.tvr { - Some(tvr) => tvr.resolve(&config, &tool, Default::default(), false)?, - None => { - let ts = ToolsetBuilder::new().build(&mut config)?; - let tv = ts - .versions - .get(&a.plugin) - .and_then(|v| v.versions.first()) - .expect("no version found"); - tv.clone() - } - }; - Ok((tool, tv)) - }) - .collect::>>()?; + + let mut tool_versions = vec![]; + if self.all { + for runtime in runtimes { + let tool = config.get_or_create_tool(&runtime.plugin); + let query = runtime.tvr.map(|tvr| tvr.version()).unwrap_or_default(); + let tvs = tool + .list_installed_versions()? + .into_iter() + .filter(|v| v.starts_with(&query)) + .map(|v| { + let tvr = ToolVersionRequest::new(tool.name.clone(), &v); + let tv = ToolVersion::new(&tool, tvr, Default::default(), v); + (tool.clone(), tv) + }) + .collect::>(); + if tvs.is_empty() { + warn!( + "no versions found for {}", + style(&tool.name).cyan().for_stderr() + ); + } + tool_versions.extend(tvs); + } + } else { + tool_versions = runtimes + .into_iter() + .map(|a| { + let tool = config.get_or_create_tool(&a.plugin); + let tvs = match a.tvr { + Some(tvr) => { + vec![tvr.resolve(&config, &tool, Default::default(), false)?] + } + None => { + let ts = ToolsetBuilder::new().build(&mut config)?; + let tvl = ts.versions.get(&a.plugin).expect("no versions found"); + tvl.versions.clone() + } + }; + Ok(tvs + .into_iter() + .map(|tv| (tool.clone(), tv)) + .collect::>()) + }) + .collect::>>()? + .into_iter() + .flatten() + .collect::>(); + } let mpr = MultiProgressReport::new(config.show_progress_bars()); for (plugin, tv) in tool_versions { @@ -50,7 +88,7 @@ impl Command for Uninstall { let mut pr = mpr.add(); plugin.decorate_progress_bar(&mut pr, Some(&tv)); - if let Err(err) = plugin.uninstall_version(&config, &tv, &pr, false) { + if let Err(err) = plugin.uninstall_version(&config, &tv, &pr, self.dry_run) { pr.error(err.to_string()); return Err(eyre!(err).wrap_err(format!("failed to uninstall {}", &tv))); } @@ -69,5 +107,6 @@ static AFTER_LONG_HELP: &str = color_print::cstr!( r#"Examples: $ rtx uninstall node@18.0.0 # will uninstall specific version $ rtx uninstall node # will uninstall current node version + $ rtx uninstall --all node@18.0.0 # will uninstall all node versions "# );