Skip to content

Commit 587fbc1

Browse files
sachiniyerclaude
andauthored
fix(repos): normalize whitespace in repo identifiers before lookup (#261)
## Summary Inputs like `' usedetail/cli'` or `'usedetail / cli'` passed `validate_owner_repo_format` (which used `.trim().is_empty()` only for emptiness checks) but were then compared verbatim against `r.full_name`. The exact match failed and users got a misleading **"Repository not found. Make sure you have access …"** error for what was actually a whitespace typo. Trim the identifier, and trim each half of an `owner/repo` pair, before matching. The "not found" error now quotes the normalized form so the hint itself is accurate. ## Stacked on **#260** (fix for GitHub remote parsing with embedded credentials). Base branch is `siyer/fix-parse-github-remote-creds`; the diff against main is just the repos.rs change. ## Test plan - [x] `cargo test --lib utils::repos` — 22 pass, including 4 new cases (leading/trailing whitespace, whitespace around slash, bare-name trim, normalized error message) - [x] `cargo clippy -- -D warnings` clean - [x] `cargo fmt --check` clean Fixes #230. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/usedetail/cli/pull/261" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open in Devin Review"> </picture> </a> <!-- devin-review-badge-end --> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent fb56132 commit 587fbc1

1 file changed

Lines changed: 46 additions & 5 deletions

File tree

‎src/utils/repos.rs‎

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,27 @@ pub async fn resolve_repo_id(client: &ApiClient, repo_identifier: &str) -> Resul
7474
}
7575

7676
pub fn resolve_repo_id_from_repos(repos: &[Repo], repo_identifier: &str) -> Result<RepoId> {
77-
if repo_identifier.contains('/') {
78-
validate_owner_repo_format(repo_identifier)?;
77+
// Normalize accidental whitespace before matching. `validate_owner_repo_format`
78+
// already tolerated whitespace-only emptiness checks but the original input
79+
// was then compared verbatim against `r.full_name`, producing a misleading
80+
// "not found" error for inputs like " usedetail/cli".
81+
let identifier = repo_identifier.trim();
82+
if identifier.contains('/') {
83+
validate_owner_repo_format(identifier)?;
84+
let normalized = identifier
85+
.split('/')
86+
.map(str::trim)
87+
.collect::<Vec<_>>()
88+
.join("/");
7989
repos
8090
.iter()
81-
.find(|r| r.full_name == repo_identifier)
91+
.find(|r| r.full_name == normalized)
8292
.map(|r| r.id.clone())
8393
.context(format!(
84-
"Repository '{repo_identifier}' not found. Make sure you have access to this repository."
94+
"Repository '{normalized}' not found. Make sure you have access to this repository."
8595
))
8696
} else {
87-
match_repo_by_name(repo_identifier, repos)
97+
match_repo_by_name(identifier, repos)
8898
}
8999
}
90100

@@ -235,4 +245,35 @@ mod tests {
235245
let err = resolve_repo_id_from_repos(&repos, "cli").unwrap_err();
236246
assert!(err.to_string().contains("Multiple repositories"));
237247
}
248+
249+
#[test]
250+
fn resolve_owner_repo_trims_surrounding_whitespace() {
251+
let repos = sample_repos();
252+
let id = resolve_repo_id_from_repos(&repos, " usedetail/cli ").unwrap();
253+
assert_eq!(id.to_string(), "repo_1");
254+
}
255+
256+
#[test]
257+
fn resolve_owner_repo_trims_around_slash() {
258+
let repos = sample_repos();
259+
let id = resolve_repo_id_from_repos(&repos, "usedetail / cli").unwrap();
260+
assert_eq!(id.to_string(), "repo_1");
261+
}
262+
263+
#[test]
264+
fn resolve_bare_name_trims_whitespace() {
265+
let repos = sample_repos();
266+
let id = resolve_repo_id_from_repos(&repos, " web ").unwrap();
267+
assert_eq!(id.to_string(), "repo_3");
268+
}
269+
270+
#[test]
271+
fn resolve_owner_repo_not_found_error_uses_normalized_form() {
272+
// The "not found" hint should quote the cleaned identifier, not the
273+
// raw whitespace-padded input.
274+
let repos = sample_repos();
275+
let err = resolve_repo_id_from_repos(&repos, " usedetail/missing ").unwrap_err();
276+
let msg = err.to_string();
277+
assert!(msg.contains("'usedetail/missing'"), "got: {msg}");
278+
}
238279
}

0 commit comments

Comments
 (0)