Add case-insensitive duplicate detection for repos
All checks were successful
Cargo Build & Test / Rust project - latest (1.90) (push) Successful in 5m25s
All checks were successful
Cargo Build & Test / Rust project - latest (1.90) (push) Successful in 5m25s
``` │ Agent powering down. Goodbye! │ │ Interaction Summary │ Session ID: 4fe7dbe6-ab78-49d0-9073-f4ed5ee0afb3 │ Tool Calls: 21 ( ✓ 20 x 1 ) │ Success Rate: 95.2% │ User Agreement: 95.2% (21 reviewed) │ Code Changes: +73 -2 │ │ Performance │ Wall Time: 17m 8s │ Agent Active: 4m 23s │ » API Time: 2m 35s (59.2%) │ » Tool Time: 1m 47s (40.8%) │ │ │ Model Usage Reqs Input Tokens Cache Reads Output Tokens │ ──────────────────────────────────────────────────────────────────────────── │ gemini-2.5-flash-lite 4 6,360 0 261 │ gemini-3-pro-preview 22 80,208 264,075 4,529 │ │ Savings Highlight: 264,075 (75.3%) of input tokens were served from the cache, reducing costs. ```
This commit is contained in:
28
src/main.rs
28
src/main.rs
@@ -92,6 +92,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
// 2. Build 'Desired' State (Map<RepoName, CloneUrl>)
|
// 2. Build 'Desired' State (Map<RepoName, CloneUrl>)
|
||||||
info!("Resolving desired state from configuration...");
|
info!("Resolving desired state from configuration...");
|
||||||
let mut desired_repos: HashMap<String, String> = HashMap::new();
|
let mut desired_repos: HashMap<String, String> = HashMap::new();
|
||||||
|
let mut seen_names: HashSet<String> = HashSet::new();
|
||||||
|
let mut has_error = false;
|
||||||
|
|
||||||
// 2a. Static Repos
|
// 2a. Static Repos
|
||||||
if let Some(repos) = &config.repos {
|
if let Some(repos) = &config.repos {
|
||||||
@@ -101,6 +103,18 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.as_deref()
|
.as_deref()
|
||||||
.or_else(|| extract_repo_name(&r.url))
|
.or_else(|| extract_repo_name(&r.url))
|
||||||
.ok_or_else(|| format!("Invalid URL: {}", 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());
|
desired_repos.insert(name.to_string(), r.url.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,12 +127,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
fetch_external_org_repos(&http_client, &org.url, org.api_key.as_deref()).await?;
|
fetch_external_org_repos(&http_client, &org.url, org.api_key.as_deref()).await?;
|
||||||
for url in urls {
|
for url in urls {
|
||||||
if let Some(name) = extract_repo_name(&url) {
|
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);
|
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<RepoName>)
|
// 3. Build 'Current' State (Set<RepoName>)
|
||||||
info!("Fetching existing repositories from Gitea ({})", owner_name);
|
info!("Fetching existing repositories from Gitea ({})", owner_name);
|
||||||
let existing_repos =
|
let existing_repos =
|
||||||
|
|||||||
23
vibe_coding_log/session_2026_01_10_duplicate_detection.md
Normal file
23
vibe_coding_log/session_2026_01_10_duplicate_detection.md
Normal file
@@ -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.
|
||||||
Reference in New Issue
Block a user