From aa08312b33e3a0cb052523e17009121b34ed1cd2 Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Mon, 15 Apr 2024 00:15:05 +0200 Subject: [PATCH] feat(ops/op_gc): add --dry-run for `gc rm` --- src/cli.rs | 3 ++ src/ops.rs | 132 ++++++++++++++++++++++++++++++----------------------- 2 files changed, 79 insertions(+), 56 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index aad3e583..b30fddfc 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -181,6 +181,9 @@ pub enum GcSubcommand { /// Also delete the root of projects that were last built before this amount of time, e.g. 30d. #[structopt(long = "older-than", parse(try_from_str = "human_friendly_duration"))] older_than: Option, + /// Only print which gc roots would be deleted. No `--json` output yet. + #[structopt(long = "dry-run")] + dry_run: bool, }, } diff --git a/src/ops.rs b/src/ops.rs index 6d5b72ae..035e99bb 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -953,6 +953,30 @@ struct GcRootInfo { alive: bool, } +impl GcRootInfo { + fn format_pretty_oneline(&self) -> String { + let target = match &self.nix_file { + Some(p) => p.display().to_string(), + None => "(?)".to_owned(), + }; + let age = match self.timestamp.elapsed() { + Err(_) => "future".to_owned(), + Ok(d) => { + let days = d.as_secs() / (24 * 60 * 60); + format!("{} days ago", days) + } + }; + let alive = if self.alive { "" } else { "[dead]" }; + format!( + "{} -> {} {} ({})", + self.gc_dir.display(), + target, + alive, + age + ) + } +} + /// Returns a list of existing gc roots along with some metadata fn list_roots(logger: &slog::Logger) -> Result, ExitError> { let paths = crate::ops::get_paths()?; @@ -1031,25 +1055,7 @@ pub fn op_gc(logger: &slog::Logger, opts: crate::cli::GcOptions) -> Result<(), E .expect("could not serialize gc roots"); } else { for info in infos { - let target = match info.nix_file { - Some(p) => p.display().to_string(), - None => "(?)".to_owned(), - }; - let age = match info.timestamp.elapsed() { - Err(_) => "future".to_owned(), - Ok(d) => { - let days = d.as_secs() / (24 * 60 * 60); - format!("{} days ago", days) - } - }; - let alive = if info.alive { "" } else { "[dead]" }; - println!( - "{} -> {} {} ({})", - info.gc_dir.display(), - target, - alive, - age - ); + println!("{}", info.format_pretty_oneline()); } } } @@ -1057,6 +1063,7 @@ pub fn op_gc(logger: &slog::Logger, opts: crate::cli::GcOptions) -> Result<(), E shell_file, all, older_than, + dry_run, } => { let files_to_remove: HashSet = shell_file.into_iter().collect(); let to_remove: Vec = infos @@ -1075,54 +1082,67 @@ pub fn op_gc(logger: &slog::Logger, opts: crate::cli::GcOptions) -> Result<(), E }) .collect(); let mut result = Vec::new(); - for info in to_remove { - match remove_dir_all(&info.gc_dir) { - Ok(_) => { - result.push(Ok(info)); - } - Err(e) => { - result.push(Err((info, e.to_string()))); + if dry_run { + if to_remove.len() > 0 { + println!("--dry-run: Would delete the following GC roots:"); + for info in to_remove { + println!("{}", info.format_pretty_oneline()); } + } else { + println!("--dry-run: Would not delete any GC roots"); } - } - if opts.json { - let res = result - .into_iter() - .map(|r| match r { - Err((info, err)) => json!({ - // Error, if any - "error": err, - // The root we tried to remove - "root": info - }), - Ok(info) => json!({ - "error": null, - "root": info - }), - }) - .collect::>(); - serde_json::to_writer(std::io::stdout(), &res).expect("failed to serialize result"); } else { - let (ok, err): (Vec<_>, Vec<_>) = result.into_iter().partition_result(); - println!("Removed {} gc roots.", ok.len()); - if err.len() > 0 { - for (info, e) in err { - warn!( - logger, - "Failed to remove gc root: {}: {}", - info.gc_dir.display(), - e - ) + for info in to_remove { + match remove_dir_all(&info.gc_dir) { + Ok(_) => { + result.push(Ok(info)); + } + Err(e) => { + result.push(Err((info, e.to_string()))); + } } } - if ok.len() > 0 { - println!("Remember to run nix-collect-garbage to actually free space."); + if opts.json { + let res = result + .into_iter() + .map(|r| match r { + Err((info, err)) => json!({ + // Error, if any + "error": err, + // The root we tried to remove + "root": info + }), + Ok(info) => json!({ + "error": null, + "root": info + }), + }) + .collect::>(); + serde_json::to_writer(std::io::stdout(), &res) + .expect("failed to serialize result"); + } else { + let (ok, err): (Vec<_>, Vec<_>) = result.into_iter().partition_result(); + println!("Removed {} gc roots.", ok.len()); + if err.len() > 0 { + for (info, e) in err { + warn!( + logger, + "Failed to remove gc root: {}: {}", + info.gc_dir.display(), + e + ) + } + } + if ok.len() > 0 { + println!("Remember to run nix-collect-garbage to actually free space."); + } } } } } Ok(()) } + #[derive(Serialize)] /// How removing a gc root went, for json ouput struct RemovalStatus {