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 = crate::config::Registry::load()
32 .map(|r| r.settings.scan_depth)
33 .unwrap_or(crate::constants::DEFAULT_SCAN_DEPTH);
34 let results = engine::restore_project_to_depth(&path, global_depth)?;
35
36 let mut failed = 0usize;
37
38 for (adapter_name, result) in &results {
39 match result {
40 Ok(()) => {
41 output::print_success(&format!("{adapter_name}: dependencies restored"));
42 }
43 Err(e) => {
44 output::print_error(&format!("{adapter_name}: {e}"));
45 failed += 1;
46 }
47 }
48 }
49
50 if failed > 0 {
54 anyhow::bail!(
55 "{failed} of {} {} failed to restore — see the errors above",
56 results.len(),
57 output::plural(results.len(), "adapter", "adapters")
58 );
59 }
60
61 output::print_success(&format!(
62 "Restored {} {}.",
63 results.len(),
64 output::plural(results.len(), "adapter", "adapters")
65 ));
66
67 Ok(())
68}
69
70pub fn run_last_run() -> Result<()> {
81 let registry = Registry::load()?;
82
83 let Some(last) = registry.last_prune.as_ref() else {
84 anyhow::bail!(
85 "No prune pass has been recorded yet, so there is nothing to put back.\n \
86 `devp restore <path>` restores a project you name."
87 );
88 };
89
90 let total: u64 = last.dirs.iter().map(|d| d.size_freed).sum();
91
92 output::print_header("dev-prune restore --last-run");
93 output::print_info(&format!(
94 "Putting back {} {} deleted on {} ({}).",
95 last.dirs.len(),
96 output::plural(last.dirs.len(), "directory", "directories"),
97 last.at.format("%Y-%m-%d %H:%M UTC"),
98 output::format_bytes(total)
99 ));
100
101 let mut by_repo: BTreeMap<PathBuf, Vec<(String, String)>> = BTreeMap::new();
104 for dir in &last.dirs {
105 by_repo
106 .entry(dir.repo_path.clone())
107 .or_default()
108 .push((dir.bloat_dir.clone(), dir.adapter.clone()));
109 }
110
111 let global_depth = registry.settings.scan_depth;
112 let mut attempted = 0usize;
113 let mut failed = 0usize;
114
115 for (repo_path, deleted) in &by_repo {
116 println!();
117 output::print_info(&output::clean_path(repo_path));
118
119 if !repo_path.exists() {
122 for (label, adapter) in deleted {
123 attempted += 1;
124 failed += 1;
125 output::print_error(&format!(
126 " {adapter} ({label}): the repository no longer exists at this path"
127 ));
128 }
129 continue;
130 }
131
132 for (label, result) in engine::restore_deleted(repo_path, deleted, global_depth) {
133 attempted += 1;
134 match result {
135 Ok(()) => output::print_success(&format!(" {label}: restored")),
136 Err(e) => {
137 failed += 1;
138 output::print_error(&format!(" {label}: {e}"));
139 }
140 }
141 }
142 }
143
144 println!();
145 if failed > 0 {
146 anyhow::bail!(
147 "{failed} of {attempted} {} failed to restore — see the errors above",
148 output::plural(attempted, "directory", "directories")
149 );
150 }
151
152 output::print_success(&format!(
153 "Restored {attempted} {} across {} {}.",
154 output::plural(attempted, "directory", "directories"),
155 by_repo.len(),
156 output::plural(by_repo.len(), "repository", "repositories")
157 ));
158
159 Ok(())
160}