Skip to content

Commit

Permalink
feat: mise rm
Browse files Browse the repository at this point in the history
Fixes #1465
  • Loading branch information
jdx committed Dec 18, 2024
1 parent ab902be commit df387b7
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 50 deletions.
22 changes: 22 additions & 0 deletions e2e/cli/test_unuse
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash

assert "mise use dummy@1.0.0"
assert_contains "mise ls dummy" "1.0.0"
assert "mise unuse dummy"
assert_empty "mise ls dummy"

assert "mise use dummy@1.0.0"
mkdir subdir
cd subdir || exit 1
assert "mise use -p mise.toml dummy@1.0.0"
assert "mise ls dummy" "dummy 1.0.0 ~/workdir/subdir/mise.toml 1.0.0"
assert "mise unuse dummy"
# version is not pruned because it's in ~/workdir/mise.toml
assert "mise ls dummy" "dummy 1.0.0 ~/workdir/mise.toml 1.0.0"
assert "mise unuse dummy"
assert_empty "mise ls dummy"

assert "mise use -g dummy@1.0.0"
assert "mise ls dummy" "dummy 1.0.0 ~/.config/mise/config.toml 1.0.0"
assert "mise unuse dummy"
assert_empty "mise ls dummy"
2 changes: 1 addition & 1 deletion src/cli/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ pub fn local(

if let Some(plugins) = &remove {
for plugin in plugins {
cf.remove_plugin(plugin)?;
cf.remove_tool(plugin)?;
}
let tools = plugins
.iter()
Expand Down
3 changes: 3 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub mod version;
mod watch;
mod r#where;
mod r#which;
mod unuse;

#[derive(clap::ValueEnum, Debug, Clone, strum::Display)]
#[strum(serialize_all = "kebab-case")]
Expand Down Expand Up @@ -222,6 +223,7 @@ pub enum Commands {
Trust(trust::Trust),
Uninstall(uninstall::Uninstall),
Unset(unset::Unset),
Unuse(unuse::Unuse),
Upgrade(upgrade::Upgrade),
Usage(usage::Usage),
Use(r#use::Use),
Expand Down Expand Up @@ -284,6 +286,7 @@ impl Commands {
Self::Trust(cmd) => cmd.run(),
Self::Uninstall(cmd) => cmd.run(),
Self::Unset(cmd) => cmd.run(),
Self::Unuse(cmd) => cmd.run(),
Self::Upgrade(cmd) => cmd.run(),
Self::Usage(cmd) => cmd.run(),
Self::Use(cmd) => cmd.run(),
Expand Down
79 changes: 40 additions & 39 deletions src/cli/prune.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use std::sync::Arc;

use crate::backend::Backend;
use crate::cli::args::BackendArg;
use crate::cli::args::{BackendArg, ToolArg};
use crate::config::tracking::Tracker;
use crate::config::{Config, SETTINGS};
use crate::toolset::{ToolVersion, Toolset, ToolsetBuilder};
Expand All @@ -22,9 +22,9 @@ use super::trust::Trust;
#[derive(Debug, clap::Args)]
#[clap(verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
pub struct Prune {
/// Prune only versions from this plugin(s)
/// Prune only these tools
#[clap()]
pub plugin: Option<Vec<BackendArg>>,
pub installed_tool: Option<Vec<ToolArg>>,

/// Do not actually delete anything
#[clap(long, short = 'n')]
Expand All @@ -45,7 +45,11 @@ impl Prune {
self.prune_configs()?;
}
if self.tools || !self.configs {
self.prune_tools()?;
let backends = self
.installed_tool
.as_ref()
.map(|it| it.iter().map(|ta| &ta.ba).collect());
prune(backends.unwrap_or_default(), self.dry_run)?;
}
Ok(())
}
Expand All @@ -60,49 +64,46 @@ impl Prune {
}
Ok(())
}
}

fn prune_tools(&self) -> Result<()> {
let config = Config::try_get()?;
let ts = ToolsetBuilder::new().build(&config)?;
let mut to_delete = ts
.list_installed_versions()?
.into_iter()
.map(|(p, tv)| ((tv.ba().short.to_string(), tv.tv_pathname()), (p, tv)))
.collect::<BTreeMap<(String, String), (Arc<dyn Backend>, ToolVersion)>>();
pub fn prune(tools: Vec<&BackendArg>, dry_run: bool) -> Result<()> {
let config = Config::try_get()?;
let ts = ToolsetBuilder::new().build(&config)?;
let mut to_delete = ts
.list_installed_versions()?
.into_iter()
.map(|(p, tv)| ((tv.ba().short.to_string(), tv.tv_pathname()), (p, tv)))
.collect::<BTreeMap<(String, String), (Arc<dyn Backend>, ToolVersion)>>();

if let Some(backends) = &self.plugin {
to_delete.retain(|_, (_, tv)| backends.contains(tv.ba()));
}
if !tools.is_empty() {
to_delete.retain(|_, (_, tv)| tools.contains(&&tv.ba()));
}

for cf in config.get_tracked_config_files()?.values() {
let mut ts = Toolset::from(cf.to_tool_request_set()?);
ts.resolve()?;
for (_, tv) in ts.list_current_versions() {
to_delete.remove(&(tv.ba().short.to_string(), tv.tv_pathname()));
}
for cf in config.get_tracked_config_files()?.values() {
let mut ts = Toolset::from(cf.to_tool_request_set()?);
ts.resolve()?;
for (_, tv) in ts.list_current_versions() {
to_delete.remove(&(tv.ba().short.to_string(), tv.tv_pathname()));
}

self.delete(to_delete.into_values().collect())
}

fn delete(&self, to_delete: Vec<(Arc<dyn Backend>, ToolVersion)>) -> Result<()> {
let mpr = MultiProgressReport::get();
for (p, tv) in to_delete {
let mut prefix = tv.style();
if self.dry_run {
prefix = format!("{} {} ", prefix, style("[dryrun]").bold());
}
let pr = mpr.add(&prefix);
if self.dry_run
|| SETTINGS.yes
|| prompt::confirm_with_all(format!("remove {} ?", &tv))?
{
p.uninstall_version(&tv, pr.as_ref(), self.dry_run)?;
pr.finish();
}
delete(dry_run, to_delete.into_values().collect())
}

fn delete(dry_run: bool, to_delete: Vec<(Arc<dyn Backend>, ToolVersion)>) -> Result<()> {
let mpr = MultiProgressReport::get();
for (p, tv) in to_delete {
let mut prefix = tv.style();
if dry_run {
prefix = format!("{} {} ", prefix, style("[dryrun]").bold());
}
let pr = mpr.add(&prefix);
if dry_run || SETTINGS.yes || prompt::confirm_with_all(format!("remove {} ?", &tv))? {
p.uninstall_version(&tv, pr.as_ref(), dry_run)?;
pr.finish();
}
Ok(())
}
Ok(())
}

static AFTER_LONG_HELP: &str = color_print::cstr!(
Expand Down
2 changes: 1 addition & 1 deletion src/cli/uninstall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{config, dirs, file};
///
/// This only removes the installed version, it does not modify mise.toml.
#[derive(Debug, clap::Args)]
#[clap(verbatim_doc_comment, visible_aliases = ["remove", "rm"], after_long_help = AFTER_LONG_HELP)]
#[clap(verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
pub struct Uninstall {
/// Tool(s) to remove
#[clap(value_name = "INSTALLED_TOOL@VERSION", required_unless_present = "all")]
Expand Down
76 changes: 76 additions & 0 deletions src/cli/unuse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use eyre::Result;

use crate::cli::args::ToolArg;
use crate::cli::prune::prune;
use crate::config;
use crate::config::config_file::ConfigFile;
use crate::config::{config_file, Config};
use crate::file::display_path;

/// Removes installed tool versions from mise.toml
///
/// Will also prune the installed version if no other configurations are using it.
#[derive(Debug, clap::Args)]
#[clap(verbatim_doc_comment, visible_aliases = ["rm", "remove"], after_long_help = AFTER_LONG_HELP)]
pub struct Unuse {
/// Tool(s) to remove
#[clap(value_name = "INSTALLED_TOOL@VERSION", required = true)]
installed_tool: Vec<ToolArg>,

/// Do not also prune the installed version
#[clap(long)]
no_prune: bool,

/// Remove tool from global config
#[clap(long)]
global: bool,
}

impl Unuse {
pub fn run(self) -> Result<()> {
let config = Config::get();
let mut cf = self.get_config_file(&config)?;
let tools = cf.to_tool_request_set()?.tools;
let mut removed = vec![];
for ta in &self.installed_tool {
if tools.contains_key(&ta.ba) {
removed.push(ta);
}
cf.remove_tool(&ta.ba)?;
}
if removed.is_empty() {
debug!("no tools to remove");
return Ok(());
}
cf.save()?;
info!("removed: {} from {}", removed.iter().map(|ta| ta.to_string()).collect::<Vec<_>>().join(", "), display_path(cf.get_path()));

if !self.no_prune {
prune(self.installed_tool.iter().map(|ta| &ta.ba).collect(), false)?;
}

Ok(())
}

pub fn get_config_file(&self, config: &Config) -> Result<Box<dyn ConfigFile>> {
for cf in config.config_files.values() {
if cf
.to_tool_request_set()?
.tools
.keys()
.any(|ba| self.installed_tool.iter().any(|ta| ta.ba == *ba))
{
return config_file::parse(cf.get_path());
}
}
config_file::parse_or_init(&config::local_toml_config_path())
}
}

static AFTER_LONG_HELP: &str = color_print::cstr!(
r#"<bold><underline>Examples:</underline></bold>
# will uninstall specific version
$ <bold>mise remove node@18.0.0</bold>
"#
);
2 changes: 1 addition & 1 deletion src/cli/use.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ impl Use {
self.warn_if_hidden(&config, cf.get_path());
}
for plugin_name in &self.remove {
cf.remove_plugin(plugin_name)?;
cf.remove_tool(plugin_name)?;
}
cf.save()?;

Expand Down
2 changes: 1 addition & 1 deletion src/config/config_file/idiomatic_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl ConfigFile for IdiomaticVersionFile {
}

#[cfg_attr(coverage_nightly, coverage(off))]
fn remove_plugin(&mut self, _fa: &BackendArg) -> Result<()> {
fn remove_tool(&mut self, _fa: &BackendArg) -> Result<()> {
unimplemented!()
}

Expand Down
4 changes: 2 additions & 2 deletions src/config/config_file/mise_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ impl ConfigFile for MiseToml {
self.tasks.0.values().collect()
}

fn remove_plugin(&mut self, fa: &BackendArg) -> eyre::Result<()> {
fn remove_tool(&mut self, fa: &BackendArg) -> eyre::Result<()> {
self.tools.shift_remove(fa);
let doc = self.doc_mut()?;
if let Some(tools) = doc.get_mut("tools") {
Expand Down Expand Up @@ -1752,7 +1752,7 @@ mod tests {
)
.unwrap();
let mut cf = MiseToml::from_file(&p).unwrap();
cf.remove_plugin(&"node".into()).unwrap();
cf.remove_tool(&"node".into()).unwrap();

assert_debug_snapshot!(cf.to_toolset().unwrap());
let cf: Box<dyn ConfigFile> = Box::new(cf);
Expand Down
13 changes: 9 additions & 4 deletions src/config/config_file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ pub trait ConfigFile: Debug + Send + Sync {
fn tasks(&self) -> Vec<&Task> {
Default::default()
}
fn remove_plugin(&mut self, ba: &BackendArg) -> eyre::Result<()>;
fn remove_tool(&mut self, ba: &BackendArg) -> eyre::Result<()>;
fn replace_versions(&mut self, ba: &BackendArg, versions: Vec<ToolRequest>)
-> eyre::Result<()>;
fn save(&self) -> eyre::Result<()>;
Expand Down Expand Up @@ -216,9 +216,14 @@ fn init(path: &Path) -> Box<dyn ConfigFile> {
}

pub fn parse_or_init(path: &Path) -> eyre::Result<Box<dyn ConfigFile>> {
let path = if path.is_dir() {
path.join("mise.toml")
} else {
path.into()
};
let cf = match path.exists() {
true => parse(path)?,
false => init(path),
true => parse(&path)?,
false => init(&path),
};
Ok(cf)
}
Expand Down Expand Up @@ -501,7 +506,7 @@ fn trust_file_hash(path: &Path) -> eyre::Result<bool> {
}

fn detect_config_file_type(path: &Path) -> Option<ConfigFileType> {
match path.file_name().unwrap().to_str().unwrap() {
match path.file_name().and_then(|f| f.to_str()).unwrap_or("mise.toml") {
f if f.ends_with(".toml") => Some(ConfigFileType::MiseToml),
f if env::MISE_OVERRIDE_CONFIG_FILENAMES.contains(f) => Some(ConfigFileType::MiseToml),
f if env::MISE_DEFAULT_CONFIG_FILENAME.as_str() == f => Some(ConfigFileType::MiseToml),
Expand Down
2 changes: 1 addition & 1 deletion src/config/config_file/tool_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ impl ConfigFile for ToolVersions {
self.path.as_path()
}

fn remove_plugin(&mut self, fa: &BackendArg) -> Result<()> {
fn remove_tool(&mut self, fa: &BackendArg) -> Result<()> {
self.plugins.shift_remove(fa);
Ok(())
}
Expand Down

0 comments on commit df387b7

Please sign in to comment.