dev_prune/commands/
restore.rs1use std::collections::BTreeMap;
9use std::path::{Path, PathBuf};
10
11use anyhow::{Context, Result};
12
13use crate::config::Registry;
14use crate::engine;
15use crate::output;
16
17pub fn run(path_str: &str) -> Result<()> {
19 let path = Path::new(path_str)
20 .canonicalize()
21 .with_context(|| format!("Path not found: {path_str}"))?;
22
23 output::print_header("dev-prune restore");
24 output::print_info(&format!(
25 "Restoring dependencies in {}",
26 output::clean_path(&path)
27 ));
28
29 let (global_depth, timeout_secs) = crate::config::Registry::load()
34 .map(|r| (r.settings.scan_depth, r.settings.command_timeout_secs))
35 .unwrap_or((
36 crate::constants::DEFAULT_SCAN_DEPTH,
37 crate::constants::DEFAULT_COMMAND_TIMEOUT_SECS,
38 ));
39 let timeout = std::time::Duration::from_secs(timeout_secs);
40 let results = engine::restore_project_to_depth(&path, global_depth, timeout)?;
41
42 let mut failed = 0usize;
43
44 for (adapter_name, result) in &results {
45 match result {
46 Ok(()) => {
47 output::print_success(&format!("{adapter_name}: dependencies restored"));
48 }
49 Err(e) => {
50 output::print_error(&format!("{adapter_name}: {e}"));
51 failed += 1;
52 }
53 }
54 }
55
56 if failed > 0 {
60 anyhow::bail!(
61 "{failed} of {} {} failed to restore — see the errors above",
62 results.len(),
63 output::plural(results.len(), "adapter", "adapters")
64 );
65 }
66
67 output::print_success(&format!(
68 "Restored {} {}.",
69 results.len(),
70 output::plural(results.len(), "adapter", "adapters")
71 ));
72
73 Ok(())
74}
75
76pub fn run_last_run() -> Result<()> {
87 let registry = Registry::load()?;
88
89 let Some(last) = registry.last_prune.as_ref() else {
90 anyhow::bail!(
91 "No prune pass has been recorded yet, so there is nothing to put back.\n \
92 `devp restore <path>` restores a project you name."
93 );
94 };
95
96 let total: u64 = last.dirs.iter().map(|d| d.size_freed).sum();
97
98 output::print_header("dev-prune restore --last-run");
99 output::print_info(&format!(
100 "Putting back {} {} deleted on {} ({}).",
101 last.dirs.len(),
102 output::plural(last.dirs.len(), "directory", "directories"),
103 last.at.format("%Y-%m-%d %H:%M UTC"),
104 output::format_bytes(total)
105 ));
106
107 let mut by_repo: BTreeMap<PathBuf, Vec<(String, String)>> = BTreeMap::new();
110 for dir in &last.dirs {
111 by_repo
112 .entry(dir.repo_path.clone())
113 .or_default()
114 .push((dir.bloat_dir.clone(), dir.adapter.clone()));
115 }
116
117 let global_depth = registry.settings.scan_depth;
118 let timeout = std::time::Duration::from_secs(registry.settings.command_timeout_secs);
119 let mut attempted = 0usize;
120 let mut failed = 0usize;
121
122 for (repo_path, deleted) in &by_repo {
123 println!();
124 output::print_info(&output::clean_path(repo_path));
125
126 if !repo_path.exists() {
129 for (label, adapter) in deleted {
130 attempted += 1;
131 failed += 1;
132 output::print_error(&format!(
133 " {adapter} ({label}): the repository no longer exists at this path"
134 ));
135 }
136 continue;
137 }
138
139 for (label, result) in engine::restore_deleted(repo_path, deleted, global_depth, timeout) {
140 attempted += 1;
141 match result {
142 Ok(()) => output::print_success(&format!(" {label}: restored")),
143 Err(e) => {
144 failed += 1;
145 output::print_error(&format!(" {label}: {e}"));
146 }
147 }
148 }
149 }
150
151 println!();
152 if failed > 0 {
153 anyhow::bail!(
154 "{failed} of {attempted} {} failed to restore — see the errors above",
155 output::plural(attempted, "directory", "directories")
156 );
157 }
158
159 output::print_success(&format!(
160 "Restored {attempted} {} across {} {}.",
161 output::plural(attempted, "directory", "directories"),
162 by_repo.len(),
163 output::plural(by_repo.len(), "repository", "repositories")
164 ));
165
166 Ok(())
167}