dev_prune/commands/
init.rs1use std::path::{Path, PathBuf};
10
11use anyhow::{Context, Result};
12
13use crate::config::{PerRepoConfig, Registry};
14use crate::output;
15use crate::{constants, discovery, scanner};
16
17pub fn run(paths: &[String], dry_run: bool, auto: bool) -> Result<()> {
22 output::print_banner();
23 output::print_header("dev-prune init");
24
25 let mut registry = Registry::load()?;
26 let mut total_found = 0;
27 let mut skipped_throwaway = 0;
28 let mut skipped_opted_out = 0;
29 let mut newly_added_repos: Vec<PathBuf> = Vec::new();
30
31 let auto_found = if auto {
35 let found = discovery::discover(®istry)?;
36 output::print_info(&format!(
37 "Looked for unregistered repositories in {} {}:",
38 found.roots.len(),
39 output::plural(found.roots.len(), "location", "locations")
40 ));
41 for root in &found.roots {
42 output::print_info(&format!(" {}", output::clean_path(root)));
43 }
44 total_found = found.found.len();
45 skipped_throwaway = found.throwaway;
46 skipped_opted_out = found.opted_out;
47 Some(found.found)
48 } else {
49 None
50 };
51
52 let scans: Vec<(PathBuf, Vec<PathBuf>)> = match auto_found {
53 Some(repos) => vec![(PathBuf::new(), repos)],
54 None => paths
55 .iter()
56 .map(|path_str| {
57 let path = Path::new(path_str)
58 .canonicalize()
59 .with_context(|| format!("Path not found: {path_str}"))?;
60 output::print_info(&format!(
61 "Scanning {} for Git repositories...",
62 output::clean_path(&path)
63 ));
64 let repos = scanner::scan_for_repos(&path)?;
65 total_found += repos.len();
66 Ok((path, repos))
67 })
68 .collect::<Result<_>>()?,
69 };
70
71 for (path, repos) in scans {
72 for repo in repos {
73 if !auto && super::link::is_throwaway_checkout(&path, &repo) {
78 skipped_throwaway += 1;
79 continue;
80 }
81 if !auto && repo.join(constants::DEVPRUNE_IGNORE_FILE).exists() {
86 skipped_opted_out += 1;
87 continue;
88 }
89 if registry.add_repo(repo.clone()) {
90 newly_added_repos.push(repo.clone());
91 let verb = if dry_run {
92 "Would register"
93 } else {
94 "Registered"
95 };
96 output::print_success(&format!("{verb}: {}", output::clean_path(&repo)));
97 let adoption =
98 registry.adopt_moved_entry(&repo, scanner::git::repo_identity(&repo));
99 super::link::report_adoption(&adoption);
100 } else {
101 output::print_info(&format!(
102 "Already registered: {}",
103 output::clean_path(&repo)
104 ));
105 if registry.needs_identity(&repo) {
109 let adoption =
110 registry.adopt_moved_entry(&repo, scanner::git::repo_identity(&repo));
111 super::link::report_adoption(&adoption);
112 }
113 }
114 }
115 }
116
117 if !dry_run {
118 registry.last_added_repos = newly_added_repos.clone();
119 registry.save()?;
120 }
121
122 output::print_header("Summary");
123 output::print_info(&format!(
126 "Found {total_found} {}{}, {} {} new",
127 if auto { "unregistered " } else { "Git " },
128 output::plural(total_found, "repo", "repos"),
129 if dry_run { "would add" } else { "added" },
130 newly_added_repos.len()
131 ));
132 if skipped_opted_out > 0 {
133 output::print_info(&format!(
134 "Skipped {skipped_opted_out} {} carrying `{}`.",
135 output::plural(skipped_opted_out, "repository", "repositories"),
136 constants::DEVPRUNE_IGNORE_FILE,
137 ));
138 }
139 if skipped_throwaway > 0 {
140 output::print_info(&format!(
143 "Skipped {skipped_throwaway} disposable {} (plugin-manager clones, temp directories). \
144 `devp link <path>` registers one anyway.",
145 output::plural(skipped_throwaway, "checkout", "checkouts"),
146 ));
147 }
148 output::print_info(&format!(
151 "{}: {}",
152 if dry_run {
153 "Would be tracked"
154 } else {
155 "Total tracked"
156 },
157 registry.repo_count()
158 ));
159
160 if !dry_run {
161 if let Some(report) = crate::setup::ensure_integrations_if_enabled(®istry)
164 && (report.changed_anything() || report.needs_attention())
165 {
166 output::print_header("Integrations");
167 report.print(false);
168 }
169 crate::setup::suppress_next_auto_setup();
170
171 if registry.settings.auto_config {
172 for repo in &newly_added_repos {
173 crate::commands::link::ensure_default_repo_config(repo);
174 }
175 }
176
177 for repo in registry.repositories.keys() {
183 if let Err(e) = PerRepoConfig::load_with_diagnostics(repo) {
184 output::print_warning(&format!(
185 "{}: `.devprune.json` could not be parsed and was left untouched — {e}",
186 output::clean_path(repo)
187 ));
188 }
189 }
190 }
191
192 output::print_header("Version");
197 output::print_info(&format!("Installed: v{}", crate::constants::VERSION));
198 if registry.settings.update_check {
199 let mut checked = registry;
200 if crate::commands::update::check_now(&mut checked) && !dry_run {
201 let _ = checked.save();
202 }
203 registry = checked;
204 } else {
205 output::print_info(
206 "The release check is off (`devp config set update_check true` re-enables it).",
207 );
208 }
209
210 if dry_run {
211 output::print_header("Dry Run Complete");
212 output::print_info("Nothing was written. Re-run without `--dry-run` to register.");
213 return Ok(());
214 }
215
216 output::print_header("Initialization Complete");
217 output::print_success(&format!(
218 "dev-prune initialization complete! All {} tracked {} registered & verified.",
219 registry.repo_count(),
220 output::plural(registry.repo_count(), "repository", "repositories")
221 ));
222
223 output::print_info("Review or undo the integrations with `devp setup --status`.");
224
225 Ok(())
226}