const GITHUB_PREFIXES: &[&str] = &[
"git@github.com:", "ssh://git@github.com/", "ssh://github.com/", "https://github.com/", "http://github.com/", "git://github.com/", "github.com:", "github.com/", ];
pub(crate) fn parse_github_slug(url: &str) -> Option<String> {
let tail = GITHUB_PREFIXES.iter().find_map(|p| url.strip_prefix(p))?;
let tail = tail.trim_end_matches('/');
let tail = tail.strip_suffix(".git").unwrap_or(tail);
let tail = tail.trim_end_matches('/');
let mut parts = tail.splitn(3, '/');
let owner = parts.next().filter(|s| !s.is_empty())?;
let repo = parts.next().filter(|s| !s.is_empty())?;
if parts.next().is_some() {
return None;
}
Some(format!("{owner}/{repo}"))
}
#[cfg(test)]
mod tests {
use super::parse_github_slug;
#[test]
fn parses_github_slugs_across_url_forms() {
assert_eq!(
parse_github_slug("git@github.com:acme/tool.git"),
Some("acme/tool".to_string())
);
assert_eq!(
parse_github_slug("https://github.com/acme/tool.git"),
Some("acme/tool".to_string())
);
assert_eq!(
parse_github_slug("https://github.com/acme/tool"),
Some("acme/tool".to_string())
);
assert_eq!(
parse_github_slug("git://github.com/acme/tool.git"),
Some("acme/tool".to_string())
);
assert_eq!(
parse_github_slug("ssh://git@github.com/acme/tool.git"),
Some("acme/tool".to_string())
);
assert_eq!(
parse_github_slug("https://github.com/acme/tool.git/"),
Some("acme/tool".to_string())
);
assert_eq!(
parse_github_slug("https://github.com/acme/tool/"),
Some("acme/tool".to_string())
);
assert_eq!(parse_github_slug("git@gitlab.com:acme/tool.git"), None);
assert_eq!(
parse_github_slug("https://mirror.example.com/github.com/acme/tool.git"),
None
);
assert_eq!(
parse_github_slug("https://github.com.evil.example/acme/tool"),
None
);
assert_eq!(
parse_github_slug("https://github.com/acme/tool/tree/main"),
None
);
assert_eq!(parse_github_slug("https://github.com/acme"), None);
}
}