|
1 | 1 | use crate::repository_manager::{RepoInfo, RepositoryManager}; |
2 | 2 |
|
3 | | -/// Extract host, owner, and repository name from a git URL |
| 3 | +/// Extract host, owner path (can include `/` for subgroups), and repo name (no .git) |
4 | 4 | fn parse_git_url(remote_url: &str) -> Option<(String, String, String)> { |
5 | | - // Handle SSH format: git@host:owner/repo |
6 | | - if let Some(ssh_part) = remote_url.strip_prefix("git@") { |
7 | | - if let Some(colon_pos) = ssh_part.find(':') { |
8 | | - let host = &ssh_part[..colon_pos]; |
9 | | - let path = &ssh_part[colon_pos + 1..]; |
10 | | - let parts: Vec<&str> = path.split('/').collect(); |
11 | | - if parts.len() >= 2 { |
12 | | - let owner = parts[0]; |
13 | | - let repo = parts[1].trim_end_matches(".git"); |
14 | | - return Some((host.to_string(), owner.to_string(), repo.to_string())); |
15 | | - } |
16 | | - } |
| 5 | + fn normalize_host(h: &str) -> String { |
| 6 | + // Drop port if present; lowercase for consistent cache paths |
| 7 | + h.split(':').next().unwrap_or(h).to_ascii_lowercase() |
17 | 8 | } |
18 | 9 |
|
19 | | - // Handle ssh:// format: ssh://git@host/owner/repo |
20 | | - if let Some(ssh_url) = remote_url.strip_prefix("ssh://") { |
21 | | - if let Some(at_pos) = ssh_url.find('@') { |
22 | | - let host_path = &ssh_url[at_pos + 1..]; |
23 | | - let parts: Vec<&str> = host_path.split('/').collect(); |
24 | | - if parts.len() >= 3 { |
25 | | - let host = parts[0]; |
26 | | - let owner = parts[1]; |
27 | | - let repo = parts[2].trim_end_matches(".git"); |
28 | | - return Some((host.to_string(), owner.to_string(), repo.to_string())); |
| 10 | + // 1) scp-like SSH: user@host:path |
| 11 | + if let Some((user_host, path)) = remote_url.split_once(':') { |
| 12 | + if let Some((_user, host)) = user_host.rsplit_once('@') { |
| 13 | + let host = normalize_host(host); |
| 14 | + let path = path.trim_start_matches('/').split('?').next().unwrap_or(""); |
| 15 | + let mut segs: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect(); |
| 16 | + if segs.len() >= 2 { |
| 17 | + let repo = segs.pop().unwrap().trim_end_matches(".git").to_string(); |
| 18 | + let owner = segs.join("/"); |
| 19 | + return Some((host, owner, repo)); |
29 | 20 | } |
30 | 21 | } |
31 | 22 | } |
32 | 23 |
|
33 | | - // Handle HTTP(S) format: https://host/owner/repo |
34 | | - if let Some(url_without_protocol) = remote_url |
35 | | - .strip_prefix("https://") |
| 24 | + // 2) ssh:// or http(s):// |
| 25 | + if let Some(rest) = remote_url |
| 26 | + .strip_prefix("ssh://") |
| 27 | + .or_else(|| remote_url.strip_prefix("https://")) |
36 | 28 | .or_else(|| remote_url.strip_prefix("http://")) |
37 | 29 | { |
38 | | - let parts: Vec<&str> = url_without_protocol.split('/').collect(); |
39 | | - if parts.len() >= 3 { |
40 | | - let host = parts[0]; |
41 | | - let owner = parts[1]; |
42 | | - let repo = parts[2].trim_end_matches(".git"); |
43 | | - return Some((host.to_string(), owner.to_string(), repo.to_string())); |
| 30 | + let rest = rest.split('#').next().unwrap_or(rest); // drop fragment |
| 31 | + let rest = rest.split('?').next().unwrap_or(rest); // drop query |
| 32 | + if let Some((host_port, path)) = rest.split_once('/') { |
| 33 | + let host = normalize_host(host_port); |
| 34 | + let mut segs: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect(); |
| 35 | + if segs.len() >= 2 { |
| 36 | + let repo = segs.pop().unwrap().trim_end_matches(".git").to_string(); |
| 37 | + let owner = segs.join("/"); |
| 38 | + return Some((host, owner, repo)); |
| 39 | + } |
44 | 40 | } |
45 | 41 | } |
46 | 42 |
|
|
0 commit comments