diff --git a/src/main.rs b/src/main.rs index 0112a4e..7f49f53 100644 --- a/src/main.rs +++ b/src/main.rs @@ -92,6 +92,8 @@ async fn main() -> Result<(), Box> { // 2. Build 'Desired' State (Map) info!("Resolving desired state from configuration..."); let mut desired_repos: HashMap = HashMap::new(); + let mut seen_names: HashSet = HashSet::new(); + let mut has_error = false; // 2a. Static Repos if let Some(repos) = &config.repos { @@ -101,6 +103,18 @@ async fn main() -> Result<(), Box> { .as_deref() .or_else(|| extract_repo_name(&r.url)) .ok_or_else(|| format!("Invalid URL: {}", r.url))?; + + let name_lower = name.to_lowercase(); + if seen_names.contains(&name_lower) { + warn!( + "Duplicate repository name detected (case-insensitive): '{}'. URL: {}", + name, r.url + ); + has_error = true; + continue; + } + seen_names.insert(name_lower); + desired_repos.insert(name.to_string(), r.url.clone()); } } @@ -113,12 +127,26 @@ async fn main() -> Result<(), Box> { fetch_external_org_repos(&http_client, &org.url, org.api_key.as_deref()).await?; for url in urls { if let Some(name) = extract_repo_name(&url) { + let name_lower = name.to_lowercase(); + if seen_names.contains(&name_lower) { + warn!( + "Duplicate repository name detected (case-insensitive) from organization import: '{}'. URL: {}", + name, url + ); + has_error = true; + continue; + } + seen_names.insert(name_lower); desired_repos.insert(name.to_string(), url); } } } } + if has_error { + return Err("Duplicate repository names detected. Please fix the configuration.".into()); + } + // 3. Build 'Current' State (Set) info!("Fetching existing repositories from Gitea ({})", owner_name); let existing_repos = diff --git a/vibe_coding_log/session_2026_01_10_duplicate_detection.md b/vibe_coding_log/session_2026_01_10_duplicate_detection.md new file mode 100644 index 0000000..c47d43a --- /dev/null +++ b/vibe_coding_log/session_2026_01_10_duplicate_detection.md @@ -0,0 +1,23 @@ +# Session 2026-01-10: Duplicate Repository Detection + +* **Date**: 2026-01-10 +* **Model**: Gemini 2.5 Flash / Gemini 3 Pro Preview +* **Goal**: Implement case-insensitive duplication detection for repository names in the configuration. +* **Outcome**: Added logic to detect duplicate repository names (case-insensitive) from both static configuration and organization imports. The tool now logs warnings for all detected duplicates and then exits with a fatal error if any duplicates were found. + +## Details + +1. **Duplicate Detection**: + * Modified `src/main.rs` to maintain a `HashSet` of lowercased repository names. + * Checks both the `repos` list and `organizations` imports. + * If a duplicate is found, a `WARN` log is emitted with details (name and URL). + * A `has_error` flag is set to true. + +2. **Error Handling**: + * After processing all sources, if `has_error` is true, the program returns a fatal error: "Duplicate repository names detected. Please fix the configuration." + * This ensures the user sees all conflicts before the program exits. + +## Testing + +* Created a `duplicate_repro.toml` with conflicting names (e.g., `ProjectA` vs `projecta`). +* Verified that `cargo run -- --config duplicate_repro.toml --dry-run` correctly outputted warnings for each duplicate and then exited with a non-zero status code and the expected error message.