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 crate::setup::consent_state() == crate::setup::SetupConsent::Granted {
167 if let Some(report) = crate::setup::ensure_integrations_if_enabled(®istry)
168 && (report.changed_anything() || report.needs_attention())
169 {
170 output::print_header("Integrations");
171 report.print(false);
172 }
173 crate::setup::suppress_next_auto_setup();
174 }
175
176 if registry.settings.auto_config {
177 for repo in &newly_added_repos {
178 crate::commands::link::ensure_default_repo_config(repo);
179 }
180 }
181
182 for repo in registry.repositories.keys() {
188 if let Err(e) = PerRepoConfig::load_with_diagnostics(repo) {
189 output::print_warning(&format!(
190 "{}: `.devprune.json` could not be parsed and was left untouched — {e}",
191 output::clean_path(repo)
192 ));
193 }
194 }
195 }
196
197 output::print_header("Version");
202 output::print_info(&format!("Installed: v{}", crate::constants::VERSION));
203 if registry.settings.update_check {
204 let mut checked = registry;
205 if crate::commands::update::check_now(&mut checked) && !dry_run {
206 let _ = checked.save();
207 }
208 registry = checked;
209 } else {
210 output::print_info(
211 "The release check is off (`devp config set update_check true` re-enables it).",
212 );
213 }
214
215 if dry_run {
216 output::print_header("Dry Run Complete");
217 output::print_info("Nothing was written. Re-run without `--dry-run` to register.");
218 return Ok(());
219 }
220
221 output::print_header("Initialization Complete");
222 output::print_success(&format!(
223 "dev-prune initialization complete! All {} tracked {} registered & verified.",
224 registry.repo_count(),
225 output::plural(registry.repo_count(), "repository", "repositories")
226 ));
227
228 output::print_info("Review or undo the integrations with `devp setup --status`.");
229
230 Ok(())
231}